The product was added to your favourites list The product was removed from your favourites list
Otto schachner logo
Error executing template "Designs/OttoSchachner/eCom/ProductCatalog/ProductView.cshtml"
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at CompiledRazorTemplates.Dynamic.RazorEngine_1dd98e66fe1c42ce946c5fb177b26448.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   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.Ecommerce.ProductCatalog.ProductViewModel> 2 @using System.Diagnostics.SymbolStore 3 @using Dynamicweb.Content 4 @using Dynamicweb.Content.Data 5 @using Dynamicweb.Core 6 @using Dynamicweb.Ecommerce 7 @using Dynamicweb.Ecommerce.CustomerExperienceCenter.Favorites 8 @using Dynamicweb.Ecommerce.Prices 9 @using Dynamicweb.Frontend 10 @using Dynamicweb.Frontend.Devices 11 @using Dynamicweb.Frontend.Navigation 12 @using Dynamicweb.Ecommerce.ProductCatalog 13 @using Dynamicweb.Ecommerce.Products 14 @using Dynamicweb.Ecommerce.Shops 15 @using Dynamicweb.Ecommerce.Variants 16 @using Dynamicweb.Rendering 17 @using OttoSchachner.CustomCode.Helpers 18 @using Dynamicweb.Ecommerce.DynamicwebLiveIntegration.Products 19 20 @{ 21 var isCsv = string.Equals(Dynamicweb.Context.Current.Request.QueryString["download"], "csv", StringComparison.OrdinalIgnoreCase); 22 if (isCsv) 23 { 24 25 var fileVirtual = OttoSchachner.CustomCode.Helpers.ProductCsvExport.CreateTempCsvFile( 26 Model.Id, 27 Model.VariantId, 28 Model.LanguageId, 29 Model, 30 Pageview.User 31 ); 32 33 Dynamicweb.Context.Current.Response.Redirect(fileVirtual); 34 return; 35 } 36 37 38 var isZip = string.Equals(Dynamicweb.Context.Current.Request.QueryString["download"], "imageszip", StringComparison.OrdinalIgnoreCase); 39 if (isZip) 40 { 41 var zipVirtual = OttoSchachner.CustomCode.Helpers.ProductImagesZipExport.CreateTempImagesZipFile( 42 Model.Id, 43 Model.VariantId, 44 Model.LanguageId, 45 Model 46 ); 47 48 Dynamicweb.Context.Current.Response.Redirect(zipVirtual); 49 return; 50 } 51 52 ProductService productService = new ProductService(); 53 ProductQuantityPrices matrixProvider = new ProductQuantityPrices(); 54 var productPriceMatrix = matrixProvider.GetProductQuantityPrices(productService.GetProductById(Model.Id, Model.VariantId, Model.LanguageId)); 55 var uniquePricesMatrix = productPriceMatrix 56 .GroupBy(p => new { p.Quantity, Unit = Model.ProductFields["VjmSalesUnit"].Value, Amount = Dynamicweb.Core.Converter.ToDouble(p.Amount) }) 57 .Select(g => g.First()) 58 .ToList(); 59 60 } 61 62 @{ 63 64 65 string RenderPriceMatrix(List<ProductPrice> uniquePrices, bool isAssortmentGroup, bool alwaysNetPrice) 66 { 67 68 <div class="pview__matrix mb-4 @(isAssortmentGroup || alwaysNetPrice ? "d-block" : "")"> 69 @if (uniquePrices.Count != 0) 70 { 71 <div class="d-flex flex-row justify-content-between mb-2"> 72 <div class="fw-bold">@Translate("price-matrix-amount", "Antal")</div> 73 <div class="fw-bold">@Translate("price-matrix-price", "Pris")</div> 74 </div> 75 76 77 bool notLast = true; 78 int index = 0; 79 80 81 foreach (var price in uniquePrices) 82 { 83 <div class="d-flex flex-row justify-content-between"> 84 <div>@price.Quantity @price.UnitId</div> 85 <div>@Dynamicweb.Core.Converter.ToDouble(price.Amount).ToString("F2")</div> 86 </div> 87 88 if (index < uniquePrices.Count - 1) 89 { 90 <hr></hr> 91 } 92 93 index++; 94 } 95 } 96 else 97 { 98 <div class="fw-bold"> 99 @Translate("price-matrix-no-prices", "Ingen mængdepriser fundet") 100 </div> 101 } 102 </div> 103 104 return ""; 105 } 106 107 108 109 string RenderSections() 110 { 111 <div class="mb-4"> 112 113 @{ 114 var download = Dynamicweb.Core.Converter.ToString(Model.ProductFields.Where(x => x.Key == "VjmDocumentLibraryHtml").FirstOrDefault().Value.Value); 115 bool hasDocDownloads = !string.IsNullOrEmpty(download); 116 117 var current = Dynamicweb.Context.Current.Request.RawUrl ?? ""; 118 var csvUrl = current + (current.Contains("?") ? "&" : "?") + "download=csv"; 119 var zipUrl = (Dynamicweb.Context.Current.Request.RawUrl ?? "/") + 120 ((Dynamicweb.Context.Current.Request.RawUrl ?? "/").Contains("?") ? "&" : "?") + 121 "download=imageszip"; 122 } 123 124 <div class="pview__section"> 125 <a href="javascript:void(0)" data-id="Downloads" 126 class="pview__section__name-container d-flex align-items-center justify-content-between"> 127 <div class="d-flex flex-row"> 128 <i class="pview__section__name-container__icon fa-solid fa-file-pdf"></i> 129 <div class="pview__section__name-container__name"> 130 @(Translate("product-downloads", "Downloads")) 131 </div> 132 </div> 133 <i class="bi-chevron-down"></i> 134 <i class="bi-chevron-up d-none"></i> 135 </a> 136 137 <div data-id="Downloads" class="pview__section__content"> 138 <div class="d-flex flex-column"> 139 <a class="os-button os-button--red mb-2" href="@csvUrl"> 140 @Translate("download-csv", "Download CSV") 141 </a> 142 143 <a class="os-button os-button--red mb-2" href="@zipUrl"> 144 @Translate("download-images-zip", "Download billeder ZIP") 145 </a> 146 147 @if (hasDocDownloads) 148 { 149 @download 150 } 151 </div> 152 </div> 153 </div> 154 </div> 155 156 return ""; 157 } 158 159 160 161 bool signedIn = PageView.Current().User != null; 162 bool purchasable = false; 163 bool alwaysNetPrice = false; 164 165 if (signedIn) 166 { 167 var alwaysNetPriceField = Pageview.User.CustomFieldValues.FirstOrDefault(x => x.CustomField.SystemName == "AccessUser_AlwaysNettopPrices"); 168 if (alwaysNetPriceField != null && alwaysNetPriceField.Value != null) 169 { 170 alwaysNetPrice = Dynamicweb.Core.Converter.ToBoolean(alwaysNetPriceField.Value); 171 } 172 173 if (Model.VariantCombinations().Count > 1 && string.IsNullOrEmpty(Model.VariantId)) 174 { 175 purchasable = false; 176 } 177 else 178 { 179 purchasable = true; 180 } 181 } 182 183 List<BreadcrumbItem> items = new List<BreadcrumbItem>(); 184 bool isMobile = PageView.Current().Device == DeviceType.Mobile; 185 186 var primaryGroup = Model.PrimaryOrDefaultGroup; 187 var categoryName = primaryGroup?.Name ?? ""; 188 var categoryId = primaryGroup?.Id ?? ""; 189 190 191 192 var groupService = new GroupService(); 193 var shop = new ShopService().GetShop("SHOP1"); 194 var groups = groupService.FindPath(shop, groupService.GetGroup(primaryGroup.Id)); 195 var shopPageId = GetPageIdByNavigationTag("Shop"); 196 var product = productService.GetProductById(Model.Id, Model.VariantId, Model.LanguageId); 197 var productImageService = new ProductImageService(); 198 var images = productImageService.GetImagesFromPatterns(product, shop).ToList(); 199 bool variantSelected = !string.IsNullOrEmpty(Model.VariantId); 200 var pageService = new PageService(); 201 string mp4Path = product.ProductFieldValues.GetProductFieldValue("MP4Video")?.Value?.ToString(); 202 bool showVideo = !string.IsNullOrEmpty(mp4Path) && mp4Path.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase); 203 204 205 if (variantSelected) 206 { 207 images.Clear(); 208 images.Add(Model.DefaultImage.Value); 209 } 210 211 foreach (var group in groups) 212 { 213 items.Add(new BreadcrumbItem 214 { 215 Title = group.Name, 216 Url = "/Default.aspx?ID=" + shopPageId + "&groupid=" + group.Id 217 }); 218 } 219 220 var parameters = new Dictionary<string, object>(); 221 parameters.Add("items", items); 222 223 var _navigationSettings = new Dynamicweb.Frontend.Navigation.NavigationSettings() 224 { 225 Parameters = parameters 226 }; 227 228 var _navigationTemplate = "../Partials/Breadcrumb.cshtml"; 229 230 var alternativeImageObject = Model.ProductFields.Where(x => x.Key == "VjmpAlternativeManufacturerBrandLogo").FirstOrDefault(); 231 var alternativeImage = Dynamicweb.Core.Converter.ToString(alternativeImageObject.Value.Value); 232 233 bool isAssortmentGroup = Model.GroupPaths?.Any(groupList => 234 groupList.Any(group => group.Name == "Logovare")) == true; 235 236 237 } 238 239 <div class="pview os-container "> 240 241 <div class="pview__top position-relative d-flex flex-row justify-content-between align-items-center"> 242 243 <div class="pview__top__breadcrumb"> 244 @Navigation.RenderNavigation(_navigationTemplate, _navigationSettings) 245 </div> 246 247 <img class="pview__top__brand" src="@alternativeImage"/> 248 </div> 249 250 <div class="d-flex flew-row flex-wrap"> 251 252 253 <div class="pview__row row z-0"> 254 255 <div class="col-12 col-lg-6"> 256 <div class="product-view-gallery-wrapper position-relative"> 257 <div class="swiper product-view-gallery"> 258 <div class="swiper-wrapper"> 259 260 @foreach (var image in images) 261 { 262 var imageSrc = ""; 263 if (!string.IsNullOrEmpty(image)) 264 { 265 Dynamicweb.VestjyskMarketing.Models.ResizeImageSettings imageSettings = new Dynamicweb.VestjyskMarketing.Models.ResizeImageSettings 266 { 267 Width = 1500, 268 Height = 1500, 269 Crop = "5", 270 Quality = 90, 271 Image = image 272 }; 273 274 imageSrc = Dynamicweb.VestjyskMarketing.Helpers.ImageHelper.ResizeImage(imageSettings); 275 } 276 else 277 { 278 imageSrc = "/Files/Images/missing_image.jpg"; 279 } 280 281 string altText = OttoSchachner.Api.AISeo.GetProductImageAlt(product.Id, Model.VariantId, Model.LanguageId, image).FirstOrDefault()?.Alt ?? string.Empty; 282 if (string.IsNullOrEmpty(altText)) 283 { 284 altText = Translate("product-image-alt", "Produkt billede ") + " " + images.IndexOf(image); 285 } 286 287 <div class="swiper-slide"> 288 <img alt="@altText" class="pview__image-container__image" src="@imageSrc"> 289 </div> 290 } 291 292 @if (showVideo) 293 { 294 <div class="swiper-slide"> 295 <div class="pview__image-container__video-wrapper"> 296 <video class="pview__image-container__video" controls> 297 <source src="@mp4Path" type="video/mp4"> 298 Din browser understøtter ikke video-tagget. 299 </video> 300 </div> 301 </div> 302 } 303 304 @if (images.Count() == 0 && !showVideo) 305 { 306 <div class="swiper-slide"> 307 <img alt="Produkt billede 0" class="pview__image-container__image" 308 src="/Files/Images/missing_image.jpg"> 309 </div> 310 } 311 312 313 314 </div> 315 </div> 316 317 @{ 318 if (signedIn && purchasable) 319 { 320 string favoriteLink = "/Default.aspx?ID=" + pageService.GetPageByNavigationTag(PageView.Current().AreaID, "FavoriteService").ID + "&ProductID=" + Model.Id + "&ProductVariantId=" + Model.VariantId + "&UserID=" + PageView.Current().User.ID + "&ReloadPage=false"; 321 bool isInFavoriteList = false; 322 var favoriteLists = Pageview.User.GetFavoriteLists(); 323 int favoriteListContainingProductId = 0; 324 string command = "add"; 325 new FavoriteListService().ClearCache(); 326 327 foreach (var favoriteList in favoriteLists) 328 { 329 isInFavoriteList = Pageview.User.IsProductInFavoriteList(favoriteList.ListId, product.Id, Model.VariantId); 330 331 if (isInFavoriteList) 332 { 333 favoriteListContainingProductId = favoriteList.ListId; 334 command = "remove"; 335 break; 336 } 337 } 338 339 <a data-command="@command" data-in-this-list="@favoriteListContainingProductId" 340 data-url="@favoriteLink" href="javascript:void(0)" 341 title="@Translate("add-or-remove-favorites")" class="product-list__favorite z-2"> 342 343 @if (isInFavoriteList) 344 { 345 <i class="fa-sharp fa-solid fa-heart"></i> 346 } 347 else 348 { 349 <i class="fa-regular fa-heart"></i> 350 } 351 352 </a> 353 } 354 355 356 } 357 <div 358 class="product-view-gallery__navigation position-absolute top-50 translate-middle-y z-1 justify-content-between d-flex "> 359 <i class="os-chevron os-chevron--prev bi-chevron-left product-view-prev"></i> 360 <i class="os-chevron bi-chevron-right product-view-next"></i> 361 </div> 362 </div> 363 364 <div class="pview__thumbnails product-view-thumbs row g-3 mt-2 "> 365 @if (images.Count + (!showVideo ? 0 : 1) > 1) 366 { 367 368 369 var imagesArray = images.ToArray(); 370 371 for (int i = 0; 372 i < images.Count(); 373 i++) 374 { 375 Dynamicweb.VestjyskMarketing.Models.ResizeImageSettings imageSettings = new Dynamicweb.VestjyskMarketing.Models.ResizeImageSettings 376 { 377 Width = 750, 378 Height = 750, 379 Crop = "5", 380 Quality = 90, 381 Image = imagesArray[i] 382 }; 383 384 var imageSrc = Dynamicweb.VestjyskMarketing.Helpers.ImageHelper.ResizeImage(imageSettings); 385 386 int number = i + 1; 387 388 string altText = OttoSchachner.Api.AISeo.GetProductImageAlt(product.Id, Model.VariantId, Model.LanguageId, imagesArray[i]).FirstOrDefault()?.Alt ?? string.Empty; 389 if (string.IsNullOrEmpty(altText)) 390 { 391 altText = Translate("product-image-alt", "Produkt billede ") + " " + i; 392 } 393 394 <a title="@Translate("go-to-image", "Gå til billede ") @number" data-slide="@i" 395 class="pview__thumbnails__item col-2 "> 396 <img alt="@altText" loading="lazy" src="@imageSrc"/> 397 </a> 398 } 399 400 401 402 @if (showVideo) 403 { 404 <a title="@Translate("go-to-video", "Gå til video")" style="" data-slide="@images.Count()" 405 class="pview__thumbnails__item col-2 position-relative"> 406 <div class="w-100 h-100" style="background-color: white; border-radius: 10px"> 407 </div> 408 <i class="fa-solid fa-play position-absolute top-50 start-50 translate-middle fs-1 text-black"></i> 409 </a> 410 } 411 412 } 413 414 </div> 415 416 @if (signedIn && isMobile == false) 417 { 418 @RenderSections() 419 } 420 421 422 </div> 423 <div class="col-12 col-lg-6"> 424 <h1 class="pview__name"> 425 @Model.Name 426 </h1> 427 <div class="pview__productid "> 428 @Translate("product-number", "Varenr: ") @Model.Number 429 </div> 430 431 432 @if (purchasable && !isAssortmentGroup) 433 { 434 <div class="pview__price d-flex flex-row align-items-center"> 435 <div class="pview__price__text me-3"> 436 <span class="pview__price__text"> 437 @Translate("recommended-price", "Vejl. pris") 438 </span> 439 </div> 440 <span class="pview__price__amount">@product.DefaultPrice.ToString("F2") KR. <span 441 class="pview__price__amount__netto @(alwaysNetPrice ? "" : "d-none")" 442 data-lazy-netprice 443 data-product-id="@Model.Id" 444 data-variant-id="@Model.VariantId" 445 data-language-id="@Model.LanguageId">(@Translate("net-price", "netto") <span class="lazy-netprice-value"><span class="spinner-border spinner-border-sm align-baseline" role="status" aria-hidden="true"></span></span> KR. )</span></span> 446 447 </div> 448 449 <div class="pview__price-toggler d-flex flex-row mb-3"> 450 451 452 <div 453 class="pview__price-toggler__container @(alwaysNetPrice ? "d-none" : "d-flex") flex-row align-items-center"> 454 <input class="me-3" type="checkbox" id="pview__netprice" name="horns" 455 @(alwaysNetPrice ? "checked" : "")/> 456 <label for="pview__netprice"> 457 @Translate("show-net-price", "Vis nettopris") 458 </label> 459 </div> 460 461 462 </div> 463 } 464 else if (signedIn == false) 465 { 466 <div class="pview__signin-message "> 467 @Translate("sign-in-to-see-prices", "LOG IND FOR AT SE PRISER OG KØBE PRODUKTET") 468 </div> 469 } 470 471 @if (purchasable) 472 { 473 @RenderPriceMatrix(uniquePricesMatrix, isAssortmentGroup, alwaysNetPrice) 474 } 475 476 <div class="pview__description"> 477 @Model.ShortDescription 478 @Model.LongDescription 479 </div> 480 481 @{ 482 string pictogramHtml = Model.ProductFields["VjmPictogramHtml"]?.Value.ToString(); 483 if (!string.IsNullOrEmpty(pictogramHtml)) 484 { 485 <div class="pictogram__section d-flex"> 486 <div class="d-flex "> 487 @pictogramHtml 488 </div> 489 </div> 490 } 491 } 492 493 @if (Model.VariantCombinations().Count > 1 && string.IsNullOrEmpty(Model.VariantId) && signedIn) 494 { 495 <div class="pview__variant-selector__message mb-3 fw-bold"> 496 @Translate("select-variant-message", "Du skal vælge variant før du kan tilføje til kurv") 497 </div> 498 } 499 @{ 500 string variantGroupName = ""; 501 bool colorGroup = Model.VariantInfo.VariantInfoGroupName == "Farver"; 502 503 @if (Model.VariantGroups().Count > 0) 504 { 505 if (Model.VariantGroups().Count == 1) 506 { 507 variantGroupName = Model.VariantGroups().First().Name.Replace(" ", ""); 508 variantGroupName = Translate("VariantGroup.Name." + variantGroupName, variantGroupName); 509 510 <div class="pview__cart__variant mb-3"> 511 <span class="fw-bold">@variantGroupName@(colorGroup ? ": " : "")</span> 512 @if (colorGroup) 513 { 514 <span>@Model.VariantName</span> 515 } 516 </div> 517 } 518 } 519 } 520 521 @if (signedIn && isMobile) 522 { 523 @RenderSections() 524 } 525 526 @{ 527 var step = Model.PurchaseQuantityStep; 528 if (step == null || step == 0) 529 { 530 step = 1; 531 } 532 533 if (isAssortmentGroup && uniquePricesMatrix.Count > 0) 534 { 535 step = uniquePricesMatrix?.FirstOrDefault()?.Quantity; 536 } 537 538 string stockLevel = Convert.ToString(product != null ? product.ProductFieldValues["VjmStockLevel"]?.Value : Model.ProductFields["VjmStockLevel"]?.Value); 539 string stockLevelText = ""; 540 541 if (!string.IsNullOrEmpty(stockLevel)) 542 { 543 stockLevel = stockLevel.ToLower(); 544 stockLevelText = StockLevelText(stockLevel); 545 } 546 547 548 <div class="pview__cart"> 549 <div class="pview__variant-selector row g-2 mb-2 mb-lg-4 d-flex flex-row flex-wrap"> 550 551 @if (Model.VariantGroups().Count == 1) 552 { 553 @foreach (var variantGroup in Model.VariantGroups()) 554 { 555 foreach (var option in variantGroup.Options) 556 { 557 string variantUrl = "/Default.aspx?ID=" + Pageview.ID + "&groupid=" + primaryGroup.Id + "&productid=" + Model.Id + "&variantid=" + option.Id; 558 var variantProduct = productService.GetProductById(Model.Id, option.Id, Model.LanguageId); 559 560 @if (Model.VariantInfo.VariantInfoGroupName == "Farver") 561 { 562 var variantImagePath = productImageService.GetImagePath(variantProduct); 563 564 Dynamicweb.VestjyskMarketing.Models.ResizeImageSettings imageSettings = new Dynamicweb.VestjyskMarketing.Models.ResizeImageSettings 565 { 566 Width = 1500, 567 Height = 1500, 568 Crop = "5", 569 Quality = 90, 570 Image = variantImagePath 571 }; 572 573 string imageSrc = Dynamicweb.VestjyskMarketing.Helpers.ImageHelper.ResizeImage(imageSettings); 574 575 <div class="w-auto"> 576 <div class="pview__variant-selector__item"> 577 <a href="@variantUrl" 578 title="@Translate("go-to", "Gå til") @variantGroupName.ToLower() @option.Name.ToLower()"> 579 <img class="w-100" alt="Variant @option.Name" src="@imageSrc"/> 580 </a> 581 </div> 582 </div> 583 } 584 else 585 { 586 <div class="w-auto"> 587 <div 588 class="pview__variant-selector__item number-selector @(Model.VariantId == option.Id ? "active" : "")"> 589 <a href="@variantUrl" 590 title="@Translate("go-to", "Gå til") @variantGroupName.ToLower() @option.Name.ToLower()" 591 class="d-flex h-100 w-100 align-items-center justify-content-center text-decoration-none"> 592 @option.Name 593 </a> 594 </div> 595 </div> 596 } 597 } 598 } 599 } 600 //multi variant håndtering 601 else 602 { 603 <div hidden="" id="variant-combinations"> 604 @string.Join(",", Model.VariantCombinations()) 605 </div> 606 @foreach (var variantGroup in Model.VariantGroups()) 607 { 608 string variantGroupNameMulti = Translate("VariantGroup.Name." + variantGroup.Name, variantGroup.Name); 609 <div class="pview__cart__variant fw-bold"> 610 @variantGroupNameMulti 611 </div> 612 <div class="d-flex mb-1 flex-wrap"> 613 @{ 614 foreach (var option in variantGroup.Options) 615 { 616 string variantUrl = "/Default.aspx?ID=" + Pageview.ID + "&groupid=" + primaryGroup.Id + "&productid=" + Model.Id + "&variantid=" + option.Id; 617 bool active = Model.VariantId.Contains(option.Id); 618 619 <div class="me-2 mb-2"> 620 <a data-group="@variantGroup.Name" data-id="@option.Id" 621 data-url="@variantUrl" href="javascript:void(0)" 622 title="@Translate("select", "vælg") @option.Name" 623 class="os-button os-button--no-hover @(active ? "" : "active os-button--transparent") d-flex h-100 w-100 align-items-center justify-content-center text-decoration-none"> 624 @option.Name 625 </a> 626 </div> 627 } 628 } 629 </div> 630 } 631 } 632 </div> 633 634 @if (purchasable) 635 { 636 <form method="post" class="h-100" id="pview-add-to-cart-form"> 637 <div class="pview__cart__container d-flex align-items-stretch mb-3"> 638 <div class="col-4"> 639 <input name="Quantity" type="number" min="@step" step="@step" value="@step" 640 class="pview__cart__container__quantity col-4"/> 641 </div> 642 <div class="col-8"> 643 644 <input type="hidden" name="ProductId" value='@Model.Id'/> 645 <input type="hidden" name="VariantId" value="@Model.VariantId"/> 646 <button class="pview__cart__container__button" type="submit" name="CartCmd" 647 value="add">Tilføj til kurv 648 </button> 649 650 </div> 651 </div> 652 </form> 653 <div class="d-flex flex-row align-items-center"> 654 <div class="col-4 d-flex flex-row justify-content-between align-items-center"> 655 <div class="pview__cart__min ">Min. antal: @step</div> 656 <div class="pview__cart__status-color pview__cart__status-color--@stockLevel"> 657 </div> 658 </div> 659 <div class="pview__cart__message d-flex align-items-center"> 660 @stockLevelText 661 </div> 662 </div> 663 } 664 </div>} 665 666 @if (signedIn == false) 667 { 668 @RenderSections() 669 } 670 </div> 671 </div> 672 </div> 673 </div> 674 675 676 677 @functions{ 678 679 string StockLevelText(string stockLevel) 680 { 681 string result = ""; 682 683 if (!string.IsNullOrEmpty(stockLevel)) 684 { 685 stockLevel = stockLevel.ToLower(); 686 687 switch (stockLevel) 688 { 689 case "green": 690 result = Translate("in-stock", "På lager"); 691 break; 692 case "yellow": 693 result = Translate("low-stock", "På vej hjem"); 694 break; 695 case "red": 696 result = Translate("out-of-stock", "Ikke på lager"); 697 break; 698 default: 699 result = Translate("out-of-stock", "Ikke på lager"); 700 break; 701 } 702 } 703 704 return result; 705 } 706 707 } 708 709 @if (Model.VariantCombinations().Count > 0 && !isAssortmentGroup) 710 { 711 var quickOrderVariants = product.GetVariantCombinations().Where(x => x.GetProduct(Model.LanguageId) != null && x.GetProduct(Model.LanguageId).Active).ToList(); 712 quickOrderVariants = quickOrderVariants.OrderBy(x => x.GetProduct(Model.LanguageId).Number).ToList(); 713 int count = 0; 714 <div class="pview__qorder"> 715 <div class="os-container"> 716 <div class="d-flex flex-column flex-lg-row justify-content-between flex-wrap mb-3 mb-lg-5"> 717 <div class="pview__qorder__header col-12 col-lg-auto mb-3 mb-lg-0"> 718 @Translate("quick-order", "Hurtig bestilling") 719 </div> 720 721 <div class="pview__qorder__input-container d-flex flex-row align-items-center col-12 col-lg-5"> 722 <i class="bi-search me-2"></i> 723 <input class="pview__qorder__input-container__input col-5" 724 placeholder='@Translate("quick-order-search", "Søg efter varenr., DB nr., materiale, størrelse m.m.")'/> 725 </div> 726 </div> 727 <form id="pview-qorder-form" method="post" action=""> 728 729 730 <input type="hidden" name="cartcmd" value="addmulti"/> 731 <input type="hidden" name="redirect" value="false"/> 732 733 <div class="product-table"> 734 <div 735 class="row header @(signedIn ? "signed-in" : "") @(Model.VariantGroups().Count > 1 ? "multi" : "")"> 736 <div>@Translate("order-variant-number", "Varenr.")</div> 737 738 @{ 739 List<string> variantGroupNames = new List<string>(); 740 } 741 742 @foreach (var variantGroup in Model.VariantGroups()) 743 { 744 string _variantGroupName = Translate("VariantGroup.Name." + variantGroup.Name, variantGroup.Name); 745 variantGroupNames.Add(_variantGroupName); 746 <div>@_variantGroupName</div> 747 } 748 749 750 <div>@Translate("db-number", "DB-nummer")</div> 751 <div>@Translate("ean-upc", "EAN/UPC")</div> 752 <div>@Translate("special-order-item", "Skaffevare")</div> 753 <div>@Translate("order-quantity", "Ordrekvantum")</div> 754 <div>@Translate("packaging", "Forpakning")</div> 755 @if (signedIn) 756 { 757 <div>@Translate("stock-status", "Lagerstatus")</div> 758 } 759 </div> 760 761 @{ 762 var matrixItems = new List<(Dynamicweb.Ecommerce.Products.Product Product, VariantCombination VariantComb, bool IsVariant)>(); 763 764 //hvis ingen varianter - hvis hovedproduktet i tabellen 765 if (quickOrderVariants.Count == 0) 766 { 767 matrixItems.Add((product, null, false)); 768 } 769 770 // Tilføj alle variants bagefter 771 foreach (var vc in quickOrderVariants) 772 { 773 matrixItems.Add((vc.GetProduct(Model.LanguageId), vc, true)); 774 } 775 } 776 777 <div class="row-wrapper"> 778 @foreach (var item in matrixItems) 779 { 780 count++; 781 782 783 var variant = item.Product; 784 var variantComb = item.VariantComb; 785 bool isVariant = item.IsVariant; 786 // DB-nummer (VjmDBNumber) 787 string dbNumber = 788 variant?.ProductFieldValues? 789 .GetProductFieldValue("VjmDBNumber")?.Value?.ToString() 790 ?? Translate("not-available", "N/A"); 791 792 // EAN 793 string EAN = !string.IsNullOrEmpty(variant?.EAN) 794 ? variant.EAN 795 : Translate("not-available", "N/A"); 796 797 string spStatus = variant?.ProductFieldValues.GetProductFieldValue("SpStatus")?.Value?.ToString().ToLower(); 798 799 if (spStatus == "skaffevare" || spStatus == "pris på forespørgsel") 800 { 801 spStatus = Translate("Yes", "Ja"); 802 } 803 else 804 { 805 spStatus = Translate("No", "Nej"); 806 } 807 808 string variantStockLevel = Convert.ToString(variant != null ? variant.ProductFieldValues["VjmStockLevel"]?.Value : "red"); 809 string variantStockLevelText = ""; 810 811 812 if (!string.IsNullOrEmpty(variantStockLevel)) 813 { 814 variantStockLevel = variantStockLevel.ToLower(); 815 variantStockLevelText = StockLevelText(variantStockLevel); 816 } 817 818 // Packaging (VjmUnitsConcat) 819 string packaging = 820 variant?.ProductFieldValues? 821 .GetProductFieldValue("VjmUnitsConcat")?.Value?.ToString() 822 ?? ""; 823 824 // Unit (VjmSalesUnit) 825 string unit = 826 variant?.ProductFieldValues? 827 .GetProductFieldValue("VjmSalesUnit")?.Value?.ToString() 828 ?? ""; 829 830 // Minimum order quantity 831 string minQty = variant?.PurchaseQuantityStep.ToString() ?? ""; 832 833 if (minQty == "0") 834 { 835 minQty = "1"; 836 unit = ""; 837 } 838 839 // Price 840 string bruttoPrice = $"{variant.DefaultPrice:0.00} KR"; 841 842 843 844 <div 845 class="row data @(count % 2 == 0 ? "visible-index-even" : "") @(signedIn ? "signed-in" : "") @(Model.VariantGroups().Count > 1 ? "multi" : "")" 846 data-id="@variant.Id" 847 data-name="@variant.Name" 848 data-variant="@(isVariant ? variantComb.VariantId : "")"> 849 <input type="hidden" name="ProductLoopCounter@(count)" id="ProductLoopCounter@(count)" 850 value="@count"/> 851 <input type="hidden" name="ProductId@(count)" id="ProductId@(count)" 852 value="@variant.Id"/> 853 @if (isVariant) 854 { 855 <input type="hidden" name="VariantId@(count)" value="@variantComb.VariantId"/> 856 } 857 858 <!-- Varenr. --> 859 <div class="d-flex justify-content-between d-lg-block"> 860 <span class="d-block d-lg-none">@Translate("order-variant-number", "Varenr.")</span> 861 <span class="searchable">@variant.Number</span> 862 </div> 863 <hr/> 864 <!-- Variantnavne --> 865 @if (isVariant) 866 { 867 int index = 0; 868 869 @foreach (var optionId in variantComb.GetVariantOptionIds()) 870 { 871 // Find gruppen og optionen mere sikkert 872 var group = Model.VariantGroups().FirstOrDefault(g => g.Options.Any(o => o.Id == optionId)); 873 var option = group?.Options.FirstOrDefault(o => o.Id == optionId); 874 875 <div class="d-flex justify-content-between d-lg-block"> 876 <span class="d-block d-lg-none">@variantGroupNames[index]</span> 877 <span class="searchable"> 878 @(option?.Name ?? "-") 879 </span> 880 </div> 881 <hr/> 882 index++; 883 } 884 } 885 886 887 <!-- DB nummer --> 888 <div class="d-flex justify-content-between d-lg-block"> 889 <span class="d-block d-lg-none">@Translate("db-number", "DB-nummer")</span> 890 <span class="searchable">@dbNumber</span> 891 </div> 892 893 <hr/> 894 <!-- EAN --> 895 <div class="d-flex justify-content-between d-lg-block"> 896 <span class="d-block d-lg-none">@Translate("ean-upc", "EAN/UPC")</span> 897 <span class="searchable">@EAN</span> 898 </div> 899 <hr/> 900 901 <!-- Skaffevare --> 902 <div class="d-flex justify-content-between d-lg-block"> 903 <span 904 class="d-block d-lg-none">@Translate("special-order-item", "Skaffevare")</span> 905 <span class="searchable">@spStatus</span> 906 </div> 907 908 <hr/> 909 <!-- Ordrekvantum --> 910 <div class="d-flex justify-content-between d-lg-block"> 911 <span class="d-block d-lg-none">@Translate("order-quantity", "Ordrekvantum")</span> 912 <span>@minQty @unit</span> 913 </div> 914 <hr/> 915 916 <!-- Forpakning --> 917 <div class="d-flex justify-content-between d-lg-block"> 918 <span class="d-block d-lg-none">@Translate("packaging", "Forpakning")</span> 919 <span>@packaging</span> 920 </div> 921 922 @if (signedIn) 923 { 924 <hr/> 925 <!-- Lagerstatus --> 926 <div class="d-flex justify-content-between d-lg-block align-items-center"> 927 <span class="d-block d-lg-none">@Translate("stock-status", "Lagerstatus")</span> 928 <div class="d-flex flex-row align-items-center p-0"> 929 <div 930 class="pview__cart__status-color pview__cart__status-color--@variantStockLevel"></div> 931 @variantStockLevelText 932 </div> 933 </div> 934 935 <hr/> 936 937 <!-- Pris --> 938 <div 939 class="d-flex justify-content-between d-lg-block flex-row flex-lg-column align-items-center"> 940 <span class="d-block d-lg-none">@Translate("price", "Pris")</span> 941 <div class="d-flex flex-column"> 942 <span class="fw-bold">@bruttoPrice</span> 943 <span 944 class="quick-order-net @(alwaysNetPrice ? "" : "d-none")" 945 data-lazy-netprice 946 data-product-id="@variant.Id" 947 data-variant-id="@(isVariant ? variantComb.VariantId : "")" 948 data-language-id="@Model.LanguageId">(<span class="lazy-netprice-value"><span class="spinner-border spinner-border-sm align-baseline" role="status" aria-hidden="true"></span></span> KR)</span> 949 <small>@Translate("quick-order-min", "Bestilles i"): @minQty @unit</small> 950 </div> 951 952 </div> 953 <hr/> 954 955 <!-- Antal vælger --> 956 <div class="d-flex justify-content-between d-lg-block align-items-center"> 957 <span class="d-block d-lg-none">@Translate("quantity", "Antal")</span> 958 <div class="pview__qorder__actions d-flex flex-row"> 959 <a class="pview__qorder__actions__decrement" href="javascript:void(0)">-</a> 960 <input class="pview__qorder__actions__input" name="Quantity@(count)" 961 type="number" step="@minQty" min="0" value="0"> 962 <a class="pview__qorder__actions__incremenet" 963 href="javascript:void(0)">+</a> 964 </div> 965 </div> 966 } 967 </div> 968 } 969 970 </div> 971 </div> 972 973 974 <div class="pview__qorder__no-results justify-content-center mt-3" style="display: none"> 975 @Translate("quick-order-no-results", "Ingen resultater fundet") 976 </div> 977 978 @if (signedIn) 979 { 980 <div class="d-flex justify-content-end"> 981 <button type="submit" class="pview__qorder__add-button os-button os-button--red mt-4"> 982 @Translate("add-all-to-cart", "Læg alle i kurv") 983 </button> 984 </div> 985 } 986 </form> 987 988 </div> 989 </div> 990 } 991 992 @{ 993 var productListParameters = new Dictionary<string, object>(); 994 productListParameters.Add("itemsToShow", 4); 995 productListParameters.Add("maxItems", 99); 996 997 List<dynamic> productsRelated = new List<dynamic>(); 998 foreach (var relatedGroup in Model.RelatedGroups) 999 { 1000 if (relatedGroup.Id == "TILBEHØR") 1001 { 1002 foreach (var relatedProduct in relatedGroup.Products) 1003 { 1004 productsRelated.Add(ProductInfoViewModelExtensions.GetProduct(relatedProduct)); 1005 } 1006 } 1007 } 1008 1009 bool slider = productsRelated.Count > 4; 1010 productListParameters.Add("slider", slider); 1011 productListParameters.Add("products", productsRelated); 1012 } 1013 1014 @if (productsRelated.Count > 0) 1015 { 1016 <div class="os-container pview__related"> 1017 <div class="pview__related__header mb-4"> 1018 @Translate("product-add-ons", "Tilbehør") 1019 </div> 1020 <div class="@(slider ? "" : "row g-3") "> 1021 @RenderPartial("/Designs/OttoSchachner/Partials/ProductList.cshtml", new ParagraphViewModel(), productListParameters) 1022 </div> 1023 </div> 1024 } 1025 1026 @if (signedIn == false) 1027 { 1028 <div class="os-container pview__signup"> 1029 @{ 1030 var signupParagraphPageId = GetPageIdByNavigationTag("NotSignedInProductViewParagraph"); 1031 @(new Content(Pageview).RenderExternalGrid(signupParagraphPageId, "")) 1032 } 1033 </div> 1034 } 1035 1036 @if (purchasable && signedIn) 1037 { 1038 <script> 1039 window.dataLayer = window.dataLayer || []; 1040 (function () { 1041 function pushAddToCart(items, currency) { 1042 if (items.length > 0) { 1043 dataLayer.push({ ecommerce: null }); 1044 dataLayer.push({ 1045 event: "add_to_cart", 1046 ecommerce: { 1047 currency: currency, 1048 items: items 1049 } 1050 }); 1051 } 1052 } 1053 1054 const quickOrderForm = document.getElementById('pview-qorder-form'); 1055 if (quickOrderForm) { 1056 function buildAndPushQuickOrderItems() { 1057 const items = []; 1058 const rows = quickOrderForm.querySelectorAll('.row.data'); 1059 rows.forEach(function (row) { 1060 const quantityInput = row.querySelector('.pview__qorder__actions__input'); 1061 const quantity = quantityInput ? parseInt(quantityInput.value || '0', 10) : 0; 1062 1063 if (quantity > 0) { 1064 items.push({ 1065 item_id: row.dataset.id, 1066 item_name: row.dataset.name, 1067 affiliation: "Online Store", 1068 variant: row.dataset.variant, 1069 price: parseFloat(row.dataset.price) || 0, 1070 quantity: quantity 1071 }); 1072 } 1073 }); 1074 1075 pushAddToCart(items, "@Model.Price.CurrencyCode"); 1076 } 1077 1078 quickOrderForm.addEventListener('submit', function (e) { 1079 var pending = (typeof window.lazyNetPriceFlush === 'function') 1080 ? window.lazyNetPriceFlush() 1081 : null; 1082 1083 if (pending && typeof pending.then === 'function') { 1084 e.preventDefault(); 1085 var submitBtn = quickOrderForm.querySelector('.pview__qorder__add-button'); 1086 if (submitBtn) submitBtn.disabled = true; 1087 1088 pending.then(buildAndPushQuickOrderItems, buildAndPushQuickOrderItems) 1089 .then(function () { 1090 if (submitBtn) submitBtn.disabled = false; 1091 quickOrderForm.submit(); 1092 }); 1093 } else { 1094 buildAndPushQuickOrderItems(); 1095 } 1096 }); 1097 } 1098 1099 const form = document.getElementById('pview-add-to-cart-form'); 1100 if(form){ 1101 form.addEventListener('submit', function () { 1102 const quantityInput = form.querySelector('input[name="Quantity"]'); 1103 const quantity = quantityInput ? parseInt(quantityInput.value || '1', 10) : 1; 1104 1105 const items = [ 1106 { 1107 item_id: "@Model.Id", 1108 item_name: "@Model.Name", 1109 affiliation: "Online Store", 1110 variant: "@Model.VariantId", 1111 price: @Model.Price?.PriceWithoutVat.ToString("F2", System.Globalization.CultureInfo.InvariantCulture), 1112 quantity: quantity 1113 } 1114 ]; 1115 1116 pushAddToCart(items, "@Model.Price.CurrencyCode"); 1117 }); 1118 } 1119 })(); 1120 1121 dataLayer.push({ ecommerce: null }); 1122 dataLayer.push({ 1123 event: 'view_item', 1124 ecommerce: { 1125 currency: "@Model.Price?.CurrencyCode", 1126 value: @Model.Price?.PriceWithoutVat.ToString("F2", System.Globalization.CultureInfo.InvariantCulture), 1127 items: [ 1128 { 1129 item_name: "@Model.Name", 1130 item_id: "@Model.Id", 1131 price: @Model.Price?.PriceWithoutVat.ToString("F2", System.Globalization.CultureInfo.InvariantCulture), 1132 item_brand: "@Model.Manufacturer?.Name", 1133 item_category: "@categoryName", 1134 item_list_name: "@categoryName", 1135 item_list_id: "@categoryId", 1136 quantity: 1 1137 } 1138 ] 1139 } 1140 }); 1141 </script> 1142 } 1143