@fayz-ai/plugin-menu 0.9.4 → 0.10.0

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 (52) hide show
  1. package/dist/MenuPage-2LLWRJGN.js +1785 -0
  2. package/dist/MenuPage-2LLWRJGN.js.map +1 -0
  3. package/dist/chunk-C6W53TML.js +65 -0
  4. package/dist/chunk-C6W53TML.js.map +1 -0
  5. package/dist/components/DraftSectionCard.d.ts +18 -0
  6. package/dist/components/DraftSectionCard.d.ts.map +1 -0
  7. package/dist/components/ItemRailPage.d.ts +36 -0
  8. package/dist/components/ItemRailPage.d.ts.map +1 -0
  9. package/dist/components/MenuPreview.d.ts +17 -0
  10. package/dist/components/MenuPreview.d.ts.map +1 -0
  11. package/dist/components/MenuRailPage.d.ts +17 -0
  12. package/dist/components/MenuRailPage.d.ts.map +1 -0
  13. package/dist/components/ScheduleEditor.d.ts +8 -0
  14. package/dist/components/ScheduleEditor.d.ts.map +1 -0
  15. package/dist/components/SectionCard.d.ts +32 -0
  16. package/dist/components/SectionCard.d.ts.map +1 -0
  17. package/dist/data/legacy-adapter.d.ts +4 -0
  18. package/dist/data/legacy-adapter.d.ts.map +1 -0
  19. package/dist/data/mock.d.ts +2 -2
  20. package/dist/data/mock.d.ts.map +1 -1
  21. package/dist/data/registries.d.ts.map +1 -1
  22. package/dist/data/supabase.d.ts +7 -0
  23. package/dist/data/supabase.d.ts.map +1 -0
  24. package/dist/data/tables.d.ts +17 -0
  25. package/dist/data/tables.d.ts.map +1 -0
  26. package/dist/data/types.d.ts +36 -2
  27. package/dist/data/types.d.ts.map +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2150 -356
  30. package/dist/index.js.map +1 -1
  31. package/dist/lib/agent-surface.d.ts +46 -0
  32. package/dist/lib/agent-surface.d.ts.map +1 -0
  33. package/dist/lib/availability.d.ts +16 -0
  34. package/dist/lib/availability.d.ts.map +1 -0
  35. package/dist/lib/extract-parse.d.ts +16 -0
  36. package/dist/lib/extract-parse.d.ts.map +1 -0
  37. package/dist/lib/extract-transport.d.ts +10 -0
  38. package/dist/lib/extract-transport.d.ts.map +1 -0
  39. package/dist/lib/images.d.ts +7 -0
  40. package/dist/lib/images.d.ts.map +1 -0
  41. package/dist/locales/en.d.ts.map +1 -1
  42. package/dist/locales/pt-BR.d.ts.map +1 -1
  43. package/dist/migrations/index.d.ts +8 -0
  44. package/dist/migrations/index.d.ts.map +1 -0
  45. package/dist/store.d.ts +46 -10
  46. package/dist/store.d.ts.map +1 -1
  47. package/dist/types.d.ts +151 -2
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/views/MenuManagerView.d.ts.map +1 -1
  50. package/package.json +12 -6
  51. package/dist/MenuPage-ZNKKQJKD.js +0 -523
  52. package/dist/MenuPage-ZNKKQJKD.js.map +0 -1
