Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsInfo_Custom.cshtml" System.ArgumentException: An item with the same key has already been added. at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource) at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) at Dynamicweb.Ecommerce.FieldTypeProviders.ConversionUnitFieldTypeProviderBase.GetLanguageUnits(Dictionary`2 settings) at Dynamicweb.Ecommerce.FieldTypeProviders.ConversionUnitFieldTypeProviderBase.GetValue(Object value, String languageId, Dictionary`2 settings) at Dynamicweb.Ecommerce.Products.ProductFieldTypeProvider.GetProductValue(Product product, FieldType fieldType, Object fieldValue) at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldValueService.GetCategoryValue(Product product, String defaultLanguageId, IEnumerable`1 orderedGroups, Field catField, Boolean includeInheritance) at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldValueService.GetCategoryValue(Product product, String categoryId, String fieldId, Boolean includeInheritance) at Dynamicweb.Ecommerce.Products.Categories.ProductCategoryFieldValueService.GetProductCategoryFieldValue(Product product, String categoryId, Field field) at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.CreateView(ProductViewModelSettings settings, Product product, Field field) at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.GetFieldDisplayGroupValues(ProductViewModelSettings settings, Product product, String languageID, Lazy`1 productIds) at Dynamicweb.Ecommerce.ProductCatalog.ViewEngine.<>c__DisplayClass3_1.<BulkCreateView>b__54() at System.Lazy`1.CreateValue() at System.Lazy`1.LazyInitValue() at CompiledRazorTemplates.Dynamic.RazorEngine_202bbcadddf84c50aa5fde16fa63f69e.Execute() in D:\dynamicweb.net\Solutions\Mennt\tysse.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsInfo_Custom.cshtml:line 53 at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
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.Environment 7 @using Mennt.Tysse.Custom.Helpers 8 9 10 @{ 11 string googleAnalyticsTrackingID = Pageview.AreaSettings.GetString("GoogleAnalyticsTrackingID"); 12 string googleAnalyticsMeasurementID = Pageview.AreaSettings.GetString("GoogleAnalyticsMeasurementID"); 13 var cookieOptInLevel = CookieManager.GetCookieOptInLevel(); 14 bool allowTracking = cookieOptInLevel == CookieOptInLevel.All || (cookieOptInLevel == CookieOptInLevel.Functional && CookieManager.GetCookieOptInCategories().Contains("Statistical")); 15 16 ProductViewModel product = new ProductViewModel(); 17 18 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 19 { 20 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 21 } 22 23 string anonymousUsersLimitations = Pageview.AreaSettings.GetRawValueString("AnonymousUsers", ""); 24 bool anonymousUser = Pageview.User == null; 25 bool hideAddToCart = anonymousUsersLimitations.Contains("cart") && anonymousUser; 26 hideAddToCart = product.VariantInfo.VariantInfo != null && Model.Item.GetBoolean("HideVariantSelector") ? true : hideAddToCart; 27 bool hidePrice = anonymousUsersLimitations.Contains("price") && anonymousUser; 28 bool hideFavoritesSelector = !string.IsNullOrEmpty(Model.Item.GetString("HideFavoritesSelector")) ? Model.Item.GetBoolean("HideFavoritesSelector") : false; 29 30 bool IsNeverOutOfStock = product.NeverOutOfstock; 31 string[] variantId = product.VariantId.Split('.'); 32 string disableAddToCart = (product.StockLevel <= 0) ? "disabled" : ""; 33 if (IsNeverOutOfStock) 34 { 35 disableAddToCart = ""; 36 } 37 38 // Does product has a expected delivery data 39 bool hasExpectedDelivery = product.ExpectedDelivery != null && product.ExpectedDelivery > DateTime.Now; 40 string expectedDeliveryDate = product.ExpectedDelivery?.ToShortDateString() ?? ""; 41 42 string url = "/Default.aspx?ID=" + (GetPageIdByNavigationTag("CartService")); 43 if (!url.Contains("LayoutTemplate")) 44 { 45 url += url.Contains("?") ? "&LayoutTemplate=Swift_MiniCart.cshtml" : "?LayoutTemplate=Swift_MiniCart.cshtml"; 46 } 47 48 IEnumerable<string> selectedDisplayGroups = Model.Item.GetRawValueString("MainFeatures").Split(',').ToList(); 49 List<CategoryFieldViewModel> mainFeatures = new List<CategoryFieldViewModel>(); 50 51 foreach (var selection in selectedDisplayGroups) 52 { 53 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values) 54 { 55 if (selection == group.Id) 56 { 57 mainFeatures.Add(group); 58 } 59 } 60 } 61 62 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 63 64 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-6"); 65 66 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 67 contentPadding = contentPadding == "small" ? "p-2 p-md-3" : contentPadding; 68 contentPadding = contentPadding == "large" ? "p-4 p-md-5" : contentPadding; 69 70 string quantityPricesLayout = Model.Item.GetRawValueString("QuantityPricesLayout", "list"); 71 72 string minQty = product.PurchaseMinimumQuantity != 1 ? "min=\"" + product.PurchaseMinimumQuantity.ToString() + "\"" : "min=\"1\""; 73 string stepQty = product.PurchaseQuantityStep > 1 ? product.PurchaseQuantityStep.ToString() : "1"; 74 string valueQty = product.PurchaseMinimumQuantity > product.PurchaseQuantityStep ? product.PurchaseMinimumQuantity.ToString() : stepQty; 75 string qtyValidCheck = stepQty != "1" ? "onkeyup=\"swift.Cart.QuantityValidate(event)\"" : ""; 76 77 string showPricesWithVat = Pageview.Area.EcomPricesWithVat.ToLower(); 78 bool neverShowVat = string.IsNullOrEmpty(showPricesWithVat); 79 80 string priceMin = ""; 81 string priceMax = ""; 82 83 var favoriteParameters = new Dictionary<string, object>(); 84 if (!anonymousUser && !hideFavoritesSelector) 85 { 86 IEnumerable<FavoriteList> favoreiteLists = Pageview.User.GetFavoriteLists(); 87 int defaultFavoriteListId = 0; 88 89 if (favoreiteLists.Count() == 1) { 90 foreach (FavoriteList list in favoreiteLists) { 91 defaultFavoriteListId = list.ListId; 92 } 93 } 94 95 favoriteParameters.Add("ListId", defaultFavoriteListId); 96 } 97 98 var clickShop = product.ProductFields.Values.FirstOrDefault(x => x.SystemName == "ClickShop"); 99 var clickShopValue = clickShop.Value.ToString(); 100 var hideBuyInformation = product.ProductFields.Values.FirstOrDefault(x => x.SystemName == "hideBuyOption"); 101 var hideBuyInformationValue = hideBuyInformation.Value.ToString(); 102 var alternativProduct = product.ProductFields.Values.FirstOrDefault(x => x.SystemName == "UseAlternativeProduct"); 103 var alternativProductValue = alternativProduct.Value.ToString(); 104 var alternativProductNumber = product.ProductFields.Values.FirstOrDefault(x => x.SystemName == "AlternativeProduct"); 105 var alternativProductNumberValue = alternativProductNumber.Value.ToString(); 106 107 var productItem = Dynamicweb.Ecommerce.Products.Product.GetProductById(product.Id); 108 var imageService = new Dynamicweb.Ecommerce.Products.ProductImageService(); 109 var image = "https://tysse.no" + imageService.GetImagePath(productItem); 110 111 string iconPath = "/Files/Icons/"; 112 var counties = Dynamicweb.Security.UserManagement.Group.GetGroupByID(112); 113 var subGroups = counties.Subgroups; 114 var i = 0; 115 116 var test = Dynamicweb.Ecommerce.Products.Product.GetProductById(product.Id); 117 var groupID = test.GetDefaultGroupByShopId("SHOP1"); 118 var parent = groupID.PrimaryParentGroupId; 119 120 Mennt.Tysse.Custom.Models.Dealer selectedDealer = DealerHelper.SelectedDealer(); 121 int dealerStock = DealerHelper.GetDealerStock(product.Id); 122 var dealersByArea = DealerHelper.GetDealersByArea(product.Id); 123 124 var groups = ""; 125 var spareParts = "false"; 126 foreach (var x in test.Groups) 127 { 128 groups = x.Name; 129 } 130 131 if (groups.Contains("Reservedeler")) 132 { 133 spareParts = "true"; 134 } 135 136 var trailerCategory = productItem?.GetCategoryValue("Tilhenger", "AreaOfUse")?.ToString().Replace("Leisure", "Fritid") != null ? productItem?.GetCategoryValue("Tilhenger", "AreaOfUse")?.ToString().Replace("Leisure", "Fritid") : ""; 137 } 138 139 <div aria-live="polite" aria-atomic="true"> 140 <div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 100000;"> 141 <a href="/handlekurv"> 142 <div id="cartNotificationToast" class="toast" data-bs-delay="2000" role="alert" aria-live="assertive" aria-atomic="true"> 143 <div class="toast-header"> 144 <strong id="cartNotificationToastHeader" class="me-auto">@Translate("Added to cart")</strong> 145 146 </div> 147 <div class="toast-body d-flex gap-3 theme theme-gray"> 148 <img id="cartNotificationToast_Image" height="50" src="@image" /> 149 <div id="cartNotificationToast_Text"></div> 150 </div> 151 </div> 152 </a> 153 </div> 154 </div> 155 156 @if (!string.IsNullOrWhiteSpace(googleAnalyticsMeasurementID) && allowTracking) 157 { 158 <script> 159 gtag("event", "view_item", { 160 currency: "@product.Price.CurrencyCode", 161 value: @product.Price.Price.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture), 162 items: [ 163 { 164 item_id: "@product.Number", 165 item_name: '@product.Name', 166 currency: "@product.Price.CurrencyCode", 167 price: @product.Price.Price.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture), 168 item_category: "Klikk og hent", 169 item_category2: "@trailerCategory" 170 } 171 ] 172 }); 173 </script> 174 175 <script> 176 gtag("event", "view_item", { 177 currency: "@product.Price.CurrencyCode", 178 value: @product.Price.Price.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture), 179 items: [ 180 { 181 item_id: "@product.Number", 182 item_name: '@product.Name', 183 currency: "@product.Price.CurrencyCode", 184 price: @product.Price.Price.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture), 185 item_category: "Sendes i post", 186 item_category2: "@trailerCategory" 187 } 188 ] 189 }); 190 </script> 191 } 192 193 <div class="h-100 @(contentPadding) @(theme)"> 194 <div class="d-flex flex-column js-product"> 195 @if (Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Mobile || Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Tablet) 196 { 197 <div> 198 <h1 class="@titleFontSize pt-4 mb-1" style="word-break: break-all;" itemprop="name">@product.Name</h1> 199 @if (!Model.Item.GetBoolean("HideProductNumber")) 200 { 201 <div class="opacity-85 pb-0 productNumber" id="productNumber" style="font-size:14px">@product.Number</div> 202 } 203 @if (alternativProductValue == "1") 204 { 205 <div>@Translate("UTGÅTT VARE - Erstattet av:") <a href="reservedeler-og-ekstrautstyr?productid=@alternativProductNumberValue">@alternativProductNumberValue</a></div> 206 <a class="btn btn-primary mt-3" href="reservedeler-og-ekstrautstyr?productid=@alternativProductNumberValue">@Translate("Se erstatningsvare")</a> 207 } 208 @if (!string.IsNullOrEmpty(product.ShortDescription)) 209 { 210 <div class="mb-0-last-child pb-0 pt-3" itemprop="disambiguatingDescription" style="font-size:14px;"> 211 @product.ShortDescription 212 </div> 213 } 214 </div> 215 } 216 else{ 217 <div> 218 <h1 class="@titleFontSize mb-1 @product.Price.Price" itemprop="name">@product.Name</h1> 219 @if ( hideBuyInformationValue == "True" ) 220 { 221 <div>@Translate("IKKE SALGSVARE - Klikk på produkt for å få frem stykkliste")</div> 222 } 223 @if (alternativProductValue == "1") 224 { 225 <div>@Translate("UTGÅTT VARE - Erstattet av:") <a href="reservedeler-og-ekstrautstyr?productid=@alternativProductNumberValue">@alternativProductNumberValue</a></div> 226 <a class="btn btn-primary mt-3" href="reservedeler-og-ekstrautstyr?productid=@alternativProductNumberValue">@Translate("Se erstatningsvare")</a> 227 } 228 @if (!Model.Item.GetBoolean("HideProductNumber")) 229 { 230 <div class="opacity-85 pb-0 small-text productNumber" id="productNumber">@product.Number</div> 231 } 232 <!--product Short Description--> 233 @if (!string.IsNullOrEmpty(product.ShortDescription)) 234 { 235 <div class="mb-0-last-child pt-2 regular-text" style="max-width: 750px;" itemprop="disambiguatingDescription"> 236 @product.ShortDescription 237 </div> 238 } 239 </div> 240 } 241 242 243 <!--Priser for frakt start--> 244 @if (spareParts != "true") { 245 246 247 <!-- Button trigger modal --> 248 <div type="button" class="small-text opacity-85 pt-1 mb-4" data-bs-toggle="modal" data-bs-target="#exampleModal"> 249 <a class="text-decoration-underline">Prisene kan variere pga. frakt og registreringsomkostninger</a> 250 </div> 251 } 252 <!-- Modal --> 253 <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> 254 <div class="modal-dialog"> 255 <div class="modal-content"> 256 <div class="modal-header theme dark"> 257 <h5 class="modal-title theme dark" id="exampleModalLabel">Frakt</h5> 258 <div type="button" class=" me-3 theme dark d-flex align-items-center justify-content-evenly" data-bs-dismiss="modal" aria-label="Close"> 259 <span class="theme dark pe-2"> Lukk</span> 260 <span> @ReadFile(iconPath + "x.svg")</span> 261 </div> 262 </div> 263 <div class="modal-body"> 264 265 266 <!--Accordions start--> 267 <div class="accordion" id="accordionExample"> 268 <div class="accordion-item"> 269 <h2 class="accordion-header" id="headingOne"> 270 <button class="accordion-button collapsed regular-bold-text" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne"> 271 @Translate("Sør,-Vest-, Østlandet") 272 </button> 273 </h2> 274 <div id="collapseOne" class="accordion-collapse collapse regular-bread-text" aria-labelledby="headingOne" data-bs-parent="#accordionExample"> 275 <div class="accordion-body"> 276 @Translate("Ved ikke lagerførte tilhengere kan det tilkomme frakt.<br> Kontakt din forhandler for avklaring <br> Frakt skaphengere uavhengig av lagerstatus:<br> kr 3.600,- inkl.mva.") 277 278 279 </div> 280 </div> 281 </div> 282 <div class="accordion-item"> 283 <h2 class="accordion-header" id="headingTwo"> 284 <button class="accordion-button collapsed regular-bold-text" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"> 285 @Translate("Trøndelag") 286 </button> 287 </h2> 288 <div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample"> 289 <div class="accordion-body regular-bread-text"> 290 @Translate("Ved ikke lagerførte tilhengere kan det tilkomme frakt.<br> Kontakt din forhandler for avklaring <br> Frakt skaphengere uavhengig av lagerstatus:<br> kr 4.300,- inkl.mva.") 291 292 </div> 293 </div> 294 </div> 295 <div class="accordion-item"> 296 <h2 class="accordion-header" id="headingThree"> 297 <button class="accordion-button collapsed regular-bold-text" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree"> 298 @Translate("Nordland t.o.m Bodø") 299 </button> 300 </h2> 301 <div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample"> 302 <div class="accordion-body regular-bread-text"> 303 @Translate("Frakttillegg kr 875,-*<br>Unntak med egne fraktpriser:<br> Skaphengere: kr 6.500,-<br> Vare-, Bil/Maskin- og Tipphengere i PRO+ og XTREME-seriene og båthenger 6426: kr 5.000,-<br> Aluminium og plastlokk t.o.m størrelse 250/150: kr 2.900,-<br> Aluminium og plastlokk f.o.m størrelse 320/150: kr 4.300,-<br><br>*) Ved ikke lagerførte tilhengere kan det tilkomme egen fraktkostnad. Kontakt din forhandler for avklaring") 304 </div> 305 </div> 306 </div> 307 <div class="accordion-item"> 308 <h2 class="accordion-header" id="headingFour"> 309 <button class="accordion-button collapsed regular-bold-text" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour"> 310 @Translate("Nordland nord for Bodø - Tromsø") 311 </button> 312 </h2> 313 <div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour" data-bs-parent="#accordionExample"> 314 <div class="accordion-body regular-bread-text"> 315 @Translate("Frakttillegg kr 1.125,-*<br> Unntak med egne fraktpriser:<br> Skaphengere: kr 7.250,-<br> Vare-, Bil/Maskin- og Tipphengere i PRO+ og XTREME-seriene og båthenger 6426: kr 5.750,-<br> Aluminium og plastlokk t.o.m størrelse 250/150: kr 2.900,-<br> Aluminium og plastlokk f.o.m størrelse 320/150: kr 4.300,-<br><br>*) Ved ikke lagerførte tilhengere kan det tilkomme egen fraktkostnad. Kontakt din forhandler for avklaring") 316 </div> 317 </div> 318 </div> 319 <div class="accordion-item"> 320 <h2 class="accordion-header" id="headingFive"> 321 <button class="accordion-button collapsed regular-bold-text" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFive" aria-expanded="false" aria-controls="collapseFive"> 322 @Translate("Troms og Finnmark nord for Tromsø") 323 </button> 324 </h2> 325 <div id="collapseFive" class="accordion-collapse collapse" aria-labelledby="headingFive" data-bs-parent="#accordionExample"> 326 <div class="accordion-body regular-bread-text"> 327 @Translate("Frakt tilkommer på alle tilhengere nord for Tromsø. <br> Kontakt din forhandler for pris.") 328 </div> 329 </div> 330 </div> 331 </div> 332 <!--Accordions end--> 333 </div> 334 </div> 335 </div> 336 </div> 337 338 339 <!--Priser for frakt end--> 340 341 342 @if (hideBuyInformationValue == "False") 343 { 344 if (!hidePrice) 345 { 346 <div> 347 <div class="h4 mb-0" itemprop="offers" itemscope itemtype="https://schema.org/Offer"> 348 <span itemprop="priceCurrency" content="@product.Price.CurrencyCode" class="d-none"></span> 349 350 @if (showPricesWithVat == "false" && !neverShowVat) 351 { 352 string beforePrice = product.PriceBeforeDiscount.PriceWithoutVatFormatted; 353 354 <span itemprop="price" content="@product.Price.PriceWithoutVat" class="d-none"></span> 355 if (product.Price.Price != product.PriceBeforeDiscount.Price) 356 { 357 <span class="text-decoration-line-through opacity-50 me-3">@beforePrice</span> 358 } 359 } 360 else 361 { 362 string beforePrice = product.PriceBeforeDiscount.PriceFormatted; 363 364 <span itemprop="price" content="@product.Price.Price" class="d-none"></span> 365 if (product.Price.Price != product.PriceBeforeDiscount.Price) 366 { 367 <span class="text-decoration-line-through opacity-50 me-3">@beforePrice</span> 368 } 369 } 370 371 @if (showPricesWithVat == "false" && !neverShowVat) 372 { 373 string price = product.Price.PriceWithoutVatFormatted; 374 if (product?.VariantInfo?.VariantInfo != null) 375 { 376 priceMin = product?.VariantInfo?.PriceMin?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithoutVatFormatted : ""; 377 priceMax = product?.VariantInfo?.PriceMax?.PriceWithoutVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithoutVatFormatted : ""; 378 } 379 if (priceMin != priceMax) 380 { 381 price = priceMin + " - " + priceMax; 382 } 383 <span class="text-price">@price</span> 384 } 385 else 386 { 387 string price = product.Price.PriceFormatted; 388 string priceWithoutVat = product.Price.PriceWithoutVatFormatted; 389 if (product?.VariantInfo?.VariantInfo != null) 390 { 391 priceMin = product?.VariantInfo?.PriceMin?.PriceFormatted != null ? product.VariantInfo.PriceMin.PriceFormatted : ""; 392 priceMax = product?.VariantInfo?.PriceMax?.PriceFormatted != null ? product.VariantInfo.PriceMax.PriceFormatted : ""; 393 } 394 if (priceMin != priceMax) 395 { 396 price = priceMin + " - " + priceMax; 397 } 398 if (alternativProductValue != "1") 399 { 400 <span class="text-price netPrice display-3 pt-3 pb-1">@price</span> 401 <span class="text-price priceWithoutVat" style="display: none;">@priceWithoutVat <span style="font-size: 14px;">@Translate("eks.MVA")</span></span> 402 } 403 } 404 </div> 405 406 @*if (showPricesWithVat == "false" && !neverShowVat) 407 { 408 string price = product.Price.PriceWithVatFormatted; 409 if (product?.VariantInfo?.VariantInfo != null) 410 { 411 priceMin = product?.VariantInfo?.PriceMin?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMin.PriceWithVatFormatted : ""; 412 priceMax = product?.VariantInfo?.PriceMax?.PriceWithVatFormatted != null ? product.VariantInfo.PriceMax.PriceWithVatFormatted : ""; 413 } 414 if (priceMin != priceMax) 415 { 416 price = priceMin + " - " + priceMax; 417 } 418 <small class="opacity-85 fst-normal">@price @Translate("Incl. VAT")</small> 419 }*@ 420 421 @if (product.Prices.Count > 0) { 422 if (quantityPricesLayout == "list") { 423 <div class="mt-3"> 424 @foreach (PriceListViewModel quantityPrice in product.Prices) 425 { 426 string quantityLabel = Translate("PCS"); 427 string quantityPriceSuffix = quantityPrice.Quantity > 1 ? Translate("pr. PCS") : ""; 428 429 <small class="d-block opacity-75"><span>@quantityPrice.Quantity @quantityLabel</span> - <span class="fw-bold">@quantityPrice.Price.PriceFormatted @quantityPriceSuffix</span></small> 430 } 431 </div> 432 } else if (quantityPricesLayout == "table") { 433 <div class="grid"> 434 <table class="table table-sm mt-3 g-col-12 g-col-lg-6"> 435 <thead> 436 <tr> 437 <td>@Translate("QTY")</td> 438 <td>@Translate("pr. PCS")</td> 439 </tr> 440 </thead> 441 <tbody> 442 @foreach (PriceListViewModel quantityPrice in product.Prices) 443 { 444 <tr> 445 <td>@quantityPrice.Quantity</td> 446 <td>@quantityPrice.Price.PriceFormatted</td> 447 </tr> 448 } 449 </tbody> 450 </table> 451 </div> 452 } 453 } 454 </div> 455 } 456 } 457 <!-- Start Form--> 458 @if (mainFeatures.Count > 0) 459 { 460 foreach (CategoryFieldViewModel mainFeatureGroup in mainFeatures) 461 { 462 <div class="border-top pt-2"></div> 463 <dl class="gap-0 mb-0" style="column-count: 2;"> 464 @foreach (var field in mainFeatureGroup.Fields) 465 { 466 @RenderField(field.Value) 467 } 468 </dl> 469 <div class="border-top pt-2"></div> 470 } 471 } 472 473 @if (product.VariantInfo.VariantInfo != null && !Model.Item.GetBoolean("HideVariantSelector")) 474 { 475 int groupNumber = 1; 476 477 <form class="mb-3 js-variant-selector" data-combinations="@string.Join(",", product.VariantCombinations())"> 478 <input type="hidden" name="variantid" /> 479 480 @foreach (var variantGroup in product.VariantGroups()) 481 { 482 VariantGroupViewModel group = variantGroup; 483 484 <h3 class="h6">@group.Name</h3> 485 <div class="mb-3 js-variant-group" data-group-id="@groupNumber"> 486 @foreach (var option in group.Options) 487 { 488 string active = variantId.Contains(option.Id) ? "active" : ""; 489 490 if (!string.IsNullOrEmpty(option.Color)) 491 { 492 <button type="button" class="btn colorbox rounded-circle me-1 mb-2 d-inline-block variant-option js-variant-option @active" style="background-color: @option.Color" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id"></button> 493 } 494 else if (!string.IsNullOrEmpty(option.Color) && !string.IsNullOrEmpty(option.Image.Value)) 495 { 496 <button type="button" class="btn p-0 d-inline-block mb-2 variant-option js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id"> 497 <img src="/Admin/Public/GetImage.ashx?image=@(option.Image.Value)&width=42&Format=WebP&Quality=70" /> 498 </button> 499 } 500 else 501 { 502 <button type="button" class="btn btn-secondary d-inline-block mb-2 variant-option js-variant-option @active" onclick="swift.VariantSelector.OptionClick(event)" data-variant-id="@option.Id"> 503 @option.Name 504 </button> 505 } 506 } 507 </div> 508 509 groupNumber++; 510 } 511 </form> 512 } 513 <!--End form--> 514 @* Add to cart *@ 515 @if (hideBuyInformationValue == "False") 516 { 517 var areaOfUse = ""; 518 519 foreach (var group in product.ProductCategories) { 520 CategoryFieldViewModel category = group.Value; 521 522 foreach (var field in category.Fields) { 523 if (field.Key == "AreaOfUse") 524 { 525 areaOfUse = field.Value.ToString(); 526 } 527 } 528 } 529 530 if (alternativProductValue != "1") 531 { 532 533 534 <div class="grid gap-2 gap-md-3 mt-4"> 535 536 <div class="g-col-6 g-col-md-6"> 537 <!-- START KLIKK OG HENT --> 538 <div> 539 <form method="post" action="@url" class="flex-fill"> 540 <input type="hidden" name="redirect" value="false" /> 541 <input type="hidden" name="ProductId" value="@product.Id" /> 542 <input type="hidden" name="ProductName" value='@product.Name' /> 543 <input type="hidden" name="ProductPrice" value="@product.Price.PriceWithVat" /> 544 <input type="hidden" name="ProductCurrency" value="NOK" /> 545 <input type="hidden" name="ordercontext" value="ORDERCONTEXT1" /> 546 <input type="hidden" name="ProductImage" value="@image"> 547 <input type="hidden" name="cartcmd" value="add"> 548 <input type="hidden" name="cartType" value="Klikk og hent" /> 549 <input type="hidden" name="TrailerType" value="@areaOfUse" /> 550 551 552 @if(selectedDealer != null) { 553 <button type="submit" onclick="swift.Cart.Update(event)" class="btn btn-primary js-add-to-cart-button w-100 d-flex align-items-center justify-content-center clickButton d-block"> 554 @Translate("Klikk og hent") 555 <span class="ps-3 icon-button">@ReadFile(iconPath + "Klikkoghent.svg")</span> 556 </button> 557 } 558 else { 559 <input id="selectedDealerAddToCart_@product.Id" type="hidden" onclick="swift.Cart.Update(event).then(() => {location.reload();})" /> 560 <button type="button" data-bs-toggle="modal" data-bs-target="#FindDealer" data-productid="@product.Id" data-productname="@product.Name" data-productimage="@image" data-addtocart="true" data-group="@spareParts" class="btn btn-primary js-add-to-cart-button w-100 d-flex align-items-center justify-content-center clickButton d-block"> 561 @Translate("Klikk og hent") 562 <span class="ps-3 icon-button">@ReadFile(iconPath + "Klikkoghent.svg")</span> 563 </button> 564 } 565 </form> 566 <button data-bs-toggle="modal" data-bs-target="#FindDealer" data-group="@spareParts" data-productid="@product.Id" class="btn btn-primary w-100 d-flex align-items-center justify-content-center modalButton d-none dealer-modal">@Translate("Klikk og hent")<span class="ps-3 icon-button">@ReadFile("/Files/icons/Klikk og hent.svg")</span></button> 567 568 <!-- USIKKER LAGERSTATUS --> 569 570 571 572 @if(selectedDealer != null){ 573 574 <div data-productid="@product.Id"> 575 @if (spareParts == "true") { 576 <div class=""> 577 <span class="icon-2" style="color: #D79C04;"> @ReadFile(iconPath + "alert-circle.svg")</span> 578 <span class="x-small-text-bold">@Translate("Usikker lagerstatus")</span> 579 </div> 580 } 581 582 else{ 583 584 585 if(dealerStock <= 0){ 586 <div data-productid="@product.Id" class="notInStock"> 587 <span class="icon-2" style="color: grey;"> @ReadFile(iconPath + "alert-circle.svg")</span> 588 <span class="x-small-text-bold">@Translate("Ikke på lager")</span> 589 <div class="x-small-text">@Translate("Kontakt oss for leveringstid")</div> 590 <div class="x-small-text">@Translate("Tlf:") @selectedDealer.Phone</div> 591 </div> 592 } 593 else 594 { 595 <div data-productid="@product.Id" class="inStock"> 596 <span class="icon-2" style="color: green;"> @ReadFile(iconPath + "check-circle.svg")</span> 597 <span data-productid="@product.Id" class="x-small-text-bold stockText">@Translate("På lager")</span> 598 </div> 599 } 600 601 } 602 603 <hr class="my-1" style="margin: 0" /> 604 <!-- VELG BUTIKK--> 605 @if (spareParts == "true") { 606 <div class="mt-1"> 607 <div class="small-text">@Translate("")</div> 608 </div> 609 } 610 611 612 <div> 613 <div class="dealerNameSlider x-small-text">@selectedDealer.Name</div> 614 <div data-bs-toggle="modal" data-bs-target="#FindDealer" data-productid="@product.Id" data-group="@spareParts" class="x-small-text text-decoration-underline dealer-modal" style=" cursor: pointer;">Endre butikk</div> 615 </div> 616 617 @RenderChooseDealer() 618 619 </div> 620 } 621 else 622 { 623 <!-- Ingen butikk valgt start--> 624 <div data-productid="@product.Id" class="noDealer d-block"> 625 <div class=""> 626 <span class="icon-2" style="color: #A9A9A9;"> @ReadFile(iconPath + "home.svg")</span> 627 <span class="x-small-text-bold">@Translate("Ingen butikk valgt")</span> 628 </div> 629 <hr class="my-1" style="margin: 0" /> 630 <div data-bs-toggle="modal" data-bs-target="#FindDealer" data-productid="@product.Id" data-group="@spareParts" class="x-small-text text-decoration-underline dealer-modal" style=" cursor: pointer;">Velg butikk for lagerstatus</div> 631 </div> 632 <!-- Ingen butikk valgt end--> 633 } 634 </div> 635 <!-- END KLIKK OG HENT --> 636 </div> 637 638 @if (clickShopValue == "2") 639 { 640 <div class="g-col-6 g-col-md-6"> 641 <!-- START SENDES I POST --> 642 <div> 643 @if (!hideAddToCart) 644 { 645 <form method="post" action="@url" class="flex-fill"> 646 <input type="hidden" name="redirect" value="false" /> 647 <input type="hidden" name="ProductId" value="@product.Id" /> 648 <input type="hidden" name="ProductName" value='@product.Name' /> 649 <input type="hidden" name="ProductPrice" value="@product.Price.PriceWithVat" /> 650 <input type="hidden" name="ProductCurrency" value="NOK" /> 651 <input type="hidden" name="cartcmd" value="add" /> 652 <input type="hidden" name="ProductImage" value="@image"> 653 <input type="hidden" name="cartType" value="Sendes i post" /> 654 <input type="hidden" name="TrailerType" value="@areaOfUse" /> 655 656 @if (!string.IsNullOrEmpty(product.VariantId)) 657 { 658 <input type="hidden" name="VariantId" value="@product.VariantId" /> 659 } 660 @if (!Model.Item.GetBoolean("QuantitySelector")) 661 { 662 663 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" type="hidden"> 664 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary js-add-to-cart-button d-flex align-items-center w-100 justify-content-center" title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)">@Translate("Add to cart")<span class="ps-3 icon-button"> @ReadFile(iconPath + "shopping-cart.svg")</span> 665 </button> 666 } else { 667 <div class="input-group input-primary-button-group js-input-group d-flex flex-row flex-nowrap"> 668 <label for="Quantity_@(product.Id)" class="visually-hidden">@Translate("Quantity")</label> 669 <input id="Quantity_@product.Id" name="Quantity" value="@valueQty" step="@stepQty" @minQty class="form-control" style="max-width: 96px; min-width:64px;" type="number"> 670 <button type="button" onclick="swift.Cart.Update(event)" class="btn btn-primary flex-fill js-add-to-cart-button" title="@Translate("Add to cart")" id="AddToCartButton@(product.Id)">@Translate("Add to cart")</button> 671 </div> 672 673 if (stepQty != "1") 674 { 675 <div class="invalid-feedback d-none"> 676 @Translate("Please select a quantity that is dividable by") @stepQty 677 </div> 678 } 679 } 680 </form> 681 if (!anonymousUser && !hideFavoritesSelector) 682 { 683 @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters) 684 } 685 686 <!-- LAGERSTATUS SENDES I POST START --> 687 688 if (!Model.Item.GetBoolean("HideStockState")){ 689 690 691 692 if (!IsNeverOutOfStock) 693 { 694 <div class=" js-stock-state"> 695 696 @if (product.StockLevel > 0) 697 { 698 699 if (!Model.Item.GetBoolean("HideInventory")) 700 { 701 @*<p class="small text-success m-0">@product.StockLevel @Translate("Products available in stock")</p>*@ 702 703 704 <div class=""> 705 <span class="icon-2" style="color: #258D5B;"> @ReadFile(iconPath + "check-circle.svg")</span> 706 <span class="x-small-text-bold">@Translate("På lager")</span> 707 </div> 708 709 710 <hr class="my-1" style="" /> 711 712 if (product.StockLevel > 100) 713 { 714 <div class="x-small-text">Antall på nettlager </div> 715 <div class="x-small-text">(100+)</div> 716 } 717 else if (product.StockLevel > 50) 718 { 719 <div class="sx-small-text pt-2">Antall på nettlager </div> 720 <div class="x-small-text">(50+)</div> 721 } 722 else if (product.StockLevel > 10) 723 { 724 <div class="x-small-text pt-2">Antall på nettlager </div> 725 <div class="x-small-text">(10+)</div> 726 } 727 else 728 { 729 <div class="x-small-text"> Antall på nettlager:</div> 730 <div class="x-small-text">@product.StockLevel stk</div> 731 } 732 } 733 } 734 735 else 736 { 737 738 <div> 739 <div class=""> 740 <span class="icon-2" style="color: grey;"> @ReadFile(iconPath + "alert-circle.svg")</span> 741 <span class="ms-2 regular-bold-text">@Translate("Ikke på lager")</span> 742 </div> 743 </div> 744 } 745 746 @if (hasExpectedDelivery) 747 { 748 <p> 749 <span>@Translate("Expected back in stock:")</span> 750 <span>@expectedDeliveryDate</span> 751 </p> 752 } 753 </div> 754 } 755 } 756 <!-- LAGERSTATUS SENDES I POST SLUTT --> 757 758 } 759 else if (!anonymousUser && !hideFavoritesSelector) 760 { 761 <div class="flex-fill"> 762 @Translate("Add to favorites") @RenderPartial("Components/ToggleFavorite.cshtml", product, favoriteParameters) 763 </div> 764 } 765 </div> 766 </div> 767 <!-- SLUTT SENDES I POST --> 768 } 769 </div> 770 } 771 772 } 773 </div> 774 775 <div class="d-none"> 776 @if (product.ProductCategories != null) { 777 if (product.ProductCategories.Count > 0) { 778 foreach (var group in product.ProductCategories) { 779 CategoryFieldViewModel category = group.Value; 780 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 781 782 @*if (!hideHeader) { 783 <h4 class="h4 mb-4">@group.Key</h4> 784 }*@ 785 786 if (product.ProductCategories.Count == 2) 787 { 788 if (group.Key == "Varehenger") 789 { 790 @RenderFieldsFromList(category.Fields); 791 } 792 } 793 else 794 { 795 @RenderFieldsFromList(category.Fields); 796 } 797 } 798 } 799 } 800 </div> 801 </div> 802 803 @helper RenderFieldsFromList(Dictionary<string, FieldValueViewModel> fields) { 804 <div class="mb-0 mt-3"> 805 <dl class="grid gap-0"> 806 @foreach (var field in fields) { 807 @RenderField(field.Value) 808 } 809 </dl> 810 </div> 811 } 812 813 @helper RenderField(FieldValueViewModel field) 814 { 815 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 816 bool noValues = false; 817 818 if (!string.IsNullOrEmpty(fieldValue)) 819 { 820 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 821 { 822 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 823 noValues = values.Count > 0 ? false : true; 824 } 825 } 826 827 if (!string.IsNullOrEmpty(fieldValue) && noValues == false) 828 {if 829 (Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Mobile || Pageview.Device == Dynamicweb.Frontend.Devices.DeviceType.Tablet) 830 { 831 832 <dt class="g-col-7 fw-normal pb-2" style="font-size:14px"> 833 <div class="product-info-text-bold">@field.Name</div> 834 <div class="product-info-text">@RenderFieldValue(field)</div> 835 </dt> 836 837 838 } 839 else{ 840 <div class="grid"> 841 <dt class="g-col-6 fw-normal small-text-extra-bold product-info-text-bold pb-2"> <span>@field.Name</span></dt> 842 <dt class="g-col-6 align-self-center pb-2 ms-2 small-text product-info product-info-text">@RenderFieldValue(field)</dt> 843 </div> 844 } 845 } 846 } 847 848 @helper RenderFieldValue(FieldValueViewModel field) 849 { 850 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 851 852 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue; 853 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue; 854 855 bool isColor = false; 856 857 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>)) 858 { 859 int valueCount = 0; 860 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 861 int totalValues = values.Count; 862 863 foreach (FieldOptionValueViewModel option in values) 864 { 865 if (option.Value.Substring(0, 1) == "#") 866 { 867 isColor = true; 868 } 869 870 if (!isColor) 871 { 872 @option.Name 873 } 874 else 875 { 876 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Value"></span> 877 } 878 879 if (valueCount != totalValues && valueCount < (totalValues - 1)) 880 { 881 if (isColor) 882 { 883 <text> </text> 884 } 885 else 886 { 887 <text>, </text> 888 } 889 } 890 valueCount++; 891 } 892 } 893 else 894 { 895 if (fieldValue.Substring(0, 1) == "#") 896 { 897 isColor = true; 898 } 899 900 if (!isColor) 901 { 902 @fieldValue 903 } 904 else 905 { 906 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span> 907 } 908 } 909 } 910 911 @if (product.VariantInfo.VariantInfo != null) 912 { 913 <script type="module"> 914 swift.VariantSelector.init(); 915 </script> 916 } 917 918 <script> 919 920 921 document.addEventListener('DOMContentLoaded', function (event) { 922 window.onload = function() { 923 } 924 925 926 </script> 927 928 @using Mennt.Tysse.Custom.Helpers 929 930 @helper RenderChooseDealer() 931 { 932 <div class="modal fade" id="FindDealer" tabindex="-1" aria-labelledby="FindDealerModal" aria-hidden="true"> 933 <div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable"> 934 <div class="modal-content"> 935 <!-- Loader --> 936 <div class="text-center loader" style="display: block;"> 937 <div class="spinner-border text-primary" role="status"> 938 <span class="visually-hidden">Loading...</span> 939 </div> 940 </div> 941 <div id="findDealerContent" style="overflow:scroll"></div> 942 </div> 943 </div> 944 </div> 945 } 946 947 948
Produktbeskrivelse
Tysse skaphengere er blant markedets kraftigste, og skapet er bygget av 12mm finèrplater med solide aluminiumslister. Kraftige karmer rundt bakdører stiver av skapet ytterligere. Platene har en kjerne av bjærkekryssfiner som er belagt med glatt og blank ABS-basert overlegg. Dette gir en overlate som er svært enkel å rengjøre, og også godt egnet til dekor om dette ønskes. Tysse skaphenger har nedfelte surrefester i stålskinner. Disse er nedfelte og kommer dermed ikke i konflikt med last. Vær obs på at oppgitt kassemål er innvendige mål og at selve lysåpning i døråpning er ca 10 mindre grunnet dørkarm. Takrails er standard og er beregnet på ca 50kg jevnt fordelt last. Dette er ideelt til stiger, plastrør og annet gods som en ikke får med seg inne i skapvognen.