@feedmepos/mf-order-setting 0.0.90 → 0.0.91

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.
Files changed (46) hide show
  1. package/dist/DisplaySystemView-n7Kqdw-h.js +6564 -0
  2. package/dist/{KioskDevicesView-WuOTJ9Fi.js → KioskDevicesView-Bz6seroM.js} +1 -1
  3. package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-DdHfATyN.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-I39sE8Pe.js} +3 -3
  4. package/dist/{KioskSettingView-Bsja2OqT.js → KioskSettingView-Cajjh4Zm.js} +3 -3
  5. package/dist/{KioskView-BH5qGTM_.js → KioskView-BPSbsMi5.js} +93 -89
  6. package/dist/{OrderSettingsView-D6V1ztiS.js → OrderSettingsView-jPxZ3JDM.js} +5 -5
  7. package/dist/{app-DsMk7W2h.js → app-DIhv5ssA.js} +266 -254
  8. package/dist/app.js +1 -1
  9. package/dist/frontend/mf-order/src/app.d.ts +18 -6
  10. package/dist/frontend/mf-order/src/main.d.ts +18 -6
  11. package/dist/frontend/mf-order/src/modules/display-system/interface.d.ts +6 -3
  12. package/dist/frontend/mf-order/src/modules/kiosk/bindDeviceError.d.ts +20 -0
  13. package/dist/frontend/mf-order/src/views/display-system/CanvasArea.vue.d.ts +48 -0
  14. package/dist/frontend/mf-order/src/views/display-system/DisplayLayoutCanvas.vue.d.ts +17 -14
  15. package/dist/frontend/mf-order/src/views/display-system/DisplayModulePickerDialog.vue.d.ts +2 -0
  16. package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
  17. package/dist/{index-DGDiI0fr.js → index-Clp_YRJ2.js} +1 -1
  18. package/dist/{index-BUDM0Rqn.js → index-CmqNrkNl.js} +1 -1
  19. package/dist/{index-C2n1MV_1.js → index-vaSLX6r3.js} +2 -2
  20. package/dist/{order.dto-BRDWWgMV.js → order.dto-DV1qL3Q_.js} +21 -1
  21. package/dist/package/entity/alert/investigation.dto.d.ts +26 -0
  22. package/dist/package/entity/alert/lark-callback.dto.d.ts +19 -0
  23. package/dist/package/entity/alert/record-resolution.dto.d.ts +35 -0
  24. package/dist/package/entity/index.d.ts +1 -0
  25. package/dist/package/entity/kiosk/kiosk.do.d.ts +105 -0
  26. package/dist/package/entity/kiosk/kiosk.dto.d.ts +76 -0
  27. package/dist/style.css +1 -1
  28. package/package.json +3 -1
  29. package/src/locales/en-US.json +11 -4
  30. package/src/locales/zh-CN.json +9 -4
  31. package/src/modules/display-system/interface.spec.ts +90 -5
  32. package/src/modules/display-system/interface.ts +31 -21
  33. package/src/modules/kiosk/bindDeviceError.spec.ts +42 -0
  34. package/src/modules/kiosk/bindDeviceError.ts +40 -0
  35. package/src/views/display-system/CanvasArea.vue +355 -0
  36. package/src/views/display-system/DisplayLayoutCanvas.spec.ts +57 -0
  37. package/src/views/display-system/DisplayLayoutCanvas.vue +173 -298
  38. package/src/views/display-system/DisplayModulePickerDialog.spec.ts +71 -0
  39. package/src/views/display-system/DisplayModulePickerDialog.vue +107 -0
  40. package/src/views/display-system/DisplayTemplateEditor.spec.ts +151 -0
  41. package/src/views/display-system/DisplayTemplateEditor.vue +233 -193
  42. package/src/views/kiosk/KioskView.vue +2 -4
  43. package/vitest.config.ts +2 -0
  44. package/dist/DisplaySystemView-tosCa7Ej.js +0 -6485
  45. package/dist/frontend/mf-order/src/views/display-system/DisplayModulePalette.vue.d.ts +0 -13
  46. package/src/views/display-system/DisplayModulePalette.vue +0 -140
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .integration-details[data-v-64de12e4]{display:flex;flex-direction:column;gap:.625rem;padding:1rem;margin-top:1rem;border:1px solid #e5e7eb;border-radius:.5rem}.integration-details--owned[data-v-64de12e4]{border-color:var(--fm-color-primary-oats);box-shadow:inset .25rem 0 0 var(--fm-color-primary)}.integration-details__header[data-v-64de12e4]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem}.integration-details__title[data-v-64de12e4]{display:flex;align-items:center;gap:.375rem}.integration-details__store-label[data-v-64de12e4]{padding:.25rem .625rem;color:var(--fm-color-primary);background:var(--fm-color-primary-oats);border-radius:999px}.integration-details__row[data-v-64de12e4]{display:grid;grid-template-columns:10rem minmax(0,1fr);align-items:center;gap:1rem}.integration-details__value[data-v-64de12e4]{display:flex;min-width:0;align-items:center;gap:.5rem}.integration-details__action[data-v-64de12e4]{display:inline-flex;flex:0 0 auto}.integration-details__value>code[data-v-64de12e4]{flex:1 1 auto}.integration-details__summary[data-v-64de12e4]{display:flex;justify-content:flex-end;margin-top:.25rem}.integration-details__message[data-v-64de12e4]{max-height:9rem;overflow-y:auto;white-space:pre-wrap}code[data-v-64de12e4]{min-width:0;overflow-wrap:anywhere}@media (max-width: 460px){.integration-details__row[data-v-64de12e4]{grid-template-columns:1fr;gap:.25rem}.integration-details__header[data-v-64de12e4]{align-items:flex-start}}.store-section[data-v-64f1ea7c]{border-top:1px solid #e5e7eb;padding-top:1.5rem}.store-card[data-v-64f1ea7c]{padding:1.25rem;margin-bottom:1rem}.store-card__top[data-v-64f1ea7c]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1rem}.store-card__title[data-v-64f1ea7c]{min-width:0;word-break:break-all}.store-card__actions[data-v-64f1ea7c]{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;flex-shrink:0}.store-card__fields[data-v-64f1ea7c]{display:flex;flex-direction:column;gap:.625rem}.store-field[data-v-64f1ea7c]{display:grid;grid-template-columns:9rem minmax(0,1fr);gap:1rem}.store-field>span[data-v-64f1ea7c]:first-child{color:var(--fm-color-neutral-gray-400)}.store-field>span[data-v-64f1ea7c]:last-child{word-break:break-word}.store-sheet-body[data-v-64f1ea7c]{display:flex;flex-direction:column;gap:1rem;padding-top:.5rem}@media (max-width: 460px){.store-field[data-v-64f1ea7c]{grid-template-columns:1fr}}.store-section[data-v-fe141f25]{border-top:1px solid #e5e7eb;padding-top:1.5rem}.store-card[data-v-fe141f25]{padding:1.25rem;margin-bottom:1rem}.store-card__top[data-v-fe141f25]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1.25rem}.store-card__title[data-v-fe141f25]{min-width:0;word-break:break-all}.store-card__actions[data-v-fe141f25]{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;flex-shrink:0}.store-card__fields[data-v-fe141f25]{display:flex;flex-direction:column;gap:.625rem}.store-field[data-v-fe141f25]{display:grid;grid-template-columns:9rem minmax(0,1fr);align-items:start;-moz-column-gap:1rem;column-gap:1rem;row-gap:.125rem}.store-field>span[data-v-fe141f25]:last-child{word-break:break-word}.store-sheet-body[data-v-fe141f25]{display:flex;flex-direction:column;gap:1rem;padding-top:.5rem}.store-sheet-activate[data-v-fe141f25]{padding-top:1.25rem;border-top:1px solid #e5e7eb}.store-sheet-activate__hint[data-v-fe141f25]{margin-top:.5rem}@media (max-width: 460px){.store-field[data-v-fe141f25]{grid-template-columns:1fr}}.store-section[data-v-b94bbdb0]{border-top:1px solid #e5e7eb;padding-top:1.5rem}.store-card[data-v-b94bbdb0]{padding:1.25rem;margin-bottom:1rem}.store-card__top[data-v-b94bbdb0]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1rem}.store-card__title[data-v-b94bbdb0]{min-width:0;word-break:break-all}.store-card__actions[data-v-b94bbdb0]{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;flex-shrink:0}.store-card__fields[data-v-b94bbdb0]{display:flex;flex-direction:column;gap:.625rem}.store-field[data-v-b94bbdb0]{display:grid;grid-template-columns:9rem minmax(0,1fr);gap:1rem}.store-field>span[data-v-b94bbdb0]:first-child{color:var(--fm-color-neutral-gray-400)}.store-field>span[data-v-b94bbdb0]:last-child{word-break:break-word}.store-sheet-body[data-v-b94bbdb0]{display:flex;flex-direction:column;gap:1rem;padding-top:.5rem}@media (max-width: 460px){.store-field[data-v-b94bbdb0]{grid-template-columns:1fr}}[data-v-91393c58] .fm-checkbox--wrapper:has(input:disabled){pointer-events:none}[data-v-a959025e] .flex.flex-col.gap-8>:first-child,[data-v-a959025e] .flex.flex-col.gap-8>:last-child{display:none}.toggle[data-v-5266c20e]{position:relative;flex-shrink:0;width:52px;height:32px;padding:0;border:none;border-radius:9999px;background:var(--fm-color-neutral-gray-200);cursor:pointer;transition:background-color .2s ease-out}.toggle[data-v-5266c20e]:focus{outline:none}.toggle[data-v-5266c20e]:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,var(--fm-color-primary) 40%,transparent)}.toggle--on[data-v-5266c20e]{background:var(--fm-color-primary)}.toggle__thumb[data-v-5266c20e]{position:absolute;top:50%;left:8px;width:18px;height:18px;border-radius:9999px;background:#fff;box-shadow:0 1px 2px #0003;transform:translateY(-50%);transition:all .2s ease-out}.toggle--on .toggle__thumb[data-v-5266c20e]{left:24px;width:24px;height:24px}.toggle:active .toggle__thumb[data-v-5266c20e]{transform:translateY(-50%) scale(1.1)}.delivery-company-filter-toolbar[data-v-e2763a83]{display:flex;flex-direction:row;justify-content:space-between}@media (max-width: 647px){.delivery-company-filter-toolbar[data-v-e2763a83]{flex-direction:column;flex-wrap:wrap}}.preview-frame[data-v-bd8d2976]{position:relative;width:100%;overflow:hidden;border:1px solid #d1d5db;border-radius:12px;aspect-ratio:16 / 9;background:#111827;box-shadow:0 16px 40px #0f172a1f}.preview-iframe[data-v-bd8d2976]{display:block;width:100%;height:100%;border:0;background:#f3f4f6}.preview-status[data-v-bd8d2976]{position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-content:center;gap:12px;background:#f9fafbf0;color:#4b5563;text-align:center;font-size:13px}.preview-status p[data-v-bd8d2976]{margin:0}.preview-status[data-v-bd8d2976] button{justify-self:center}.preview-loading[data-v-bd8d2976]{display:grid;justify-items:center;gap:10px}.preview-loading span[data-v-bd8d2976]{width:24px;height:24px;border:3px solid #fed7aa;border-top-color:#ef661f;border-radius:50%;animation:preview-spin-bd8d2976 .8s linear infinite}@keyframes preview-spin-bd8d2976{to{transform:rotate(360deg)}}.canvas[data-v-18083337]{display:flex;min-height:0;flex:1;border-radius:12px;background:#f3f4f6;padding:8px}.canvas-row[data-v-18083337]{flex-direction:row}.canvas-column[data-v-18083337]{flex-direction:column}.canvas-group[data-v-18083337]{display:flex;min-width:0;min-height:0;gap:0}.canvas-area[data-v-18083337]{display:flex;min-width:0;min-height:78px;flex-direction:column;gap:8px;overflow:hidden;border:1.5px dashed #d1d5db;border-radius:10px;padding:10px;background:#fff;text-align:left}.canvas-area-nested[data-v-18083337]{min-height:62px}.canvas-area[data-v-18083337]:hover{border-color:#fdba74}.canvas-area-active[data-v-18083337]{border-style:solid;border-color:#f97316;background:#fffbf5;box-shadow:0 0 0 3px #f973161f}.canvas-area-context[data-v-18083337]{border-style:solid;border-color:#fed7aa;background:#fffdfa}.canvas-area-droppable[data-v-18083337]{border-color:#f97316;background:#fff7ed}.canvas-area-drop-over[data-v-18083337]{border-style:solid;box-shadow:0 0 0 3px #f9731633}.canvas-area-head[data-v-18083337]{display:flex;align-items:center;gap:8px}.canvas-area-name[data-v-18083337]{color:#6b7280;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.canvas-area-share[data-v-18083337]{margin-left:auto;color:#374151;font-size:12px;font-variant-numeric:tabular-nums;font-weight:700}.canvas-area-action[data-v-18083337]{display:grid;flex:0 0 auto;place-items:center;border-radius:6px;padding:2px;color:#9ca3af}.canvas-area-action[data-v-18083337]:hover{background:#fff7ed;color:#f97316}.canvas-chips[data-v-18083337]{display:flex;min-height:0;flex:1;flex-direction:column;gap:4px}.canvas-chip[data-v-18083337]{display:flex;align-items:center;gap:6px;border:1px solid #e5e7eb;border-radius:8px;padding:6px 8px;background:#fff;color:#374151;font-size:12px;text-align:left;cursor:grab}.canvas-chip[data-v-18083337]:hover{border-color:#fdba74}.canvas-chip-active[data-v-18083337]{border-color:#f97316;background:#fff;box-shadow:inset 4px 0 #f97316,0 0 0 3px #f973161f;font-weight:600}.canvas-chip-active .canvas-chip-grip[data-v-18083337]{color:#f97316}.canvas-chip-context[data-v-18083337]{border-color:#fed7aa;background:#fffdfa}.canvas-chip[data-v-18083337]:active{cursor:grabbing}.canvas-chip-grip[data-v-18083337]{flex:0 0 auto;color:#9ca3af}.canvas-chip-label[data-v-18083337]{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.canvas-area-empty[data-v-18083337]{display:grid;flex:1;place-items:center;border:1px dashed #d1d5db;border-radius:8px;color:#9ca3af;font-size:11px}.canvas-divider[data-v-18083337]{display:grid;flex:0 0 auto;place-items:center}.divider-vertical[data-v-18083337]{width:10px;cursor:col-resize}.divider-horizontal[data-v-18083337]{height:10px;cursor:row-resize}.canvas-divider-grip[data-v-18083337]{border-radius:999px;background:#d1d5db}.divider-vertical .canvas-divider-grip[data-v-18083337]{height:34px;width:3px}.divider-horizontal .canvas-divider-grip[data-v-18083337]{height:3px;width:34px}.canvas-divider:hover .canvas-divider-grip[data-v-18083337]{background:#f97316}.palette[data-v-29c6bf88]{display:flex;flex-direction:column;gap:16px}.palette-head[data-v-29c6bf88]{display:flex;flex-direction:column;gap:4px}.palette-title[data-v-29c6bf88]{color:#111827;font-size:13px;font-weight:600}.palette-hint[data-v-29c6bf88]{color:#6b7280;font-size:11px;line-height:1.4}.palette-group[data-v-29c6bf88]{display:flex;flex-direction:column;gap:6px}.palette-group-label[data-v-29c6bf88]{color:#9ca3af;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.palette-card[data-v-29c6bf88]{display:flex;align-items:center;gap:8px;border:1px solid #e5e7eb;border-radius:8px;padding:8px 10px;background:#fff;color:#374151;font-size:13px;text-align:left;cursor:grab}.palette-card[data-v-29c6bf88]:hover:not(:disabled){border-color:#f97316;background:#fffbf5}.palette-card[data-v-29c6bf88]:disabled{cursor:not-allowed;opacity:.5}.palette-card[data-v-29c6bf88]:active{cursor:grabbing}.palette-icon[data-v-29c6bf88]{display:grid;height:24px;width:24px;flex:0 0 auto;place-items:center;border-radius:6px;background:#f3f4f6;color:#4b5563}.palette-label[data-v-29c6bf88]{min-width:0;flex:1;line-height:1.25;overflow-wrap:anywhere}.media-field[data-v-839ff846]{display:flex;flex-direction:column;gap:6px}.media-label[data-v-839ff846]{color:#4b5563;font-size:12px}.media-thumb[data-v-839ff846]{height:72px;width:100%;border-radius:8px;border:1px solid #e5e7eb;-o-object-fit:cover;object-fit:cover}.media-empty[data-v-839ff846]{display:grid;height:72px;place-items:center;border:1px dashed #d1d5db;border-radius:8px;color:#9ca3af;font-size:12px}.media-warning[data-v-839ff846]{display:grid;height:72px;place-items:center;border:1px dashed #d1d5db;border-radius:8px;color:#b54708;font-size:12px}.media-actions[data-v-839ff846]{display:flex;gap:6px}.inspector[data-v-4cc442cf]{display:flex;flex-direction:column;gap:14px}.inspector-scope[data-v-4cc442cf]{align-self:stretch}.inspector-breadcrumb[data-v-4cc442cf]{display:flex;align-items:center;gap:2px;color:#6b7280;font-size:11px}.inspector-section[data-v-4cc442cf]{display:flex;flex-direction:column;gap:10px;border-top:1px solid #e5e7eb;padding-top:12px}.inspector-section-title[data-v-4cc442cf]{color:#9ca3af;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.inspector-note[data-v-4cc442cf]{color:#6b7280;font-size:11px;line-height:1.45}.inspector-warning[data-v-4cc442cf]{display:flex;align-items:flex-start;gap:6px;border-radius:8px;background:#fffbeb;padding:8px 10px;color:#92400e;font-size:12px}.inspector-list[data-v-4cc442cf]{display:flex;flex-direction:column;gap:8px}.inspector-list-head[data-v-4cc442cf]{display:flex;align-items:baseline;gap:6px}.inspector-list-count[data-v-4cc442cf]{border-radius:999px;background:#f3f4f6;padding:0 6px;color:#6b7280;font-size:10px;font-variant-numeric:tabular-nums}.inspector-item[data-v-4cc442cf]{display:flex;flex-direction:column;gap:10px;border:1px solid #e5e7eb;border-radius:10px;padding:10px;background:#fcfcfd}.inspector-item-head[data-v-4cc442cf]{display:flex;align-items:center;gap:2px}.inspector-item-name[data-v-4cc442cf]{flex:1;color:#6b7280;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.inspector-item-action[data-v-4cc442cf]{display:grid;flex:0 0 auto;place-items:center;border-radius:6px;padding:2px;color:#9ca3af}.inspector-item-action[data-v-4cc442cf]:hover:not(:disabled){background:#fff7ed;color:#f97316}.inspector-item-action[data-v-4cc442cf]:disabled{opacity:.35}.inspector-list-add[data-v-4cc442cf]{display:flex;gap:6px}.media-field[data-v-4cc442cf]{display:flex;flex-direction:column;gap:6px}.media-label[data-v-4cc442cf]{color:#4b5563;font-size:12px}.media-thumb[data-v-4cc442cf]{height:72px;width:100%;border-radius:8px;border:1px solid #e5e7eb;-o-object-fit:cover;object-fit:cover}.media-empty[data-v-4cc442cf]{display:grid;height:72px;place-items:center;border:1px dashed #d1d5db;border-radius:8px;color:#9ca3af;font-size:12px}.media-actions[data-v-4cc442cf]{display:flex;gap:6px}.editor-head[data-v-fe5dec51]{display:flex;shrink:0;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid #e5e7eb;padding:12px 20px}.editor-identity[data-v-fe5dec51]{display:flex;min-width:0;align-items:center;gap:10px}.editor-name[data-v-fe5dec51]{width:min(320px,40vw)}.editor-orientation[data-v-fe5dec51]{width:160px}.editor-error[data-v-fe5dec51]{flex-shrink:0;margin:12px 16px 0;border-radius:8px;background:#fef2f2;padding:8px 12px;color:#b91c1c;font-size:13px}.rail-scale[data-v-fe5dec51]{display:flex;flex-direction:column;gap:6px;margin-bottom:18px;border-bottom:1px solid #e5e7eb;padding-bottom:16px}.rail-scale-label[data-v-fe5dec51]{color:#4b5563;font-size:12px;font-weight:600}.rail-scale-hint[data-v-fe5dec51]{margin:0;color:#6b7280;font-size:11px;line-height:1.4}.rail-scale-warning[data-v-fe5dec51]{margin:0;border-radius:8px;padding:6px 8px;color:#9a3412;background:#fff7ed;font-size:11px;line-height:1.4}.editor-body[data-v-fe5dec51]{display:grid;min-height:0;flex:1;grid-template-columns:268px minmax(0,1fr) 320px}.editor-rail[data-v-fe5dec51],.editor-inspector[data-v-fe5dec51]{min-height:0;overflow:auto;padding:16px}.editor-rail[data-v-fe5dec51]{border-right:1px solid #e5e7eb;background:#f9fafb}.editor-inspector[data-v-fe5dec51]{border-left:1px solid #e5e7eb;background:#f9fafb}.editor-stage[data-v-fe5dec51]{display:flex;min-height:0;flex-direction:column;gap:12px;overflow:auto;padding:16px}.editor-stage-portrait[data-v-fe5dec51]{flex-direction:row;align-items:flex-start;gap:16px;height:100%}.editor-stage-portrait .stage-preview[data-v-fe5dec51]{min-height:0;flex:0 0 auto;align-self:stretch}.editor-stage-portrait .preview-portrait[data-v-fe5dec51]{height:100%;max-height:none}.editor-stage-portrait .stage-layout[data-v-fe5dec51]{min-width:0;flex:1}.editor-stage-portrait .layout-bar[data-v-fe5dec51]{flex-direction:column;align-items:stretch;gap:8px}.editor-stage-portrait .layout-actions[data-v-fe5dec51]{flex-wrap:wrap}.stage-preview[data-v-fe5dec51]{display:flex;flex-direction:column}.preview-frame[data-v-fe5dec51]{overflow:hidden;border:1px solid #e5e7eb;border-radius:12px;background:#101828;margin:0 auto}.preview-landscape[data-v-fe5dec51]{width:100%;max-height:52vh;aspect-ratio:16 / 9}.preview-portrait[data-v-fe5dec51]{height:52vh;max-width:100%;aspect-ratio:9 / 16}.preview-frame[data-v-fe5dec51]>*{width:100%;height:100%}.stage-layout[data-v-fe5dec51]{display:flex;min-height:0;flex:0 0 auto;flex-direction:column;gap:10px}.stage-layout[data-v-fe5dec51] .canvas-row{min-height:176px;max-height:260px}.stage-layout[data-v-fe5dec51] .canvas-column{min-height:calc(var(--area-count, 2) * 96px + 16px)}.layout-bar[data-v-fe5dec51]{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.layout-bar-title[data-v-fe5dec51]{display:flex;min-width:0;flex-direction:column;gap:2px}.layout-title[data-v-fe5dec51]{color:#111827;font-size:13px;font-weight:600}.layout-hint[data-v-fe5dec51]{color:#9ca3af;font-size:11px}.layout-actions[data-v-fe5dec51]{display:flex;flex-shrink:0;align-items:center;gap:6px}.layout-conflict[data-v-fe5dec51]{display:flex;align-items:flex-start;gap:6px;border-radius:8px;background:#fffbeb;padding:8px 10px;color:#92400e;font-size:12px;cursor:pointer}.preset-row[data-v-fe5dec51]{display:flex;flex-wrap:wrap;gap:8px;border-radius:10px;background:#f9fafb;padding:10px}.preset-card[data-v-fe5dec51]{display:flex;width:108px;flex-direction:column;gap:6px;border:1px solid #d1d5db;border-radius:10px;padding:8px;background:#fff;text-align:left}.preset-card[data-v-fe5dec51]:hover{border-color:#f97316;background:#fff7ed}.preset-diagram[data-v-fe5dec51]{display:flex;height:34px;gap:3px;border-radius:6px;background:#f3f4f6;padding:3px}.preset-row-dir[data-v-fe5dec51]{flex-direction:row}.preset-column-dir[data-v-fe5dec51]{flex-direction:column}.preset-cell[data-v-fe5dec51]{flex-basis:0;border-radius:4px;background:#fdba74}.preset-label[data-v-fe5dec51]{color:#374151;font-size:11px;line-height:1.3}@media (max-width: 1280px){.editor-body[data-v-fe5dec51]{grid-template-columns:220px minmax(0,1fr)}.editor-inspector[data-v-fe5dec51]{grid-column:1 / -1;border-left:0;border-top:1px solid #e5e7eb}}.layout-split[data-v-5595b416]{display:flex;min-width:0;min-height:0;flex:1;gap:6px}.layout-row[data-v-5595b416]{flex-direction:row}.layout-column[data-v-5595b416]{flex-direction:column}.layout-zone[data-v-5595b416]{display:flex;min-width:0;min-height:36px;flex:1;flex-direction:row;align-items:center;gap:8px;overflow:hidden;border:1.5px dashed #d1d5db;border-radius:10px;padding:6px 10px;background:#fff;text-align:left;transition:border-color .12s ease,background .12s ease}.layout-zone[data-v-5595b416]:hover{border-color:#fdba74;background:#fffbf5}.layout-zone-selected[data-v-5595b416]{border-style:solid;border-color:#f97316;background:#fff7ed;box-shadow:inset 0 0 0 1px #f97316}.layout-zone-index[data-v-5595b416]{flex:none;color:#6b7280;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.layout-zone-blocks[data-v-5595b416]{display:flex;min-width:0;gap:4px}.layout-zone-chip[data-v-5595b416]{display:inline-flex;max-width:100%;align-items:center;gap:4px;border-radius:999px;background:#f3f4f6;padding:3px 8px;color:#374151;font-size:11px}.display-toolbar[data-v-a2dda95a]{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid #e5e7eb;padding-bottom:14px}.display-controls[data-v-a2dda95a]{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-top:12px}.display-restaurant-select[data-v-a2dda95a]{width:min(360px,100%)}.display-outlet[data-v-a2dda95a]{display:flex;align-items:center;gap:16px}.display-entitlement[data-v-a2dda95a]{color:#6b7280;font-size:12px;white-space:nowrap}.restriction-note[data-v-a2dda95a]{margin-top:12px;border:1px solid #fed7aa;border-radius:10px;padding:10px 12px;color:#9a3412;background:#fff7ed;font-size:13px}.empty-state[data-v-a2dda95a]{display:grid;justify-items:center;gap:6px;border:1px dashed #d1d5db;border-radius:12px;padding:56px 20px;color:#6b7280;text-align:center}.empty-state strong[data-v-a2dda95a]{color:#111827}.layout-thumb[data-v-a2dda95a]{display:flex;height:auto;min-height:96px;margin-top:16px;border-radius:10px;background:#f3f4f6;padding:5px;pointer-events:none}.thumb-caption[data-v-a2dda95a]{margin-top:8px;color:#6b7280;font-size:11px}.resource-card[data-v-a2dda95a]{border:1px solid #e5e7eb;border-radius:12px;padding:20px;background:#fff;box-shadow:0 2px 8px #0000000a}.mode-badge[data-v-a2dda95a],.status-badge[data-v-a2dda95a]{border-radius:999px;padding:3px 8px;background:#f3f4f6;font-size:11px;font-weight:700}.status-badge.active[data-v-a2dda95a]{color:#166534;background:#dcfce7}.status-badge.disabled[data-v-a2dda95a]{color:#991b1b;background:#fee2e2}.status-badge.unlicensed[data-v-a2dda95a]{color:#b42318;background:#fef3f2;border:1px solid #fecdca}.online-dot[data-v-a2dda95a]{color:#6b7280;font-size:11px}.online-dot[data-v-a2dda95a]:before{display:inline-block;width:7px;height:7px;margin-right:5px;border-radius:50%;background:#9ca3af;content:""}.online-dot.online[data-v-a2dda95a]{color:#15803d}.online-dot.online[data-v-a2dda95a]:before{background:#22c55e}.meta-grid[data-v-a2dda95a]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:18px;border-top:1px solid #f0f0f0;padding-top:16px}.meta-grid dt[data-v-a2dda95a]{color:#6b7280;font-size:11px}.meta-grid dd[data-v-a2dda95a]{overflow:hidden;margin-top:2px;font-size:12px;text-overflow:ellipsis;white-space:nowrap}@media (max-width:640px){.display-toolbar[data-v-a2dda95a]{align-items:flex-start}.display-controls[data-v-a2dda95a]{align-items:stretch;flex-direction:column}.display-outlet[data-v-a2dda95a]{align-items:stretch;flex-direction:column;gap:8px}.display-restaurant-select[data-v-a2dda95a]{width:100%}}
1
+ .integration-details[data-v-64de12e4]{display:flex;flex-direction:column;gap:.625rem;padding:1rem;margin-top:1rem;border:1px solid #e5e7eb;border-radius:.5rem}.integration-details--owned[data-v-64de12e4]{border-color:var(--fm-color-primary-oats);box-shadow:inset .25rem 0 0 var(--fm-color-primary)}.integration-details__header[data-v-64de12e4]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem}.integration-details__title[data-v-64de12e4]{display:flex;align-items:center;gap:.375rem}.integration-details__store-label[data-v-64de12e4]{padding:.25rem .625rem;color:var(--fm-color-primary);background:var(--fm-color-primary-oats);border-radius:999px}.integration-details__row[data-v-64de12e4]{display:grid;grid-template-columns:10rem minmax(0,1fr);align-items:center;gap:1rem}.integration-details__value[data-v-64de12e4]{display:flex;min-width:0;align-items:center;gap:.5rem}.integration-details__action[data-v-64de12e4]{display:inline-flex;flex:0 0 auto}.integration-details__value>code[data-v-64de12e4]{flex:1 1 auto}.integration-details__summary[data-v-64de12e4]{display:flex;justify-content:flex-end;margin-top:.25rem}.integration-details__message[data-v-64de12e4]{max-height:9rem;overflow-y:auto;white-space:pre-wrap}code[data-v-64de12e4]{min-width:0;overflow-wrap:anywhere}@media (max-width: 460px){.integration-details__row[data-v-64de12e4]{grid-template-columns:1fr;gap:.25rem}.integration-details__header[data-v-64de12e4]{align-items:flex-start}}.store-section[data-v-64f1ea7c]{border-top:1px solid #e5e7eb;padding-top:1.5rem}.store-card[data-v-64f1ea7c]{padding:1.25rem;margin-bottom:1rem}.store-card__top[data-v-64f1ea7c]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1rem}.store-card__title[data-v-64f1ea7c]{min-width:0;word-break:break-all}.store-card__actions[data-v-64f1ea7c]{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;flex-shrink:0}.store-card__fields[data-v-64f1ea7c]{display:flex;flex-direction:column;gap:.625rem}.store-field[data-v-64f1ea7c]{display:grid;grid-template-columns:9rem minmax(0,1fr);gap:1rem}.store-field>span[data-v-64f1ea7c]:first-child{color:var(--fm-color-neutral-gray-400)}.store-field>span[data-v-64f1ea7c]:last-child{word-break:break-word}.store-sheet-body[data-v-64f1ea7c]{display:flex;flex-direction:column;gap:1rem;padding-top:.5rem}@media (max-width: 460px){.store-field[data-v-64f1ea7c]{grid-template-columns:1fr}}.store-section[data-v-fe141f25]{border-top:1px solid #e5e7eb;padding-top:1.5rem}.store-card[data-v-fe141f25]{padding:1.25rem;margin-bottom:1rem}.store-card__top[data-v-fe141f25]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1.25rem}.store-card__title[data-v-fe141f25]{min-width:0;word-break:break-all}.store-card__actions[data-v-fe141f25]{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;flex-shrink:0}.store-card__fields[data-v-fe141f25]{display:flex;flex-direction:column;gap:.625rem}.store-field[data-v-fe141f25]{display:grid;grid-template-columns:9rem minmax(0,1fr);align-items:start;-moz-column-gap:1rem;column-gap:1rem;row-gap:.125rem}.store-field>span[data-v-fe141f25]:last-child{word-break:break-word}.store-sheet-body[data-v-fe141f25]{display:flex;flex-direction:column;gap:1rem;padding-top:.5rem}.store-sheet-activate[data-v-fe141f25]{padding-top:1.25rem;border-top:1px solid #e5e7eb}.store-sheet-activate__hint[data-v-fe141f25]{margin-top:.5rem}@media (max-width: 460px){.store-field[data-v-fe141f25]{grid-template-columns:1fr}}.store-section[data-v-b94bbdb0]{border-top:1px solid #e5e7eb;padding-top:1.5rem}.store-card[data-v-b94bbdb0]{padding:1.25rem;margin-bottom:1rem}.store-card__top[data-v-b94bbdb0]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1rem}.store-card__title[data-v-b94bbdb0]{min-width:0;word-break:break-all}.store-card__actions[data-v-b94bbdb0]{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.5rem;flex-shrink:0}.store-card__fields[data-v-b94bbdb0]{display:flex;flex-direction:column;gap:.625rem}.store-field[data-v-b94bbdb0]{display:grid;grid-template-columns:9rem minmax(0,1fr);gap:1rem}.store-field>span[data-v-b94bbdb0]:first-child{color:var(--fm-color-neutral-gray-400)}.store-field>span[data-v-b94bbdb0]:last-child{word-break:break-word}.store-sheet-body[data-v-b94bbdb0]{display:flex;flex-direction:column;gap:1rem;padding-top:.5rem}@media (max-width: 460px){.store-field[data-v-b94bbdb0]{grid-template-columns:1fr}}[data-v-91393c58] .fm-checkbox--wrapper:has(input:disabled){pointer-events:none}[data-v-a959025e] .flex.flex-col.gap-8>:first-child,[data-v-a959025e] .flex.flex-col.gap-8>:last-child{display:none}.toggle[data-v-5266c20e]{position:relative;flex-shrink:0;width:52px;height:32px;padding:0;border:none;border-radius:9999px;background:var(--fm-color-neutral-gray-200);cursor:pointer;transition:background-color .2s ease-out}.toggle[data-v-5266c20e]:focus{outline:none}.toggle[data-v-5266c20e]:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb,var(--fm-color-primary) 40%,transparent)}.toggle--on[data-v-5266c20e]{background:var(--fm-color-primary)}.toggle__thumb[data-v-5266c20e]{position:absolute;top:50%;left:8px;width:18px;height:18px;border-radius:9999px;background:#fff;box-shadow:0 1px 2px #0003;transform:translateY(-50%);transition:all .2s ease-out}.toggle--on .toggle__thumb[data-v-5266c20e]{left:24px;width:24px;height:24px}.toggle:active .toggle__thumb[data-v-5266c20e]{transform:translateY(-50%) scale(1.1)}.delivery-company-filter-toolbar[data-v-e2763a83]{display:flex;flex-direction:row;justify-content:space-between}@media (max-width: 647px){.delivery-company-filter-toolbar[data-v-e2763a83]{flex-direction:column;flex-wrap:wrap}}.preview-frame[data-v-bd8d2976]{position:relative;width:100%;overflow:hidden;border:1px solid #d1d5db;border-radius:12px;aspect-ratio:16 / 9;background:#111827;box-shadow:0 16px 40px #0f172a1f}.preview-iframe[data-v-bd8d2976]{display:block;width:100%;height:100%;border:0;background:#f3f4f6}.preview-status[data-v-bd8d2976]{position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-content:center;gap:12px;background:#f9fafbf0;color:#4b5563;text-align:center;font-size:13px}.preview-status p[data-v-bd8d2976]{margin:0}.preview-status[data-v-bd8d2976] button{justify-self:center}.preview-loading[data-v-bd8d2976]{display:grid;justify-items:center;gap:10px}.preview-loading span[data-v-bd8d2976]{width:24px;height:24px;border:3px solid #fed7aa;border-top-color:#ef661f;border-radius:50%;animation:preview-spin-bd8d2976 .8s linear infinite}@keyframes preview-spin-bd8d2976{to{transform:rotate(360deg)}}.canvas-area[data-v-cdd742c6]{position:relative;display:flex;min-width:0;min-height:0;flex-direction:column}.canvas-area-card[data-v-cdd742c6]{display:flex;flex:1;min-width:0;min-height:0;flex-direction:column;gap:8px;overflow:visible;border:1.5px dashed rgb(255 255 255 / 55%);border-radius:10px;padding:6px;background:#11182752;text-align:left}.canvas-area-card[data-v-cdd742c6]:hover{background:#1118276b}.canvas-area-active[data-v-cdd742c6]{border-style:solid;border-color:#f97316;background:#f973164d;box-shadow:0 0 0 3px #f973161f}.canvas-area-context[data-v-cdd742c6]{border-style:solid;border-color:#fed7aa;background:#fdba7433}.canvas-area-droppable[data-v-cdd742c6]{border-color:#f97316;background:#f973165c}.canvas-area-drop-over[data-v-cdd742c6]{border-style:solid;box-shadow:0 0 0 3px #f9731633}.canvas-area-head[data-v-cdd742c6]{display:flex;align-items:center;gap:8px}.canvas-area-name[data-v-cdd742c6],.canvas-area-share[data-v-cdd742c6]{color:#f9fafb;text-shadow:0 1px 3px rgb(0 0 0 / 70%);font-weight:700}.canvas-area-name[data-v-cdd742c6]{font-size:10px;letter-spacing:.06em;text-transform:uppercase}.canvas-area-share[data-v-cdd742c6]{margin-left:auto;font-size:12px;font-variant-numeric:tabular-nums}.canvas-area-remove[data-v-cdd742c6]{display:grid;flex:0 0 auto;place-items:center;border-radius:999px;padding:2px;background:#1118278c;color:#f9fafb;opacity:0;transition:opacity .1s ease}.canvas-area-remove[data-v-cdd742c6]:hover{background:#fef2f2;color:#dc2626}.canvas-edge[data-v-cdd742c6]{position:absolute;display:grid;place-items:center;width:20px;height:20px;border-radius:999px;background:#fff;box-shadow:0 0 0 1.5px #d1d5db;color:#9ca3af;opacity:0;transition:opacity .1s ease;z-index:1}.canvas-area:hover .canvas-edge[data-v-cdd742c6],.canvas-area:focus-within .canvas-edge[data-v-cdd742c6],.canvas-area:hover .canvas-area-remove[data-v-cdd742c6],.canvas-area:focus-within .canvas-area-remove[data-v-cdd742c6]{opacity:1}.canvas-edge[data-v-cdd742c6]:hover{box-shadow:0 0 0 1.5px #f97316;color:#f97316}.canvas-edge-right[data-v-cdd742c6]{top:50%;right:-10px;transform:translateY(-50%)}.canvas-edge-bottom[data-v-cdd742c6]{bottom:-10px;left:50%;transform:translate(-50%)}.canvas-chips[data-v-cdd742c6]{display:flex;min-height:0;flex:1;flex-direction:column}.canvas-chip[data-v-cdd742c6]{display:flex;align-items:center;align-self:flex-start;max-width:100%;gap:6px;border:1px solid #e5e7eb;border-radius:8px;padding:6px 8px;background:#ffffffeb;color:#374151;font-size:12px;text-align:left;cursor:grab}.canvas-chip[data-v-cdd742c6]:hover{border-color:#fdba74}.canvas-chip-active[data-v-cdd742c6]{border-color:#f97316;box-shadow:inset 4px 0 #f97316,0 0 0 3px #f973161f;font-weight:600}.canvas-chip-active .canvas-chip-grip[data-v-cdd742c6]{color:#f97316}.canvas-chip-context[data-v-cdd742c6]{border-color:#fed7aa;background:#fffdfa}.canvas-chip[data-v-cdd742c6]:active{cursor:grabbing}.canvas-chip-grip[data-v-cdd742c6]{flex:0 0 auto;color:#9ca3af}.canvas-chip-label[data-v-cdd742c6]{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.canvas-chip-swap[data-v-cdd742c6]{display:grid;flex:0 0 auto;place-items:center;margin-left:2px;border-radius:6px;padding:2px;color:#9ca3af;opacity:0;transition:opacity .1s ease}.canvas-chip:hover .canvas-chip-swap[data-v-cdd742c6],.canvas-chip:focus-within .canvas-chip-swap[data-v-cdd742c6]{opacity:1}.canvas-chip-swap[data-v-cdd742c6]:hover{background:#fff;color:#f97316}.canvas-area-empty[data-v-cdd742c6]{display:grid;flex:1;place-items:center;border:1px dashed rgb(255 255 255 / 60%);border-radius:8px;background:transparent;color:#f3f4f6;font-size:11px;font-family:inherit;cursor:pointer}.canvas-area-empty[data-v-cdd742c6]:hover{border-color:#f97316;color:#f97316}@media (hover: none),(pointer: coarse){.canvas-edge[data-v-cdd742c6],.canvas-area-remove[data-v-cdd742c6],.canvas-chip-swap[data-v-cdd742c6]{opacity:1}}.canvas[data-v-eb790981]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;min-height:0}.canvas-row[data-v-eb790981]{flex-direction:row}.canvas-column[data-v-eb790981]{flex-direction:column}.canvas-group[data-v-eb790981]{position:relative;display:flex;min-width:0;min-height:0}.canvas-divider[data-v-eb790981]{position:absolute;z-index:2;display:grid;place-items:center;touch-action:none}.divider-vertical[data-v-eb790981]{top:0;bottom:0;width:10px;transform:translate(-50%);cursor:col-resize}.divider-horizontal[data-v-eb790981]{left:0;right:0;height:10px;transform:translateY(-50%);cursor:row-resize}.canvas-divider-grip[data-v-eb790981]{border-radius:999px;background:#d1d5db}.divider-vertical .canvas-divider-grip[data-v-eb790981]{height:34px;width:3px}.divider-horizontal .canvas-divider-grip[data-v-eb790981]{height:3px;width:34px}.canvas-divider:hover .canvas-divider-grip[data-v-eb790981]{background:#f97316}.module-picker[data-v-2892f182]{display:flex;flex-direction:column;gap:18px}.module-picker-hint[data-v-2892f182]{margin:0;color:#6b7280;font-size:12px}.module-picker-group[data-v-2892f182]{display:flex;flex-direction:column;gap:8px}.module-picker-group-label[data-v-2892f182]{margin:0;color:#9ca3af;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.module-picker-grid[data-v-2892f182]{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px}.module-picker-card[data-v-2892f182]{display:flex;flex-direction:column;align-items:center;gap:8px;border:1px solid #e5e7eb;border-radius:10px;padding:14px 10px;background:#fff;color:#374151;font-size:13px;text-align:center;cursor:pointer}.module-picker-card[data-v-2892f182]:hover{border-color:#f97316;background:#fffbf5}.module-picker-icon[data-v-2892f182]{display:grid;height:36px;width:36px;place-items:center;border-radius:8px;background:#f3f4f6;color:#4b5563}.module-picker-label[data-v-2892f182]{line-height:1.3;overflow-wrap:anywhere}.media-field[data-v-839ff846]{display:flex;flex-direction:column;gap:6px}.media-label[data-v-839ff846]{color:#4b5563;font-size:12px}.media-thumb[data-v-839ff846]{height:72px;width:100%;border-radius:8px;border:1px solid #e5e7eb;-o-object-fit:cover;object-fit:cover}.media-empty[data-v-839ff846]{display:grid;height:72px;place-items:center;border:1px dashed #d1d5db;border-radius:8px;color:#9ca3af;font-size:12px}.media-warning[data-v-839ff846]{display:grid;height:72px;place-items:center;border:1px dashed #d1d5db;border-radius:8px;color:#b54708;font-size:12px}.media-actions[data-v-839ff846]{display:flex;gap:6px}.inspector[data-v-4cc442cf]{display:flex;flex-direction:column;gap:14px}.inspector-scope[data-v-4cc442cf]{align-self:stretch}.inspector-breadcrumb[data-v-4cc442cf]{display:flex;align-items:center;gap:2px;color:#6b7280;font-size:11px}.inspector-section[data-v-4cc442cf]{display:flex;flex-direction:column;gap:10px;border-top:1px solid #e5e7eb;padding-top:12px}.inspector-section-title[data-v-4cc442cf]{color:#9ca3af;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.inspector-note[data-v-4cc442cf]{color:#6b7280;font-size:11px;line-height:1.45}.inspector-warning[data-v-4cc442cf]{display:flex;align-items:flex-start;gap:6px;border-radius:8px;background:#fffbeb;padding:8px 10px;color:#92400e;font-size:12px}.inspector-list[data-v-4cc442cf]{display:flex;flex-direction:column;gap:8px}.inspector-list-head[data-v-4cc442cf]{display:flex;align-items:baseline;gap:6px}.inspector-list-count[data-v-4cc442cf]{border-radius:999px;background:#f3f4f6;padding:0 6px;color:#6b7280;font-size:10px;font-variant-numeric:tabular-nums}.inspector-item[data-v-4cc442cf]{display:flex;flex-direction:column;gap:10px;border:1px solid #e5e7eb;border-radius:10px;padding:10px;background:#fcfcfd}.inspector-item-head[data-v-4cc442cf]{display:flex;align-items:center;gap:2px}.inspector-item-name[data-v-4cc442cf]{flex:1;color:#6b7280;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.inspector-item-action[data-v-4cc442cf]{display:grid;flex:0 0 auto;place-items:center;border-radius:6px;padding:2px;color:#9ca3af}.inspector-item-action[data-v-4cc442cf]:hover:not(:disabled){background:#fff7ed;color:#f97316}.inspector-item-action[data-v-4cc442cf]:disabled{opacity:.35}.inspector-list-add[data-v-4cc442cf]{display:flex;gap:6px}.media-field[data-v-4cc442cf]{display:flex;flex-direction:column;gap:6px}.media-label[data-v-4cc442cf]{color:#4b5563;font-size:12px}.media-thumb[data-v-4cc442cf]{height:72px;width:100%;border-radius:8px;border:1px solid #e5e7eb;-o-object-fit:cover;object-fit:cover}.media-empty[data-v-4cc442cf]{display:grid;height:72px;place-items:center;border:1px dashed #d1d5db;border-radius:8px;color:#9ca3af;font-size:12px}.media-actions[data-v-4cc442cf]{display:flex;gap:6px}.editor-head[data-v-a673333e]{display:flex;shrink:0;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid #e5e7eb;padding:12px 20px}.editor-identity[data-v-a673333e]{display:flex;min-width:0;align-items:center;gap:10px}.editor-name[data-v-a673333e]{width:min(320px,40vw)}.editor-orientation[data-v-a673333e]{width:160px}.editor-error[data-v-a673333e]{flex-shrink:0;margin:12px 16px 0;border-radius:8px;background:#fef2f2;padding:8px 12px;color:#b91c1c;font-size:13px}.rail-scale[data-v-a673333e]{display:flex;flex-direction:column;gap:6px;margin-bottom:18px;border-bottom:1px solid #e5e7eb;padding-bottom:16px}.rail-scale-label[data-v-a673333e]{color:#4b5563;font-size:12px;font-weight:600}.rail-scale-hint[data-v-a673333e]{margin:0;color:#6b7280;font-size:11px;line-height:1.4}.rail-scale-warning[data-v-a673333e]{margin:0;border-radius:8px;padding:6px 8px;color:#9a3412;background:#fff7ed;font-size:11px;line-height:1.4}.editor-body[data-v-a673333e]{display:grid;min-height:0;flex:1;grid-template-columns:268px minmax(0,1fr) 320px}.editor-rail[data-v-a673333e],.editor-inspector[data-v-a673333e]{min-height:0;overflow:auto;padding:16px}.editor-rail[data-v-a673333e]{border-right:1px solid #e5e7eb;background:#f9fafb}.editor-inspector[data-v-a673333e]{border-left:1px solid #e5e7eb;background:#f9fafb}.editor-stage[data-v-a673333e]{display:flex;min-height:0;flex-direction:column;gap:12px;overflow:auto;padding:16px}.stage-preview[data-v-a673333e]{display:flex;min-height:0;flex:1;align-items:center;justify-content:center}.preview-box[data-v-a673333e]{position:relative;margin:0 auto}.preview-box.preview-landscape[data-v-a673333e]{width:100%;max-height:72vh;aspect-ratio:16 / 9}.preview-box.preview-portrait[data-v-a673333e]{height:72vh;max-width:100%;aspect-ratio:9 / 16}.preview-frame[data-v-a673333e]{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden;border:1px solid #e5e7eb;border-radius:12px;background:#101828}.preview-frame[data-v-a673333e]>*{width:100%;height:100%}.preview-edit-overlay[data-v-a673333e]{position:absolute;top:0;right:0;bottom:0;left:0;opacity:0;pointer-events:none;transition:opacity .15s ease}.preview-box:hover .preview-edit-overlay[data-v-a673333e],.preview-box:focus-within .preview-edit-overlay[data-v-a673333e],.preview-edit-overlay.preview-edit-overlay-active[data-v-a673333e]{opacity:1;pointer-events:auto}@media (hover: none),(pointer: coarse){.preview-edit-overlay[data-v-a673333e]{opacity:1;pointer-events:auto}}.layout-bar[data-v-a673333e]{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.layout-bar-title[data-v-a673333e]{display:flex;min-width:0;flex-direction:column;gap:2px}.layout-title[data-v-a673333e]{color:#111827;font-size:13px;font-weight:600}.layout-hint[data-v-a673333e]{color:#9ca3af;font-size:11px}.layout-actions[data-v-a673333e]{display:flex;flex-shrink:0;align-items:center;gap:6px}.layout-conflict[data-v-a673333e]{display:flex;align-items:flex-start;gap:6px;border-radius:8px;background:#fffbeb;padding:8px 10px;color:#92400e;font-size:12px;cursor:pointer}.preset-row[data-v-a673333e]{display:flex;flex-wrap:wrap;gap:8px;border-radius:10px;background:#f9fafb;padding:10px}.preset-card[data-v-a673333e]{display:flex;width:108px;flex-direction:column;gap:6px;border:1px solid #d1d5db;border-radius:10px;padding:8px;background:#fff;text-align:left}.preset-card[data-v-a673333e]:hover{border-color:#f97316;background:#fff7ed}.preset-diagram[data-v-a673333e]{display:flex;height:34px;gap:3px;border-radius:6px;background:#f3f4f6;padding:3px}.preset-row-dir[data-v-a673333e]{flex-direction:row}.preset-column-dir[data-v-a673333e]{flex-direction:column}.preset-cell[data-v-a673333e]{flex-basis:0;border-radius:4px;background:#fdba74}.preset-label[data-v-a673333e]{color:#374151;font-size:11px;line-height:1.3}@media (max-width: 1280px){.editor-body[data-v-a673333e]{grid-template-columns:220px minmax(0,1fr)}.editor-inspector[data-v-a673333e]{grid-column:1 / -1;border-left:0;border-top:1px solid #e5e7eb}}.layout-split[data-v-5595b416]{display:flex;min-width:0;min-height:0;flex:1;gap:6px}.layout-row[data-v-5595b416]{flex-direction:row}.layout-column[data-v-5595b416]{flex-direction:column}.layout-zone[data-v-5595b416]{display:flex;min-width:0;min-height:36px;flex:1;flex-direction:row;align-items:center;gap:8px;overflow:hidden;border:1.5px dashed #d1d5db;border-radius:10px;padding:6px 10px;background:#fff;text-align:left;transition:border-color .12s ease,background .12s ease}.layout-zone[data-v-5595b416]:hover{border-color:#fdba74;background:#fffbf5}.layout-zone-selected[data-v-5595b416]{border-style:solid;border-color:#f97316;background:#fff7ed;box-shadow:inset 0 0 0 1px #f97316}.layout-zone-index[data-v-5595b416]{flex:none;color:#6b7280;font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.layout-zone-blocks[data-v-5595b416]{display:flex;min-width:0;gap:4px}.layout-zone-chip[data-v-5595b416]{display:inline-flex;max-width:100%;align-items:center;gap:4px;border-radius:999px;background:#f3f4f6;padding:3px 8px;color:#374151;font-size:11px}.display-toolbar[data-v-a2dda95a]{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid #e5e7eb;padding-bottom:14px}.display-controls[data-v-a2dda95a]{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-top:12px}.display-restaurant-select[data-v-a2dda95a]{width:min(360px,100%)}.display-outlet[data-v-a2dda95a]{display:flex;align-items:center;gap:16px}.display-entitlement[data-v-a2dda95a]{color:#6b7280;font-size:12px;white-space:nowrap}.restriction-note[data-v-a2dda95a]{margin-top:12px;border:1px solid #fed7aa;border-radius:10px;padding:10px 12px;color:#9a3412;background:#fff7ed;font-size:13px}.empty-state[data-v-a2dda95a]{display:grid;justify-items:center;gap:6px;border:1px dashed #d1d5db;border-radius:12px;padding:56px 20px;color:#6b7280;text-align:center}.empty-state strong[data-v-a2dda95a]{color:#111827}.layout-thumb[data-v-a2dda95a]{display:flex;height:auto;min-height:96px;margin-top:16px;border-radius:10px;background:#f3f4f6;padding:5px;pointer-events:none}.thumb-caption[data-v-a2dda95a]{margin-top:8px;color:#6b7280;font-size:11px}.resource-card[data-v-a2dda95a]{border:1px solid #e5e7eb;border-radius:12px;padding:20px;background:#fff;box-shadow:0 2px 8px #0000000a}.mode-badge[data-v-a2dda95a],.status-badge[data-v-a2dda95a]{border-radius:999px;padding:3px 8px;background:#f3f4f6;font-size:11px;font-weight:700}.status-badge.active[data-v-a2dda95a]{color:#166534;background:#dcfce7}.status-badge.disabled[data-v-a2dda95a]{color:#991b1b;background:#fee2e2}.status-badge.unlicensed[data-v-a2dda95a]{color:#b42318;background:#fef3f2;border:1px solid #fecdca}.online-dot[data-v-a2dda95a]{color:#6b7280;font-size:11px}.online-dot[data-v-a2dda95a]:before{display:inline-block;width:7px;height:7px;margin-right:5px;border-radius:50%;background:#9ca3af;content:""}.online-dot.online[data-v-a2dda95a]{color:#15803d}.online-dot.online[data-v-a2dda95a]:before{background:#22c55e}.meta-grid[data-v-a2dda95a]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:18px;border-top:1px solid #f0f0f0;padding-top:16px}.meta-grid dt[data-v-a2dda95a]{color:#6b7280;font-size:11px}.meta-grid dd[data-v-a2dda95a]{overflow:hidden;margin-top:2px;font-size:12px;text-overflow:ellipsis;white-space:nowrap}@media (max-width:640px){.display-toolbar[data-v-a2dda95a]{align-items:flex-start}.display-controls[data-v-a2dda95a]{align-items:stretch;flex-direction:column}.display-outlet[data-v-a2dda95a]{align-items:stretch;flex-direction:column;gap:8px}.display-restaurant-select[data-v-a2dda95a]{width:100%}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feedmepos/mf-order-setting",
3
- "version": "0.0.90",
3
+ "version": "0.0.91",
4
4
  "type": "module",
5
5
  "module": "./dist/app.js",
6
6
  "license": "UNLICENSED",
@@ -48,12 +48,14 @@
48
48
  "@vitejs/plugin-vue": "^4.3.4",
49
49
  "@vue/eslint-config-prettier": "^8.0.0",
50
50
  "@vue/eslint-config-typescript": "^12.0.0",
51
+ "@vue/test-utils": "^2.4.6",
51
52
  "@vue/tsconfig": "^0.4.0",
52
53
  "autoprefixer": "^10.4.16",
53
54
  "cross-env": "^7.0.3",
54
55
  "dotenv-cli": "^7.4.2",
55
56
  "eslint": "^8.49.0",
56
57
  "eslint-plugin-vue": "^9.17.0",
58
+ "happy-dom": "^9.20.3",
57
59
  "npm-run-all2": "^6.0.6",
58
60
  "postcss": "^8.4.31",
59
61
  "prettier": "^3.0.3",
@@ -125,6 +125,8 @@
125
125
  "bindDevice": "Bind device",
126
126
  "bindDeviceFailed": "Couldn't start device binding. Please try again.",
127
127
  "bindDeviceMessage": "Enter this code to your kiosk device to bind.",
128
+ "bindDeviceNetworkError": "Couldn't reach the server. Check your internet connection and try again.",
129
+ "bindDeviceNoQuota": "No kiosk license left for this business. Add kiosk devices in the license portal, or unbind a kiosk you no longer use.",
128
130
  "bindDevicePermissionDenied": "You don't have permission to bind devices.",
129
131
  "cancel": "Cancel",
130
132
  "cancelResetPin": "Cancel reset PIN",
@@ -724,21 +726,23 @@
724
726
  },
725
727
  "ui": {
726
728
  "splitAreaHint": "Split this area {direction}",
727
- "mergeGroupHint": "Merge back into one area",
728
729
  "splitLimit": "An area inside a group cannot be split again.",
729
730
  "moveItemUp": "Move up",
730
731
  "moveItemDown": "Move down",
731
732
  "removeItem": "Remove slide",
732
733
  "presetAds": "Board + ads",
733
- "emptyArea": "Drop a module here",
734
+ "emptyArea": "Click to add a module",
735
+ "pickModuleTitle": "Choose a module",
736
+ "pickModuleHint": "Pick what this area should show.",
737
+ "replaceModuleConfirmTitle": "Replace this area's module?",
738
+ "replaceModuleConfirmMessage": "This area already has {current}. Adding a new module replaces it, and the current one cannot be recovered.",
739
+ "replaceModuleConfirmAction": "Replace",
734
740
  "areaNeedsModule": "{area} has no module yet. Add one before saving.",
735
741
  "sectionContent": "Content",
736
742
  "sectionAnnounce": "Announcements",
737
743
  "inspectorArea": "Area",
738
744
  "inspectorModule": "Module",
739
745
  "inspectorEmpty": "Pick an area or a module on the canvas.",
740
- "paletteTitle": "Modules",
741
- "paletteHint": "Drag onto an area, or click to add to the selected area.",
742
746
  "uiScale": "UI scale",
743
747
  "uiScaleHint": "Shrinks every element on the TV so more orders and queue numbers fit. A screen can override this.",
744
748
  "hideFooter": "Hide the FeedMe footer",
@@ -749,7 +753,10 @@
749
753
  "directionColumn": "Stacked",
750
754
  "resetLayout": "Start from a layout",
751
755
  "areaOnly": "Only area",
756
+ "removeAreaConfirmTitle": "Remove this area?",
757
+ "removeAreaConfirmMessage": "This area and its configured module will be deleted and cannot be recovered.",
752
758
  "removeAreaTooltip": "Remove this area",
759
+ "changeModuleTooltip": "Change module",
753
760
  "splitArea": "Split this area",
754
761
  "templateName": "Template name",
755
762
  "orientation": "Screen orientation",
@@ -672,21 +672,23 @@
672
672
  },
673
673
  "ui": {
674
674
  "splitAreaHint": "把这个区域{direction}拆成两个",
675
- "mergeGroupHint": "合并回一个区域",
676
675
  "splitLimit": "组内的区域不能再拆分。",
677
676
  "moveItemUp": "上移",
678
677
  "moveItemDown": "下移",
679
678
  "removeItem": "删除这一项",
680
679
  "presetAds": "看板 + 广告",
681
- "emptyArea": "把模块放到这里",
680
+ "emptyArea": "点击添加模块",
681
+ "pickModuleTitle": "选择一个模块",
682
+ "pickModuleHint": "选一个这个区域要展示的内容。",
683
+ "replaceModuleConfirmTitle": "替换这个区域的模块?",
684
+ "replaceModuleConfirmMessage": "这个区域已经有「{current}」了,新增模块会直接替换掉它,替换后无法恢复。",
685
+ "replaceModuleConfirmAction": "替换",
682
686
  "areaNeedsModule": "{area}还没有模块,保存前请先添加一个。",
683
687
  "sectionContent": "内容",
684
688
  "sectionAnnounce": "语音播报",
685
689
  "inspectorArea": "区域",
686
690
  "inspectorModule": "模块",
687
691
  "inspectorEmpty": "在画布上选择一个区域或模块。",
688
- "paletteTitle": "模块",
689
- "paletteHint": "拖到某个区域,或点击加入当前选中的区域。",
690
692
  "uiScale": "界面缩放",
691
693
  "uiScaleHint": "等比缩小电视上的所有元素,从而放下更多订单和排队号码。单个显示屏可以覆盖此设置。",
692
694
  "hideFooter": "隐藏 FeedMe 页脚",
@@ -697,7 +699,10 @@
697
699
  "directionColumn": "上下堆叠",
698
700
  "resetLayout": "套用起步版面",
699
701
  "areaOnly": "唯一区域",
702
+ "removeAreaConfirmTitle": "删除这个区域?",
703
+ "removeAreaConfirmMessage": "该区域及已配置的模块将被删除,删除后无法恢复。",
700
704
  "removeAreaTooltip": "删除该区域",
705
+ "changeModuleTooltip": "更换模块",
701
706
  "splitArea": "拆分该区域",
702
707
  "templateName": "模板名称",
703
708
  "orientation": "屏幕方向",
@@ -2,7 +2,7 @@ import { DISPLAY_BLOCK_DEFINITIONS } from '@entity'
2
2
  import { reactive } from 'vue'
3
3
  import { describe, expect, it } from 'vitest'
4
4
 
5
- import { addArea, clearBlock, crossDirection, defaultBlock, defaultDisplayTemplateDraft, displayTemplatePayload, mergeGroup, removeArea, setBlock, splitArea, swapBlocks, templateCapabilities, type DisplaySplitDraft, unlicensedScreenIds, walkZones, zoneFitsMinimum, zoneParent } from './interface'
5
+ import { addArea, addAreaBeside, applyDividerRatio, setLayoutDirection, clearBlock, crossDirection, defaultBlock, defaultDisplayTemplateDraft, displayTemplatePayload, removeArea, setBlock, splitArea, swapBlocks, templateCapabilities, type DisplaySplitDraft, unlicensedScreenIds, walkZones, zoneFitsMinimum, zoneParent } from './interface'
6
6
 
7
7
  describe('display template drafts', () => {
8
8
  it('serializes a reactive editor draft into an API payload', () => {
@@ -43,6 +43,80 @@ describe('display template drafts', () => {
43
43
  expect(templateCapabilities(draft)).toEqual(['PICKUP', 'MEDIA'])
44
44
  })
45
45
 
46
+ it('rotates every nested group across its parent without changing content or weights', () => {
47
+ const draft = defaultDisplayTemplateDraft('ads')
48
+ const root = draft.root as DisplaySplitDraft
49
+ splitArea(draft, 'main_zone', defaultBlock('info.text'))
50
+ const original = structuredClone(draft.root)
51
+ for (const direction of ['COLUMN', 'ROW'] as const) {
52
+ setLayoutDirection(root, direction)
53
+ expect(root.direction).toBe(direction)
54
+ for (const child of root.children) {
55
+ expect((child.node as DisplaySplitDraft).direction).toBe(crossDirection(direction))
56
+ }
57
+ expect(walkZones(root)).toEqual(walkZones(original))
58
+ expect(root.children.map((child) => child.weight)).toEqual([700, 300])
59
+ }
60
+ })
61
+
62
+ it('adds top-level areas after a trailing group up to the existing cap', () => {
63
+ const draft = defaultDisplayTemplateDraft('ads')
64
+ const root = draft.root as DisplaySplitDraft
65
+ const original = structuredClone(walkZones(root))
66
+ for (let count = 2; count < 6; count += 1) {
67
+ expect(addArea(draft, 'ROW', defaultBlock('info.text'))).toBeTruthy()
68
+ }
69
+ expect(root.children).toHaveLength(6)
70
+ expect(addArea(draft, 'ROW')).toBeUndefined()
71
+ expect(walkZones(root).slice(0, 3)).toEqual(original)
72
+ })
73
+
74
+ it('funds an added top-level area from the last slot, preserving all other weights', () => {
75
+ const draft = defaultDisplayTemplateDraft('ads')
76
+ const root = draft.root as DisplaySplitDraft
77
+ addArea(draft, 'ROW')
78
+ expect(root.children.map((child) => child.weight)).toEqual([700, 150, 150])
79
+ expect((root.children[1]!.node as DisplaySplitDraft).children.map((child) => child.weight)).toEqual([500, 500])
80
+ })
81
+
82
+ it('funds a nested addition from the clicked area and keeps sibling proportions', () => {
83
+ const draft = defaultDisplayTemplateDraft('ads')
84
+ const group = zoneParent(draft.root, 'promo_zone')!.split
85
+ group.children[0]!.weight = 800
86
+ group.children[1]!.weight = 200
87
+ addAreaBeside(draft, 'promo_zone')
88
+ expect(group.children.map((child) => child.weight)).toEqual([400, 200, 400])
89
+ })
90
+
91
+ it('keeps valid positive integer weights when splitting a one-unit slot', () => {
92
+ const draft = defaultDisplayTemplateDraft('mixed')
93
+ const root = draft.root as DisplaySplitDraft
94
+ root.children[0]!.weight = 999
95
+ root.children[1]!.weight = 1
96
+ addArea(draft, 'ROW')
97
+ expect(root.children.map((child) => child.weight)).toEqual([999, 1, 1])
98
+ })
99
+
100
+ it('removes only the clicked area without equalizing remaining siblings', () => {
101
+ const draft = defaultDisplayTemplateDraft('mixed')
102
+ addArea(draft, 'ROW')
103
+ const root = draft.root as DisplaySplitDraft
104
+ root.children.forEach((child, index) => { child.weight = [600, 300, 100][index]! })
105
+ removeArea(draft, walkZones(root)[2]!.id)
106
+ expect(root.children.map((child) => child.weight)).toEqual([600, 300])
107
+ })
108
+
109
+ it('resizes only the adjacent pair and preserves its total weight', () => {
110
+ const draft = defaultDisplayTemplateDraft('mixed')
111
+ addArea(draft, 'ROW')
112
+ const root = draft.root as DisplaySplitDraft
113
+ root.children.forEach((child, index) => { child.weight = [333, 333, 334][index]! })
114
+ applyDividerRatio(root.children, 1, 333 / 667)
115
+ expect(root.children.map((child) => child.weight)).toEqual([333, 333, 334])
116
+ applyDividerRatio(root.children, 1, 0.6)
117
+ expect(root.children.map((child) => child.weight)).toEqual([333, 400, 267])
118
+ })
119
+
46
120
  it('splits a top-level area across its parent and refuses a third level', () => {
47
121
  const draft = defaultDisplayTemplateDraft('sidebar')
48
122
  const [, side] = walkZones(draft.root)
@@ -57,6 +131,18 @@ describe('display template drafts', () => {
57
131
  expect(splitArea(draft, created!, defaultBlock('info.text'))).toBeUndefined()
58
132
  })
59
133
 
134
+ it('grows a nested group past two areas, up to the same cap as the top level', () => {
135
+ const draft = defaultDisplayTemplateDraft('sidebar')
136
+ const [, side] = walkZones(draft.root)
137
+ const created = splitArea(draft, side!.id, defaultBlock('media.carousel'))!
138
+
139
+ for (let count = 0; count < 4; count += 1) {
140
+ expect(addAreaBeside(draft, created, defaultBlock('info.text'))).toBeTruthy()
141
+ }
142
+ expect(zoneParent(draft.root, created)?.split.children).toHaveLength(6)
143
+ expect(addAreaBeside(draft, created, defaultBlock('info.text'))).toBeUndefined()
144
+ })
145
+
60
146
  it('collapses a group when it drops to one area', () => {
61
147
  const draft = defaultDisplayTemplateDraft('sidebar')
62
148
  const [, side] = walkZones(draft.root)
@@ -68,14 +154,13 @@ describe('display template drafts', () => {
68
154
  expect(walkZones(draft.root)).toHaveLength(2)
69
155
  })
70
156
 
71
- it('merges a group back into the area whose module is kept', () => {
157
+ it('removing one area of a group keeps the other, module and all', () => {
72
158
  const draft = defaultDisplayTemplateDraft('ads')
73
159
  const [, promo, ads] = walkZones(draft.root)
74
160
 
75
- expect(mergeGroup(draft, ads!.id)).toBe(ads!.id)
161
+ expect(removeArea(draft, promo!.id)).toBe(true)
76
162
  const remaining = walkZones(draft.root)
77
- expect(remaining.map((zone) => zone.id)).toEqual(['main_zone', ads!.id])
78
- expect(remaining.some((zone) => zone.id === promo!.id)).toBe(false)
163
+ expect(remaining).toEqual([expect.objectContaining({ id: 'main_zone' }), ads])
79
164
  })
80
165
 
81
166
  it('collapses the top level when only one area is left', () => {
@@ -316,11 +316,13 @@ function newZone(block?: DisplayBlockDraft): DisplayZoneDraft {
316
316
  return { kind: 'ZONE', id: uniqueLayoutId('zone'), block }
317
317
  }
318
318
 
319
- function normalizeWeights(split: DisplaySplitDraft): void {
320
- const even = Math.round(1000 / split.children.length)
321
- split.children.forEach((child) => {
322
- child.weight = even
323
- })
319
+ /** Take the new area's share from its neighbor, keeping every other sibling's
320
+ * weight. At the one-unit minimum both areas need one unit to remain valid. */
321
+ function appendArea(split: DisplaySplitDraft, index: number, node: DisplayZoneDraft): void {
322
+ const neighbor = split.children[index]!
323
+ const share = Math.max(1, Math.floor(neighbor.weight / 2))
324
+ neighbor.weight = Math.max(1, neighbor.weight - share)
325
+ split.children.push({ weight: share, node })
324
326
  }
325
327
 
326
328
  /** Direction a container nested inside `direction` must run. Same-direction
@@ -331,6 +333,14 @@ export function crossDirection(
331
333
  return direction === 'ROW' ? 'COLUMN' : 'ROW'
332
334
  }
333
335
 
336
+ /** Rotate a layout while preserving the API's alternating container directions. */
337
+ export function setLayoutDirection(split: DisplaySplitDraft, direction: DisplaySplitDraft['direction']): void {
338
+ split.direction = direction
339
+ for (const child of split.children) {
340
+ if (child.node.kind === 'SPLIT') setLayoutDirection(child.node, crossDirection(direction))
341
+ }
342
+ }
343
+
334
344
  export const DISPLAY_CONTAINER_CHILD_LIMIT = 6
335
345
 
336
346
  /** Turns a single-area template into a two-area split, or adds one more area to
@@ -353,8 +363,22 @@ export function addArea(
353
363
  return created.id
354
364
  }
355
365
  if (draft.root.children.length >= DISPLAY_CONTAINER_CHILD_LIMIT) return undefined
356
- draft.root.children.push({ weight: 300, node: created })
357
- normalizeWeights(draft.root)
366
+ appendArea(draft.root, draft.root.children.length - 1, created)
367
+ return created.id
368
+ }
369
+
370
+ /** Adds a sibling to whichever container holds `zoneId` -- the top-level split
371
+ * or a nested group -- up to the same limit as the top level. Returns the id
372
+ * of the area that was created. */
373
+ export function addAreaBeside(
374
+ draft: DisplayTemplateDraft,
375
+ zoneId: string,
376
+ block?: DisplayBlockDraft
377
+ ): string | undefined {
378
+ const parent = zoneParent(draft.root, zoneId)
379
+ if (!parent || parent.split.children.length >= DISPLAY_CONTAINER_CHILD_LIMIT) return undefined
380
+ const created = newZone(block)
381
+ appendArea(parent.split, parent.index, created)
358
382
  return created.id
359
383
  }
360
384
 
@@ -395,7 +419,6 @@ export function removeArea(draft: DisplayTemplateDraft, zoneId: string): boolean
395
419
  if (remaining.length === parent.split.children.length) return false
396
420
  if (remaining.length >= 2) {
397
421
  parent.split.children = remaining
398
- normalizeWeights(parent.split)
399
422
  return true
400
423
  }
401
424
  // A container with one child is not a container: collapse it into whatever
@@ -413,19 +436,6 @@ export function removeArea(draft: DisplayTemplateDraft, zoneId: string): boolean
413
436
  return true
414
437
  }
415
438
 
416
- /** Collapses the nested container holding `zoneId` back into a single area,
417
- * keeping that area's module. Returns the id of the area that remains. */
418
- export function mergeGroup(draft: DisplayTemplateDraft, zoneId: string): string | undefined {
419
- const parent = zoneParent(draft.root, zoneId)
420
- if (!parent || parent.depth !== 2) return undefined
421
- const kept = parent.split.children[parent.index]!.node as DisplayZoneDraft
422
- const outer = draft.root as DisplaySplitDraft
423
- const slot = outer.children.find((child) => child.node === parent.split)
424
- if (!slot) return undefined
425
- slot.node = kept
426
- return kept.id
427
- }
428
-
429
439
  // --- Module operations ----------------------------------------------------
430
440
 
431
441
  function findZone(node: DisplayLayoutDraft, zoneId: string): DisplayZoneDraft | undefined {
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { KIOSK_LICENSE_QUOTA_EXHAUSTED, bindDeviceErrorKey } from './bindDeviceError'
3
+
4
+ describe('bindDeviceErrorKey', () => {
5
+ it('explains a missing kiosk license from the error code', () => {
6
+ expect(
7
+ bindDeviceErrorKey({
8
+ status: 400,
9
+ code: KIOSK_LICENSE_QUOTA_EXHAUSTED,
10
+ message: 'No quota available for binding'
11
+ })
12
+ ).toBe('order.bindDeviceNoQuota')
13
+ })
14
+
15
+ it('explains a missing kiosk license from the message when the backend sends no code', () => {
16
+ expect(
17
+ bindDeviceErrorKey({ status: 400, message: 'No quota available for binding' })
18
+ ).toBe('order.bindDeviceNoQuota')
19
+ })
20
+
21
+ it('reports a permission problem for 403, even if the text mentions quota', () => {
22
+ expect(
23
+ bindDeviceErrorKey({ status: 403, message: 'No quota available for binding' })
24
+ ).toBe('order.bindDevicePermissionDenied')
25
+ })
26
+
27
+ it('reports a network problem when no response came back', () => {
28
+ expect(bindDeviceErrorKey({ status: undefined, message: 'Network Error' })).toBe(
29
+ 'order.bindDeviceNetworkError'
30
+ )
31
+ })
32
+
33
+ it('falls back to the generic message for a server error', () => {
34
+ expect(bindDeviceErrorKey({ status: 500, message: 'Internal Server Error' })).toBe(
35
+ 'order.bindDeviceFailed'
36
+ )
37
+ })
38
+
39
+ it('falls back to the generic message for an error that is not from the API', () => {
40
+ expect(bindDeviceErrorKey(null)).toBe('order.bindDeviceFailed')
41
+ })
42
+ })
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Error code kiosk-backend puts on the 400 it returns when the business has no
3
+ * free, unexpired kiosk license (see KIOSK_LICENSE_QUOTA_EXHAUSTED in
4
+ * backend/apps/kiosk-backend/src/kiosk/kiosk.service.ts).
5
+ */
6
+ export const KIOSK_LICENSE_QUOTA_EXHAUSTED = 'KIOSK_LICENSE_QUOTA_EXHAUSTED'
7
+
8
+ export type BindDeviceErrorKey =
9
+ | 'order.bindDevicePermissionDenied'
10
+ | 'order.bindDeviceNoQuota'
11
+ | 'order.bindDeviceNetworkError'
12
+ | 'order.bindDeviceFailed'
13
+
14
+ /** The parts of an `SvcCompanyApiError` that decide which message to show. */
15
+ export interface BindDeviceApiError {
16
+ status?: number
17
+ code?: string
18
+ message?: string
19
+ }
20
+
21
+ /**
22
+ * Picks the message for a failed "Bind device" (bind code request), so the
23
+ * merchant learns WHY it failed instead of a bare "please try again".
24
+ *
25
+ * `apiError` is null when the failure was not an API error at all.
26
+ */
27
+ export function bindDeviceErrorKey(apiError: BindDeviceApiError | null): BindDeviceErrorKey {
28
+ if (!apiError) return 'order.bindDeviceFailed'
29
+ if (apiError.status === 403) return 'order.bindDevicePermissionDenied'
30
+ // The message check covers a kiosk-backend that does not send the code yet.
31
+ if (
32
+ apiError.code === KIOSK_LICENSE_QUOTA_EXHAUSTED ||
33
+ /no quota available/i.test(apiError.message ?? '')
34
+ ) {
35
+ return 'order.bindDeviceNoQuota'
36
+ }
37
+ // An API error with no HTTP status never got a response: offline, DNS, CORS.
38
+ if (apiError.status === undefined) return 'order.bindDeviceNetworkError'
39
+ return 'order.bindDeviceFailed'
40
+ }