@@ -0,0 +1,1785 @@
1
+ import { normalizeExtraction, normalizeName } from './chunk-C6W53TML.js';
2
+ import { createPluginContext, useAgentSurface, askAI, RightRailPage, formatCurrency, requestChatUpload } from '@fayz-ai/admin';
3
+ import { useState, useMemo, useEffect } from 'react';
4
+ import { Sparkles, Eye, Plus, Pencil, Copy, Trash2, Search, UtensilsCrossed, GripVertical, ChevronRight, ChevronDown, Loader2, MoreHorizontal, X, Check, ImageOff, Star, XCircle } from 'lucide-react';
5
+ import { useSensors, useSensor, PointerSensor, DndContext, closestCenter, DragOverlay } from '@dnd-kit/core';
6
+ import { SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
7
+ import { useTranslation, getSupabaseClientOptional } from '@fayz-ai/core';
8
+ import { PageHeaderActions, Button, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator, toast, ConfirmDialog, SheetBody, Input, Switch, SheetFooter, ImageDrop, CurrencyInput, SegmentedControl, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, TimePicker } from '@fayz-ai/ui';
9
+ import { CSS } from '@dnd-kit/utilities';
10
+ import { jsx, jsxs } from 'react/jsx-runtime';
11
+
12
+ var ctx = createPluginContext("MenuPage");
13
+ var MenuContextProvider = ctx.ContextProvider;
14
+ var useMenuConfig = ctx.useConfig;
15
+ ctx.useProvider;
16
+ var useMenuStore = ctx.useStore;
17
+ function parseTime(value) {
18
+ if (!value) return null;
19
+ const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
20
+ if (!match) return null;
21
+ const hours = Number(match[1]);
22
+ const minutes = Number(match[2]);
23
+ if (hours > 23 || minutes > 59) return null;
24
+ return hours * 60 + minutes;
25
+ }
26
+ function isAlwaysAvailable(availability) {
27
+ if (!availability) return true;
28
+ const { days, start, end, from, to } = availability;
29
+ return (!days || days.length === 0) && !start && !end && !from && !to;
30
+ }
31
+ function isServingAt(availability, at = /* @__PURE__ */ new Date()) {
32
+ if (isAlwaysAvailable(availability)) return true;
33
+ const rule = availability;
34
+ const isoDate = `${at.getFullYear()}-${String(at.getMonth() + 1).padStart(2, "0")}-${String(at.getDate()).padStart(2, "0")}`;
35
+ if (rule.from && isoDate < rule.from) return false;
36
+ if (rule.to && isoDate > rule.to) return false;
37
+ const start = parseTime(rule.start);
38
+ const end = parseTime(rule.end);
39
+ const nowMinutes = at.getHours() * 60 + at.getMinutes();
40
+ const overnight = start !== null && end !== null && end <= start;
41
+ const dayToCheck = overnight && start !== null && nowMinutes < start ? (at.getDay() + 6) % 7 : at.getDay();
42
+ if (rule.days && rule.days.length > 0 && !rule.days.includes(dayToCheck)) return false;
43
+ if (start === null || end === null) return true;
44
+ return overnight ? nowMinutes >= start || nowMinutes < end : nowMinutes >= start && nowMinutes < end;
45
+ }
46
+ function weekdayLabels(locale) {
47
+ const formatter = new Intl.DateTimeFormat(locale, { weekday: "short" });
48
+ return Array.from({ length: 7 }, (_, i) => formatter.format(new Date(2024, 0, 7 + i, 12)).replace(".", ""));
49
+ }
50
+ function describeAvailability(availability, locale, alwaysLabel) {
51
+ if (isAlwaysAvailable(availability)) return alwaysLabel;
52
+ const rule = availability;
53
+ const parts = [];
54
+ if (rule.days && rule.days.length > 0 && rule.days.length < 7) {
55
+ const labels = weekdayLabels(locale);
56
+ const sorted = [...rule.days].sort((a, b) => a - b);
57
+ const contiguous = sorted.every((day, i) => i === 0 || day === sorted[i - 1] + 1);
58
+ parts.push(contiguous && sorted.length > 2 ? `${labels[sorted[0]]} a ${labels[sorted[sorted.length - 1]]}` : sorted.map((d) => labels[d]).join(", "));
59
+ }
60
+ if (rule.start && rule.end) parts.push(`${rule.start}\u2013${rule.end}`);
61
+ else if (rule.start) parts.push(`a partir de ${rule.start}`);
62
+ if (rule.from || rule.to) parts.push(`${rule.from ?? "\u2026"} \u2192 ${rule.to ?? "\u2026"}`);
63
+ return parts.length > 0 ? parts.join(" \xB7 ") : alwaysLabel;
64
+ }
65
+ function resolveMenuExtractor(options) {
66
+ const client = getSupabaseClientOptional();
67
+ if (!client?.functions?.invoke) return null;
68
+ const functionName = "menu-extract";
69
+ return async (images, hint) => {
70
+ const { data, error } = await client.functions.invoke(functionName, {
71
+ body: { images, hint }
72
+ });
73
+ if (error) {
74
+ const detail = await safeMessage(error) ?? error.message;
75
+ throw new Error(detail || "menu extraction failed");
76
+ }
77
+ return normalizeExtraction(data);
78
+ };
79
+ }
80
+ async function safeMessage(error) {
81
+ try {
82
+ const body = await error?.context?.json?.();
83
+ return typeof body?.error === "string" ? body.error : null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ // src/lib/images.ts
90
+ async function downscaleImage(file, options) {
91
+ const maxDimension = 1600;
92
+ const quality = 0.82;
93
+ const bitmap = await loadImage(file);
94
+ const scale = Math.min(1, maxDimension / Math.max(bitmap.width, bitmap.height));
95
+ const width = Math.round(bitmap.width * scale);
96
+ const height = Math.round(bitmap.height * scale);
97
+ const canvas = document.createElement("canvas");
98
+ canvas.width = width;
99
+ canvas.height = height;
100
+ const context = canvas.getContext("2d");
101
+ if (!context) return fileToDataUrl(file);
102
+ context.drawImage(bitmap, 0, 0, width, height);
103
+ return canvas.toDataURL("image/jpeg", quality);
104
+ }
105
+ async function loadImage(file) {
106
+ if (typeof createImageBitmap === "function") {
107
+ try {
108
+ return await createImageBitmap(file);
109
+ } catch {
110
+ }
111
+ }
112
+ const url = await fileToDataUrl(file);
113
+ return new Promise((resolve, reject) => {
114
+ const image = new Image();
115
+ image.onload = () => resolve(image);
116
+ image.onerror = () => reject(new Error("Could not read the image"));
117
+ image.src = url;
118
+ });
119
+ }
120
+ function fileToDataUrl(file) {
121
+ return new Promise((resolve, reject) => {
122
+ const reader = new FileReader();
123
+ reader.onload = () => resolve(String(reader.result));
124
+ reader.onerror = () => reject(new Error("Could not read the file"));
125
+ reader.readAsDataURL(file);
126
+ });
127
+ }
128
+ var INSTRUCTIONS = `Voc\xEA est\xE1 com a tela de Card\xE1pio aberta e \xE9 VOC\xCA quem conduz o cadastro.
129
+ - Trabalhe sempre no card\xE1pio ativo mostrado no estado; se o usu\xE1rio pedir outro, troque ou crie.
130
+ - Se\xE7\xF5es e itens s\xE3o identificados por NOME, do jeito que o usu\xE1rio fala. Pre\xE7o \xE9 n\xFAmero (29.9), sem moeda.
131
+
132
+ Cadastro por FOTO (o caminho principal para um card\xE1pio novo):
133
+ 1. Diga em uma linha o que vai fazer e chame requestMenuPhotos. Isso p\xF5e um bot\xE3o grande aqui na conversa ("Subir foto do card\xE1pio") que abre a c\xE2mera ou os arquivos \u2014 n\xE3o pe\xE7a para o usu\xE1rio usar o clipe, e n\xE3o mande ele fazer nada na tela.
134
+ 2. Espere. O envio dispara a leitura sozinho.
135
+ 3. Quando \`draft\` aparecer no estado, LEIA em voz alta o que veio: quantas se\xE7\xF5es, quantos itens, e quais ficaram com pre\xE7o 0.
136
+ 4. Resolva as pend\xEAncias conversando \u2014 pergunte o pre\xE7o que faltou e chame updateMenuDraftItem. O usu\xE1rio tamb\xE9m pode editar direto na tela; o estado sempre reflete o que est\xE1 l\xE1.
137
+ 5. S\xF3 chame commitMenuDraft depois que o usu\xE1rio confirmar. Se ele desistir, discardMenuDraft.
138
+
139
+ NUNCA escreva um bot\xE3o em texto (nada de "[Subir foto do card\xE1pio]" ou "clique no bot\xE3o abaixo") \u2014 o bot\xE3o s\xF3 existe se voc\xEA chamar a ferramenta. Se ela n\xE3o estiver dispon\xEDvel, diga isso com todas as letras e ofere\xE7a cadastrar item a item.
140
+
141
+ Nunca invente pre\xE7o, descri\xE7\xE3o ou item que n\xE3o estava na foto: um pre\xE7o errado vai para a mesa do cliente. Prefira perguntar.`;
142
+ function tool(id, name, description, properties, required, mode = "persist") {
143
+ return {
144
+ id,
145
+ name,
146
+ description,
147
+ mode,
148
+ parameters: { type: "object", properties, required },
149
+ execution: { plane: "client" },
150
+ // The screen is in front of the user and every one of these is reversible
151
+ // by hand; a confirmation card per rename would make the agent useless for
152
+ // the one job it is good at here — bulk tidying.
153
+ requiresConfirmation: false,
154
+ permission: { feature: "menu", action: "edit" }
155
+ };
156
+ }
157
+ function useMenuAgentSurface(deps) {
158
+ const contribution = useMemo(() => {
159
+ const findSection = (name) => {
160
+ const key = normalizeName(name ?? "");
161
+ return deps.sections.find((s) => normalizeName(s.name) === key) ?? deps.sections.find((s) => normalizeName(s.name).includes(key));
162
+ };
163
+ const findDraftSection = (draft, name) => {
164
+ const key = normalizeName(name ?? "");
165
+ return draft?.sections.find((s) => normalizeName(s.name) === key) ?? draft?.sections.find((s) => normalizeName(s.name).includes(key));
166
+ };
167
+ const findDraft = (draft, sectionName, itemName) => {
168
+ const section = findDraftSection(draft, sectionName);
169
+ if (!section) return void 0;
170
+ const key = normalizeName(itemName ?? "");
171
+ const item = section.items.find((i) => normalizeName(i.name) === key) ?? section.items.find((i) => normalizeName(i.name).includes(key));
172
+ return item ? { section, item } : void 0;
173
+ };
174
+ const findItem = (name) => {
175
+ const key = normalizeName(name ?? "");
176
+ const items = Array.from(deps.itemsById.values());
177
+ const item = items.find((i) => normalizeName(i.name) === key) ?? items.find((i) => normalizeName(i.name).includes(key));
178
+ if (!item) return void 0;
179
+ return { item, placement: deps.placements.find((p) => p.itemId === item.id) };
180
+ };
181
+ const tools = [
182
+ {
183
+ tool: tool(
184
+ "menu.surface.create-section",
185
+ "createMenuSection",
186
+ "Cria uma se\xE7\xE3o no card\xE1pio aberto (ex.: Sobremesas, Entradas).",
187
+ { name: { type: "string", description: "Nome da se\xE7\xE3o" } },
188
+ ["name"]
189
+ ),
190
+ execute: async ({ name }) => {
191
+ const created = await deps.actions.createSection(String(name));
192
+ return created ? { ok: true, section: created.name } : { error: "section_not_created" };
193
+ }
194
+ },
195
+ {
196
+ tool: tool(
197
+ "menu.surface.rename-section",
198
+ "renameMenuSection",
199
+ "Renomeia uma se\xE7\xE3o do card\xE1pio aberto.",
200
+ { sectionName: { type: "string" }, newName: { type: "string" } },
201
+ ["sectionName", "newName"]
202
+ ),
203
+ execute: async ({ sectionName, newName }) => {
204
+ const section = findSection(sectionName);
205
+ if (!section) return { error: "section_not_found", sectionName };
206
+ await deps.actions.renameSection(section.id, String(newName));
207
+ return { ok: true };
208
+ }
209
+ },
210
+ {
211
+ tool: tool(
212
+ "menu.surface.reorder-sections",
213
+ "reorderMenuSections",
214
+ "Reordena as se\xE7\xF5es do card\xE1pio aberto. Passe TODOS os nomes na ordem desejada.",
215
+ { orderedNames: { type: "array", items: { type: "string" }, description: "Nomes das se\xE7\xF5es na nova ordem" } },
216
+ ["orderedNames"]
217
+ ),
218
+ execute: async ({ orderedNames }) => {
219
+ const ids = orderedNames.map((name) => findSection(name)?.id).filter(Boolean);
220
+ if (ids.length !== deps.sections.length) return { error: "incomplete_order", expected: deps.sections.map((s) => s.name) };
221
+ await deps.actions.reorderSections(ids);
222
+ return { ok: true };
223
+ }
224
+ },
225
+ {
226
+ tool: tool(
227
+ "menu.surface.create-item",
228
+ "createMenuItem",
229
+ "Cria um item no card\xE1pio aberto, dentro de uma se\xE7\xE3o.",
230
+ {
231
+ name: { type: "string" },
232
+ price: { type: "number" },
233
+ sectionName: { type: "string", description: "Se\xE7\xE3o onde entra" },
234
+ description: { type: "string" }
235
+ },
236
+ ["name", "price", "sectionName"]
237
+ ),
238
+ execute: async ({ name, price, sectionName, description }) => {
239
+ const section = findSection(sectionName);
240
+ if (!section) return { error: "section_not_found", sectionName, available: deps.sections.map((s) => s.name) };
241
+ const created = await deps.actions.createItem(section.id, {
242
+ name: String(name),
243
+ price: Number(price) || 0,
244
+ description: description ? String(description) : void 0
245
+ });
246
+ return created ? { ok: true, item: created.name } : { error: "item_not_created" };
247
+ }
248
+ },
249
+ {
250
+ tool: tool(
251
+ "menu.surface.update-item",
252
+ "updateMenuItem",
253
+ "Altera nome, pre\xE7o, descri\xE7\xE3o ou disponibilidade de um item.",
254
+ {
255
+ itemName: { type: "string" },
256
+ price: { type: "number" },
257
+ name: { type: "string", description: "Novo nome" },
258
+ description: { type: "string" },
259
+ status: { type: "string", enum: ["available", "sold_out", "hidden"] }
260
+ },
261
+ ["itemName"]
262
+ ),
263
+ execute: async ({ itemName, price, name, description, status }) => {
264
+ const found = findItem(itemName);
265
+ if (!found) return { error: "item_not_found", itemName };
266
+ if (price !== void 0 || name !== void 0 || description !== void 0) {
267
+ await deps.actions.updateItem(found.item.id, {
268
+ price: price !== void 0 ? Number(price) : void 0,
269
+ name: name !== void 0 ? String(name) : void 0,
270
+ description: description !== void 0 ? String(description) : void 0
271
+ });
272
+ }
273
+ if (status) await deps.actions.setItemStatus(found.item.id, status);
274
+ return { ok: true };
275
+ }
276
+ },
277
+ {
278
+ tool: tool(
279
+ "menu.surface.move-item",
280
+ "moveMenuItem",
281
+ "Move um item para outra se\xE7\xE3o do card\xE1pio aberto.",
282
+ { itemName: { type: "string" }, toSectionName: { type: "string" } },
283
+ ["itemName", "toSectionName"]
284
+ ),
285
+ execute: async ({ itemName, toSectionName }) => {
286
+ const found = findItem(itemName);
287
+ if (!found?.placement) return { error: "item_not_in_menu", itemName };
288
+ const section = findSection(toSectionName);
289
+ if (!section) return { error: "section_not_found", toSectionName };
290
+ const target = deps.placements.filter((p) => p.sectionId === section.id).length;
291
+ await deps.actions.moveItem(found.placement.id, section.id, target);
292
+ return { ok: true };
293
+ }
294
+ },
295
+ {
296
+ tool: tool(
297
+ "menu.surface.create-menu",
298
+ "createMenu",
299
+ "Cria outro card\xE1pio (ex.: Executivo de segunda a sexta, Fim de semana). Pode copiar o card\xE1pio aberto.",
300
+ {
301
+ name: { type: "string" },
302
+ copyFromOpenMenu: { type: "boolean", description: "Copiar se\xE7\xF5es e itens do card\xE1pio aberto" },
303
+ days: { type: "array", items: { type: "number" }, description: "0=domingo \u2026 6=s\xE1bado" },
304
+ start: { type: "string", description: "HH:MM" },
305
+ end: { type: "string", description: "HH:MM" }
306
+ },
307
+ ["name"]
308
+ ),
309
+ execute: async ({ name, copyFromOpenMenu, days, start, end }) => {
310
+ if (copyFromOpenMenu && deps.menu) {
311
+ const copy = await deps.actions.duplicateMenu(deps.menu.id, String(name));
312
+ return { ok: true, menu: copy.name, note: "A c\xF3pia come\xE7a desativada." };
313
+ }
314
+ const created = await deps.actions.createMenu({ name: String(name), days, start, end });
315
+ return { ok: true, menu: created.name };
316
+ }
317
+ },
318
+ {
319
+ tool: tool(
320
+ "menu.surface.request-photos",
321
+ "requestMenuPhotos",
322
+ "Pede as fotos do card\xE1pio ao usu\xE1rio: aparece um bot\xE3o grande aqui na conversa para tirar a foto ou escolher o arquivo. O que for lido vira um rascunho no estado, que voc\xEA confere com ele antes de gravar.",
323
+ {},
324
+ [],
325
+ "read"
326
+ ),
327
+ execute: async () => {
328
+ if (!deps.hasExtractor) {
329
+ return { error: "extractor_unavailable", message: "Este espa\xE7o ainda n\xE3o tem a leitura por foto configurada. O cadastro pode ser feito item a item." };
330
+ }
331
+ deps.actions.requestPhotos();
332
+ return { ok: true, asked: true, next: "O bot\xE3o j\xE1 est\xE1 na conversa. Espere o usu\xE1rio enviar \u2014 o rascunho aparece no estado quando a leitura terminar." };
333
+ }
334
+ },
335
+ {
336
+ tool: tool(
337
+ "menu.surface.update-draft-item",
338
+ "updateMenuDraftItem",
339
+ "Corrige um item do RASCUNHO lido das fotos (pre\xE7o, nome, descri\xE7\xE3o). N\xE3o grava nada ainda.",
340
+ {
341
+ sectionName: { type: "string" },
342
+ itemName: { type: "string" },
343
+ price: { type: "number" },
344
+ name: { type: "string", description: "Novo nome" },
345
+ description: { type: "string" }
346
+ },
347
+ ["sectionName", "itemName"]
348
+ ),
349
+ execute: async ({ sectionName, itemName, price, name, description }) => {
350
+ const found = findDraft(deps.draft, sectionName, itemName);
351
+ if (!found) return { error: "draft_item_not_found", itemName };
352
+ deps.actions.updateDraftItem(found.section.key, found.item.key, {
353
+ price: price !== void 0 ? Number(price) : void 0,
354
+ name: name !== void 0 ? String(name) : void 0,
355
+ description: description !== void 0 ? String(description) : void 0
356
+ });
357
+ return { ok: true };
358
+ }
359
+ },
360
+ {
361
+ tool: tool(
362
+ "menu.surface.remove-draft-item",
363
+ "removeMenuDraftItem",
364
+ "Tira um item do rascunho (leitura errada, item repetido).",
365
+ { sectionName: { type: "string" }, itemName: { type: "string" } },
366
+ ["sectionName", "itemName"]
367
+ ),
368
+ execute: async ({ sectionName, itemName }) => {
369
+ const found = findDraft(deps.draft, sectionName, itemName);
370
+ if (!found) return { error: "draft_item_not_found", itemName };
371
+ deps.actions.removeDraftItem(found.section.key, found.item.key);
372
+ return { ok: true };
373
+ }
374
+ },
375
+ {
376
+ tool: tool(
377
+ "menu.surface.update-draft-section",
378
+ "updateMenuDraftSection",
379
+ "Renomeia uma se\xE7\xE3o do rascunho, ou manda os itens dela para uma se\xE7\xE3o que j\xE1 existe no card\xE1pio.",
380
+ {
381
+ sectionName: { type: "string" },
382
+ newName: { type: "string" },
383
+ intoExistingSection: { type: "string", description: "Nome de uma se\xE7\xE3o existente para receber os itens" }
384
+ },
385
+ ["sectionName"]
386
+ ),
387
+ execute: async ({ sectionName, newName, intoExistingSection }) => {
388
+ const section = findDraftSection(deps.draft, sectionName);
389
+ if (!section) return { error: "draft_section_not_found", sectionName };
390
+ const target = intoExistingSection ? findSection(intoExistingSection) : void 0;
391
+ if (intoExistingSection && !target) return { error: "section_not_found", intoExistingSection };
392
+ deps.actions.updateDraftSection(section.key, {
393
+ name: newName ? String(newName) : void 0,
394
+ targetSectionId: target?.id
395
+ });
396
+ return { ok: true };
397
+ }
398
+ },
399
+ {
400
+ tool: tool(
401
+ "menu.surface.commit-draft",
402
+ "commitMenuDraft",
403
+ "Grava o rascunho no card\xE1pio. S\xF3 chame depois que o usu\xE1rio confirmar.",
404
+ {},
405
+ []
406
+ ),
407
+ execute: async () => {
408
+ if (!deps.draft) return { error: "no_draft" };
409
+ const result = await deps.actions.commitDraft();
410
+ return { ok: true, ...result };
411
+ }
412
+ },
413
+ {
414
+ tool: tool(
415
+ "menu.surface.discard-draft",
416
+ "discardMenuDraft",
417
+ "Descarta o rascunho lido das fotos sem gravar nada.",
418
+ {},
419
+ []
420
+ ),
421
+ execute: async () => {
422
+ deps.actions.discardDraft();
423
+ return { ok: true };
424
+ }
425
+ }
426
+ ];
427
+ return {
428
+ id: "menu.manager",
429
+ pluginId: "menu",
430
+ // No role on purpose: whoever is answering is answering ABOUT this
431
+ // screen, and a persona filter here is what silently removed these tools
432
+ // from the catalogue while the instructions still arrived.
433
+ instructions: INSTRUCTIONS,
434
+ tools,
435
+ describe: () => ({
436
+ title: deps.menu?.name ?? "Card\xE1pio",
437
+ state: {
438
+ activeMenu: deps.menu?.name,
439
+ allMenus: deps.menus.map((m) => m.name),
440
+ photoImportAvailable: deps.hasExtractor,
441
+ // The draft is the conversation's subject while it exists, so it goes
442
+ // FIRST in the snapshot the model reads.
443
+ draft: deps.draft ? {
444
+ source: deps.draft.source,
445
+ model: deps.draft.model,
446
+ sections: deps.draft.sections.map((section) => ({
447
+ name: section.name,
448
+ appendingTo: section.targetSectionId ? deps.sections.find((s) => s.id === section.targetSectionId)?.name : void 0,
449
+ items: section.items.map((item) => ({ name: item.name, price: item.price, description: item.description }))
450
+ })),
451
+ itemsMissingPrice: deps.draft.sections.flatMap((section) => section.items.filter((item) => item.price === 0).map((item) => item.name))
452
+ } : void 0,
453
+ sections: deps.sections.map((section) => ({
454
+ name: section.name,
455
+ items: deps.placements.filter((p) => p.sectionId === section.id).map((p) => {
456
+ const item = deps.itemsById.get(p.itemId);
457
+ return item ? { name: item.name, price: p.priceOverride ?? item.price, status: item.status } : null;
458
+ }).filter(Boolean)
459
+ }))
460
+ }
461
+ })
462
+ };
463
+ }, [deps.menu, deps.menus, deps.sections, deps.placements, deps.itemsById, deps.draft, deps.hasExtractor, deps.actions]);
464
+ useAgentSurface(contribution);
465
+ }
466
+ var STATUS_STYLE = {
467
+ available: "bg-success-soft text-success-soft-foreground",
468
+ sold_out: "bg-destructive-soft text-destructive-soft-foreground",
469
+ hidden: "bg-muted text-muted-foreground"
470
+ };
471
+ function InlinePrice({ value, currency, muted, onSave }) {
472
+ const [editing, setEditing] = useState(false);
473
+ const [draft, setDraft] = useState("");
474
+ if (!editing) {
475
+ return /* @__PURE__ */ jsx(
476
+ "button",
477
+ {
478
+ type: "button",
479
+ onClick: (e) => {
480
+ e.stopPropagation();
481
+ setDraft(String(value ?? 0));
482
+ setEditing(true);
483
+ },
484
+ className: `rounded px-1.5 py-0.5 text-sm tabular-nums transition-colors hover:bg-muted ${muted ? "text-muted-foreground" : "font-medium"}`,
485
+ children: formatCurrency(value ?? 0, currency)
486
+ }
487
+ );
488
+ }
489
+ const commit = () => {
490
+ const parsed = Number(draft.replace(",", "."));
491
+ setEditing(false);
492
+ if (Number.isFinite(parsed) && parsed !== value) onSave(Math.max(0, parsed));
493
+ };
494
+ return /* @__PURE__ */ jsx(
495
+ "input",
496
+ {
497
+ autoFocus: true,
498
+ value: draft,
499
+ onChange: (e) => setDraft(e.target.value),
500
+ onBlur: commit,
501
+ onClick: (e) => e.stopPropagation(),
502
+ onKeyDown: (e) => {
503
+ if (e.key === "Enter") commit();
504
+ if (e.key === "Escape") setEditing(false);
505
+ },
506
+ className: "w-20 rounded border bg-card px-1.5 py-0.5 text-right text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-ring"
507
+ }
508
+ );
509
+ }
510
+ function ItemRow({ placement, item, currency, deliveryPricing, multiMenu, pending, handlers }) {
511
+ const t = useTranslation();
512
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
513
+ id: placement.id,
514
+ data: { type: "item", sectionId: placement.sectionId }
515
+ });
516
+ if (!item) return null;
517
+ const price = placement.priceOverride ?? item.price;
518
+ const overridden = placement.priceOverride !== void 0 && placement.priceOverride !== null;
519
+ return /* @__PURE__ */ jsxs(
520
+ "div",
521
+ {
522
+ ref: setNodeRef,
523
+ style: { transform: CSS.Transform.toString(transform), transition },
524
+ className: `group flex items-center gap-3 border-b px-3 py-2 last:border-0 ${isDragging ? "opacity-40" : "hover:bg-muted/20"} ${pending ? "pointer-events-none opacity-60" : ""}`,
525
+ children: [
526
+ /* @__PURE__ */ jsx(
527
+ "button",
528
+ {
529
+ ...attributes,
530
+ ...listeners,
531
+ "aria-label": t("menu.manager.dragItem"),
532
+ className: "cursor-grab text-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100 active:cursor-grabbing",
533
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" })
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted/40", children: item.imageUrl ? /* @__PURE__ */ jsx("img", { src: item.imageUrl, alt: "", className: "h-full w-full object-cover" }) : /* @__PURE__ */ jsx(ImageOff, { className: "h-3.5 w-3.5 text-muted-foreground/40" }) }),
537
+ /* @__PURE__ */ jsxs(
538
+ "button",
539
+ {
540
+ type: "button",
541
+ onClick: () => handlers.onEditItem(item, placement),
542
+ className: "min-w-0 flex-1 text-left",
543
+ children: [
544
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
545
+ /* @__PURE__ */ jsx("span", { className: `truncate text-sm font-medium ${item.status === "hidden" ? "text-muted-foreground" : ""}`, children: item.name }),
546
+ item.isFeatured && /* @__PURE__ */ jsx(Star, { className: "h-3 w-3 shrink-0 fill-warning text-warning" }),
547
+ item.status !== "available" && /* @__PURE__ */ jsx("span", { className: `rounded-full px-1.5 py-0.5 text-[9px] font-medium ${STATUS_STYLE[item.status]}`, children: item.status === "sold_out" ? t("menu.manager.soldOut") : t("menu.manager.hidden") })
548
+ ] }),
549
+ item.description && /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] text-muted-foreground", children: item.description })
550
+ ]
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
554
+ /* @__PURE__ */ jsx(
555
+ InlinePrice,
556
+ {
557
+ value: price,
558
+ currency,
559
+ onSave: (next) => {
560
+ if (multiMenu) handlers.onSetPlacementPrice(placement.id, next === item.price ? void 0 : next);
561
+ else handlers.onEditItem({ ...item, price: next }, placement);
562
+ }
563
+ }
564
+ ),
565
+ overridden && /* @__PURE__ */ jsx("span", { title: t("menu.manager.overriddenHint"), className: "text-[9px] font-medium uppercase text-primary", children: t("menu.manager.overridden") })
566
+ ] }),
567
+ deliveryPricing && /* @__PURE__ */ jsx("span", { className: "hidden w-20 text-right text-xs text-muted-foreground sm:block", children: item.deliveryPrice != null ? formatCurrency(item.deliveryPrice, currency) : "\u2014" }),
568
+ /* @__PURE__ */ jsxs("div", { className: "hidden items-center gap-3 md:flex", children: [
569
+ /* @__PURE__ */ jsx(Switch, { size: "sm", checked: item.availableForPos, onCheckedChange: (v) => handlers.onToggleItem(item.id, "availableForPos", v), label: t("menu.manager.pos") }),
570
+ /* @__PURE__ */ jsx(Switch, { size: "sm", checked: item.availableForDelivery, onCheckedChange: (v) => handlers.onToggleItem(item.id, "availableForDelivery", v), label: t("menu.manager.delivery") })
571
+ ] }),
572
+ /* @__PURE__ */ jsxs(Dropdown, { children: [
573
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": t("menu.manager.itemActions"), children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }),
574
+ /* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
575
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => handlers.onEditItem(item, placement), children: [
576
+ /* @__PURE__ */ jsx(Pencil, { className: "mr-2 h-3.5 w-3.5" }),
577
+ " ",
578
+ t("menu.manager.edit")
579
+ ] }),
580
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => handlers.onDuplicateItem(item, placement.sectionId), children: [
581
+ /* @__PURE__ */ jsx(Copy, { className: "mr-2 h-3.5 w-3.5" }),
582
+ " ",
583
+ t("menu.manager.duplicate")
584
+ ] }),
585
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => handlers.onSetItemStatus(item.id, item.status === "sold_out" ? "available" : "sold_out"), children: [
586
+ /* @__PURE__ */ jsx(XCircle, { className: "mr-2 h-3.5 w-3.5" }),
587
+ item.status === "sold_out" ? t("menu.manager.markAvailable") : t("menu.manager.markSoldOut")
588
+ ] }),
589
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => handlers.onToggleItem(item.id, "isFeatured", !item.isFeatured), children: [
590
+ /* @__PURE__ */ jsx(Star, { className: "mr-2 h-3.5 w-3.5" }),
591
+ item.isFeatured ? t("menu.manager.unfeature") : t("menu.manager.feature")
592
+ ] }),
593
+ /* @__PURE__ */ jsx(DropdownSeparator, {}),
594
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => handlers.onRemoveFromMenu(placement), children: [
595
+ /* @__PURE__ */ jsx(XCircle, { className: "mr-2 h-3.5 w-3.5" }),
596
+ " ",
597
+ t("menu.manager.removeFromMenu")
598
+ ] }),
599
+ /* @__PURE__ */ jsxs(DropdownItem, { className: "text-destructive focus:text-destructive", onClick: () => handlers.onDeleteItem(item), children: [
600
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-3.5 w-3.5" }),
601
+ " ",
602
+ t("menu.manager.deleteItem")
603
+ ] })
604
+ ] })
605
+ ] })
606
+ ]
607
+ }
608
+ );
609
+ }
610
+ function SectionCard({
611
+ section,
612
+ placements,
613
+ itemsById,
614
+ currency,
615
+ locale,
616
+ deliveryPricing,
617
+ multiMenu,
618
+ pendingIds,
619
+ handlers
620
+ }) {
621
+ const t = useTranslation();
622
+ const [collapsed, setCollapsed] = useState(false);
623
+ const [renaming, setRenaming] = useState(false);
624
+ const [draft, setDraft] = useState(section.name);
625
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
626
+ id: section.id,
627
+ data: { type: "section" }
628
+ });
629
+ const scheduled = !isAlwaysAvailable(section.availability);
630
+ const servingNow = isServingAt(section.availability);
631
+ const pending = new Set(pendingIds ?? []);
632
+ const sectionPending = pending.has(section.id);
633
+ return /* @__PURE__ */ jsxs(
634
+ "div",
635
+ {
636
+ ref: setNodeRef,
637
+ style: { transform: CSS.Transform.toString(transform), transition },
638
+ className: `overflow-hidden rounded-card border bg-card shadow-sm ${isDragging ? "opacity-50" : ""} ${sectionPending ? "opacity-70" : ""}`,
639
+ children: [
640
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b bg-muted/20 px-3 py-2", children: [
641
+ /* @__PURE__ */ jsx(
642
+ "button",
643
+ {
644
+ ...attributes,
645
+ ...listeners,
646
+ "aria-label": t("menu.manager.dragSection"),
647
+ className: "cursor-grab text-muted-foreground/50 hover:text-foreground active:cursor-grabbing",
648
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" })
649
+ }
650
+ ),
651
+ /* @__PURE__ */ jsx(
652
+ "button",
653
+ {
654
+ type: "button",
655
+ onClick: () => setCollapsed((c) => !c),
656
+ className: "text-muted-foreground hover:text-foreground",
657
+ "aria-label": collapsed ? t("menu.manager.expand") : t("menu.manager.collapse"),
658
+ children: collapsed ? /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
659
+ }
660
+ ),
661
+ renaming ? /* @__PURE__ */ jsx(
662
+ "input",
663
+ {
664
+ autoFocus: true,
665
+ value: draft,
666
+ onChange: (e) => setDraft(e.target.value),
667
+ onBlur: () => {
668
+ setRenaming(false);
669
+ if (draft.trim() && draft !== section.name) handlers.onRenameSection(section.id, draft.trim());
670
+ },
671
+ onKeyDown: (e) => {
672
+ if (e.key === "Enter") e.target.blur();
673
+ if (e.key === "Escape") {
674
+ setDraft(section.name);
675
+ setRenaming(false);
676
+ }
677
+ },
678
+ className: "rounded border bg-card px-2 py-0.5 text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-ring"
679
+ }
680
+ ) : /* @__PURE__ */ jsx(
681
+ "button",
682
+ {
683
+ type: "button",
684
+ onClick: () => {
685
+ setDraft(section.name);
686
+ setRenaming(true);
687
+ },
688
+ className: "rounded px-1 py-0.5 text-sm font-semibold transition-colors hover:bg-muted",
689
+ children: section.name
690
+ }
691
+ ),
692
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: t("menu.manager.itemsCount", { count: String(placements.length) }) }),
693
+ sectionPending && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin text-muted-foreground" }),
694
+ scheduled && /* @__PURE__ */ jsx(
695
+ "span",
696
+ {
697
+ className: `hidden rounded-full px-2 py-0.5 text-[10px] font-medium sm:inline ${servingNow ? "bg-success-soft text-success-soft-foreground" : "bg-muted text-muted-foreground"}`,
698
+ title: describeAvailability(section.availability, locale, t("menu.schedule.always")),
699
+ children: describeAvailability(section.availability, locale, t("menu.schedule.always"))
700
+ }
701
+ ),
702
+ !section.isActive && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: t("menu.manager.sectionOff") }),
703
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
704
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => handlers.onAddItem(section.id), children: [
705
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
706
+ " ",
707
+ t("menu.manager.addItem")
708
+ ] }),
709
+ /* @__PURE__ */ jsxs(Dropdown, { children: [
710
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", "aria-label": t("menu.manager.sectionActions"), children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4" }) }) }),
711
+ /* @__PURE__ */ jsxs(DropdownContent, { align: "end", children: [
712
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => handlers.onEditSection(section), children: [
713
+ /* @__PURE__ */ jsx(Pencil, { className: "mr-2 h-3.5 w-3.5" }),
714
+ " ",
715
+ t("menu.manager.editSection")
716
+ ] }),
717
+ /* @__PURE__ */ jsx(DropdownSeparator, {}),
718
+ /* @__PURE__ */ jsxs(DropdownItem, { className: "text-destructive focus:text-destructive", onClick: () => handlers.onDeleteSection(section), children: [
719
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-3.5 w-3.5" }),
720
+ " ",
721
+ t("menu.manager.deleteSection")
722
+ ] })
723
+ ] })
724
+ ] })
725
+ ] })
726
+ ] }),
727
+ !collapsed && (placements.length === 0 ? /* @__PURE__ */ jsxs(
728
+ "button",
729
+ {
730
+ type: "button",
731
+ onClick: () => handlers.onAddItem(section.id),
732
+ className: "flex w-full flex-col items-center gap-1 border-2 border-dashed border-transparent py-6 text-center transition-colors hover:bg-muted/20",
733
+ children: [
734
+ /* @__PURE__ */ jsx(UtensilsCrossed, { className: "h-5 w-5 text-muted-foreground/40" }),
735
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("menu.manager.emptySection") })
736
+ ]
737
+ }
738
+ ) : /* @__PURE__ */ jsx("div", { children: placements.map((placement) => /* @__PURE__ */ jsx(
739
+ ItemRow,
740
+ {
741
+ placement,
742
+ item: itemsById.get(placement.itemId),
743
+ currency,
744
+ deliveryPricing,
745
+ multiMenu,
746
+ pending: pending.has(placement.id),
747
+ handlers
748
+ },
749
+ placement.id
750
+ )) }))
751
+ ]
752
+ }
753
+ );
754
+ }
755
+
756
+ // src/types.ts
757
+ var MENU_CHANNELS = ["pos", "delivery", "digital"];
758
+ var WEEKDAY_PRESETS = [
759
+ { id: "menu.schedule.presetWeekdays", days: [1, 2, 3, 4, 5] },
760
+ { id: "menu.schedule.presetWeekend", days: [0, 6] },
761
+ { id: "menu.schedule.presetEveryDay", days: [0, 1, 2, 3, 4, 5, 6] }
762
+ ];
763
+ function ScheduleEditor({ value, onChange, locale }) {
764
+ const t = useTranslation();
765
+ const labels = weekdayLabels(locale);
766
+ const restricted = !isAlwaysAvailable(value);
767
+ const days = value.days ?? [];
768
+ const toggleDay = (day) => {
769
+ const next = days.includes(day) ? days.filter((d) => d !== day) : [...days, day].sort((a, b) => a - b);
770
+ onChange({ ...value, days: next });
771
+ };
772
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
773
+ /* @__PURE__ */ jsx(
774
+ Switch,
775
+ {
776
+ size: "md",
777
+ checked: restricted,
778
+ onCheckedChange: (checked) => onChange(checked ? { days: [1, 2, 3, 4, 5], start: "11:30", end: "15:00" } : {}),
779
+ label: /* @__PURE__ */ jsxs("span", { children: [
780
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: t("menu.schedule.restrict") }),
781
+ /* @__PURE__ */ jsx("span", { className: "block text-[11px] text-muted-foreground", children: t("menu.schedule.restrictHint") })
782
+ ] })
783
+ }
784
+ ),
785
+ restricted && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-xl border p-3", children: [
786
+ /* @__PURE__ */ jsxs("div", { children: [
787
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: t("menu.schedule.days") }),
788
+ /* @__PURE__ */ jsx("div", { className: "mt-1.5 flex flex-wrap gap-1.5", children: labels.map((label, day) => /* @__PURE__ */ jsx(
789
+ "button",
790
+ {
791
+ type: "button",
792
+ onClick: () => toggleDay(day),
793
+ className: `rounded-full px-2.5 py-1 text-xs font-medium capitalize transition-colors ${days.includes(day) ? "bg-primary text-primary-foreground" : "bg-muted/50 text-muted-foreground hover:bg-muted"}`,
794
+ children: label
795
+ },
796
+ day
797
+ )) }),
798
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: WEEKDAY_PRESETS.map((preset) => /* @__PURE__ */ jsx(
799
+ "button",
800
+ {
801
+ type: "button",
802
+ onClick: () => onChange({ ...value, days: preset.days }),
803
+ className: "text-[11px] text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
804
+ children: t(preset.id)
805
+ },
806
+ preset.id
807
+ )) })
808
+ ] }),
809
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3", children: [
810
+ /* @__PURE__ */ jsxs("div", { children: [
811
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: t("menu.schedule.from") }),
812
+ /* @__PURE__ */ jsx(TimePicker, { value: value.start ?? "11:00", onChange: (next) => onChange({ ...value, start: next }) })
813
+ ] }),
814
+ /* @__PURE__ */ jsxs("div", { children: [
815
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: t("menu.schedule.to") }),
816
+ /* @__PURE__ */ jsx(TimePicker, { value: value.end ?? "15:00", onChange: (next) => onChange({ ...value, end: next }) })
817
+ ] })
818
+ ] }),
819
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("menu.schedule.overnightHint") })
820
+ ] })
821
+ ] });
822
+ }
823
+ var CHANNEL_LABEL = {
824
+ pos: "menu.channel.pos",
825
+ delivery: "menu.channel.delivery",
826
+ digital: "menu.channel.digital"
827
+ };
828
+ function MenuRailPage({ open, menu, locale, onClose, onSubmit }) {
829
+ const t = useTranslation();
830
+ const [name, setName] = useState("");
831
+ const [description, setDescription] = useState("");
832
+ const [channels, setChannels] = useState([...MENU_CHANNELS]);
833
+ const [availability, setAvailability] = useState({});
834
+ const [isActive, setIsActive] = useState(true);
835
+ const [saving, setSaving] = useState(false);
836
+ useEffect(() => {
837
+ if (!open) return;
838
+ setName(menu?.name ?? "");
839
+ setDescription(menu?.description ?? "");
840
+ setChannels(menu?.channels ?? [...MENU_CHANNELS]);
841
+ setAvailability(menu?.availability ?? {});
842
+ setIsActive(menu?.isActive ?? true);
843
+ setSaving(false);
844
+ }, [open, menu]);
845
+ const toggleChannel = (channel) => {
846
+ setChannels((current) => current.includes(channel) ? current.filter((c) => c !== channel) : [...current, channel]);
847
+ };
848
+ const submit = async () => {
849
+ if (!name.trim() || saving) return;
850
+ setSaving(true);
851
+ try {
852
+ await onSubmit({ name: name.trim(), description: description.trim() || void 0, channels, availability, isActive });
853
+ onClose();
854
+ } finally {
855
+ setSaving(false);
856
+ }
857
+ };
858
+ return /* @__PURE__ */ jsxs(
859
+ RightRailPage,
860
+ {
861
+ title: menu ? t("menu.menuDialog.editTitle") : t("menu.menuDialog.newTitle"),
862
+ open,
863
+ onClose,
864
+ width: "max-w-md",
865
+ children: [
866
+ /* @__PURE__ */ jsxs(SheetBody, { className: "space-y-4", children: [
867
+ /* @__PURE__ */ jsxs("div", { children: [
868
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("menu.menuDialog.name") }),
869
+ /* @__PURE__ */ jsx(
870
+ Input,
871
+ {
872
+ autoFocus: true,
873
+ value: name,
874
+ onChange: (e) => setName(e.target.value),
875
+ placeholder: t("menu.menuDialog.namePlaceholder"),
876
+ onKeyDown: (e) => {
877
+ if (e.key === "Enter") void submit();
878
+ }
879
+ }
880
+ )
881
+ ] }),
882
+ /* @__PURE__ */ jsxs("div", { children: [
883
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("menu.menuDialog.description") }),
884
+ /* @__PURE__ */ jsx(
885
+ Input,
886
+ {
887
+ value: description,
888
+ onChange: (e) => setDescription(e.target.value),
889
+ placeholder: t("menu.menuDialog.descriptionPlaceholder")
890
+ }
891
+ )
892
+ ] }),
893
+ /* @__PURE__ */ jsxs("div", { children: [
894
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: t("menu.menuDialog.channels") }),
895
+ /* @__PURE__ */ jsx("div", { className: "mt-1.5 flex flex-wrap gap-1.5", children: MENU_CHANNELS.map((channel) => /* @__PURE__ */ jsx(
896
+ "button",
897
+ {
898
+ type: "button",
899
+ onClick: () => toggleChannel(channel),
900
+ className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${channels.includes(channel) ? "bg-primary text-primary-foreground" : "bg-muted/50 text-muted-foreground hover:bg-muted"}`,
901
+ children: t(CHANNEL_LABEL[channel])
902
+ },
903
+ channel
904
+ )) })
905
+ ] }),
906
+ /* @__PURE__ */ jsx(ScheduleEditor, { value: availability, onChange: setAvailability, locale }),
907
+ /* @__PURE__ */ jsx(
908
+ Switch,
909
+ {
910
+ size: "md",
911
+ checked: isActive,
912
+ onCheckedChange: setIsActive,
913
+ label: /* @__PURE__ */ jsxs("span", { children: [
914
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: t("menu.menuDialog.active") }),
915
+ /* @__PURE__ */ jsx("span", { className: "block text-[11px] text-muted-foreground", children: t("menu.menuDialog.activeHint") })
916
+ ] })
917
+ }
918
+ )
919
+ ] }),
920
+ /* @__PURE__ */ jsxs(SheetFooter, { className: "justify-end", children: [
921
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: saving, children: t("menu.form.cancel") }),
922
+ /* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: !name.trim() || saving, children: saving ? t("menu.form.saving") : t("menu.form.save") })
923
+ ] })
924
+ ]
925
+ }
926
+ );
927
+ }
928
+ function ItemRailPage({
929
+ open,
930
+ item,
931
+ sections,
932
+ sectionId,
933
+ currency,
934
+ deliveryPricing,
935
+ multiMenu,
936
+ priceOverride,
937
+ onClose,
938
+ onSubmit,
939
+ onUploadImage
940
+ }) {
941
+ const t = useTranslation();
942
+ const [values, setValues] = useState({
943
+ name: "",
944
+ price: 0,
945
+ status: "available",
946
+ availableForPos: true,
947
+ availableForDelivery: true,
948
+ isFeatured: false,
949
+ sectionId
950
+ });
951
+ const [saving, setSaving] = useState(false);
952
+ useEffect(() => {
953
+ if (!open) return;
954
+ setValues({
955
+ name: item?.name ?? "",
956
+ description: item?.description ?? "",
957
+ price: item?.price ?? 0,
958
+ cost: item?.cost,
959
+ deliveryPrice: item?.deliveryPrice,
960
+ prepTimeMinutes: item?.prepTimeMinutes,
961
+ imageUrl: item?.imageUrl,
962
+ status: item?.status ?? "available",
963
+ availableForPos: item?.availableForPos ?? true,
964
+ availableForDelivery: item?.availableForDelivery ?? true,
965
+ isFeatured: item?.isFeatured ?? false,
966
+ sectionId,
967
+ priceOverride
968
+ });
969
+ setSaving(false);
970
+ }, [open, item, sectionId, priceOverride]);
971
+ const patch = (next) => setValues((current) => ({ ...current, ...next }));
972
+ const submit = async () => {
973
+ if (!values.name.trim() || saving) return;
974
+ setSaving(true);
975
+ try {
976
+ await onSubmit({ ...values, name: values.name.trim() });
977
+ onClose();
978
+ } finally {
979
+ setSaving(false);
980
+ }
981
+ };
982
+ return /* @__PURE__ */ jsxs(
983
+ RightRailPage,
984
+ {
985
+ title: item ? t("menu.itemForm.editTitle") : t("menu.itemForm.newTitle"),
986
+ open,
987
+ onClose,
988
+ width: "max-w-md",
989
+ children: [
990
+ /* @__PURE__ */ jsxs(SheetBody, { className: "space-y-6", children: [
991
+ /* @__PURE__ */ jsxs(Group, { children: [
992
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
993
+ /* @__PURE__ */ jsx("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ jsx(
994
+ ImageDrop,
995
+ {
996
+ shape: "square",
997
+ value: values.imageUrl,
998
+ onUpload: onUploadImage ? async (file) => await onUploadImage(file) ?? "" : void 0,
999
+ onChange: (url) => patch({ imageUrl: url })
1000
+ }
1001
+ ) }),
1002
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1003
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-foreground", children: t("menu.itemForm.photo") }),
1004
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[11px] leading-snug text-muted-foreground", children: t("menu.itemForm.photoHint") })
1005
+ ] })
1006
+ ] }),
1007
+ /* @__PURE__ */ jsx(Field, { label: t("menu.itemForm.name"), children: /* @__PURE__ */ jsx(
1008
+ Input,
1009
+ {
1010
+ autoFocus: true,
1011
+ value: values.name,
1012
+ onChange: (e) => patch({ name: e.target.value }),
1013
+ placeholder: t("menu.itemForm.namePlaceholder")
1014
+ }
1015
+ ) }),
1016
+ /* @__PURE__ */ jsx(Field, { label: t("menu.itemForm.description"), children: /* @__PURE__ */ jsx(
1017
+ Input,
1018
+ {
1019
+ value: values.description ?? "",
1020
+ onChange: (e) => patch({ description: e.target.value }),
1021
+ placeholder: t("menu.itemForm.descriptionPlaceholder")
1022
+ }
1023
+ ) })
1024
+ ] }),
1025
+ /* @__PURE__ */ jsxs(Group, { title: t("menu.itemForm.pricingGroup"), children: [
1026
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
1027
+ /* @__PURE__ */ jsx(
1028
+ CurrencyInput,
1029
+ {
1030
+ label: t("menu.itemForm.price"),
1031
+ value: values.price,
1032
+ onChange: (price) => patch({ price }),
1033
+ symbol: currency.symbol,
1034
+ locale: currency.locale,
1035
+ currencyCode: currency.code
1036
+ }
1037
+ ),
1038
+ /* @__PURE__ */ jsx(
1039
+ CurrencyInput,
1040
+ {
1041
+ label: t("menu.itemForm.cost"),
1042
+ value: values.cost ?? 0,
1043
+ onChange: (cost) => patch({ cost }),
1044
+ symbol: currency.symbol,
1045
+ locale: currency.locale,
1046
+ currencyCode: currency.code
1047
+ }
1048
+ )
1049
+ ] }),
1050
+ deliveryPricing && /* @__PURE__ */ jsx(
1051
+ CurrencyInput,
1052
+ {
1053
+ label: t("menu.itemForm.deliveryPrice"),
1054
+ value: values.deliveryPrice ?? 0,
1055
+ onChange: (deliveryPrice) => patch({ deliveryPrice }),
1056
+ symbol: currency.symbol,
1057
+ locale: currency.locale,
1058
+ currencyCode: currency.code
1059
+ }
1060
+ ),
1061
+ multiMenu && /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-muted/20 p-3", children: [
1062
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-foreground", children: t("menu.itemForm.priceOverride") }),
1063
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[11px] leading-snug text-muted-foreground", children: t("menu.itemForm.priceOverrideHint") }),
1064
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-1.5", children: [
1065
+ /* @__PURE__ */ jsx(
1066
+ CurrencyInput,
1067
+ {
1068
+ value: values.priceOverride ?? 0,
1069
+ onChange: (next) => patch({ priceOverride: next }),
1070
+ symbol: currency.symbol,
1071
+ locale: currency.locale,
1072
+ currencyCode: currency.code
1073
+ }
1074
+ ),
1075
+ values.priceOverride !== void 0 && /* @__PURE__ */ jsx(
1076
+ "button",
1077
+ {
1078
+ type: "button",
1079
+ onClick: () => patch({ priceOverride: void 0 }),
1080
+ className: "text-[11px] text-muted-foreground underline-offset-2 transition-colors hover:text-foreground hover:underline",
1081
+ children: t("menu.itemForm.clearOverride")
1082
+ }
1083
+ )
1084
+ ] })
1085
+ ] })
1086
+ ] }),
1087
+ /* @__PURE__ */ jsxs(Group, { title: t("menu.itemForm.menuGroup"), children: [
1088
+ /* @__PURE__ */ jsx(Field, { label: t("menu.itemForm.section"), children: /* @__PURE__ */ jsx(
1089
+ "select",
1090
+ {
1091
+ value: values.sectionId,
1092
+ onChange: (e) => patch({ sectionId: e.target.value }),
1093
+ className: "h-9 w-full rounded-md border border-input bg-background px-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
1094
+ children: sections.map((section) => /* @__PURE__ */ jsx("option", { value: section.id, children: section.name }, section.id))
1095
+ }
1096
+ ) }),
1097
+ /* @__PURE__ */ jsx(Field, { label: t("menu.itemForm.status"), children: /* @__PURE__ */ jsx(
1098
+ SegmentedControl,
1099
+ {
1100
+ className: "w-full",
1101
+ value: values.status,
1102
+ onChange: (status) => patch({ status }),
1103
+ options: [
1104
+ { value: "available", label: t("menu.manager.available") },
1105
+ { value: "sold_out", label: t("menu.manager.soldOut") },
1106
+ { value: "hidden", label: t("menu.manager.hidden") }
1107
+ ]
1108
+ }
1109
+ ) }),
1110
+ /* @__PURE__ */ jsx(Field, { label: t("menu.itemForm.prepTime"), children: /* @__PURE__ */ jsx(
1111
+ Input,
1112
+ {
1113
+ type: "number",
1114
+ min: 0,
1115
+ inputMode: "numeric",
1116
+ value: values.prepTimeMinutes ?? "",
1117
+ onChange: (e) => patch({ prepTimeMinutes: e.target.value ? Number(e.target.value) : void 0 }),
1118
+ placeholder: "0"
1119
+ }
1120
+ ) }),
1121
+ /* @__PURE__ */ jsxs("div", { className: "divide-y rounded-xl border", children: [
1122
+ /* @__PURE__ */ jsx(
1123
+ ToggleRow,
1124
+ {
1125
+ label: t("menu.manager.pos"),
1126
+ hint: t("menu.itemForm.posHint"),
1127
+ checked: values.availableForPos,
1128
+ onChange: (v) => patch({ availableForPos: v })
1129
+ }
1130
+ ),
1131
+ /* @__PURE__ */ jsx(
1132
+ ToggleRow,
1133
+ {
1134
+ label: t("menu.manager.delivery"),
1135
+ hint: t("menu.itemForm.deliveryHint"),
1136
+ checked: values.availableForDelivery,
1137
+ onChange: (v) => patch({ availableForDelivery: v })
1138
+ }
1139
+ ),
1140
+ /* @__PURE__ */ jsx(
1141
+ ToggleRow,
1142
+ {
1143
+ label: t("menu.manager.featured"),
1144
+ hint: t("menu.itemForm.featuredHint"),
1145
+ checked: values.isFeatured,
1146
+ onChange: (v) => patch({ isFeatured: v })
1147
+ }
1148
+ )
1149
+ ] })
1150
+ ] })
1151
+ ] }),
1152
+ /* @__PURE__ */ jsxs(SheetFooter, { className: "justify-end", children: [
1153
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: saving, children: t("menu.form.cancel") }),
1154
+ /* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: !values.name.trim() || saving, children: saving ? t("menu.form.saving") : t("menu.form.save") })
1155
+ ] })
1156
+ ]
1157
+ }
1158
+ );
1159
+ }
1160
+ function Field({ label, children }) {
1161
+ return /* @__PURE__ */ jsxs("div", { children: [
1162
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: label }),
1163
+ children
1164
+ ] });
1165
+ }
1166
+ function Group({ title, children }) {
1167
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
1168
+ title && /* @__PURE__ */ jsx("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: title }),
1169
+ children
1170
+ ] });
1171
+ }
1172
+ function ToggleRow({ label, hint, checked, onChange }) {
1173
+ return /* @__PURE__ */ jsxs(
1174
+ "div",
1175
+ {
1176
+ role: "button",
1177
+ tabIndex: 0,
1178
+ onClick: () => onChange(!checked),
1179
+ onKeyDown: (e) => {
1180
+ if (e.key === "Enter" || e.key === " ") {
1181
+ e.preventDefault();
1182
+ onChange(!checked);
1183
+ }
1184
+ },
1185
+ className: "flex min-h-11 cursor-pointer items-center gap-3 px-3 py-2 transition-colors hover:bg-muted/30",
1186
+ children: [
1187
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1188
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: label }),
1189
+ hint && /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: hint })
1190
+ ] }),
1191
+ /* @__PURE__ */ jsx(Switch, { size: "md", checked, onCheckedChange: onChange, "aria-label": label })
1192
+ ]
1193
+ }
1194
+ );
1195
+ }
1196
+ function DraftRow({ item, onChange, onRemove }) {
1197
+ const t = useTranslation();
1198
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-dashed px-3 py-1.5 last:border-0", children: [
1199
+ /* @__PURE__ */ jsx(
1200
+ "input",
1201
+ {
1202
+ value: item.name,
1203
+ onChange: (e) => onChange({ name: e.target.value }),
1204
+ className: "min-w-0 flex-1 rounded border border-transparent bg-transparent px-1 py-0.5 text-sm hover:border-border focus:border-border focus:outline-none focus:ring-2 focus:ring-ring"
1205
+ }
1206
+ ),
1207
+ /* @__PURE__ */ jsx(
1208
+ "input",
1209
+ {
1210
+ value: item.description ?? "",
1211
+ onChange: (e) => onChange({ description: e.target.value }),
1212
+ placeholder: t("menu.itemForm.description"),
1213
+ className: "hidden min-w-0 flex-1 rounded border border-transparent bg-transparent px-1 py-0.5 text-[11px] text-muted-foreground hover:border-border focus:border-border focus:outline-none focus:ring-2 focus:ring-ring sm:block"
1214
+ }
1215
+ ),
1216
+ /* @__PURE__ */ jsx(
1217
+ "input",
1218
+ {
1219
+ value: item.price,
1220
+ inputMode: "decimal",
1221
+ onChange: (e) => onChange({ price: Number(e.target.value.replace(",", ".")) || 0 }),
1222
+ className: `w-20 rounded border bg-card px-1.5 py-0.5 text-right text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-ring ${item.price === 0 ? "border-warning text-warning" : ""}`
1223
+ }
1224
+ ),
1225
+ /* @__PURE__ */ jsx(
1226
+ "button",
1227
+ {
1228
+ onClick: onRemove,
1229
+ className: "rounded p-1 text-muted-foreground hover:text-destructive",
1230
+ "aria-label": t("menu.import.dropItem"),
1231
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
1232
+ }
1233
+ )
1234
+ ] });
1235
+ }
1236
+ function DraftSectionCard({ section, sections, onPatchSection, onRemoveSection, onPatchItem, onRemoveItem }) {
1237
+ const t = useTranslation();
1238
+ const existing = sections.find((s) => s.id === section.targetSectionId);
1239
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-card border-2 border-dashed border-primary/40 bg-primary/[0.03]", children: [
1240
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-dashed border-primary/30 px-3 py-2", children: [
1241
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-primary" }),
1242
+ /* @__PURE__ */ jsx(
1243
+ "input",
1244
+ {
1245
+ value: section.name,
1246
+ onChange: (e) => onPatchSection({ name: e.target.value }),
1247
+ className: "rounded border border-transparent bg-transparent px-1 py-0.5 text-sm font-semibold hover:border-border focus:border-border focus:outline-none focus:ring-2 focus:ring-ring"
1248
+ }
1249
+ ),
1250
+ /* @__PURE__ */ jsxs(
1251
+ "select",
1252
+ {
1253
+ value: section.targetSectionId ?? "",
1254
+ onChange: (e) => onPatchSection({ targetSectionId: e.target.value || void 0 }),
1255
+ className: "h-7 rounded-md border bg-card px-2 text-xs",
1256
+ children: [
1257
+ /* @__PURE__ */ jsx("option", { value: "", children: t("menu.import.createSection") }),
1258
+ sections.map((s) => /* @__PURE__ */ jsx("option", { value: s.id, children: t("menu.import.intoSection", { name: s.name }) }, s.id))
1259
+ ]
1260
+ }
1261
+ ),
1262
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: t("menu.manager.itemsCount", { count: String(section.items.length) }) }),
1263
+ existing && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[10px] text-muted-foreground", children: t("menu.import.willAppend", { name: existing.name }) }),
1264
+ /* @__PURE__ */ jsx(
1265
+ "button",
1266
+ {
1267
+ onClick: onRemoveSection,
1268
+ className: "ml-auto rounded p-1 text-muted-foreground hover:text-destructive",
1269
+ "aria-label": t("menu.import.dropSection"),
1270
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
1271
+ }
1272
+ )
1273
+ ] }),
1274
+ /* @__PURE__ */ jsx("div", { children: section.items.map((item) => /* @__PURE__ */ jsx(
1275
+ DraftRow,
1276
+ {
1277
+ item,
1278
+ onChange: (patch) => onPatchItem(item.key, patch),
1279
+ onRemove: () => onRemoveItem(item.key)
1280
+ },
1281
+ item.key
1282
+ )) })
1283
+ ] });
1284
+ }
1285
+ function DraftBar({ draft, saving, onCommit, onDiscard }) {
1286
+ const t = useTranslation();
1287
+ const items = draft.sections.reduce((count, section) => count + section.items.length, 0);
1288
+ const missingPrice = draft.sections.reduce(
1289
+ (count, section) => count + section.items.filter((item) => item.price === 0).length,
1290
+ 0
1291
+ );
1292
+ return /* @__PURE__ */ jsxs("div", { className: "sticky bottom-3 z-10 flex flex-wrap items-center gap-3 rounded-card border bg-card px-4 py-2.5 shadow-lg", children: [
1293
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 text-primary" }),
1294
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1295
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("menu.import.draftCount", { items: String(items), sections: String(draft.sections.length) }) }),
1296
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: missingPrice > 0 ? t("menu.import.draftMissingPrice", { count: String(missingPrice) }) : t("menu.import.draftHint") })
1297
+ ] }),
1298
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onDiscard, disabled: saving, children: t("menu.import.discard") }),
1299
+ /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: onCommit, disabled: saving, children: [
1300
+ /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
1301
+ " ",
1302
+ t("menu.import.commit", { count: String(items) })
1303
+ ] })
1304
+ ] });
1305
+ }
1306
+ function MenuPreview({ open, menu, sections, placements, itemsById, currency, locale, onClose }) {
1307
+ const t = useTranslation();
1308
+ const serving = menu ? isServingAt(menu.availability) : true;
1309
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
1310
+ if (!next) onClose();
1311
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "xl", children: [
1312
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
1313
+ /* @__PURE__ */ jsx(ModalTitle, { children: menu?.name ?? t("menu.preview.title") }),
1314
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1315
+ menu ? describeAvailability(menu.availability, locale, t("menu.schedule.always")) : "",
1316
+ menu && !serving && ` \xB7 ${t("menu.preview.notServingNow")}`
1317
+ ] })
1318
+ ] }),
1319
+ /* @__PURE__ */ jsx(ModalBody, { className: "space-y-6 py-2", children: sections.filter((section) => section.isActive).map((section) => {
1320
+ const rows = placements.filter((p) => p.sectionId === section.id && p.isActive).map((p) => ({ placement: p, item: itemsById.get(p.itemId) })).filter((row) => row.item && row.item.status !== "hidden");
1321
+ if (rows.length === 0) return null;
1322
+ return /* @__PURE__ */ jsxs("section", { children: [
1323
+ /* @__PURE__ */ jsx("h3", { className: "border-b pb-1 text-sm font-semibold uppercase tracking-wider text-muted-foreground", children: section.name }),
1324
+ section.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: section.description }),
1325
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-2", children: rows.map(({ placement, item }) => /* @__PURE__ */ jsxs("li", { className: "flex items-baseline gap-3", children: [
1326
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1327
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: item.name }),
1328
+ item.status === "sold_out" && /* @__PURE__ */ jsx("span", { className: "ml-2 text-[10px] uppercase text-destructive", children: t("menu.manager.soldOut") }),
1329
+ item.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: item.description })
1330
+ ] }),
1331
+ /* @__PURE__ */ jsx("span", { className: "hidden flex-1 translate-y-[-3px] border-b border-dotted border-muted-foreground/30 sm:block" }),
1332
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: formatCurrency(placement.priceOverride ?? item.price, currency) })
1333
+ ] }, placement.id)) })
1334
+ ] }, section.id);
1335
+ }) })
1336
+ ] }) });
1337
+ }
1338
+ function MenuManagerView() {
1339
+ const t = useTranslation();
1340
+ const { currency, modules } = useMenuConfig();
1341
+ const multiMenu = useMenuStore((s) => s.multiMenu);
1342
+ const menus = useMenuStore((s) => s.menus);
1343
+ const menusLoading = useMenuStore((s) => s.menusLoading);
1344
+ const activeMenuId = useMenuStore((s) => s.activeMenuId);
1345
+ const sections = useMenuStore((s) => s.sections);
1346
+ const placements = useMenuStore((s) => s.placements);
1347
+ const menuLoading = useMenuStore((s) => s.menuLoading);
1348
+ const menuItems = useMenuStore((s) => s.menuItems);
1349
+ useMenuStore((s) => s.extracting);
1350
+ const draft = useMenuStore((s) => s.draft);
1351
+ const pendingIds = useMenuStore((s) => s.pendingIds);
1352
+ const fetchMenus = useMenuStore((s) => s.fetchMenus);
1353
+ const fetchMenuItems = useMenuStore((s) => s.fetchMenuItems);
1354
+ const openMenu = useMenuStore((s) => s.openMenu);
1355
+ const createMenu = useMenuStore((s) => s.createMenu);
1356
+ const updateMenu = useMenuStore((s) => s.updateMenu);
1357
+ const deleteMenu = useMenuStore((s) => s.deleteMenu);
1358
+ const duplicateMenu = useMenuStore((s) => s.duplicateMenu);
1359
+ const createSection = useMenuStore((s) => s.createSection);
1360
+ const updateSection = useMenuStore((s) => s.updateSection);
1361
+ const deleteSection = useMenuStore((s) => s.deleteSection);
1362
+ const reorderSections = useMenuStore((s) => s.reorderSections);
1363
+ const addItemToSection = useMenuStore((s) => s.addItemToSection);
1364
+ const removePlacement = useMenuStore((s) => s.removePlacement);
1365
+ const setPlacementPrice = useMenuStore((s) => s.setPlacementPrice);
1366
+ const reorderPlacements = useMenuStore((s) => s.reorderPlacements);
1367
+ const movePlacement = useMenuStore((s) => s.movePlacement);
1368
+ const updateMenuItem = useMenuStore((s) => s.updateMenuItem);
1369
+ const deleteMenuItem = useMenuStore((s) => s.deleteMenuItem);
1370
+ const duplicateMenuItem = useMenuStore((s) => s.duplicateMenuItem);
1371
+ const setItemStatus = useMenuStore((s) => s.setItemStatus);
1372
+ const toggleAvailability = useMenuStore((s) => s.toggleAvailability);
1373
+ const uploadImage = useMenuStore((s) => s.uploadImage);
1374
+ const runExtraction = useMenuStore((s) => s.runExtraction);
1375
+ const updateDraftItem = useMenuStore((s) => s.updateDraftItem);
1376
+ const removeDraftItem = useMenuStore((s) => s.removeDraftItem);
1377
+ const updateDraftSection = useMenuStore((s) => s.updateDraftSection);
1378
+ const removeDraftSection = useMenuStore((s) => s.removeDraftSection);
1379
+ const discardDraft = useMenuStore((s) => s.discardDraft);
1380
+ const commitDraft = useMenuStore((s) => s.commitDraft);
1381
+ const [search, setSearch] = useState("");
1382
+ const [menuEditor, setMenuEditor] = useState({ open: false });
1383
+ const [itemEditor, setItemEditor] = useState({ open: false, sectionId: "" });
1384
+ const [previewOpen, setPreviewOpen] = useState(false);
1385
+ const [confirm, setConfirm] = useState(null);
1386
+ const [dragging, setDragging] = useState(null);
1387
+ const [committing, setCommitting] = useState(false);
1388
+ const extractor = useMemo(() => resolveMenuExtractor(), []);
1389
+ const readPhotos = async (files) => {
1390
+ if (!extractor) return;
1391
+ const images = await Promise.all(files.map((file) => downscaleImage(file)));
1392
+ await runExtraction(images, extractor);
1393
+ };
1394
+ const askForPhotos = () => requestChatUpload({
1395
+ label: t("menu.import.uploadCta"),
1396
+ description: t("menu.import.uploadHint"),
1397
+ accept: "image/*",
1398
+ multiple: true,
1399
+ capture: "environment",
1400
+ onFiles: readPhotos
1401
+ });
1402
+ const startAiImport = () => {
1403
+ askForPhotos();
1404
+ askAI({
1405
+ prompt: t("menu.import.askPrompt"),
1406
+ hint: t("menu.import.askHint"),
1407
+ intro: {
1408
+ role: "operations",
1409
+ persona: t("menu.import.askPersona"),
1410
+ pitch: t("menu.import.askPitch"),
1411
+ description: t("menu.import.askDescription"),
1412
+ suggestions: [
1413
+ { label: t("menu.import.askSuggestion.0") },
1414
+ { label: t("menu.import.askSuggestion.1") },
1415
+ { label: t("menu.import.askSuggestion.2") }
1416
+ ]
1417
+ }
1418
+ });
1419
+ };
1420
+ useEffect(() => {
1421
+ void fetchMenus();
1422
+ void fetchMenuItems({});
1423
+ }, []);
1424
+ const activeMenu = menus.find((m) => m.id === activeMenuId);
1425
+ const itemsById = useMemo(() => new Map(menuItems.map((item) => [item.id, item])), [menuItems]);
1426
+ const visible = useMemo(() => {
1427
+ const term = search.trim().toLowerCase();
1428
+ const matches = (placement) => {
1429
+ if (!term) return true;
1430
+ const item = itemsById.get(placement.itemId);
1431
+ return !!item && (item.name.toLowerCase().includes(term) || (item.description ?? "").toLowerCase().includes(term));
1432
+ };
1433
+ const bySection = /* @__PURE__ */ new Map();
1434
+ for (const placement of [...placements].sort((a, b) => a.sortOrder - b.sortOrder)) {
1435
+ if (!matches(placement)) continue;
1436
+ const list = bySection.get(placement.sectionId) ?? [];
1437
+ list.push(placement);
1438
+ bySection.set(placement.sectionId, list);
1439
+ }
1440
+ const ordered = [...sections].sort((a, b) => a.sortOrder - b.sortOrder);
1441
+ return {
1442
+ sections: term ? ordered.filter((s) => (bySection.get(s.id)?.length ?? 0) > 0) : ordered,
1443
+ bySection
1444
+ };
1445
+ }, [sections, placements, itemsById, search]);
1446
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
1447
+ const handleDragEnd = (event) => {
1448
+ setDragging(null);
1449
+ const { active, over } = event;
1450
+ if (!over || active.id === over.id) return;
1451
+ const type = active.data.current?.type;
1452
+ if (type === "section") {
1453
+ const ids = visible.sections.map((s) => s.id);
1454
+ const from = ids.indexOf(String(active.id));
1455
+ const to = ids.indexOf(String(over.id));
1456
+ if (from < 0 || to < 0) return;
1457
+ const next = [...ids];
1458
+ next.splice(to, 0, next.splice(from, 1)[0]);
1459
+ void reorderSections(next);
1460
+ return;
1461
+ }
1462
+ if (type === "item") {
1463
+ const fromSection = String(active.data.current?.sectionId ?? "");
1464
+ const overType = over.data.current?.type;
1465
+ const toSection = overType === "section" ? String(over.id) : String(over.data.current?.sectionId ?? "");
1466
+ if (!toSection) return;
1467
+ const target = (visible.bySection.get(toSection) ?? []).map((p) => p.id);
1468
+ if (fromSection === toSection) {
1469
+ const from = target.indexOf(String(active.id));
1470
+ const to = target.indexOf(String(over.id));
1471
+ if (from < 0 || to < 0) return;
1472
+ const next = [...target];
1473
+ next.splice(to, 0, next.splice(from, 1)[0]);
1474
+ void reorderPlacements(toSection, next);
1475
+ } else {
1476
+ const index = overType === "section" ? target.length : Math.max(0, target.indexOf(String(over.id)));
1477
+ void movePlacement(String(active.id), toSection, index);
1478
+ }
1479
+ }
1480
+ };
1481
+ const handlers = {
1482
+ onRenameSection: (sectionId, name) => void updateSection(sectionId, { name }),
1483
+ onEditSection: (section) => void updateSection(section.id, { isActive: !section.isActive }),
1484
+ onDeleteSection: (section) => setConfirm({ kind: "section", id: section.id, name: section.name }),
1485
+ onAddItem: (sectionId) => setItemEditor({ open: true, sectionId }),
1486
+ onEditItem: (item, placement) => setItemEditor({ open: true, item, sectionId: placement.sectionId, placement }),
1487
+ onDuplicateItem: (item, sectionId) => void duplicateMenuItem(item.id, sectionId),
1488
+ onRemoveFromMenu: (placement) => void removePlacement(placement.id),
1489
+ onDeleteItem: (item) => setConfirm({ kind: "item", id: item.id, name: item.name }),
1490
+ onToggleItem: (itemId, field, value) => void toggleAvailability(itemId, field, value),
1491
+ onSetItemStatus: (itemId, status) => void setItemStatus(itemId, status),
1492
+ onSetPlacementPrice: (placementId, price) => void setPlacementPrice(placementId, price)
1493
+ };
1494
+ useMenuAgentSurface({
1495
+ menu: activeMenu,
1496
+ menus,
1497
+ sections,
1498
+ placements,
1499
+ itemsById,
1500
+ draft,
1501
+ hasExtractor: !!extractor,
1502
+ actions: {
1503
+ createSection: (name) => createSection(name),
1504
+ renameSection: (sectionId, name) => updateSection(sectionId, { name }),
1505
+ reorderSections,
1506
+ createItem: (sectionId, input) => addItemToSection(sectionId, input),
1507
+ updateItem: (itemId, patch) => updateMenuItem(itemId, patch),
1508
+ setItemStatus,
1509
+ moveItem: movePlacement,
1510
+ createMenu: ({ name, days, start, end }) => createMenu({
1511
+ name,
1512
+ availability: days || start || end ? { days, start, end } : {}
1513
+ }),
1514
+ duplicateMenu,
1515
+ requestPhotos: askForPhotos,
1516
+ updateDraftItem,
1517
+ removeDraftItem,
1518
+ updateDraftSection,
1519
+ removeDraftSection,
1520
+ commitDraft: async () => {
1521
+ const result = await commitDraft();
1522
+ return result;
1523
+ },
1524
+ discardDraft
1525
+ }
1526
+ });
1527
+ const submitItem = async (values) => {
1528
+ const { sectionId, priceOverride, ...rest } = values;
1529
+ if (itemEditor.item) {
1530
+ await updateMenuItem(itemEditor.item.id, rest);
1531
+ if (itemEditor.placement) {
1532
+ if (priceOverride !== itemEditor.placement.priceOverride) {
1533
+ await setPlacementPrice(itemEditor.placement.id, priceOverride);
1534
+ }
1535
+ if (sectionId !== itemEditor.placement.sectionId) {
1536
+ await movePlacement(itemEditor.placement.id, sectionId, 0);
1537
+ }
1538
+ }
1539
+ } else {
1540
+ await addItemToSection(sectionId, rest);
1541
+ }
1542
+ };
1543
+ const confirmDelete = async () => {
1544
+ if (!confirm) return;
1545
+ if (confirm.kind === "section") await deleteSection(confirm.id);
1546
+ if (confirm.kind === "item") await deleteMenuItem(confirm.id);
1547
+ if (confirm.kind === "menu") await deleteMenu(confirm.id);
1548
+ setConfirm(null);
1549
+ };
1550
+ const scheduleLabel = activeMenu ? describeAvailability(activeMenu.availability, currency.locale, t("menu.schedule.always")) : "";
1551
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1552
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1553
+ extractor && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "default", onClick: startAiImport, children: [
1554
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
1555
+ " ",
1556
+ t("menu.import.action")
1557
+ ] }),
1558
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "default", onClick: () => setPreviewOpen(true), children: [
1559
+ /* @__PURE__ */ jsx(Eye, { className: "h-3.5 w-3.5" }),
1560
+ " ",
1561
+ t("menu.preview.action")
1562
+ ] }),
1563
+ multiMenu && /* @__PURE__ */ jsxs(Button, { size: "default", onClick: () => setMenuEditor({ open: true }), children: [
1564
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1565
+ " ",
1566
+ t("menu.manager.newMenu")
1567
+ ] })
1568
+ ] }) }),
1569
+ multiMenu && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: menus.map((menu) => {
1570
+ const active = menu.id === activeMenuId;
1571
+ const serving = menu.isActive && isServingAt(menu.availability);
1572
+ return /* @__PURE__ */ jsxs(
1573
+ "button",
1574
+ {
1575
+ onClick: () => void openMenu(menu.id),
1576
+ className: `group inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm transition-colors ${active ? "border-primary bg-primary/10 font-medium text-foreground" : "bg-card text-muted-foreground hover:bg-muted"}`,
1577
+ children: [
1578
+ /* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 rounded-full ${serving ? "bg-success" : "bg-muted-foreground/30"}` }),
1579
+ menu.name,
1580
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: menu.itemCount ?? 0 })
1581
+ ]
1582
+ },
1583
+ menu.id
1584
+ );
1585
+ }) }),
1586
+ activeMenu && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
1587
+ /* @__PURE__ */ jsx("span", { children: scheduleLabel }),
1588
+ !isAlwaysAvailable(activeMenu.availability) && /* @__PURE__ */ jsx("span", { className: `rounded-full px-2 py-0.5 text-[10px] font-medium ${isServingAt(activeMenu.availability) ? "bg-success-soft text-success-soft-foreground" : "bg-muted"}`, children: isServingAt(activeMenu.availability) ? t("menu.manager.servingNow") : t("menu.manager.notServing") }),
1589
+ !activeMenu.isActive && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium", children: t("menu.manager.menuOff") }),
1590
+ multiMenu && /* @__PURE__ */ jsxs(Dropdown, { children: [
1591
+ /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "rounded px-1.5 py-0.5 text-[11px] underline-offset-2 hover:underline", children: t("menu.manager.menuActions") }) }),
1592
+ /* @__PURE__ */ jsxs(DropdownContent, { align: "start", children: [
1593
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => setMenuEditor({ open: true, editing: true }), children: [
1594
+ /* @__PURE__ */ jsx(Pencil, { className: "mr-2 h-3.5 w-3.5" }),
1595
+ " ",
1596
+ t("menu.manager.editMenu")
1597
+ ] }),
1598
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => void duplicateMenu(activeMenu.id, `${activeMenu.name} (c\xF3pia)`), children: [
1599
+ /* @__PURE__ */ jsx(Copy, { className: "mr-2 h-3.5 w-3.5" }),
1600
+ " ",
1601
+ t("menu.manager.duplicateMenu")
1602
+ ] }),
1603
+ /* @__PURE__ */ jsxs(DropdownItem, { onClick: () => void updateMenu(activeMenu.id, { isActive: !activeMenu.isActive }), children: [
1604
+ /* @__PURE__ */ jsx(Eye, { className: "mr-2 h-3.5 w-3.5" }),
1605
+ activeMenu.isActive ? t("menu.manager.deactivateMenu") : t("menu.manager.activateMenu")
1606
+ ] }),
1607
+ /* @__PURE__ */ jsx(DropdownSeparator, {}),
1608
+ /* @__PURE__ */ jsxs(
1609
+ DropdownItem,
1610
+ {
1611
+ className: "text-destructive focus:text-destructive",
1612
+ onClick: () => setConfirm({ kind: "menu", id: activeMenu.id, name: activeMenu.name }),
1613
+ children: [
1614
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-2 h-3.5 w-3.5" }),
1615
+ " ",
1616
+ t("menu.manager.deleteMenu")
1617
+ ]
1618
+ }
1619
+ )
1620
+ ] })
1621
+ ] })
1622
+ ] }),
1623
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1624
+ /* @__PURE__ */ jsxs("div", { className: "relative min-w-[12rem] flex-1", children: [
1625
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
1626
+ /* @__PURE__ */ jsx(
1627
+ "input",
1628
+ {
1629
+ value: search,
1630
+ onChange: (e) => setSearch(e.target.value),
1631
+ placeholder: t("menu.manager.search"),
1632
+ className: "w-full rounded-input border bg-card py-1.5 pl-8 pr-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1633
+ }
1634
+ )
1635
+ ] }),
1636
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => void createSection(t("menu.manager.newSectionName")), children: [
1637
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1638
+ " ",
1639
+ t("menu.manager.addSection")
1640
+ ] })
1641
+ ] }),
1642
+ menusLoading || menuLoading ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx("div", { className: "h-24 animate-pulse rounded-card border bg-muted/20" }, i)) }) : visible.sections.length === 0 && !draft ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 rounded-card border-2 border-dashed py-14 text-center", children: [
1643
+ /* @__PURE__ */ jsx(UtensilsCrossed, { className: "h-8 w-8 text-muted-foreground/30" }),
1644
+ /* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: t("menu.manager.empty") }),
1645
+ /* @__PURE__ */ jsx("p", { className: "max-w-sm text-xs text-muted-foreground", children: t("menu.manager.emptyHint") }),
1646
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex gap-2", children: [
1647
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => void createSection(t("menu.manager.newSectionName")), children: [
1648
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1649
+ " ",
1650
+ t("menu.manager.addSection")
1651
+ ] }),
1652
+ extractor && /* @__PURE__ */ jsxs(Button, { onClick: startAiImport, children: [
1653
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" }),
1654
+ " ",
1655
+ t("menu.import.action")
1656
+ ] })
1657
+ ] })
1658
+ ] }) : /* @__PURE__ */ jsxs(
1659
+ DndContext,
1660
+ {
1661
+ sensors,
1662
+ collisionDetection: closestCenter,
1663
+ onDragStart: (event) => setDragging(String(event.active.id)),
1664
+ onDragEnd: handleDragEnd,
1665
+ onDragCancel: () => setDragging(null),
1666
+ children: [
1667
+ /* @__PURE__ */ jsx(SortableContext, { items: visible.sections.map((s) => s.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx("div", { className: "space-y-3", children: visible.sections.map((section) => {
1668
+ const rows = visible.bySection.get(section.id) ?? [];
1669
+ return /* @__PURE__ */ jsx(SortableContext, { items: rows.map((p) => p.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx(
1670
+ SectionCard,
1671
+ {
1672
+ section,
1673
+ placements: rows,
1674
+ itemsById,
1675
+ currency,
1676
+ locale: currency.locale,
1677
+ deliveryPricing: modules.deliveryPricing,
1678
+ multiMenu,
1679
+ pendingIds,
1680
+ handlers
1681
+ }
1682
+ ) }, section.id);
1683
+ }) }) }),
1684
+ /* @__PURE__ */ jsx(DragOverlay, { children: dragging ? /* @__PURE__ */ jsx("div", { className: "h-10 rounded-card border bg-card shadow-lg" }) : null })
1685
+ ]
1686
+ }
1687
+ ),
1688
+ draft && /* @__PURE__ */ jsxs("div", { className: "space-y-3 pt-1", children: [
1689
+ draft.sections.map((section) => /* @__PURE__ */ jsx(
1690
+ DraftSectionCard,
1691
+ {
1692
+ section,
1693
+ sections,
1694
+ onPatchSection: (patch) => updateDraftSection(section.key, patch),
1695
+ onRemoveSection: () => removeDraftSection(section.key),
1696
+ onPatchItem: (itemKey, patch) => updateDraftItem(section.key, itemKey, patch),
1697
+ onRemoveItem: (itemKey) => removeDraftItem(section.key, itemKey)
1698
+ },
1699
+ section.key
1700
+ )),
1701
+ /* @__PURE__ */ jsx(
1702
+ DraftBar,
1703
+ {
1704
+ draft,
1705
+ saving: committing,
1706
+ onDiscard: discardDraft,
1707
+ onCommit: async () => {
1708
+ setCommitting(true);
1709
+ try {
1710
+ const result = await commitDraft();
1711
+ toast.success(t("menu.import.done", {
1712
+ items: String(result.itemsCreated),
1713
+ sections: String(result.sectionsCreated)
1714
+ }));
1715
+ } finally {
1716
+ setCommitting(false);
1717
+ }
1718
+ }
1719
+ }
1720
+ )
1721
+ ] }),
1722
+ /* @__PURE__ */ jsx(
1723
+ MenuRailPage,
1724
+ {
1725
+ open: menuEditor.open,
1726
+ menu: menuEditor.editing ? activeMenu : void 0,
1727
+ locale: currency.locale,
1728
+ onClose: () => setMenuEditor({ open: false }),
1729
+ onSubmit: async (values) => {
1730
+ if (menuEditor.editing && activeMenu) await updateMenu(activeMenu.id, values);
1731
+ else await createMenu(values);
1732
+ }
1733
+ }
1734
+ ),
1735
+ /* @__PURE__ */ jsx(
1736
+ ItemRailPage,
1737
+ {
1738
+ open: itemEditor.open,
1739
+ item: itemEditor.item,
1740
+ sections,
1741
+ sectionId: itemEditor.sectionId,
1742
+ currency,
1743
+ deliveryPricing: modules.deliveryPricing,
1744
+ multiMenu,
1745
+ priceOverride: itemEditor.placement?.priceOverride,
1746
+ onClose: () => setItemEditor({ open: false, sectionId: "" }),
1747
+ onSubmit: submitItem,
1748
+ onUploadImage: (file) => uploadImage(file, "item")
1749
+ }
1750
+ ),
1751
+ /* @__PURE__ */ jsx(
1752
+ MenuPreview,
1753
+ {
1754
+ open: previewOpen,
1755
+ menu: activeMenu,
1756
+ sections,
1757
+ placements,
1758
+ itemsById,
1759
+ currency,
1760
+ locale: currency.locale,
1761
+ onClose: () => setPreviewOpen(false)
1762
+ }
1763
+ ),
1764
+ /* @__PURE__ */ jsx(
1765
+ ConfirmDialog,
1766
+ {
1767
+ open: !!confirm,
1768
+ variant: "destructive",
1769
+ title: confirm ? t(`menu.confirm.${confirm.kind}Title`, { name: confirm.name }) : "",
1770
+ description: confirm ? t(`menu.confirm.${confirm.kind}Desc`) : void 0,
1771
+ confirmLabel: t("menu.confirm.delete"),
1772
+ cancelLabel: t("menu.form.cancel"),
1773
+ onConfirm: () => void confirmDelete(),
1774
+ onCancel: () => setConfirm(null)
1775
+ }
1776
+ )
1777
+ ] });
1778
+ }
1779
+ function MenuPage({ config, provider, store, registries }) {
1780
+ return /* @__PURE__ */ jsx(MenuContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(MenuManagerView, {}) });
1781
+ }
1782
+
1783
+ export { MenuPage };
1784
+ //# sourceMappingURL=MenuPage-2LLWRJGN.js.map
1785
+ //# sourceMappingURL=MenuPage-2LLWRJGN.js.map