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