1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>
2 @using Dynamicweb.Ecommerce.ProductCatalog
3 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites
4 @using Dynamicweb.Ecommerce.Products.FieldDisplayGroups
5 @using Dynamicweb.Frontend
6 @using Dynamicweb.Core
7 @using System.Drawing
8 @using Dynamicweb.Environment
9
10 @functions {
11 //Find contrast color (white, black)
12 public static string GetContrastColor(string hexString)
13 {
14 System.Drawing.Color bg = System.Drawing.ColorTranslator.FromHtml(hexString);
15
16 int nThreshold = 105;
17 int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) +
18 (bg.B * 0.114));
19
20 string foreColor = (255 - bgDelta < nThreshold) ? "#333" : "#fff";
21 return foreColor;
22 }
23 }
24
25 @{
26 string googleAnalyticsTrackingID = Pageview.AreaSettings.GetString("GoogleAnalyticsTrackingID");
27 string googleAnalyticsMeasurementID = Pageview.AreaSettings.GetString("GoogleAnalyticsMeasurementID");
28 var cookieOptInLevel = CookieManager.GetCookieOptInLevel();
29 bool allowTracking = cookieOptInLevel == CookieOptInLevel.All || (cookieOptInLevel == CookieOptInLevel.Functional && CookieManager.GetCookieOptInCategories().Contains("Statistical"));
30
31 ProductViewModel product = new ProductViewModel();
32
33 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails"))
34 {
35 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"];
36 }
37
38 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", "");
39 bool anonymousUser = Pageview.User == null;
40 bool isErpConnectionDown = !Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsWebServiceConnectionAvailable();
41 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHideAddToCart") && isErpConnectionDown;
42 hideAddToCart = product?.VariantInfo?.VariantInfo != null && Model.Item.GetBoolean("HideVariantSelector") ? true : hideAddToCart;
43 bool hideStock = Model.Item.GetBoolean("HideStockState") || (Pageview.AreaSettings.GetBoolean("ErpDownHideStock") && isErpConnectionDown);
44 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser || Pageview.AreaSettings.GetBoolean("ErpDownHidePrices") && isErpConnectionDown;
45 bool hideFavoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("HideFavoritesSelector")) ? Model.Item.GetBoolean("HideFavoritesSelector") : false;
46
47 bool isDiscontinued = product.Discontinued;
48 bool IsNeverOutOfStock = product.NeverOutOfstock;
49 string[] variantId = { };
50
51 if (product?.VariantId != null) {
52 variantId = product.VariantId.Split('.');
53 }
54
55 string disableAddToCart = (product.StockLevel <= 0) ? "disabled" : "";
56 disableAddToCart = isDiscontinued ? "disabled" : disableAddToCart;
57 disableAddToCart = IsNeverOutOfStock ? "" : disableAddToCart;
58
59 // Does product has a expected delivery data
60 bool hasExpectedDelivery = product.ExpectedDelivery != null && product.ExpectedDelivery > DateTime.Now;
61 string expectedDeliveryDate = product.ExpectedDelivery?.ToShortDateString() ?? "";
62
63 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService"));
64 if (!url.Contains("LayoutTemplate"))
65 {
66 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml";
67 }
68
69 IEnumerable<string> selectedDisplayGroups = Model.Item.GetRawValueString("MainFeatures").Split(',').ToList();
70 List<CategoryFieldViewModel> mainFeatures = new List<CategoryFieldViewModel>();
71
72 foreach (var selection in selectedDisplayGroups)
73 {
74 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values)
75 {
76 if (selection == group.Id)
77 {
78 mainFeatures.Add(group);
79 }
80 }
81 }
82
83 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : "";
84
85 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-6");
86
87 string contentPadding = Model.Item.GetRawValueString("ContentPadding", "");
88 contentPadding = contentPadding == "small" ? "p-2 p-md-3" : contentPadding;
89 contentPadding = contentPadding == "large" ? "p-4 p-md-5" : contentPadding;
90
91 string quantityPricesLayout = Model.Item.GetRawValueString("QuantityPricesLayout", "list");
92
93 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\"";
94 string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1";
95 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty;
96 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : "";
97
98 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower();
99 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat);
100
101 string priceMin = "";
102 string priceMax = "";
103
104 var favoriteParameters = new Dictionary<string, object>();
105 if (!anonymousUser && !hideFavoritesSelector)
106 {
107 IEnumerable<FavoriteList> favoreiteLists = Pageview.User.GetFavoriteLists();
108 int defaultFavoriteListId = 0;
109
110 if (favoreiteLists.Count() == 1) {
111 foreach (FavoriteList list in favoreiteLists) {
112 defaultFavoriteListId = list.ListId;
113 }
114 }
115
116 favoriteParameters.Add("ListId", defaultFavoriteListId);
117 }
118
119 var priceParms = new Dictionary<string, object>();
120 priceParms.Add("theme", "");
121
122 var badgeParms = new Dictionary<string, object>();
123 badgeParms.Add("size", "h7");
124 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType"));
125 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign"));
126 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign"));
127 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays"));
128 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges"));
129
130 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false;
131 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false;
132 DateTime createdDate = product.Created.Value;
133 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false;
134 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges;
135 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges;
136
137 string liveInfoClass = "";
138 string productInfoFeed = "";
139 bool isLazyLoadingForProductInfoEnabled = Dynamicweb.Ecommerce.DynamicwebLiveIntegration.TemplatesHelper.IsLazyLoadingForProductInfoEnabled;
140 if (isLazyLoadingForProductInfoEnabled)
141 {
142 if (Dynamicweb.Context.Current.Items.Contains("ProductInfoFeed"))
143 {
144 productInfoFeed = Dynamicweb.Context.Current.Items["ProductInfoFeed"]?.ToString();
145 if (!string.IsNullOrEmpty(productInfoFeed))
146 {
147 productInfoFeed = $"data-product-info-feed=\"{productInfoFeed}\"";
148 }
149 }
150 liveInfoClass = "js-live-info";
151 }
152 }
153
154 @if (!string.IsNullOrWhiteSpace(googleAnalyticsMeasurementID) && allowTracking)
155 {
156 <script>
157 gtag("event", "view_item", {
158 currency: "@product.Price.CurrencyCode",
159 value: @product.Price.Price,
160 items: [
161 {
162 item_id: "@product.Number",
163 item_name: "@product.Name",
164 currency: "@product.Price.CurrencyCode",
165 price: @product.Price.Price
166 }
167 ]
168 });
169 </script>
170 }
171
172 <div class="h-100 @(contentPadding) @(theme)
[email protected]()" @productInfoFeed>
173 <div class="d-flex flex-column gap-4 js-product" data-product-id="@product.Id">
174 @if (showBadges) {
175 <div class="swift_badge-collection">
176 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms)
177 </div>
178 }
179
180 <div class="d-flex flex-column gap-2">
181 <h1 class="@titleFontSize m-0" itemprop="name">@product.Name</h1>
182 @if (!Model.Item.GetBoolean("HideProductNumber"))
183 {
184 @RenderPartial("Paragraph/Swift_ProductNumber.cshtml", Model)
185 }
186 </div>
187
188 @if (!hidePrice && !isDiscontinued)
189 {
190 @RenderPartial("Paragraph/Swift_ProductPrice.cshtml", Model, priceParms)
191
192 if (isLazyLoadingForProductInfoEnabled)
193 {
194 <div class="product-prices-container @liveInfoClass d-none" data-show-if="LiveProductInfo.product.Prices.length > 0">
195 @if (quantityPricesLayout == "list")
196 {
197 <div class="mt-3 product-prices">
198 <small class="d-block opacity-75 product-prices-template"><span><span class="js-text-price-quantity"></span> @Translate("PCS")</span> - <span class="fw-bold"><span class="js-text-price-price"></span> <span class="d-none" data-show-if="LiveProductInfo.productPrice.Quantity > 1">@Translate("pr. PCS")</span></span></small>
199 </div>
200 }
201 else if (quantityPricesLayout == "table")
202 {
203 <div class="grid">
204 <table class="table table-sm mt-3 g-col-12 g-col-lg-6">
205 <thead>
206 <tr>
207 <td>@Translate("QTY")</td>
208 <td>@Translate("pr. PCS")</td>
209 </tr>
210 </thead>
211 <tbody class="product-prices">
212 <tr class="product-prices-template">
213 <td class="js-text-price-quantity"></td>
214 <td class="js-text-price-price"></td>
215 </tr>
216 </tbody>
217 </table>
218 </div>
219 }
220 </div>
221 }
222 else
223 {
224 if (product.Prices.Count > 0)
225 {
226 <div>
227 @if (quantityPricesLayout == "list")
228 {
229 <div class="mt-3">
230 @foreach (PriceListViewModel quantityPrice in product.Prices)
231 {
232 string quantityLabel = Translate("PCS");
233 string quantityPriceSuffix = quantityPrice.Quantity > 1 ? Translate("pr. PCS") : "";
234
235 <small class="d-block opacity-75"><span>@quantityPrice.Quantity @quantityLabel</span> - <span class="fw-bold">@quantityPrice.Price.PriceFormatted @quantityPriceSuffix</span></small>
236 }
237 </div>
238 }
239 else if (quantityPricesLayout == "table")
240 {
241 <div class="grid">
242 <table class="table table-sm mt-3 g-col-12 g-col-lg-6">
243 <thead>
244 <tr>
245 <td>@Translate("QTY")</td>
246 <td>@Translate("pr. PCS")</td>
247 </tr>
248 </thead>
249 <tbody>
250 @foreach (PriceListViewModel quantityPrice in product.Prices)
251 {
252 <tr>
253 <td>@quantityPrice.Quantity</td>
254 <td>@quantityPrice.Price.PriceFormatted</td>
255 </tr>
256 }
257 </tbody>
258 </table>
259 </div>
260 }
261 </div>
262 }
263 }
264 }
265
266 @RenderPartial("Paragraph/Swift_ProductShortDescription.cshtml", Model)
267
268 @if (mainFeatures.Count > 0)
269 {
270 foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures)
271 {
272 <dl class="grid gap-0">
273 @foreach (var field in mainFeatureGroup.Fields)
274 {
275 @RenderField(field.Value)
276 }
277 </dl>
278 }
279 }
280
281 @if (product.VariantInfo.VariantInfo != null && !Model.Item.GetBoolean("HideVariantSelector"))
282 {
283 int groupNumber = 1;
284
285 string baseUrl = $"Default.aspx?ID={GetPageIdByNavigationTag("Shop")}&GroupID={product.PrimaryOrDefaultGroup.Id}&ProductID={product.Id}";
286 string variantUrl = "";
287 if (!string.IsNullOrEmpty(product.VariantId))
288 {
289 variantUrl = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl($"Default.aspx?ID={GetPageIdByNavigationTag("Shop")}&GroupID={product.PrimaryOrDefaultGroup.Id}&ProductID={product.Id}&VariantID={product.VariantId}");
290 }
291
292 <form class="js-variant-selector" data-combinations="@string.Join(",", product.VariantCombinations())" data-base-url="@baseUrl" data-friendly-url="@variantUrl">
293 <input type="hidden" name="variantid" />
294
295 @foreach (var variantGroup in product.VariantGroups())
296 {
297 VariantGroupViewModel group = variantGroup;
298
299 <h3 class="h6">@group.Name</h3>
300 <div class="d-flex gap-2 flex-wrap js-variant-group" data-group-id="@groupNumber">
301 @foreach (var option in group.Options)
302 {
303 string active = variantId.Contains(option.Id) ? "active" : "";
304
305 if (!string.IsNullOrEmpty(option.Color))
306 {
307 string contrastColor = GetContrastColor(option.Color);
308 <button type="button" class="btn colorbox rounded-circle d-inline-block variant-option border js-variant-option @active" style="background-color: @option.Color; --variantoption-check-color: @contrastColor" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id" id="@(product.Id)_@(option.Id)
[email protected]"></button>
309 }
310 else if (!string.IsNullOrEmpty(option.Color) && !string.IsNullOrEmpty(option.Image.Value))
311 {
312 <button type="button" class="btn p-0 d-inline-block variant-option border js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id">
313 <img src="/Admin/Public/GetImage.ashx?image=@(option.Image.Value)&width=42&Format=WebP&Quality=70" />
314 </button>
315 }
316 else
317 {
318 <button type="button" class="btn btn-secondary d-inline-block variant-option js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id">
319 @option.Name
320 </button>
321 }
322 }
323 </div>
324
325 groupNumber++;
326 }
327 </form>
328 }
329
330 <div class="d-flex flex-row flex-nowrap gap-2">
331 @if (!hideAddToCart)
332 {
333 <form method="post" action="@url" class="flex-fill">
334 <input type="hidden" name="redirect" value="false" />
335 <input type="hidden" name="ProductId" value="@product.Id" />
336 <input type="hidden" name="ProductName" value="@product.Name" />
337 <input type="hidden" name="ProductPrice" value="@product.Price.Price" />
338 <input type="hidden" name="ProductCurrency" value="@product.Price.CurrencyCode" />
339 <input type="hidden" name="ProductReferer" value="product_details_info">
340 <input type="hidden" name="cartcmd" value="add" />
341
342 @if (!string.IsNullOrEmpty(product.VariantId))
343 {
344 <input type="hidden" name="VariantId" value="@product.VariantId" />
345 }
346 @if (!Model.Item.GetBoolean("QuantitySelector"))
347 {
348 <input id="
[email protected]" name="Quantity" value="@valueQty" type="hidden">
349 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary w-100 js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)
[email protected]">@Translate("Add to cart")</button>
350 } else {
351 <div class="input-group input-primary-button-group js-input-group d-flex flex-row flex-nowrap">
352 <label for="Quantity_@(product.Id)" class="visually-hidden">@Translate("Quantity")</label>
353 <input id="
[email protected]" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control" style="max-width: 96px; min-width:64px;" type="number" onkeydown="swift.Cart.UpdateOnEnterKey(event)">
354 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button @disableAddToCart" @disableAddToCart title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)
[email protected]">@Translate("Add to cart")</button>
355 </div>
356
357 if (stepQty != "1")
358 {
359 <div class="invalid-feedback d-none">
360 @Translate("Please select a quantity that is dividable by") @stepQty
361 </div>
362 }
363 }
364 </form>
365 if (!anonymousUser && !hideFavoritesSelector)
366 {
367 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
368 }
369 }
370 else if (!anonymousUser && !hideFavoritesSelector && !isDiscontinued)
371 {
372 <div class="flex-fill" id="
[email protected]">
373 @Translate("Add to favorites") @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters)
374 </div>
375 }
376
377 @if (isDiscontinued && product.ReplacementProduct != null) {
378 List<ProductInfoViewModel> replacementProductList = new List<ProductInfoViewModel>();
379 replacementProductList.Add(product.ReplacementProduct);
380 var replacementProduct = replacementProductList.GetProducts().FirstOrDefault();
381
382 if ((product.DiscontinuedAction == 0 || product.DiscontinuedAction == 1) && product?.ReplacementProduct.ProductId != null) {
383 var parms = new Dictionary<string, object>();
384 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto");
385 parms.Add("fullwidth", true);
386 parms.Add("columns", Model.GridRowColumnCount);
387
388 string imagePath = replacementProduct?.DefaultImage?.Value != null ? replacementProduct.DefaultImage.Value : "";
389
390 string link = "Default.aspx?ID=" + GetPageIdByNavigationTag("Shop");
391 link += $"&GroupID={replacementProduct.PrimaryOrDefaultGroup.Id}";
392 link += $"&ProductID={replacementProduct.Id}";
393 link += !string.IsNullOrEmpty(replacementProduct.VariantId) ? $"&VariantID={replacementProduct.VariantId}" : "";
394 link = Dynamicweb.Frontend.SearchEngineFriendlyURLs.GetFriendlyUrl(link);
395
396 <div class="w-100">
397 <div class="fw-bold w-100">@Translate("Sorry, this product is no longer available").</div>
398 <div>@Translate("We recommend this replacement product instead"):</div>
399
400 <a href="@link">
401 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms)
402 </a>
403
404 <div>@replacementProduct.Name</div>
405
406 @if (!hidePrice)
407 {
408 <div class="mb-3">
409 <div class="h4" itemprop="offers" itemscope itemtype="https://schema.org/Offer">
410 @if (showPricesWithVat == "false" && !neverShowVat)
411 {
412 if (isLazyLoadingForProductInfoEnabled)
413 {
414 <span itemprop="price" content="" class="d-none"></span>
415 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price != LiveProductInfo.product.PriceBeforeDiscount.Price"></span>
416 }
417 else
418 {
419 string beforePrice = product.PriceBeforeDiscount.PriceWithoutVatFormatted;
420
421 <span itemprop="price" content="@product.Price.PriceWithoutVat" class="d-none"></span>
422 if (product.Price.Price != product.PriceBeforeDiscount.Price)
423 {
424 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span>
425 }
426 }
427 }
428 else
429 {
430 if (isLazyLoadingForProductInfoEnabled)
431 {
432 <span itemprop="price" content="" class="d-none"></span>
433 <span class="text-decoration-line-through js-text-decoration-line-through opacity-75 me-3 text-price js-text-price d-none" data-show-if="LiveProductInfo.product.Price.Price != LiveProductInfo.product.PriceBeforeDiscount.Price"></span>
434 }
435 else
436 {
437 string beforePrice = product.PriceBeforeDiscount.PriceFormatted;
438
439 <span itemprop="price" content="@product.Price.Price" class="d-none"></span>
440 if (product.Price.Price != product.PriceBeforeDiscount.Price)
441 {
442 <span class="text-decoration-line-through opacity-75 me-3">@beforePrice</span>
443 }
444 }
445 }
446
447 @if (showPricesWithVat == "false" && !neverShowVat)
448 {
449 if (isLazyLoadingForProductInfoEnabled)
450 {
451 <span class="text-price js-text-price"><div class="spinner-border" role="status"></div></span>
452 }
453 else
454 {
455 string price = product.Price.PriceWithoutVatFormatted;
456 if (product?.VariantInfo?.VariantInfo != null)
457 {
458 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : "";
459 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : "";
460 }
461 if (priceMin != priceMax)
462 {
463 price = priceMin + " - " + priceMax;
464 }
465 <span class="text-price">@price</span>
466 }
467 }
468 else
469 {
470 if (isLazyLoadingForProductInfoEnabled)
471 {
472 <span class="text-price js-text-price"><div class="spinner-border" role="status"></div></span>
473 }
474 else
475 {
476 string price = product.Price.PriceFormatted;
477 if (product?.VariantInfo?.VariantInfo != null)
478 {
479 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : "";
480 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : "";
481 }
482 if (priceMin != priceMax)
483 {
484 price = priceMin + " - " + priceMax;
485 }
486 <span class="text-price">@price</span>
487 }
488 }
489 </div>
490
491 @if (showPricesWithVat == "false" && !neverShowVat)
492 {
493 if (isLazyLoadingForProductInfoEnabled)
494 {
495 <small class="opacity-85 fst-normal js-text-price-with-vat d-none" data-suffix="@Translate("Incl. VAT")"></small>
496 }
497 else
498 {
499 string price = product.Price.PriceWithVatFormatted;
500 if (product?.VariantInfo?.VariantInfo != null)
501 {
502 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : "";
503 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : "";
504 }
505 if (priceMin != priceMax)
506 {
507 price = priceMin + " - " + priceMax;
508 }
509 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small>
510 }
511 }
512 </div>
513 }
514
515 <a href="@link" class="btn btn-primary w-100">@Translate("Go to the replacement")</a>
516 </div>
517 }
518 }
519 </div>
520 </div>
521 @if (!hideStock)
522 {
523 if (!IsNeverOutOfStock)
524 {
525 if (isLazyLoadingForProductInfoEnabled)
526 {
527 string hideStockState = string.IsNullOrEmpty(product.VariantId) && product.VariantInfo.VariantInfo != null ? "d-none" : "";
528 <div class="js-product @liveInfoClass" data-product-id="@product.Id">
529 <div class="mt-3 js-stock-state spinner-border @hideStockState">
530 @if (!Model.Item.GetBoolean("HideInventory"))
531 {
532 <div class="small text-success d-none" data-show-if="LiveProductInfo.product.StockLevel > 0">
533 <span class="js-text-stock"></span>
534 @Translate("Products available in stock")
535 </div>
536 }
537 else
538 {
539 <div class="small text-success d-none" data-show-if="LiveProductInfo.product.StockLevel > 0">@Translate("Available in stock")</div>
540 }
541 <div class="small text-danger d-none" data-show-if="LiveProductInfo.product.StockLevel <= 0">@Translate("Out of Stock")</div>
542
543 <div class="d-none" data-show-if="LiveProductInfo.product.ExpectedDelivery != null && new Date(LiveProductInfo.product.ExpectedDelivery) > new Date()">
544 <span>@Translate("Expected back in stock:")</span>
545 <span class="js-text-expected-delivery"></span>
546 </div>
547 </div>
548 </div>
549 }
550 else
551 {
552 <div class="mt-3 js-stock-state">
553
554 @if (product.StockLevel > 0)
555 {
556 if (!Model.Item.GetBoolean("HideInventory"))
557 {
558 <div class="small text-success">@product.StockLevel @Translate("Products available in stock")</div>
559 }
560 else
561 {
562 <div class="small text-success">@Translate("Available in stock")</div>
563 }
564 }
565
566 else
567 {
568 <div class="small text-danger">@Translate("Out of Stock")</div>
569 }
570
571 @if (hasExpectedDelivery)
572 {
573 <div>
574 <span>@Translate("Expected back in stock:")</span>
575 <span>@expectedDeliveryDate</span>
576 </div>
577 }
578
579 </div>
580 }
581 }
582 }
583 </div>
584
585 @helper RenderField(FieldValueViewModel field)
586 {
587 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
588 bool noValues = false;
589
590 if (!string.IsNullOrEmpty(fieldValue))
591 {
592 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>))
593 {
594 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
595 noValues = values.Count > 0 ? false : true;
596 }
597 }
598
599 if (!string.IsNullOrEmpty(fieldValue) && noValues == false)
600 {
601 <dt class="g-col-12 g-col-sm-4 g-col-lg-12 fw-bold m-0">@field.Name</dt>
602 <dd class="g-col-12 g-col-sm-8 g-col-lg-12 mb-3">
603 @RenderFieldValue(field)
604 </dd>
605 }
606 }
607
608 @helper RenderFieldValue(FieldValueViewModel field)
609 {
610 string fieldValue = field?.Value != null ? field.Value.ToString() : "";
611
612 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue;
613 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue;
614
615 bool isColor = false;
616
617 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>))
618 {
619 int valueCount = 0;
620 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>;
621 int totalValues = values.Count;
622
623 foreach (FieldOptionValueViewModel option in values)
624 {
625 if (option.Value.Substring(0, 1) == "#")
626 {
627 isColor = true;
628 }
629
630 if (!isColor)
631 {
632 @option.Name
633 }
634 else
635 {
636 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Value"></span>
637 }
638
639 if (valueCount != totalValues && valueCount < (totalValues - 1))
640 {
641 if (isColor)
642 {
643 <text> </text>
644 }
645 else
646 {
647 <text>, </text>
648 }
649 }
650 valueCount++;
651 }
652 }
653 else
654 {
655 if (fieldValue.Substring(0, 1) == "#")
656 {
657 isColor = true;
658 }
659
660 if (!isColor)
661 {
662 @fieldValue
663 }
664 else
665 {
666 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span>
667 }
668 }
669 }
670