tango-app-ui-store-builder 1.2.12 → 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -586,6 +586,9 @@ class StoreBuilderService {
586
586
  runAlocation(data) {
587
587
  return this.http.post(`${this.storeBuilderApiUrl}/onboardPlano/runAlocation`, data);
588
588
  }
589
+ runLooksAllocation(data) {
590
+ return this.http.post(`${this.storeBuilderApiUrl}/onboardPlano/runLooksAllocation`, data);
591
+ }
589
592
  getIvmConversionAlias() {
590
593
  return this.http.get(`${this.storeBuilderApiUrl}/onboardPlano/ivmConversionAlias`);
591
594
  }
@@ -64921,7 +64924,27 @@ class OnboardStorePlanoComponent {
64921
64924
  initial: true,
64922
64925
  sort: false,
64923
64926
  };
64924
- const response = await firstValueFrom(this.apiService.runAlocation(payload));
64927
+ // Prefer the Looks-driven allocation when an Active Look Plano
64928
+ // Collection exists for this client; the backend returns 404 with a
64929
+ // "No active Look Plano Collection" message when none is configured,
64930
+ // in which case we fall back to the legacy spreadsheet-driven flow.
64931
+ let response;
64932
+ let usedLooks = false;
64933
+ try {
64934
+ response = await firstValueFrom(this.apiService.runLooksAllocation(payload));
64935
+ usedLooks = true;
64936
+ }
64937
+ catch (looksErr) {
64938
+ const status = looksErr?.status ?? looksErr?.error?.status;
64939
+ const msg = looksErr?.error?.message ||
64940
+ looksErr?.error?.error ||
64941
+ looksErr?.message ||
64942
+ "";
64943
+ const noActiveCollection = status === 404 || /no active look plano collection/i.test(msg);
64944
+ if (!noActiveCollection)
64945
+ throw looksErr;
64946
+ response = await firstValueFrom(this.apiService.runAlocation(payload));
64947
+ }
64925
64948
  if (response && response.data) {
64926
64949
  this.floorData.layoutPolygon =
64927
64950
  response.data.layoutPolygon || this.floorData.layoutPolygon;
@@ -64937,7 +64960,16 @@ class OnboardStorePlanoComponent {
64937
64960
  this.renderFloor(false);
64938
64961
  this.togglePanel("left", true);
64939
64962
  this.togglePanel("right", false);
64940
- this.toastService.getSuccessToast("Allocation logic applied successfully");
64963
+ if (usedLooks) {
64964
+ const stats = response.data.looksAllocation || {};
64965
+ const name = stats.looksCollectionName
64966
+ ? ` (${stats.looksCollectionName})`
64967
+ : "";
64968
+ this.toastService.getSuccessToast(`Looks allocation applied${name}: ${stats.matched ?? 0} matched, ${stats.unmatched ?? 0} unmatched`);
64969
+ }
64970
+ else {
64971
+ this.toastService.getSuccessToast("Allocation logic applied successfully");
64972
+ }
64941
64973
  }
64942
64974
  this.modalService.dismissAll();
64943
64975
  this.isButtonLoading = false;
@@ -69393,6 +69425,57 @@ class LookPlanoCollectionFormComponent {
69393
69425
  }
69394
69426
  ctrl.setValue(current);
69395
69427
  }
69428
+ /**
69429
+ * Initial vmConfig coords for a freshly-added VM entry, derived from the
69430
+ * vmType and the target library's shelf layout. Mirrors the wall-layout
69431
+ * rules in `runLooksAllocation` (onboard.controller.js:buildWallVmEntry)
69432
+ * so what the form pre-positions matches what the backend allocator would
69433
+ * place there.
69434
+ *
69435
+ * Top Masking -> first shelf, stretch (top strip of fixture)
69436
+ * Mid 1 / Mid -> first mid shelf, left
69437
+ * Mid 2 -> last mid shelf, right
69438
+ * Mid 3 -> middle mid shelf (or last), stretch
69439
+ * Bottom Slanter -> last shelf, stretch (bottom strip)
69440
+ *
69441
+ * Anything else falls back to the previous neutral default (top, no zone)
69442
+ * so behaviour doesn't change for unknown labels.
69443
+ */
69444
+ initialVmLayout(vmType, shelfConfig) {
69445
+ const norm = String(vmType ?? '').trim().toLowerCase();
69446
+ const shelves = Array.isArray(shelfConfig)
69447
+ ? shelfConfig.slice().sort((a, b) => (a?.shelfNumber || 0) - (b?.shelfNumber || 0))
69448
+ : [];
69449
+ const inZone = (z) => {
69450
+ const target = z.toLowerCase();
69451
+ return shelves.filter((s) => String(s?.zone ?? '').trim().toLowerCase() === target);
69452
+ };
69453
+ const firstShelf = shelves[0]?.shelfNumber || 1;
69454
+ const lastShelf = shelves[shelves.length - 1]?.shelfNumber || firstShelf;
69455
+ if (norm === 'top masking' || norm === 'top') {
69456
+ return { startYPosition: firstShelf, endYPosition: firstShelf, xZone: 'stretch', yZone: 'stretch' };
69457
+ }
69458
+ if (norm === 'mid 1' || norm === 'mid') {
69459
+ const m = inZone('Mid');
69460
+ const sn = m[0]?.shelfNumber || firstShelf;
69461
+ return { startYPosition: sn, endYPosition: sn, xZone: 'left', yZone: 'stretch' };
69462
+ }
69463
+ if (norm === 'mid 2') {
69464
+ const m = inZone('Mid');
69465
+ const sn = m[m.length - 1]?.shelfNumber || firstShelf;
69466
+ return { startYPosition: sn, endYPosition: sn, xZone: 'right', yZone: 'stretch' };
69467
+ }
69468
+ if (norm === 'mid 3') {
69469
+ const m = inZone('Mid');
69470
+ const target = m.length >= 3 ? m[Math.floor(m.length / 2)] : m[m.length - 1];
69471
+ const sn = target?.shelfNumber || firstShelf;
69472
+ return { startYPosition: sn, endYPosition: sn, xZone: 'stretch', yZone: 'stretch' };
69473
+ }
69474
+ if (norm === 'bottom slanter') {
69475
+ return { startYPosition: lastShelf, endYPosition: lastShelf, xZone: 'stretch', yZone: 'stretch' };
69476
+ }
69477
+ return { startYPosition: 1, endYPosition: 1, xZone: '', yZone: '' };
69478
+ }
69396
69479
  /** Resolve a VM placement's artwork NAME (+ vmType) to its planovm _id. */
69397
69480
  resolveVmId(vmName, vmType) {
69398
69481
  const name = this.normVmType(vmName);
@@ -69413,7 +69496,7 @@ class LookPlanoCollectionFormComponent {
69413
69496
  * artwork (vmId/vmName) is updated while the library keeps its own VM
69414
69497
  * positions. vmTypes not yet present are appended.
69415
69498
  */
69416
- applyPlacementsToAssignedLibraries() {
69499
+ async applyPlacementsToAssignedLibraries() {
69417
69500
  const slot = this.selectedSlotGroup;
69418
69501
  if (!slot)
69419
69502
  return;
@@ -69446,10 +69529,30 @@ class LookPlanoCollectionFormComponent {
69446
69529
  label,
69447
69530
  });
69448
69531
  }
69532
+ // Nothing to apply — bail out before any fetch.
69533
+ if (pidByZone.size === 0 && vmByType.size === 0)
69534
+ return;
69535
+ // For any library that doesn't have a snapshot yet, fetch its details so
69536
+ // we can use the doc as the base. Without this, applyPlacements silently
69537
+ // skipped libraries the user hadn't viewed and only the currently-visible
69538
+ // chip ever received updates.
69539
+ const fetchedBases = new Map();
69540
+ await Promise.all(refs.map(async (libId) => {
69541
+ if (this.getLibrarySnapshot(libId))
69542
+ return;
69543
+ try {
69544
+ const res = await lastValueFrom(this.builderService.getFixtureLibraryDetails(libId));
69545
+ if (res?.data)
69546
+ fetchedBases.set(libId, res.data);
69547
+ }
69548
+ catch (err) {
69549
+ console.log('@@ ~ applyPlacementsToAssignedLibraries [fetch ERR]:', err);
69550
+ }
69551
+ }));
69449
69552
  for (const libId of refs) {
69450
- const target = this.getLibrarySnapshot(libId);
69553
+ const target = this.getLibrarySnapshot(libId) || fetchedBases.get(libId);
69451
69554
  if (!target)
69452
- continue; // seed creates one per assigned library
69555
+ continue;
69453
69556
  const shelves = Array.isArray(target.shelfConfig) ? target.shelfConfig : [];
69454
69557
  const newShelfConfig = shelves.map((shelf) => {
69455
69558
  const zone = norm(shelf?.zone || shelf?.label);
@@ -69472,14 +69575,12 @@ class LookPlanoCollectionFormComponent {
69472
69575
  for (const [key, upd] of vmByType) {
69473
69576
  if (seen.has(key))
69474
69577
  continue;
69578
+ const layout = this.initialVmLayout(upd.label, newShelfConfig);
69475
69579
  newVmConfig.push({
69476
69580
  vmType: upd.label,
69477
69581
  vmId: upd.vmId,
69478
69582
  vmName: upd.vmName,
69479
- startYPosition: 1,
69480
- endYPosition: 1,
69481
- xZone: '',
69482
- yZone: '',
69583
+ ...layout,
69483
69584
  position: '',
69484
69585
  });
69485
69586
  }
@@ -69754,6 +69855,14 @@ class LookPlanoCollectionFormComponent {
69754
69855
  ? seed.fixtureSlots
69755
69856
  : Array.from({ length: Math.max(1, Number(seed.fixtureCount) || 1) }, (_, i) => ({ slotIndex: i + 1 }));
69756
69857
  const slots = seedSlots.map((s, i) => this.createSlotGroup({ ...s, slotIndex: i + 1 }));
69858
+ // Legacy data may have stored cadHeaderVariants at the slot level. If the
69859
+ // incoming look doesn't have its own variants array, fold the union of
69860
+ // slot-level variants into the look so editing in the new UI doesn't lose
69861
+ // them.
69862
+ const seedLookVariants = Array.isArray(seed.cadHeaderVariants)
69863
+ ? seed.cadHeaderVariants
69864
+ : Array.from(new Set((Array.isArray(seed.fixtureSlots) ? seed.fixtureSlots : [])
69865
+ .flatMap((s) => Array.isArray(s?.cadHeaderVariants) ? s.cadHeaderVariants : [])));
69757
69866
  const lookGroup = this.fb.group({
69758
69867
  keyType: [seed.keyType || 'mbq'],
69759
69868
  mbqBucket: [seed.mbqBucket || ''],
@@ -69762,6 +69871,7 @@ class LookPlanoCollectionFormComponent {
69762
69871
  fixtureType: [Array.isArray(seed.fixtureType) ? seed.fixtureType : []],
69763
69872
  layoutVariant: [seed.layoutVariant || 'shelf'],
69764
69873
  storeProto: [seed.storeProto || ''],
69874
+ cadHeaderVariants: [seedLookVariants],
69765
69875
  fixtureSlots: this.fb.array(slots),
69766
69876
  });
69767
69877
  // fixtureCount → slots length (user-edited count adjusts the slots array)
@@ -69805,7 +69915,6 @@ class LookPlanoCollectionFormComponent {
69805
69915
  fixtureLibraryRef: [seed.fixtureLibraryRef || ''],
69806
69916
  fixtureLibraryLabel: [seed.fixtureLibraryLabel || ''],
69807
69917
  fixtureLibraryRefs: [Array.isArray(seed.fixtureLibraryRefs) ? seed.fixtureLibraryRefs : []],
69808
- cadHeaderVariants: [Array.isArray(seed.cadHeaderVariants) ? seed.cadHeaderVariants : []],
69809
69918
  placements: this.fb.array(placements),
69810
69919
  libraryConfigurations: this.fb.array(libraryConfigs),
69811
69920
  // Per-library snapshot of the embedded fixture-template editor state
@@ -70160,15 +70269,17 @@ class LookPlanoCollectionFormComponent {
70160
70269
  if (existingVms.length === 0) {
70161
70270
  const vmPlacements = placements.filter((p) => p?.kind === 'vm' && p?.rawValue);
70162
70271
  if (vmPlacements.length > 0) {
70163
- newVmConfig = vmPlacements.map((p) => ({
70164
- vmType: String(p?.position || ''),
70165
- vmId: String(p?.rawValue || ''),
70166
- startYPosition: 1,
70167
- endYPosition: 1,
70168
- xZone: '',
70169
- yZone: '',
70170
- position: '',
70171
- }));
70272
+ newVmConfig = vmPlacements.map((p) => {
70273
+ const vmType = String(p?.position || '');
70274
+ const layout = this.initialVmLayout(vmType, newShelfConfig);
70275
+ return {
70276
+ vmType,
70277
+ vmId: String(p?.rawValue || ''),
70278
+ vmName: String(p?.rawValue || ''),
70279
+ ...layout,
70280
+ position: '',
70281
+ };
70282
+ });
70172
70283
  vmsApplied = true;
70173
70284
  }
70174
70285
  }
@@ -70285,6 +70396,32 @@ class LookPlanoCollectionFormComponent {
70285
70396
  const count = lookGroup.get('fixtureCount')?.value;
70286
70397
  return `${key || '(unnamed)'} — ${count || 0} fixtures`;
70287
70398
  }
70399
+ /**
70400
+ * Short visual indicator for the slotType mix of a look — rendered as a
70401
+ * coloured chip in the MBQ Bucket - Fixture Combinations list. Returns
70402
+ * `null` for an empty look so the chip is hidden.
70403
+ */
70404
+ fixtureTypeBadge(lookGroup) {
70405
+ const slots = lookGroup.get('fixtureSlots');
70406
+ if (!slots || slots.length === 0)
70407
+ return null;
70408
+ let hasShelf = false;
70409
+ let hasEuro = false;
70410
+ for (const c of slots.controls) {
70411
+ const t = c.get('slotType')?.value;
70412
+ if (t === 'eurocenter')
70413
+ hasEuro = true;
70414
+ else
70415
+ hasShelf = true;
70416
+ if (hasShelf && hasEuro)
70417
+ break;
70418
+ }
70419
+ if (hasShelf && hasEuro)
70420
+ return { label: 'Wall + Floor', cls: 'badge-mixed' };
70421
+ if (hasEuro)
70422
+ return { label: 'Floor', cls: 'badge-floor' };
70423
+ return { label: 'Wall', cls: 'badge-wall' };
70424
+ }
70288
70425
  bucketKey(lookGroup) {
70289
70426
  const keyType = lookGroup.get('keyType')?.value;
70290
70427
  const key = keyType === 'brandCategory'
@@ -70427,11 +70564,11 @@ class LookPlanoCollectionFormComponent {
70427
70564
  this.planoDataService.fixtureTemplateDetails.next(null);
70428
70565
  }
70429
70566
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LookPlanoCollectionFormComponent, deps: [{ token: i1$2.FormBuilder }, { token: i2.ActivatedRoute }, { token: i2.Router }, { token: i2$1.GlobalStateService }, { token: StoreBuilderService }, { token: i4.ToastService }, { token: i2$1.PageInfoService }, { token: PlanoDataService }], target: i0.ɵɵFactoryTarget.Component });
70430
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: LookPlanoCollectionFormComponent, selector: "lib-look-plano-collection-form", ngImport: i0, template: "<section class=\"look-form\">\r\n <!-- Top bar (bound to the outer form) -->\r\n <div class=\"card p-4 mb-4\" [formGroup]=\"form\">\r\n <div class=\"row align-items-end\">\r\n <div class=\"col-md-4\">\r\n <label class=\"form-label fw-bold\">Name *</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"name\" placeholder=\"e.g. Master Look Plan FY26\" />\r\n </div>\r\n <div class=\"col-md-5\">\r\n <label class=\"form-label fw-bold\">Description</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"description\" placeholder=\"Optional description\" />\r\n </div>\r\n <div class=\"col-md-1 text-center\">\r\n <label class=\"form-label fw-bold d-block\">Active</label>\r\n <input type=\"checkbox\" class=\"form-check-input\" formControlName=\"isActive\" />\r\n </div>\r\n <div class=\"col-md-2 text-end\">\r\n <button type=\"button\" class=\"btn btn-light me-2\" (click)=\"cancel()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving\" (click)=\"save()\">\r\n {{ saving ? 'Saving\u2026' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Three-pane editor -->\r\n <div class=\"row mx-0 g-3\">\r\n <!-- Left: Looks list (no form binding needed \u2014 just navigation) -->\r\n <div class=\"col-md-3\">\r\n <div class=\"card p-3 h-100\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <h6 class=\"m-0\">MBQ Bucket - Fixture Combinations</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-primary\" (click)=\"addLook()\">+ Add Look</button>\r\n </div>\r\n <div *ngIf=\"looks.length === 0\" class=\"text-muted small\">No looks yet. Add one to start.</div>\r\n <div class=\"looks-tree\">\r\n <div\r\n *ngFor=\"let look of looks.controls; let i = index; trackBy: trackByIndex\"\r\n class=\"look-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedLookIndex === i\"\r\n [style.background-color]=\"bucketColor($any(look))\"\r\n (click)=\"selectLook(i)\"\r\n >\r\n <span class=\"d-flex align-items-center gap-2 text-truncate\">\r\n <span class=\"bucket-dot\" [style.background-color]=\"bucketColor($any(look))\"></span>\r\n <span class=\"text-truncate\">{{ lookTitle($any(look)) }}</span>\r\n </span>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeLook(i)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Centre: selected Look metadata + slots -->\r\n <div class=\"col-md-3\">\r\n <ng-container *ngIf=\"selectedLookGroup as lookGroup; else noLook\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"lookGroup\">\r\n <h6 class=\"mb-3\">Fixture Combination Details</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">MBQ bucket</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"mbqBucket\" placeholder=\"e.g. JJ / OD Eye\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture count *</label>\r\n <input type=\"number\" min=\"0\" max=\"30\" class=\"form-control form-control-sm\" formControlName=\"fixtureCount\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Positions</label>\r\n <multiselect-chip-dropdown\r\n [items]=\"fixtureTypeOptions\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n placeholder=\"Select fixture position\"\r\n formControlName=\"fixtureType\">\r\n </multiselect-chip-dropdown>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Fixtures</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addSlot()\">+ Add Fixture</button>\r\n </div>\r\n <div class=\"slots-list\">\r\n <div\r\n *ngFor=\"let slot of (selectedSlotsArray?.controls || []); let s = index; trackBy: trackByIndex\"\r\n class=\"slot-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedSlotIndex === s\"\r\n (click)=\"selectSlot(s)\"\r\n >\r\n <div class=\"d-flex flex-column\">\r\n <span class=\"fw-bold small\">\r\n Fixture {{ $any(slot).get('slotIndex')?.value }} \u2014 {{ $any(slot).get('slotType')?.value }}\r\n </span>\r\n <span class=\"text-muted small\">\r\n {{ $any(slot).get('brand')?.value || '(no brand)' }} \u00B7\r\n {{ $any(slot).get('fixtureLevelZone')?.value || '(no zone)' }}\r\n </span>\r\n </div>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeSlot(s)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noLook>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select or add a Look to edit it.</div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Right: selected slot \u2014 placements editor -->\r\n <div class=\"col-md-6\">\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroup; else noSlot\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"slotGroup\">\r\n <h6 class=\"mb-3\">Fixture {{ selectedSlotIndex + 1 }} - Definition</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small text-muted\">Slot index (auto)</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [value]=\"selectedSlotIndex + 1\" readonly disabled />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture Type</label>\r\n <select class=\"form-select form-select-sm\" formControlName=\"slotType\">\r\n <option *ngFor=\"let opt of availableSlotTypes\" [value]=\"opt.id\">{{ opt.name }}</option>\r\n </select>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Brand</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"brand\" placeholder=\"John Jacobs\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Header</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"fixtureLevelZone\" placeholder=\"Premium Trending\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">CAD Header Varients</label>\r\n <lib-chips-input\r\n formControlName=\"cadHeaderVariants\"\r\n placeholder=\"Type a variant and press Enter\">\r\n </lib-chips-input>\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Templates</label>\r\n <multiselect-chip-dropdown\r\n class=\"fixture-libraries-select\"\r\n [items]=\"fixtureLibraryItemsForSlot\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n [search]=\"true\"\r\n searchField=\"name\"\r\n placeholder=\"Select fixture templates\"\r\n formControlName=\"fixtureLibraryRefs\">\r\n </multiselect-chip-dropdown>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value === 'eurocenter'\">\r\n Showing floor fixtures (eurocenter slot).\r\n </small>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value !== 'eurocenter'\">\r\n Showing wall fixtures (shelf slot).\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Look and Visual Merchandising Placements</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addPlacement()\">+ Add placement</button>\r\n </div>\r\n <div *ngIf=\"(selectedPlacements?.length || 0) === 0\" class=\"text-muted small mb-2\">\r\n No placements yet. Click <strong>+ Add placement</strong> to add one.\r\n </div>\r\n <div class=\"placements-scroll\" formArrayName=\"placements\">\r\n <div\r\n *ngFor=\"let p of (selectedPlacements?.controls || []); let pi = index; trackBy: trackByIndex\"\r\n class=\"placement-row mb-2\"\r\n [formGroupName]=\"pi\"\r\n >\r\n <div class=\"d-flex align-items-start gap-2 placement-fields\">\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"kind\">\r\n <option value=\"pid\">Product Merchandising</option>\r\n <option value=\"vm\">Visual Merchandising</option>\r\n </select>\r\n </div>\r\n\r\n <!-- Visual Merchandising rows: VM Type (single) + VM Artwork (single) -->\r\n <ng-container *ngIf=\"$any(p).get('kind')?.value === 'vm'; else pidInputs\">\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmTypeOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Type\"\r\n formControlName=\"position\">\r\n </lib-searchable-select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmArtworkOptionsFor($any(p).get('position')?.value)\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Artwork\"\r\n formControlName=\"rawValue\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Product Merchandising rows: Top/Mid/Bottom position + multi-select product/SKU -->\r\n <ng-template #pidInputs>\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"position\">\r\n <option value=\"\">Position</option>\r\n <option value=\"Top\">Top</option>\r\n <option value=\"Mid\">Mid</option>\r\n <option value=\"Bottom\">Bottom</option>\r\n </select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"productOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n [multiple]=\"true\"\r\n placeholder=\"Product / SKU\"\r\n formControlName=\"rawValues\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-template>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ph-remove\" (click)=\"removePlacement(pi)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noSlot>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select a fixture slot to edit placements.</div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n\r\n <!-- Per-slot Fixture Library Configuration card -->\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroupForMap\">\r\n <div class=\"card p-4 mt-4 library-config-card\" [formGroup]=\"slotGroupForMap\">\r\n <div class=\"mb-4\">\r\n <h5 class=\"form-label d-block mb-3\">Fixture library</h5>\r\n <div class=\"library-chips\">\r\n <button\r\n *ngFor=\"let lib of selectedSlotLibrariesForDropdown\"\r\n type=\"button\"\r\n class=\"library-chip\"\r\n [class.active]=\"selectedLibraryIdInMap === lib.id\"\r\n (click)=\"onLibraryMapSelect(selectedLibraryIdInMap === lib.id ? '' : lib.id)\">\r\n {{ lib.name }}\r\n </button>\r\n </div>\r\n <small *ngIf=\"selectedSlotLibrariesForDropdown.length === 0\" class=\"text-muted\">\r\n No libraries linked to this slot yet. Add some in the \"Fixture libraries\" multiselect above.\r\n </small>\r\n </div>\r\n\r\n <div *ngIf=\"loadingLibraryDetails\" class=\"text-muted\">Loading fixture details\u2026</div>\r\n\r\n <!--\r\n Use *ngFor + trackBy keyed on (look, slot, library) so Angular treats\r\n a switch as a new item \u2014 that forces the embedded template-products\r\n / template-vms components to remount and rerun their ngOnInit\r\n (otherwise their `isPageLoading` guard skips the form rebuild).\r\n -->\r\n <ng-container *ngFor=\"let _ of embeddedHostKeys; trackBy: trackByEmbeddedKey\">\r\n <ul class=\"nav nav-tabs custom-tabs mb-3\">\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'products'\"\r\n (click)=\"embeddedTab = 'products'\">Product Merchandising</button>\r\n </li>\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'vms'\"\r\n (click)=\"embeddedTab = 'vms'\">Visual Merchandising</button>\r\n </li>\r\n </ul>\r\n\r\n <div *ngIf=\"embeddedTab === 'products'\">\r\n <lib-template-products [looksMode]=\"true\"></lib-template-products>\r\n </div>\r\n <div *ngIf=\"embeddedTab === 'vms'\">\r\n <lib-template-vms [looksMode]=\"true\"></lib-template-vms>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n</section>\r\n", styles: [".look-form .col-md-3>.card,.look-form .col-md-6>.card{display:flex!important;flex-direction:column;height:75vh!important;min-height:600px}.look-form .looks-tree,.look-form .slots-list,.look-form .placements-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.look-form .look-row,.look-form .slot-row{cursor:pointer;border:1px solid transparent;background:#f9fafb;transition:filter .15s ease,box-shadow .15s ease,border-color .15s ease}.look-form .look-row:hover,.look-form .slot-row:hover{filter:brightness(.96)}.look-form .look-row.selected,.look-form .slot-row.selected{filter:brightness(.96);outline:1px solid rgba(14,165,233,.55);outline-offset:-1px}.look-form .bucket-dot{display:inline-block;width:10px;height:10px;border-radius:50%;flex-shrink:0;border:1px solid rgba(0,0,0,.12)}.look-form .fixture-libraries-select{display:block}.look-form .fixture-libraries-select ::ng-deep .multiselect-container{align-items:flex-start}.look-form .fixture-libraries-select ::ng-deep .chip-list{max-height:84px;overflow-y:auto;padding-right:4px}.look-form .placement-row .placement-fields .ph-field{flex:1 1 0;min-width:0}.look-form .placement-row .placement-fields .ph-remove{flex:0 0 auto}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multiselect-container{padding:4px 10px;min-height:31px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .chip-list{min-height:21px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multi-placeholder{font-size:13px;line-height:21px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.look-form .library-config-card .library-chips{display:flex;flex-wrap:wrap;gap:8px}.look-form .library-config-card .library-chip{background:#f1f5f9;border:1px solid #cbd5e1;color:#1e293b;border-radius:999px;padding:6px 14px;font-size:13px;font-weight:500;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.look-form .library-config-card .library-chip:hover{background:#e2e8f0;border-color:#94a3b8}.look-form .library-config-card .library-chip.active{background:#0ea5e9;border-color:#0284c7;color:#fff}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col-8>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products input[readonly]{background-color:var(--bs-gray-200, #e9ecef);cursor:not-allowed}.look-form .library-config-card .fixture-preview{border:1px solid #e2e8f0;border-radius:8px;background:#fff;padding:12px}.look-form .library-config-card .fixture-preview .fx-header,.look-form .library-config-card .fixture-preview .fx-footer{background:#f1f5f9;border:1px dashed #cbd5e1;border-radius:6px;padding:6px 10px;font-size:12px;color:#475569;text-align:center;margin:4px 0}.look-form .library-config-card .fixture-preview .fx-body{display:flex;flex-direction:column;gap:4px;padding:4px 0}.look-form .library-config-card .fixture-preview .fx-shelf{background:linear-gradient(180deg,#f8fafc,#eef2f7);border:1px solid #cbd5e1;border-radius:4px;padding:8px 10px;display:flex;align-items:center;gap:8px;font-size:13px;min-height:36px}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-badge{background:#0ea5e9;color:#fff;font-weight:700;padding:2px 8px;border-radius:999px;font-size:11px;flex-shrink:0}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-label{font-weight:500;color:#1e293b}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-meta{color:#64748b;font-size:11px;margin-left:auto}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray{background:linear-gradient(180deg,#fef3c7,#fde68a);border-color:#f59e0b}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray .fx-shelf-badge{background:#d97706}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly{background:linear-gradient(180deg,#ede9fe,#ddd6fe);border-color:#8b5cf6}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly .fx-shelf-badge{background:#7c3aed}.look-form .library-config-card .fixture-preview .fx-dims{text-align:center}.look-form .library-config-card .shelf-mappings{max-height:70vh;overflow-y:auto;padding-right:4px}.look-form .library-config-card .shelf-mappings .shelf-mapping{border:1px solid #e5e7eb;border-radius:8px;padding:12px;background:#f9fafb}\n"], dependencies: [{ kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: MultiselectChipDropdownComponent, selector: "multiselect-chip-dropdown", inputs: ["idField", "nameField", "placeholder", "items", "search", "searchField", "maxSelection", "compact", "extraActionLabel", "extraActionActive", "disabled"], outputs: ["extraActionClick"] }, { kind: "component", type: TemplateProductsComponent, selector: "lib-template-products", inputs: ["looksMode"] }, { kind: "component", type: SearchableSelectComponent, selector: "lib-searchable-select", inputs: ["items", "idField", "nameField", "searchField", "placeholder", "multiple", "compact"] }, { kind: "component", type: ChipsInputComponent, selector: "lib-chips-input", inputs: ["placeholder", "allowDuplicates"] }, { kind: "component", type: TemplateVmsComponent, selector: "lib-template-vms", inputs: ["looksMode"] }] });
70567
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: LookPlanoCollectionFormComponent, selector: "lib-look-plano-collection-form", ngImport: i0, template: "<section class=\"look-form\">\r\n <!-- Top bar (bound to the outer form) -->\r\n <div class=\"card p-4 mb-4\" [formGroup]=\"form\">\r\n <div class=\"row align-items-end\">\r\n <div class=\"col-md-4\">\r\n <label class=\"form-label fw-bold\">Name *</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"name\" placeholder=\"e.g. Master Look Plan FY26\" />\r\n </div>\r\n <div class=\"col-md-5\">\r\n <label class=\"form-label fw-bold\">Description</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"description\" placeholder=\"Optional description\" />\r\n </div>\r\n <div class=\"col-md-1 text-center\">\r\n <label class=\"form-label fw-bold d-block\">Active</label>\r\n <input type=\"checkbox\" class=\"form-check-input\" formControlName=\"isActive\" />\r\n </div>\r\n <div class=\"col-md-2 text-end\">\r\n <button type=\"button\" class=\"btn btn-light me-2\" (click)=\"cancel()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving\" (click)=\"save()\">\r\n {{ saving ? 'Saving\u2026' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Three-pane editor -->\r\n <div class=\"row mx-0 g-3\">\r\n <!-- Left: Looks list (no form binding needed \u2014 just navigation) -->\r\n <div class=\"col-md-3\">\r\n <div class=\"card p-3 h-100\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <h6 class=\"m-0\">MBQ Bucket - Fixture Combinations</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-primary\" (click)=\"addLook()\">+ Add Look</button>\r\n </div>\r\n <div *ngIf=\"looks.length === 0\" class=\"text-muted small\">No looks yet. Add one to start.</div>\r\n <div class=\"looks-tree\">\r\n <div\r\n *ngFor=\"let look of looks.controls; let i = index; trackBy: trackByIndex\"\r\n class=\"look-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedLookIndex === i\"\r\n [style.background-color]=\"bucketColor($any(look))\"\r\n (click)=\"selectLook(i)\"\r\n >\r\n <span class=\"d-flex align-items-center gap-2 text-truncate\">\r\n <span class=\"bucket-dot\" [style.background-color]=\"bucketColor($any(look))\"></span>\r\n <span class=\"text-truncate\">{{ lookTitle($any(look)) }}</span>\r\n <ng-container *ngIf=\"fixtureTypeBadge($any(look)) as ftb\">\r\n <span class=\"fixture-type-badge\" [ngClass]=\"ftb.cls\">{{ ftb.label }}</span>\r\n </ng-container>\r\n </span>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeLook(i)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Centre: selected Look metadata + slots -->\r\n <div class=\"col-md-3\">\r\n <ng-container *ngIf=\"selectedLookGroup as lookGroup; else noLook\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"lookGroup\">\r\n <h6 class=\"mb-3\">Fixture Combination Details</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">MBQ bucket</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"mbqBucket\" placeholder=\"e.g. JJ / OD Eye\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture count *</label>\r\n <input type=\"number\" min=\"0\" max=\"30\" class=\"form-control form-control-sm\" formControlName=\"fixtureCount\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">CAD Header Varients</label>\r\n <lib-chips-input\r\n class=\"cad-header-variants-input\"\r\n formControlName=\"cadHeaderVariants\"\r\n placeholder=\"Type a variant and press Enter\">\r\n </lib-chips-input>\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Positions</label>\r\n <multiselect-chip-dropdown\r\n [items]=\"fixtureTypeOptions\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n placeholder=\"Select fixture position\"\r\n formControlName=\"fixtureType\">\r\n </multiselect-chip-dropdown>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Fixtures</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addSlot()\">+ Add Fixture</button>\r\n </div>\r\n <div class=\"slots-list\">\r\n <div\r\n *ngFor=\"let slot of (selectedSlotsArray?.controls || []); let s = index; trackBy: trackByIndex\"\r\n class=\"slot-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedSlotIndex === s\"\r\n (click)=\"selectSlot(s)\"\r\n >\r\n <div class=\"d-flex flex-column\">\r\n <span class=\"fw-bold small\">\r\n Fixture {{ $any(slot).get('slotIndex')?.value }} \u2014 {{ $any(slot).get('slotType')?.value }}\r\n </span>\r\n <span class=\"text-muted small\">\r\n {{ $any(slot).get('brand')?.value || '(no brand)' }} \u00B7\r\n {{ $any(slot).get('fixtureLevelZone')?.value || '(no zone)' }}\r\n </span>\r\n </div>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeSlot(s)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noLook>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select or add a Look to edit it.</div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Right: selected slot \u2014 placements editor -->\r\n <div class=\"col-md-6\">\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroup; else noSlot\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"slotGroup\">\r\n <h6 class=\"mb-3\">Fixture {{ selectedSlotIndex + 1 }} - Definition</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small text-muted\">Slot index (auto)</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [value]=\"selectedSlotIndex + 1\" readonly disabled />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture Type</label>\r\n <select class=\"form-select form-select-sm\" formControlName=\"slotType\">\r\n <option *ngFor=\"let opt of availableSlotTypes\" [value]=\"opt.id\">{{ opt.name }}</option>\r\n </select>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Brand</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"brand\" placeholder=\"John Jacobs\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Header</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"fixtureLevelZone\" placeholder=\"Premium Trending\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Templates</label>\r\n <multiselect-chip-dropdown\r\n class=\"fixture-libraries-select\"\r\n [items]=\"fixtureLibraryItemsForSlot\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n [search]=\"true\"\r\n searchField=\"name\"\r\n placeholder=\"Select fixture templates\"\r\n formControlName=\"fixtureLibraryRefs\">\r\n </multiselect-chip-dropdown>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value === 'eurocenter'\">\r\n Showing floor fixtures (eurocenter slot).\r\n </small>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value !== 'eurocenter'\">\r\n Showing wall fixtures (shelf slot).\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Look and Visual Merchandising Placements</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addPlacement()\">+ Add placement</button>\r\n </div>\r\n <div *ngIf=\"(selectedPlacements?.length || 0) === 0\" class=\"text-muted small mb-2\">\r\n No placements yet. Click <strong>+ Add placement</strong> to add one.\r\n </div>\r\n <div class=\"placements-scroll\" formArrayName=\"placements\">\r\n <div\r\n *ngFor=\"let p of (selectedPlacements?.controls || []); let pi = index; trackBy: trackByIndex\"\r\n class=\"placement-row mb-2\"\r\n [formGroupName]=\"pi\"\r\n >\r\n <div class=\"d-flex align-items-start gap-2 placement-fields\">\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"kind\">\r\n <option value=\"pid\">Product Merchandising</option>\r\n <option value=\"vm\">Visual Merchandising</option>\r\n </select>\r\n </div>\r\n\r\n <!-- Visual Merchandising rows: VM Type (single) + VM Artwork (single) -->\r\n <ng-container *ngIf=\"$any(p).get('kind')?.value === 'vm'; else pidInputs\">\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmTypeOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Type\"\r\n formControlName=\"position\">\r\n </lib-searchable-select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmArtworkOptionsFor($any(p).get('position')?.value)\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Artwork\"\r\n formControlName=\"rawValue\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Product Merchandising rows: Top/Mid/Bottom position + multi-select product/SKU -->\r\n <ng-template #pidInputs>\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"position\">\r\n <option value=\"\">Position</option>\r\n <option value=\"Top\">Top</option>\r\n <option value=\"Mid\">Mid</option>\r\n <option value=\"Bottom\">Bottom</option>\r\n </select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"productOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n [multiple]=\"true\"\r\n placeholder=\"Product / SKU\"\r\n formControlName=\"rawValues\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-template>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ph-remove\" (click)=\"removePlacement(pi)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noSlot>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select a fixture slot to edit placements.</div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n\r\n <!-- Per-slot Fixture Library Configuration card -->\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroupForMap\">\r\n <div class=\"card p-4 mt-4 library-config-card\" [formGroup]=\"slotGroupForMap\">\r\n <div class=\"mb-4\">\r\n <h5 class=\"form-label d-block mb-3\">Fixture library</h5>\r\n <div class=\"library-chips\">\r\n <button\r\n *ngFor=\"let lib of selectedSlotLibrariesForDropdown\"\r\n type=\"button\"\r\n class=\"library-chip\"\r\n [class.active]=\"selectedLibraryIdInMap === lib.id\"\r\n (click)=\"onLibraryMapSelect(selectedLibraryIdInMap === lib.id ? '' : lib.id)\">\r\n {{ lib.name }}\r\n </button>\r\n </div>\r\n <small *ngIf=\"selectedSlotLibrariesForDropdown.length === 0\" class=\"text-muted\">\r\n No libraries linked to this slot yet. Add some in the \"Fixture libraries\" multiselect above.\r\n </small>\r\n </div>\r\n\r\n <div *ngIf=\"loadingLibraryDetails\" class=\"text-muted\">Loading fixture details\u2026</div>\r\n\r\n <!--\r\n Use *ngFor + trackBy keyed on (look, slot, library) so Angular treats\r\n a switch as a new item \u2014 that forces the embedded template-products\r\n / template-vms components to remount and rerun their ngOnInit\r\n (otherwise their `isPageLoading` guard skips the form rebuild).\r\n -->\r\n <ng-container *ngFor=\"let _ of embeddedHostKeys; trackBy: trackByEmbeddedKey\">\r\n <ul class=\"nav nav-tabs custom-tabs mb-3\">\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'products'\"\r\n (click)=\"embeddedTab = 'products'\">Product Merchandising</button>\r\n </li>\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'vms'\"\r\n (click)=\"embeddedTab = 'vms'\">Visual Merchandising</button>\r\n </li>\r\n </ul>\r\n\r\n <div *ngIf=\"embeddedTab === 'products'\">\r\n <lib-template-products [looksMode]=\"true\"></lib-template-products>\r\n </div>\r\n <div *ngIf=\"embeddedTab === 'vms'\">\r\n <lib-template-vms [looksMode]=\"true\"></lib-template-vms>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n</section>\r\n", styles: [".look-form .col-md-3>.card,.look-form .col-md-6>.card{display:flex!important;flex-direction:column;height:85vh!important;min-height:720px}.look-form .looks-tree,.look-form .slots-list,.look-form .placements-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.look-form .look-row,.look-form .slot-row{cursor:pointer;border:1px solid transparent;background:#f9fafb;transition:filter .15s ease,box-shadow .15s ease,border-color .15s ease}.look-form .look-row:hover,.look-form .slot-row:hover{filter:brightness(.96)}.look-form .look-row.selected,.look-form .slot-row.selected{filter:brightness(.96);outline:1px solid rgba(14,165,233,.55);outline-offset:-1px}.look-form .bucket-dot{display:inline-block;width:10px;height:10px;border-radius:50%;flex-shrink:0;border:1px solid rgba(0,0,0,.12)}.look-form .fixture-type-badge{display:inline-block;flex-shrink:0;padding:1px 8px;border-radius:999px;font-size:10px;font-weight:600;line-height:14px;letter-spacing:.2px;border:1px solid transparent;white-space:nowrap}.look-form .fixture-type-badge.badge-wall{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.look-form .fixture-type-badge.badge-floor{background:#fed7aa;color:#9a3412;border-color:#fdba74}.look-form .fixture-type-badge.badge-mixed{background:#ede9fe;color:#6d28d9;border-color:#c4b5fd}.look-form .fixture-libraries-select{display:block}.look-form .fixture-libraries-select ::ng-deep .multiselect-container{align-items:flex-start}.look-form .fixture-libraries-select ::ng-deep .chip-list{max-height:84px;overflow-y:auto;padding-right:4px}.look-form .cad-header-variants-input{display:block}.look-form .cad-header-variants-input ::ng-deep .chips-input{max-height:84px;overflow-y:auto;align-content:flex-start;padding-right:4px}.look-form .placement-row .placement-fields .ph-field{flex:1 1 0;min-width:0}.look-form .placement-row .placement-fields .ph-remove{flex:0 0 auto}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multiselect-container{padding:4px 10px;min-height:31px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .chip-list{min-height:21px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multi-placeholder{font-size:13px;line-height:21px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.look-form .library-config-card .library-chips{display:flex;flex-wrap:wrap;gap:8px}.look-form .library-config-card .library-chip{background:#f1f5f9;border:1px solid #cbd5e1;color:#1e293b;border-radius:999px;padding:6px 14px;font-size:13px;font-weight:500;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.look-form .library-config-card .library-chip:hover{background:#e2e8f0;border-color:#94a3b8}.look-form .library-config-card .library-chip.active{background:#0ea5e9;border-color:#0284c7;color:#fff}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col-8>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products input[readonly]{background-color:var(--bs-gray-200, #e9ecef);cursor:not-allowed}.look-form .library-config-card .fixture-preview{border:1px solid #e2e8f0;border-radius:8px;background:#fff;padding:12px}.look-form .library-config-card .fixture-preview .fx-header,.look-form .library-config-card .fixture-preview .fx-footer{background:#f1f5f9;border:1px dashed #cbd5e1;border-radius:6px;padding:6px 10px;font-size:12px;color:#475569;text-align:center;margin:4px 0}.look-form .library-config-card .fixture-preview .fx-body{display:flex;flex-direction:column;gap:4px;padding:4px 0}.look-form .library-config-card .fixture-preview .fx-shelf{background:linear-gradient(180deg,#f8fafc,#eef2f7);border:1px solid #cbd5e1;border-radius:4px;padding:8px 10px;display:flex;align-items:center;gap:8px;font-size:13px;min-height:36px}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-badge{background:#0ea5e9;color:#fff;font-weight:700;padding:2px 8px;border-radius:999px;font-size:11px;flex-shrink:0}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-label{font-weight:500;color:#1e293b}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-meta{color:#64748b;font-size:11px;margin-left:auto}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray{background:linear-gradient(180deg,#fef3c7,#fde68a);border-color:#f59e0b}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray .fx-shelf-badge{background:#d97706}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly{background:linear-gradient(180deg,#ede9fe,#ddd6fe);border-color:#8b5cf6}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly .fx-shelf-badge{background:#7c3aed}.look-form .library-config-card .fixture-preview .fx-dims{text-align:center}.look-form .library-config-card .shelf-mappings{max-height:70vh;overflow-y:auto;padding-right:4px}.look-form .library-config-card .shelf-mappings .shelf-mapping{border:1px solid #e5e7eb;border-radius:8px;padding:12px;background:#f9fafb}\n"], dependencies: [{ kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$2.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: MultiselectChipDropdownComponent, selector: "multiselect-chip-dropdown", inputs: ["idField", "nameField", "placeholder", "items", "search", "searchField", "maxSelection", "compact", "extraActionLabel", "extraActionActive", "disabled"], outputs: ["extraActionClick"] }, { kind: "component", type: TemplateProductsComponent, selector: "lib-template-products", inputs: ["looksMode"] }, { kind: "component", type: SearchableSelectComponent, selector: "lib-searchable-select", inputs: ["items", "idField", "nameField", "searchField", "placeholder", "multiple", "compact"] }, { kind: "component", type: ChipsInputComponent, selector: "lib-chips-input", inputs: ["placeholder", "allowDuplicates"] }, { kind: "component", type: TemplateVmsComponent, selector: "lib-template-vms", inputs: ["looksMode"] }] });
70431
70568
  }
70432
70569
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LookPlanoCollectionFormComponent, decorators: [{
70433
70570
  type: Component,
70434
- args: [{ selector: 'lib-look-plano-collection-form', template: "<section class=\"look-form\">\r\n <!-- Top bar (bound to the outer form) -->\r\n <div class=\"card p-4 mb-4\" [formGroup]=\"form\">\r\n <div class=\"row align-items-end\">\r\n <div class=\"col-md-4\">\r\n <label class=\"form-label fw-bold\">Name *</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"name\" placeholder=\"e.g. Master Look Plan FY26\" />\r\n </div>\r\n <div class=\"col-md-5\">\r\n <label class=\"form-label fw-bold\">Description</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"description\" placeholder=\"Optional description\" />\r\n </div>\r\n <div class=\"col-md-1 text-center\">\r\n <label class=\"form-label fw-bold d-block\">Active</label>\r\n <input type=\"checkbox\" class=\"form-check-input\" formControlName=\"isActive\" />\r\n </div>\r\n <div class=\"col-md-2 text-end\">\r\n <button type=\"button\" class=\"btn btn-light me-2\" (click)=\"cancel()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving\" (click)=\"save()\">\r\n {{ saving ? 'Saving\u2026' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Three-pane editor -->\r\n <div class=\"row mx-0 g-3\">\r\n <!-- Left: Looks list (no form binding needed \u2014 just navigation) -->\r\n <div class=\"col-md-3\">\r\n <div class=\"card p-3 h-100\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <h6 class=\"m-0\">MBQ Bucket - Fixture Combinations</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-primary\" (click)=\"addLook()\">+ Add Look</button>\r\n </div>\r\n <div *ngIf=\"looks.length === 0\" class=\"text-muted small\">No looks yet. Add one to start.</div>\r\n <div class=\"looks-tree\">\r\n <div\r\n *ngFor=\"let look of looks.controls; let i = index; trackBy: trackByIndex\"\r\n class=\"look-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedLookIndex === i\"\r\n [style.background-color]=\"bucketColor($any(look))\"\r\n (click)=\"selectLook(i)\"\r\n >\r\n <span class=\"d-flex align-items-center gap-2 text-truncate\">\r\n <span class=\"bucket-dot\" [style.background-color]=\"bucketColor($any(look))\"></span>\r\n <span class=\"text-truncate\">{{ lookTitle($any(look)) }}</span>\r\n </span>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeLook(i)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Centre: selected Look metadata + slots -->\r\n <div class=\"col-md-3\">\r\n <ng-container *ngIf=\"selectedLookGroup as lookGroup; else noLook\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"lookGroup\">\r\n <h6 class=\"mb-3\">Fixture Combination Details</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">MBQ bucket</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"mbqBucket\" placeholder=\"e.g. JJ / OD Eye\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture count *</label>\r\n <input type=\"number\" min=\"0\" max=\"30\" class=\"form-control form-control-sm\" formControlName=\"fixtureCount\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Positions</label>\r\n <multiselect-chip-dropdown\r\n [items]=\"fixtureTypeOptions\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n placeholder=\"Select fixture position\"\r\n formControlName=\"fixtureType\">\r\n </multiselect-chip-dropdown>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Fixtures</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addSlot()\">+ Add Fixture</button>\r\n </div>\r\n <div class=\"slots-list\">\r\n <div\r\n *ngFor=\"let slot of (selectedSlotsArray?.controls || []); let s = index; trackBy: trackByIndex\"\r\n class=\"slot-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedSlotIndex === s\"\r\n (click)=\"selectSlot(s)\"\r\n >\r\n <div class=\"d-flex flex-column\">\r\n <span class=\"fw-bold small\">\r\n Fixture {{ $any(slot).get('slotIndex')?.value }} \u2014 {{ $any(slot).get('slotType')?.value }}\r\n </span>\r\n <span class=\"text-muted small\">\r\n {{ $any(slot).get('brand')?.value || '(no brand)' }} \u00B7\r\n {{ $any(slot).get('fixtureLevelZone')?.value || '(no zone)' }}\r\n </span>\r\n </div>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeSlot(s)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noLook>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select or add a Look to edit it.</div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Right: selected slot \u2014 placements editor -->\r\n <div class=\"col-md-6\">\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroup; else noSlot\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"slotGroup\">\r\n <h6 class=\"mb-3\">Fixture {{ selectedSlotIndex + 1 }} - Definition</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small text-muted\">Slot index (auto)</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [value]=\"selectedSlotIndex + 1\" readonly disabled />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture Type</label>\r\n <select class=\"form-select form-select-sm\" formControlName=\"slotType\">\r\n <option *ngFor=\"let opt of availableSlotTypes\" [value]=\"opt.id\">{{ opt.name }}</option>\r\n </select>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Brand</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"brand\" placeholder=\"John Jacobs\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Header</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"fixtureLevelZone\" placeholder=\"Premium Trending\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">CAD Header Varients</label>\r\n <lib-chips-input\r\n formControlName=\"cadHeaderVariants\"\r\n placeholder=\"Type a variant and press Enter\">\r\n </lib-chips-input>\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Templates</label>\r\n <multiselect-chip-dropdown\r\n class=\"fixture-libraries-select\"\r\n [items]=\"fixtureLibraryItemsForSlot\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n [search]=\"true\"\r\n searchField=\"name\"\r\n placeholder=\"Select fixture templates\"\r\n formControlName=\"fixtureLibraryRefs\">\r\n </multiselect-chip-dropdown>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value === 'eurocenter'\">\r\n Showing floor fixtures (eurocenter slot).\r\n </small>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value !== 'eurocenter'\">\r\n Showing wall fixtures (shelf slot).\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Look and Visual Merchandising Placements</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addPlacement()\">+ Add placement</button>\r\n </div>\r\n <div *ngIf=\"(selectedPlacements?.length || 0) === 0\" class=\"text-muted small mb-2\">\r\n No placements yet. Click <strong>+ Add placement</strong> to add one.\r\n </div>\r\n <div class=\"placements-scroll\" formArrayName=\"placements\">\r\n <div\r\n *ngFor=\"let p of (selectedPlacements?.controls || []); let pi = index; trackBy: trackByIndex\"\r\n class=\"placement-row mb-2\"\r\n [formGroupName]=\"pi\"\r\n >\r\n <div class=\"d-flex align-items-start gap-2 placement-fields\">\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"kind\">\r\n <option value=\"pid\">Product Merchandising</option>\r\n <option value=\"vm\">Visual Merchandising</option>\r\n </select>\r\n </div>\r\n\r\n <!-- Visual Merchandising rows: VM Type (single) + VM Artwork (single) -->\r\n <ng-container *ngIf=\"$any(p).get('kind')?.value === 'vm'; else pidInputs\">\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmTypeOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Type\"\r\n formControlName=\"position\">\r\n </lib-searchable-select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmArtworkOptionsFor($any(p).get('position')?.value)\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Artwork\"\r\n formControlName=\"rawValue\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Product Merchandising rows: Top/Mid/Bottom position + multi-select product/SKU -->\r\n <ng-template #pidInputs>\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"position\">\r\n <option value=\"\">Position</option>\r\n <option value=\"Top\">Top</option>\r\n <option value=\"Mid\">Mid</option>\r\n <option value=\"Bottom\">Bottom</option>\r\n </select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"productOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n [multiple]=\"true\"\r\n placeholder=\"Product / SKU\"\r\n formControlName=\"rawValues\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-template>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ph-remove\" (click)=\"removePlacement(pi)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noSlot>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select a fixture slot to edit placements.</div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n\r\n <!-- Per-slot Fixture Library Configuration card -->\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroupForMap\">\r\n <div class=\"card p-4 mt-4 library-config-card\" [formGroup]=\"slotGroupForMap\">\r\n <div class=\"mb-4\">\r\n <h5 class=\"form-label d-block mb-3\">Fixture library</h5>\r\n <div class=\"library-chips\">\r\n <button\r\n *ngFor=\"let lib of selectedSlotLibrariesForDropdown\"\r\n type=\"button\"\r\n class=\"library-chip\"\r\n [class.active]=\"selectedLibraryIdInMap === lib.id\"\r\n (click)=\"onLibraryMapSelect(selectedLibraryIdInMap === lib.id ? '' : lib.id)\">\r\n {{ lib.name }}\r\n </button>\r\n </div>\r\n <small *ngIf=\"selectedSlotLibrariesForDropdown.length === 0\" class=\"text-muted\">\r\n No libraries linked to this slot yet. Add some in the \"Fixture libraries\" multiselect above.\r\n </small>\r\n </div>\r\n\r\n <div *ngIf=\"loadingLibraryDetails\" class=\"text-muted\">Loading fixture details\u2026</div>\r\n\r\n <!--\r\n Use *ngFor + trackBy keyed on (look, slot, library) so Angular treats\r\n a switch as a new item \u2014 that forces the embedded template-products\r\n / template-vms components to remount and rerun their ngOnInit\r\n (otherwise their `isPageLoading` guard skips the form rebuild).\r\n -->\r\n <ng-container *ngFor=\"let _ of embeddedHostKeys; trackBy: trackByEmbeddedKey\">\r\n <ul class=\"nav nav-tabs custom-tabs mb-3\">\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'products'\"\r\n (click)=\"embeddedTab = 'products'\">Product Merchandising</button>\r\n </li>\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'vms'\"\r\n (click)=\"embeddedTab = 'vms'\">Visual Merchandising</button>\r\n </li>\r\n </ul>\r\n\r\n <div *ngIf=\"embeddedTab === 'products'\">\r\n <lib-template-products [looksMode]=\"true\"></lib-template-products>\r\n </div>\r\n <div *ngIf=\"embeddedTab === 'vms'\">\r\n <lib-template-vms [looksMode]=\"true\"></lib-template-vms>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n</section>\r\n", styles: [".look-form .col-md-3>.card,.look-form .col-md-6>.card{display:flex!important;flex-direction:column;height:75vh!important;min-height:600px}.look-form .looks-tree,.look-form .slots-list,.look-form .placements-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.look-form .look-row,.look-form .slot-row{cursor:pointer;border:1px solid transparent;background:#f9fafb;transition:filter .15s ease,box-shadow .15s ease,border-color .15s ease}.look-form .look-row:hover,.look-form .slot-row:hover{filter:brightness(.96)}.look-form .look-row.selected,.look-form .slot-row.selected{filter:brightness(.96);outline:1px solid rgba(14,165,233,.55);outline-offset:-1px}.look-form .bucket-dot{display:inline-block;width:10px;height:10px;border-radius:50%;flex-shrink:0;border:1px solid rgba(0,0,0,.12)}.look-form .fixture-libraries-select{display:block}.look-form .fixture-libraries-select ::ng-deep .multiselect-container{align-items:flex-start}.look-form .fixture-libraries-select ::ng-deep .chip-list{max-height:84px;overflow-y:auto;padding-right:4px}.look-form .placement-row .placement-fields .ph-field{flex:1 1 0;min-width:0}.look-form .placement-row .placement-fields .ph-remove{flex:0 0 auto}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multiselect-container{padding:4px 10px;min-height:31px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .chip-list{min-height:21px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multi-placeholder{font-size:13px;line-height:21px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.look-form .library-config-card .library-chips{display:flex;flex-wrap:wrap;gap:8px}.look-form .library-config-card .library-chip{background:#f1f5f9;border:1px solid #cbd5e1;color:#1e293b;border-radius:999px;padding:6px 14px;font-size:13px;font-weight:500;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.look-form .library-config-card .library-chip:hover{background:#e2e8f0;border-color:#94a3b8}.look-form .library-config-card .library-chip.active{background:#0ea5e9;border-color:#0284c7;color:#fff}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col-8>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products input[readonly]{background-color:var(--bs-gray-200, #e9ecef);cursor:not-allowed}.look-form .library-config-card .fixture-preview{border:1px solid #e2e8f0;border-radius:8px;background:#fff;padding:12px}.look-form .library-config-card .fixture-preview .fx-header,.look-form .library-config-card .fixture-preview .fx-footer{background:#f1f5f9;border:1px dashed #cbd5e1;border-radius:6px;padding:6px 10px;font-size:12px;color:#475569;text-align:center;margin:4px 0}.look-form .library-config-card .fixture-preview .fx-body{display:flex;flex-direction:column;gap:4px;padding:4px 0}.look-form .library-config-card .fixture-preview .fx-shelf{background:linear-gradient(180deg,#f8fafc,#eef2f7);border:1px solid #cbd5e1;border-radius:4px;padding:8px 10px;display:flex;align-items:center;gap:8px;font-size:13px;min-height:36px}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-badge{background:#0ea5e9;color:#fff;font-weight:700;padding:2px 8px;border-radius:999px;font-size:11px;flex-shrink:0}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-label{font-weight:500;color:#1e293b}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-meta{color:#64748b;font-size:11px;margin-left:auto}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray{background:linear-gradient(180deg,#fef3c7,#fde68a);border-color:#f59e0b}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray .fx-shelf-badge{background:#d97706}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly{background:linear-gradient(180deg,#ede9fe,#ddd6fe);border-color:#8b5cf6}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly .fx-shelf-badge{background:#7c3aed}.look-form .library-config-card .fixture-preview .fx-dims{text-align:center}.look-form .library-config-card .shelf-mappings{max-height:70vh;overflow-y:auto;padding-right:4px}.look-form .library-config-card .shelf-mappings .shelf-mapping{border:1px solid #e5e7eb;border-radius:8px;padding:12px;background:#f9fafb}\n"] }]
70571
+ args: [{ selector: 'lib-look-plano-collection-form', template: "<section class=\"look-form\">\r\n <!-- Top bar (bound to the outer form) -->\r\n <div class=\"card p-4 mb-4\" [formGroup]=\"form\">\r\n <div class=\"row align-items-end\">\r\n <div class=\"col-md-4\">\r\n <label class=\"form-label fw-bold\">Name *</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"name\" placeholder=\"e.g. Master Look Plan FY26\" />\r\n </div>\r\n <div class=\"col-md-5\">\r\n <label class=\"form-label fw-bold\">Description</label>\r\n <input type=\"text\" class=\"form-control\" formControlName=\"description\" placeholder=\"Optional description\" />\r\n </div>\r\n <div class=\"col-md-1 text-center\">\r\n <label class=\"form-label fw-bold d-block\">Active</label>\r\n <input type=\"checkbox\" class=\"form-check-input\" formControlName=\"isActive\" />\r\n </div>\r\n <div class=\"col-md-2 text-end\">\r\n <button type=\"button\" class=\"btn btn-light me-2\" (click)=\"cancel()\">Cancel</button>\r\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving\" (click)=\"save()\">\r\n {{ saving ? 'Saving\u2026' : 'Save' }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Three-pane editor -->\r\n <div class=\"row mx-0 g-3\">\r\n <!-- Left: Looks list (no form binding needed \u2014 just navigation) -->\r\n <div class=\"col-md-3\">\r\n <div class=\"card p-3 h-100\">\r\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\r\n <h6 class=\"m-0\">MBQ Bucket - Fixture Combinations</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-primary\" (click)=\"addLook()\">+ Add Look</button>\r\n </div>\r\n <div *ngIf=\"looks.length === 0\" class=\"text-muted small\">No looks yet. Add one to start.</div>\r\n <div class=\"looks-tree\">\r\n <div\r\n *ngFor=\"let look of looks.controls; let i = index; trackBy: trackByIndex\"\r\n class=\"look-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedLookIndex === i\"\r\n [style.background-color]=\"bucketColor($any(look))\"\r\n (click)=\"selectLook(i)\"\r\n >\r\n <span class=\"d-flex align-items-center gap-2 text-truncate\">\r\n <span class=\"bucket-dot\" [style.background-color]=\"bucketColor($any(look))\"></span>\r\n <span class=\"text-truncate\">{{ lookTitle($any(look)) }}</span>\r\n <ng-container *ngIf=\"fixtureTypeBadge($any(look)) as ftb\">\r\n <span class=\"fixture-type-badge\" [ngClass]=\"ftb.cls\">{{ ftb.label }}</span>\r\n </ng-container>\r\n </span>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeLook(i)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Centre: selected Look metadata + slots -->\r\n <div class=\"col-md-3\">\r\n <ng-container *ngIf=\"selectedLookGroup as lookGroup; else noLook\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"lookGroup\">\r\n <h6 class=\"mb-3\">Fixture Combination Details</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">MBQ bucket</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"mbqBucket\" placeholder=\"e.g. JJ / OD Eye\" />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture count *</label>\r\n <input type=\"number\" min=\"0\" max=\"30\" class=\"form-control form-control-sm\" formControlName=\"fixtureCount\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">CAD Header Varients</label>\r\n <lib-chips-input\r\n class=\"cad-header-variants-input\"\r\n formControlName=\"cadHeaderVariants\"\r\n placeholder=\"Type a variant and press Enter\">\r\n </lib-chips-input>\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Positions</label>\r\n <multiselect-chip-dropdown\r\n [items]=\"fixtureTypeOptions\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n placeholder=\"Select fixture position\"\r\n formControlName=\"fixtureType\">\r\n </multiselect-chip-dropdown>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Fixtures</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addSlot()\">+ Add Fixture</button>\r\n </div>\r\n <div class=\"slots-list\">\r\n <div\r\n *ngFor=\"let slot of (selectedSlotsArray?.controls || []); let s = index; trackBy: trackByIndex\"\r\n class=\"slot-row d-flex justify-content-between align-items-center px-2 py-2 mb-1 rounded\"\r\n [class.selected]=\"selectedSlotIndex === s\"\r\n (click)=\"selectSlot(s)\"\r\n >\r\n <div class=\"d-flex flex-column\">\r\n <span class=\"fw-bold small\">\r\n Fixture {{ $any(slot).get('slotIndex')?.value }} \u2014 {{ $any(slot).get('slotType')?.value }}\r\n </span>\r\n <span class=\"text-muted small\">\r\n {{ $any(slot).get('brand')?.value || '(no brand)' }} \u00B7\r\n {{ $any(slot).get('fixtureLevelZone')?.value || '(no zone)' }}\r\n </span>\r\n </div>\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ms-2\" (click)=\"$event.stopPropagation(); removeSlot(s)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noLook>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select or add a Look to edit it.</div>\r\n </ng-template>\r\n </div>\r\n\r\n <!-- Right: selected slot \u2014 placements editor -->\r\n <div class=\"col-md-6\">\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroup; else noSlot\">\r\n <div class=\"card p-3 h-100\" [formGroup]=\"slotGroup\">\r\n <h6 class=\"mb-3\">Fixture {{ selectedSlotIndex + 1 }} - Definition</h6>\r\n\r\n <div class=\"row g-2 mb-3\">\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small text-muted\">Slot index (auto)</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" [value]=\"selectedSlotIndex + 1\" readonly disabled />\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Fixture Type</label>\r\n <select class=\"form-select form-select-sm\" formControlName=\"slotType\">\r\n <option *ngFor=\"let opt of availableSlotTypes\" [value]=\"opt.id\">{{ opt.name }}</option>\r\n </select>\r\n </div>\r\n <div class=\"col-md-6\">\r\n <label class=\"form-label small\">Brand</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"brand\" placeholder=\"John Jacobs\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Header</label>\r\n <input type=\"text\" class=\"form-control form-control-sm\" formControlName=\"fixtureLevelZone\" placeholder=\"Premium Trending\" />\r\n </div>\r\n <div class=\"col-md-12\">\r\n <label class=\"form-label small\">Fixture Templates</label>\r\n <multiselect-chip-dropdown\r\n class=\"fixture-libraries-select\"\r\n [items]=\"fixtureLibraryItemsForSlot\"\r\n idField=\"id\"\r\n nameField=\"name\"\r\n [search]=\"true\"\r\n searchField=\"name\"\r\n placeholder=\"Select fixture templates\"\r\n formControlName=\"fixtureLibraryRefs\">\r\n </multiselect-chip-dropdown>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value === 'eurocenter'\">\r\n Showing floor fixtures (eurocenter slot).\r\n </small>\r\n <small class=\"text-muted\" *ngIf=\"slotGroup.get('slotType')?.value !== 'eurocenter'\">\r\n Showing wall fixtures (shelf slot).\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\r\n <h6 class=\"m-0\">Look and Visual Merchandising Placements</h6>\r\n <button type=\"button\" class=\"btn btn-sm btn-light\" (click)=\"addPlacement()\">+ Add placement</button>\r\n </div>\r\n <div *ngIf=\"(selectedPlacements?.length || 0) === 0\" class=\"text-muted small mb-2\">\r\n No placements yet. Click <strong>+ Add placement</strong> to add one.\r\n </div>\r\n <div class=\"placements-scroll\" formArrayName=\"placements\">\r\n <div\r\n *ngFor=\"let p of (selectedPlacements?.controls || []); let pi = index; trackBy: trackByIndex\"\r\n class=\"placement-row mb-2\"\r\n [formGroupName]=\"pi\"\r\n >\r\n <div class=\"d-flex align-items-start gap-2 placement-fields\">\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"kind\">\r\n <option value=\"pid\">Product Merchandising</option>\r\n <option value=\"vm\">Visual Merchandising</option>\r\n </select>\r\n </div>\r\n\r\n <!-- Visual Merchandising rows: VM Type (single) + VM Artwork (single) -->\r\n <ng-container *ngIf=\"$any(p).get('kind')?.value === 'vm'; else pidInputs\">\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmTypeOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Type\"\r\n formControlName=\"position\">\r\n </lib-searchable-select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"vmArtworkOptionsFor($any(p).get('position')?.value)\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n placeholder=\"Visual Merchandising Artwork\"\r\n formControlName=\"rawValue\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Product Merchandising rows: Top/Mid/Bottom position + multi-select product/SKU -->\r\n <ng-template #pidInputs>\r\n <div class=\"ph-field\">\r\n <select class=\"form-select form-select-sm\" formControlName=\"position\">\r\n <option value=\"\">Position</option>\r\n <option value=\"Top\">Top</option>\r\n <option value=\"Mid\">Mid</option>\r\n <option value=\"Bottom\">Bottom</option>\r\n </select>\r\n </div>\r\n <div class=\"ph-field\">\r\n <lib-searchable-select\r\n [items]=\"productOptions\"\r\n idField=\"value\"\r\n nameField=\"label\"\r\n [multiple]=\"true\"\r\n placeholder=\"Product / SKU\"\r\n formControlName=\"rawValues\">\r\n </lib-searchable-select>\r\n </div>\r\n </ng-template>\r\n\r\n <button type=\"button\" class=\"btn btn-sm btn-light-danger ph-remove\" (click)=\"removePlacement(pi)\">\u00D7</button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-template #noSlot>\r\n <div class=\"card p-5 h-100 text-center text-muted\">Select a fixture slot to edit placements.</div>\r\n </ng-template>\r\n </div>\r\n </div>\r\n\r\n <!-- Per-slot Fixture Library Configuration card -->\r\n <ng-container *ngIf=\"selectedSlotGroup as slotGroupForMap\">\r\n <div class=\"card p-4 mt-4 library-config-card\" [formGroup]=\"slotGroupForMap\">\r\n <div class=\"mb-4\">\r\n <h5 class=\"form-label d-block mb-3\">Fixture library</h5>\r\n <div class=\"library-chips\">\r\n <button\r\n *ngFor=\"let lib of selectedSlotLibrariesForDropdown\"\r\n type=\"button\"\r\n class=\"library-chip\"\r\n [class.active]=\"selectedLibraryIdInMap === lib.id\"\r\n (click)=\"onLibraryMapSelect(selectedLibraryIdInMap === lib.id ? '' : lib.id)\">\r\n {{ lib.name }}\r\n </button>\r\n </div>\r\n <small *ngIf=\"selectedSlotLibrariesForDropdown.length === 0\" class=\"text-muted\">\r\n No libraries linked to this slot yet. Add some in the \"Fixture libraries\" multiselect above.\r\n </small>\r\n </div>\r\n\r\n <div *ngIf=\"loadingLibraryDetails\" class=\"text-muted\">Loading fixture details\u2026</div>\r\n\r\n <!--\r\n Use *ngFor + trackBy keyed on (look, slot, library) so Angular treats\r\n a switch as a new item \u2014 that forces the embedded template-products\r\n / template-vms components to remount and rerun their ngOnInit\r\n (otherwise their `isPageLoading` guard skips the form rebuild).\r\n -->\r\n <ng-container *ngFor=\"let _ of embeddedHostKeys; trackBy: trackByEmbeddedKey\">\r\n <ul class=\"nav nav-tabs custom-tabs mb-3\">\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'products'\"\r\n (click)=\"embeddedTab = 'products'\">Product Merchandising</button>\r\n </li>\r\n <li class=\"nav-item\">\r\n <button type=\"button\" class=\"nav-link\"\r\n [class.active]=\"embeddedTab === 'vms'\"\r\n (click)=\"embeddedTab = 'vms'\">Visual Merchandising</button>\r\n </li>\r\n </ul>\r\n\r\n <div *ngIf=\"embeddedTab === 'products'\">\r\n <lib-template-products [looksMode]=\"true\"></lib-template-products>\r\n </div>\r\n <div *ngIf=\"embeddedTab === 'vms'\">\r\n <lib-template-vms [looksMode]=\"true\"></lib-template-vms>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </ng-container>\r\n</section>\r\n", styles: [".look-form .col-md-3>.card,.look-form .col-md-6>.card{display:flex!important;flex-direction:column;height:85vh!important;min-height:720px}.look-form .looks-tree,.look-form .slots-list,.look-form .placements-scroll{flex:1 1 auto;min-height:0;overflow-y:auto}.look-form .look-row,.look-form .slot-row{cursor:pointer;border:1px solid transparent;background:#f9fafb;transition:filter .15s ease,box-shadow .15s ease,border-color .15s ease}.look-form .look-row:hover,.look-form .slot-row:hover{filter:brightness(.96)}.look-form .look-row.selected,.look-form .slot-row.selected{filter:brightness(.96);outline:1px solid rgba(14,165,233,.55);outline-offset:-1px}.look-form .bucket-dot{display:inline-block;width:10px;height:10px;border-radius:50%;flex-shrink:0;border:1px solid rgba(0,0,0,.12)}.look-form .fixture-type-badge{display:inline-block;flex-shrink:0;padding:1px 8px;border-radius:999px;font-size:10px;font-weight:600;line-height:14px;letter-spacing:.2px;border:1px solid transparent;white-space:nowrap}.look-form .fixture-type-badge.badge-wall{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.look-form .fixture-type-badge.badge-floor{background:#fed7aa;color:#9a3412;border-color:#fdba74}.look-form .fixture-type-badge.badge-mixed{background:#ede9fe;color:#6d28d9;border-color:#c4b5fd}.look-form .fixture-libraries-select{display:block}.look-form .fixture-libraries-select ::ng-deep .multiselect-container{align-items:flex-start}.look-form .fixture-libraries-select ::ng-deep .chip-list{max-height:84px;overflow-y:auto;padding-right:4px}.look-form .cad-header-variants-input{display:block}.look-form .cad-header-variants-input ::ng-deep .chips-input{max-height:84px;overflow-y:auto;align-content:flex-start;padding-right:4px}.look-form .placement-row .placement-fields .ph-field{flex:1 1 0;min-width:0}.look-form .placement-row .placement-fields .ph-remove{flex:0 0 auto}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multiselect-container{padding:4px 10px;min-height:31px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .chip-list{min-height:21px}.look-form .placement-row .placement-fields ::ng-deep multiselect-chip-dropdown .multi-placeholder{font-size:13px;line-height:21px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.look-form .library-config-card .library-chips{display:flex;flex-wrap:wrap;gap:8px}.look-form .library-config-card .library-chip{background:#f1f5f9;border:1px solid #cbd5e1;color:#1e293b;border-radius:999px;padding:6px 14px;font-size:13px;font-weight:500;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.look-form .library-config-card .library-chip:hover{background:#e2e8f0;border-color:#94a3b8}.look-form .library-config-card .library-chip.active{background:#0ea5e9;border-color:#0284c7;color:#fff}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products .cols.col-8>ul.nav.nav-tabs.custom-tabs{display:none!important}.look-form .library-config-card ::ng-deep #fixture-template-products input[readonly]{background-color:var(--bs-gray-200, #e9ecef);cursor:not-allowed}.look-form .library-config-card .fixture-preview{border:1px solid #e2e8f0;border-radius:8px;background:#fff;padding:12px}.look-form .library-config-card .fixture-preview .fx-header,.look-form .library-config-card .fixture-preview .fx-footer{background:#f1f5f9;border:1px dashed #cbd5e1;border-radius:6px;padding:6px 10px;font-size:12px;color:#475569;text-align:center;margin:4px 0}.look-form .library-config-card .fixture-preview .fx-body{display:flex;flex-direction:column;gap:4px;padding:4px 0}.look-form .library-config-card .fixture-preview .fx-shelf{background:linear-gradient(180deg,#f8fafc,#eef2f7);border:1px solid #cbd5e1;border-radius:4px;padding:8px 10px;display:flex;align-items:center;gap:8px;font-size:13px;min-height:36px}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-badge{background:#0ea5e9;color:#fff;font-weight:700;padding:2px 8px;border-radius:999px;font-size:11px;flex-shrink:0}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-label{font-weight:500;color:#1e293b}.look-form .library-config-card .fixture-preview .fx-shelf .fx-shelf-meta{color:#64748b;font-size:11px;margin-left:auto}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray{background:linear-gradient(180deg,#fef3c7,#fde68a);border-color:#f59e0b}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-tray .fx-shelf-badge{background:#d97706}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly{background:linear-gradient(180deg,#ede9fe,#ddd6fe);border-color:#8b5cf6}.look-form .library-config-card .fixture-preview .fx-shelf.fx-shelf-vmonly .fx-shelf-badge{background:#7c3aed}.look-form .library-config-card .fixture-preview .fx-dims{text-align:center}.look-form .library-config-card .shelf-mappings{max-height:70vh;overflow-y:auto;padding-right:4px}.look-form .library-config-card .shelf-mappings .shelf-mapping{border:1px solid #e5e7eb;border-radius:8px;padding:12px;background:#f9fafb}\n"] }]
70435
70572
  }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: i2.ActivatedRoute }, { type: i2.Router }, { type: i2$1.GlobalStateService }, { type: StoreBuilderService }, { type: i4.ToastService }, { type: i2$1.PageInfoService }, { type: PlanoDataService }] });
70436
70573
 
70437
70574
  const routes$1 = [