@fayz-ai/plugin-menu 0.8.0 → 0.9.0-next.1

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.
package/dist/index.cjs DELETED
@@ -1,1057 +0,0 @@
1
- 'use strict';
2
-
3
- var React = require('react');
4
- var core = require('@fayz-ai/core');
5
- var vanilla = require('zustand/vanilla');
6
- var saas = require('@fayz-ai/saas');
7
- var sonner = require('sonner');
8
- var sdk = require('@fayz-ai/sdk');
9
-
10
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
-
12
- var React__default = /*#__PURE__*/_interopDefault(React);
13
-
14
- // src/index.ts
15
-
16
- // src/data/mock.ts
17
- var uid = 0;
18
- function nextId(prefix) {
19
- return prefix + "-" + ++uid;
20
- }
21
- function now() {
22
- return (/* @__PURE__ */ new Date()).toISOString();
23
- }
24
- function paginate(items, page, pageSize) {
25
- const p = page ?? 1;
26
- const ps = pageSize ?? 50;
27
- const start = (p - 1) * ps;
28
- return { data: items.slice(start, start + ps), total: items.length };
29
- }
30
- function seedCategories() {
31
- const t = "mock-tenant";
32
- const ts = now();
33
- return [
34
- { id: nextId("cat"), name: "Burgers", icon: "\u{1F354}", color: "#f59e0b", sortOrder: 0, isActive: true, itemCount: 3, tenantId: t, createdAt: ts, updatedAt: ts },
35
- { id: nextId("cat"), name: "Sides", icon: "\u{1F35F}", color: "#22c55e", sortOrder: 1, isActive: true, itemCount: 3, tenantId: t, createdAt: ts, updatedAt: ts },
36
- { id: nextId("cat"), name: "Drinks", icon: "\u{1F964}", color: "#3b82f6", sortOrder: 2, isActive: true, itemCount: 4, tenantId: t, createdAt: ts, updatedAt: ts },
37
- { id: nextId("cat"), name: "Desserts", icon: "\u{1F370}", color: "#ec4899", sortOrder: 3, isActive: true, itemCount: 2, tenantId: t, createdAt: ts, updatedAt: ts },
38
- { id: nextId("cat"), name: "Combos", icon: "\u2B50", color: "#8b5cf6", sortOrder: 4, isActive: true, itemCount: 0, tenantId: t, createdAt: ts, updatedAt: ts }
39
- ];
40
- }
41
- function seedItems(categories) {
42
- const t = "mock-tenant";
43
- const ts = now();
44
- const [burgers, sides, drinks, desserts] = categories.map((c) => c.id);
45
- const item = (name, price, categoryId, categoryName, opts = {}) => ({
46
- id: nextId("mi"),
47
- name,
48
- price,
49
- currency: "BRL",
50
- categoryId,
51
- categoryName,
52
- status: "available",
53
- isActive: true,
54
- availableForPos: true,
55
- availableForDelivery: true,
56
- sortOrder: 0,
57
- isFeatured: false,
58
- tenantId: t,
59
- createdAt: ts,
60
- updatedAt: ts,
61
- ...opts
62
- });
63
- let order = 0;
64
- return [
65
- item("Classic Burger", 32, burgers, "Burgers", { prepTimeMinutes: 12, sortOrder: order++, isFeatured: true }),
66
- item("Cheese Burger", 36, burgers, "Burgers", { prepTimeMinutes: 12, sortOrder: order++ }),
67
- item("BBQ Bacon", 42, burgers, "Burgers", { prepTimeMinutes: 15, sortOrder: order++, isFeatured: true }),
68
- item("Fries", 15, sides, "Sides", { prepTimeMinutes: 8, sortOrder: order++ }),
69
- item("Onion Rings", 18, sides, "Sides", { prepTimeMinutes: 8, sortOrder: order++ }),
70
- item("Coleslaw", 12, sides, "Sides", { prepTimeMinutes: 2, sortOrder: order++, availableForDelivery: false }),
71
- item("Soda", 8, drinks, "Drinks", { prepTimeMinutes: 1, sortOrder: order++ }),
72
- item("Juice", 12, drinks, "Drinks", { prepTimeMinutes: 3, sortOrder: order++ }),
73
- item("Water", 5, drinks, "Drinks", { prepTimeMinutes: 0, sortOrder: order++ }),
74
- item("Milkshake", 16, drinks, "Drinks", { prepTimeMinutes: 5, sortOrder: order++, isFeatured: true }),
75
- item("Brownie", 14, desserts, "Desserts", { prepTimeMinutes: 1, sortOrder: order++ }),
76
- item("Ice Cream", 12, desserts, "Desserts", { prepTimeMinutes: 2, sortOrder: order++, availableForDelivery: false })
77
- ];
78
- }
79
- function seedModifierGroups() {
80
- const t = "mock-tenant";
81
- const ts = now();
82
- const g1Id = nextId("mg");
83
- const g2Id = nextId("mg");
84
- return [
85
- {
86
- id: g1Id,
87
- name: "Extra Toppings",
88
- minSelections: 0,
89
- maxSelections: 3,
90
- isRequired: false,
91
- sortOrder: 0,
92
- tenantId: t,
93
- createdAt: ts,
94
- items: [
95
- { id: nextId("mod"), groupId: g1Id, name: "Extra Cheese", priceAdjustment: 5, isActive: true, sortOrder: 0 },
96
- { id: nextId("mod"), groupId: g1Id, name: "Bacon", priceAdjustment: 7, isActive: true, sortOrder: 1 },
97
- { id: nextId("mod"), groupId: g1Id, name: "Jalape\xF1os", priceAdjustment: 4, isActive: true, sortOrder: 2 }
98
- ]
99
- },
100
- {
101
- id: g2Id,
102
- name: "Drink Size",
103
- minSelections: 1,
104
- maxSelections: 1,
105
- isRequired: true,
106
- sortOrder: 1,
107
- tenantId: t,
108
- createdAt: ts,
109
- items: [
110
- { id: nextId("mod"), groupId: g2Id, name: "Small", priceAdjustment: 0, isActive: true, sortOrder: 0 },
111
- { id: nextId("mod"), groupId: g2Id, name: "Medium", priceAdjustment: 3, isActive: true, sortOrder: 1 },
112
- { id: nextId("mod"), groupId: g2Id, name: "Large", priceAdjustment: 5, isActive: true, sortOrder: 2 }
113
- ]
114
- }
115
- ];
116
- }
117
- function createMockMenuProvider() {
118
- const tenantId = "mock-tenant";
119
- const categories = seedCategories();
120
- const items = seedItems(categories);
121
- const modifierGroups = seedModifierGroups();
122
- return {
123
- async getCategories() {
124
- return [...categories].sort((a, b) => a.sortOrder - b.sortOrder);
125
- },
126
- async getCategoryById(id) {
127
- return categories.find((c) => c.id === id) ?? null;
128
- },
129
- async createCategory(input) {
130
- const cat = {
131
- id: nextId("cat"),
132
- name: input.name,
133
- parentId: input.parentId,
134
- icon: input.icon,
135
- color: input.color,
136
- sortOrder: input.sortOrder ?? categories.length,
137
- isActive: true,
138
- itemCount: 0,
139
- tenantId,
140
- createdAt: now(),
141
- updatedAt: now()
142
- };
143
- categories.push(cat);
144
- return cat;
145
- },
146
- async updateCategory(id, data) {
147
- const cat = categories.find((c) => c.id === id);
148
- if (!cat) throw new Error(`Category ${id} not found`);
149
- Object.assign(cat, data, { updatedAt: now() });
150
- return cat;
151
- },
152
- async deleteCategory(id) {
153
- const idx = categories.findIndex((c) => c.id === id);
154
- if (idx >= 0) categories.splice(idx, 1);
155
- },
156
- async reorderCategories(ids) {
157
- ids.forEach((id, i) => {
158
- const cat = categories.find((c) => c.id === id);
159
- if (cat) cat.sortOrder = i;
160
- });
161
- },
162
- async getMenuItems(query) {
163
- let results = [...items];
164
- if (query.categoryId) results = results.filter((i) => i.categoryId === query.categoryId);
165
- if (query.status) {
166
- const statuses = Array.isArray(query.status) ? query.status : [query.status];
167
- results = results.filter((i) => statuses.includes(i.status));
168
- }
169
- if (query.availableForPos !== void 0) results = results.filter((i) => i.availableForPos === query.availableForPos);
170
- if (query.availableForDelivery !== void 0) results = results.filter((i) => i.availableForDelivery === query.availableForDelivery);
171
- if (query.isFeatured !== void 0) results = results.filter((i) => i.isFeatured === query.isFeatured);
172
- if (query.search) {
173
- const s = query.search.toLowerCase();
174
- results = results.filter((i) => i.name.toLowerCase().includes(s) || i.description?.toLowerCase().includes(s));
175
- }
176
- results.sort((a, b) => a.sortOrder - b.sortOrder);
177
- return paginate(results, query.page, query.pageSize);
178
- },
179
- async getMenuItemById(id) {
180
- return items.find((i) => i.id === id) ?? null;
181
- },
182
- async createMenuItem(input) {
183
- const cat = input.categoryId ? categories.find((c) => c.id === input.categoryId) : void 0;
184
- const item = {
185
- id: nextId("mi"),
186
- name: input.name,
187
- description: input.description,
188
- price: input.price,
189
- cost: input.cost,
190
- currency: "BRL",
191
- imageUrl: input.imageUrl,
192
- categoryId: input.categoryId,
193
- categoryName: cat?.name,
194
- status: input.status ?? "available",
195
- isActive: true,
196
- prepTimeMinutes: input.prepTimeMinutes,
197
- allergens: input.allergens,
198
- dietaryTags: input.dietaryTags,
199
- availableForPos: input.availableForPos ?? true,
200
- availableForDelivery: input.availableForDelivery ?? true,
201
- deliveryPrice: input.deliveryPrice,
202
- sortOrder: items.length,
203
- isFeatured: input.isFeatured ?? false,
204
- modifierGroupIds: input.modifierGroupIds,
205
- tenantId,
206
- createdAt: now(),
207
- updatedAt: now()
208
- };
209
- items.push(item);
210
- if (cat) cat.itemCount = (cat.itemCount ?? 0) + 1;
211
- return item;
212
- },
213
- async updateMenuItem(id, data) {
214
- const item = items.find((i) => i.id === id);
215
- if (!item) throw new Error(`MenuItem ${id} not found`);
216
- Object.assign(item, data, { updatedAt: now() });
217
- return item;
218
- },
219
- async deleteMenuItem(id) {
220
- const idx = items.findIndex((i) => i.id === id);
221
- if (idx >= 0) {
222
- const [removed] = items.splice(idx, 1);
223
- if (removed.categoryId) {
224
- const cat = categories.find((c) => c.id === removed.categoryId);
225
- if (cat && cat.itemCount) cat.itemCount--;
226
- }
227
- }
228
- },
229
- async toggleAvailability(id, field, value) {
230
- const item = items.find((i) => i.id === id);
231
- if (!item) throw new Error(`MenuItem ${id} not found`);
232
- item[field] = value;
233
- item.updatedAt = now();
234
- return item;
235
- },
236
- async reorderItems(categoryId, ids) {
237
- ids.forEach((id, i) => {
238
- const item = items.find((it) => it.id === id && it.categoryId === categoryId);
239
- if (item) item.sortOrder = i;
240
- });
241
- },
242
- async getModifierGroups() {
243
- return [...modifierGroups].sort((a, b) => a.sortOrder - b.sortOrder);
244
- },
245
- async createModifierGroup(input) {
246
- const group = {
247
- id: nextId("mg"),
248
- name: input.name ?? "",
249
- minSelections: input.minSelections ?? 0,
250
- maxSelections: input.maxSelections ?? 1,
251
- isRequired: input.isRequired ?? false,
252
- sortOrder: input.sortOrder ?? modifierGroups.length,
253
- items: input.items ?? [],
254
- tenantId,
255
- createdAt: now()
256
- };
257
- modifierGroups.push(group);
258
- return group;
259
- },
260
- async updateModifierGroup(id, data) {
261
- const group = modifierGroups.find((g) => g.id === id);
262
- if (!group) throw new Error(`ModifierGroup ${id} not found`);
263
- Object.assign(group, data);
264
- return group;
265
- },
266
- async deleteModifierGroup(id) {
267
- const idx = modifierGroups.findIndex((g) => g.id === id);
268
- if (idx >= 0) modifierGroups.splice(idx, 1);
269
- },
270
- async getSummary() {
271
- const available = items.filter((i) => i.status === "available");
272
- const soldOut = items.filter((i) => i.status === "sold_out");
273
- const hidden = items.filter((i) => i.status === "hidden");
274
- const prices = items.filter((i) => i.price > 0).map((i) => i.price);
275
- return {
276
- totalItems: items.length,
277
- availableItems: available.length,
278
- soldOutItems: soldOut.length,
279
- hiddenItems: hidden.length,
280
- categoryCount: categories.length,
281
- averagePrice: prices.length ? prices.reduce((a, b) => a + b, 0) / prices.length : 0,
282
- featuredCount: items.filter((i) => i.isFeatured).length
283
- };
284
- }
285
- };
286
- }
287
- function createMenuStore(provider) {
288
- return vanilla.createStore((set, get) => ({
289
- categories: [],
290
- categoriesLoading: false,
291
- menuItems: [],
292
- menuItemsTotal: 0,
293
- menuItemsLoading: false,
294
- menuItemQuery: {},
295
- modifierGroups: [],
296
- modifiersLoading: false,
297
- summary: null,
298
- summaryLoading: false,
299
- async fetchCategories() {
300
- return saas.dedup("menu:categories", async () => {
301
- set({ categoriesLoading: true });
302
- const categories = await provider.getCategories();
303
- set({ categories, categoriesLoading: false });
304
- });
305
- },
306
- async fetchMenuItems(query) {
307
- return saas.dedup("menu:items:" + JSON.stringify(query), async () => {
308
- set({ menuItemsLoading: true, menuItemQuery: query });
309
- const result = await provider.getMenuItems(query);
310
- set({ menuItems: result.data, menuItemsTotal: result.total, menuItemsLoading: false });
311
- });
312
- },
313
- async fetchModifierGroups() {
314
- return saas.dedup("menu:modifiers", async () => {
315
- set({ modifiersLoading: true });
316
- const modifierGroups = await provider.getModifierGroups();
317
- set({ modifierGroups, modifiersLoading: false });
318
- });
319
- },
320
- async fetchSummary() {
321
- return saas.dedup("menu:summary", async () => {
322
- set({ summaryLoading: true });
323
- const summary = await provider.getSummary();
324
- set({ summary, summaryLoading: false });
325
- });
326
- },
327
- async createMenuItem(input) {
328
- try {
329
- const item = await provider.createMenuItem(input);
330
- const query = get().menuItemQuery;
331
- const [result, summary] = await Promise.all([provider.getMenuItems(query), provider.getSummary()]);
332
- set({ menuItems: result.data, menuItemsTotal: result.total, summary });
333
- sonner.toast.success("Menu item created");
334
- return item;
335
- } catch (err) {
336
- sonner.toast.error("Failed to create menu item", { description: err?.message });
337
- throw err;
338
- }
339
- },
340
- async updateMenuItem(id, data) {
341
- try {
342
- const item = await provider.updateMenuItem(id, data);
343
- const query = get().menuItemQuery;
344
- const result = await provider.getMenuItems(query);
345
- set({ menuItems: result.data, menuItemsTotal: result.total });
346
- sonner.toast.success("Menu item updated");
347
- return item;
348
- } catch (err) {
349
- sonner.toast.error("Failed to update menu item", { description: err?.message });
350
- throw err;
351
- }
352
- },
353
- async deleteMenuItem(id) {
354
- try {
355
- await provider.deleteMenuItem(id);
356
- const query = get().menuItemQuery;
357
- const [result, summary] = await Promise.all([provider.getMenuItems(query), provider.getSummary()]);
358
- set({ menuItems: result.data, menuItemsTotal: result.total, summary });
359
- sonner.toast.success("Menu item deleted");
360
- } catch (err) {
361
- sonner.toast.error("Failed to delete menu item", { description: err?.message });
362
- throw err;
363
- }
364
- },
365
- async toggleAvailability(id, field, value) {
366
- try {
367
- await provider.toggleAvailability(id, field, value);
368
- const query = get().menuItemQuery;
369
- const result = await provider.getMenuItems(query);
370
- set({ menuItems: result.data, menuItemsTotal: result.total });
371
- } catch (err) {
372
- sonner.toast.error("Failed to update availability", { description: err?.message });
373
- throw err;
374
- }
375
- },
376
- async createCategory(input) {
377
- try {
378
- const category = await provider.createCategory(input);
379
- const categories = await provider.getCategories();
380
- set({ categories });
381
- sonner.toast.success("Category created");
382
- return category;
383
- } catch (err) {
384
- sonner.toast.error("Failed to create category", { description: err?.message });
385
- throw err;
386
- }
387
- }
388
- }));
389
- }
390
-
391
- // src/registries.ts
392
- var allergenEntity = {
393
- name: "Allergen",
394
- namePlural: "Allergens",
395
- icon: "AlertTriangle",
396
- displayField: "name",
397
- defaultSort: "name",
398
- fields: [
399
- { key: "name", label: "Name", type: "text", required: true, showInTable: true },
400
- { key: "icon", label: "Icon", type: "text", showInTable: true },
401
- { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
402
- ],
403
- data: { table: "menu_allergens", tenantScoped: true }
404
- };
405
- var dietaryTagEntity = {
406
- name: "Dietary Tag",
407
- namePlural: "Dietary Tags",
408
- icon: "Leaf",
409
- displayField: "name",
410
- defaultSort: "name",
411
- fields: [
412
- { key: "name", label: "Name", type: "text", required: true, showInTable: true },
413
- { key: "color", label: "Color", type: "text", showInTable: true },
414
- { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
415
- ],
416
- data: { table: "menu_dietary_tags", tenantScoped: true }
417
- };
418
- var modifierGroupEntity = {
419
- name: "Modifier Group",
420
- namePlural: "Modifier Groups",
421
- icon: "ListPlus",
422
- displayField: "name",
423
- defaultSort: "name",
424
- fields: [
425
- { key: "name", label: "Name", type: "text", required: true, showInTable: true },
426
- { key: "minSelections", label: "Min Selections", type: "number", showInTable: true, defaultValue: 0 },
427
- { key: "maxSelections", label: "Max Selections", type: "number", showInTable: true, defaultValue: 1 },
428
- { key: "isRequired", label: "Required", type: "boolean", showInTable: true, defaultValue: false }
429
- ],
430
- data: { table: "menu_modifier_groups", tenantScoped: true }
431
- };
432
- var menuRegistries = [
433
- {
434
- id: "allergens",
435
- entity: allergenEntity,
436
- icon: "AlertTriangle",
437
- description: "Allergen types for menu items",
438
- display: "table",
439
- readOnly: true,
440
- seedData: [
441
- { id: "a-gluten", name: "Gluten", icon: "\u{1F33E}", isActive: true },
442
- { id: "a-dairy", name: "Dairy", icon: "\u{1F95B}", isActive: true },
443
- { id: "a-nuts", name: "Nuts", icon: "\u{1F95C}", isActive: true },
444
- { id: "a-shellfish", name: "Shellfish", icon: "\u{1F990}", isActive: true },
445
- { id: "a-eggs", name: "Eggs", icon: "\u{1F95A}", isActive: true },
446
- { id: "a-soy", name: "Soy", icon: "\u{1FAD8}", isActive: true }
447
- ]
448
- },
449
- {
450
- id: "dietary-tags",
451
- entity: dietaryTagEntity,
452
- icon: "Leaf",
453
- description: "Dietary tags (vegan, vegetarian, etc.)",
454
- display: "table",
455
- readOnly: true,
456
- seedData: [
457
- { id: "dt-vegan", name: "Vegan", color: "green", isActive: true },
458
- { id: "dt-vegetarian", name: "Vegetarian", color: "lime", isActive: true },
459
- { id: "dt-gf", name: "Gluten Free", color: "amber", isActive: true }
460
- ]
461
- },
462
- {
463
- id: "modifier-groups",
464
- entity: modifierGroupEntity,
465
- icon: "ListPlus",
466
- description: "Modifier groups for item customization",
467
- display: "table"
468
- }
469
- ];
470
-
471
- // src/locales/en.ts
472
- var en = {
473
- "menu.title": "Menu",
474
- "menu.nav.manager": "Menu",
475
- "menu.nav.categories": "Categories",
476
- "menu.nav.items": "Items",
477
- "menu.nav.modifiers": "Modifiers",
478
- "menu.manager.title": "Menu Manager",
479
- "menu.manager.subtitle": "Manage your menu items and categories",
480
- "menu.manager.search": "Search menu...",
481
- "menu.manager.allCategories": "All Categories",
482
- "menu.manager.newItem": "New Item",
483
- "menu.manager.noItems": "No menu items yet",
484
- "menu.manager.noItemsDesc": "Add your first menu item to get started",
485
- "menu.manager.addFirst": "Add first item",
486
- "menu.manager.available": "Available",
487
- "menu.manager.soldOut": "Sold Out",
488
- "menu.manager.hidden": "Hidden",
489
- "menu.manager.pos": "POS",
490
- "menu.manager.delivery": "Delivery",
491
- "menu.manager.featured": "Featured",
492
- "menu.manager.prepTime": "Prep time",
493
- "menu.manager.min": "min",
494
- "menu.manager.addCategory": "Add Category",
495
- "menu.manager.addProduct": "Add Product",
496
- "menu.manager.item": "Item",
497
- "menu.manager.price": "Price",
498
- "menu.manager.deliveryPrice": "Delivery $",
499
- "menu.manager.itemsCount": "{{count}} items",
500
- "menu.manager.noResults": "No categories or items match your search.",
501
- "menu.manager.noResultsDesc": "Try a different search term or category filter.",
502
- "menu.itemForm.newTitle": "New Menu Item",
503
- "menu.itemForm.editTitle": "Edit Menu Item",
504
- "menu.itemForm.name": "Item name",
505
- "menu.itemForm.description": "Description",
506
- "menu.itemForm.price": "Price",
507
- "menu.itemForm.cost": "Cost",
508
- "menu.itemForm.category": "Category",
509
- "menu.itemForm.prepTime": "Prep time (min)",
510
- "menu.itemForm.image": "Image URL",
511
- "menu.itemForm.allergens": "Allergens",
512
- "menu.itemForm.dietaryTags": "Dietary tags",
513
- "menu.itemForm.availableForPos": "Available for POS",
514
- "menu.itemForm.availableForDelivery": "Available for Delivery",
515
- "menu.itemForm.deliveryPrice": "Delivery price",
516
- "menu.itemForm.isFeatured": "Featured item",
517
- "menu.itemForm.save": "Save",
518
- "menu.itemForm.cancel": "Cancel",
519
- "menu.itemForm.saving": "Saving...",
520
- "menu.categories.title": "Categories",
521
- "menu.categories.newCategory": "New Category",
522
- "menu.categories.name": "Category name",
523
- "menu.settings.title": "Menu Settings",
524
- "menu.settings.general": "General",
525
- "menu.settings.taxRate": "Default tax rate",
526
- "menu.settings.currency": "Currency",
527
- "menu.form.categoryName": "Category name",
528
- "menu.form.itemName": "Item name",
529
- "menu.form.description": "Description",
530
- "menu.form.category": "Category",
531
- "menu.form.uncategorized": "Uncategorized",
532
- "menu.form.optional": "optional",
533
- "menu.form.save": "Save",
534
- "menu.form.cancel": "Cancel"
535
- };
536
-
537
- // src/locales/pt-BR.ts
538
- var ptBR = {
539
- "menu.title": "Card\xE1pio",
540
- "menu.nav.manager": "Card\xE1pio",
541
- "menu.nav.categories": "Categorias",
542
- "menu.nav.items": "Itens",
543
- "menu.nav.modifiers": "Modificadores",
544
- "menu.manager.title": "Gerenciar Card\xE1pio",
545
- "menu.manager.subtitle": "Gerencie itens e categorias do card\xE1pio",
546
- "menu.manager.search": "Buscar no card\xE1pio...",
547
- "menu.manager.allCategories": "Todas as Categorias",
548
- "menu.manager.newItem": "Novo Item",
549
- "menu.manager.noItems": "Nenhum item no card\xE1pio",
550
- "menu.manager.noItemsDesc": "Adicione o primeiro item para come\xE7ar",
551
- "menu.manager.addFirst": "Adicionar primeiro item",
552
- "menu.manager.available": "Dispon\xEDvel",
553
- "menu.manager.soldOut": "Esgotado",
554
- "menu.manager.hidden": "Oculto",
555
- "menu.manager.pos": "PDV",
556
- "menu.manager.delivery": "Delivery",
557
- "menu.manager.featured": "Destaque",
558
- "menu.manager.prepTime": "Tempo de preparo",
559
- "menu.manager.min": "min",
560
- "menu.manager.addCategory": "Adicionar Categoria",
561
- "menu.manager.addProduct": "Adicionar Produto",
562
- "menu.manager.item": "Item",
563
- "menu.manager.price": "Pre\xE7o",
564
- "menu.manager.deliveryPrice": "Delivery R$",
565
- "menu.manager.itemsCount": "{{count}} itens",
566
- "menu.manager.noResults": "Nenhuma categoria ou item encontrado.",
567
- "menu.manager.noResultsDesc": "Tente um termo diferente ou filtro de categoria.",
568
- "menu.itemForm.newTitle": "Novo Item do Card\xE1pio",
569
- "menu.itemForm.editTitle": "Editar Item do Card\xE1pio",
570
- "menu.itemForm.name": "Nome do item",
571
- "menu.itemForm.description": "Descri\xE7\xE3o",
572
- "menu.itemForm.price": "Pre\xE7o",
573
- "menu.itemForm.cost": "Custo",
574
- "menu.itemForm.category": "Categoria",
575
- "menu.itemForm.prepTime": "Tempo de preparo (min)",
576
- "menu.itemForm.image": "URL da imagem",
577
- "menu.itemForm.allergens": "Al\xE9rgenos",
578
- "menu.itemForm.dietaryTags": "Tags diet\xE9ticas",
579
- "menu.itemForm.availableForPos": "Dispon\xEDvel no PDV",
580
- "menu.itemForm.availableForDelivery": "Dispon\xEDvel para Delivery",
581
- "menu.itemForm.deliveryPrice": "Pre\xE7o delivery",
582
- "menu.itemForm.isFeatured": "Item em destaque",
583
- "menu.itemForm.save": "Salvar",
584
- "menu.itemForm.cancel": "Cancelar",
585
- "menu.itemForm.saving": "Salvando...",
586
- "menu.categories.title": "Categorias",
587
- "menu.categories.newCategory": "Nova Categoria",
588
- "menu.categories.name": "Nome da categoria",
589
- "menu.settings.title": "Configura\xE7\xF5es do Card\xE1pio",
590
- "menu.settings.general": "Geral",
591
- "menu.settings.taxRate": "Taxa padr\xE3o",
592
- "menu.settings.currency": "Moeda",
593
- "menu.form.categoryName": "Nome da categoria",
594
- "menu.form.itemName": "Nome do item",
595
- "menu.form.description": "Descri\xE7\xE3o",
596
- "menu.form.category": "Categoria",
597
- "menu.form.uncategorized": "Sem categoria",
598
- "menu.form.optional": "opcional",
599
- "menu.form.save": "Salvar",
600
- "menu.form.cancel": "Cancelar"
601
- };
602
-
603
- // src/locales/index.ts
604
- var menuLocales = { en, "pt-BR": ptBR };
605
- var DEFAULT_CATEGORY_KIND = "menu_category";
606
- var MENU_METADATA_KEY = "fayzMenu";
607
- function nowIso() {
608
- return (/* @__PURE__ */ new Date()).toISOString();
609
- }
610
- function slugify(value) {
611
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
612
- }
613
- function numberValue(value) {
614
- if (typeof value === "number") return value;
615
- if (typeof value === "string") return Number(value) || 0;
616
- return 0;
617
- }
618
- function menuMetadata(row) {
619
- const raw = row.metadata?.[MENU_METADATA_KEY];
620
- return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
621
- }
622
- function rowToCategory(row, itemCount = 0) {
623
- return {
624
- id: row.id,
625
- name: row.name,
626
- parentId: row.parent_id ?? void 0,
627
- icon: row.icon ?? void 0,
628
- color: row.color ?? void 0,
629
- sortOrder: row.sort_order ?? 0,
630
- isActive: row.is_active ?? true,
631
- itemCount,
632
- tenantId: row.tenant_id ?? "runtime-tenant",
633
- createdAt: row.created_at ?? nowIso(),
634
- updatedAt: row.updated_at ?? row.created_at ?? nowIso()
635
- };
636
- }
637
- function rowToItem(row, categoryName) {
638
- const metadata = menuMetadata(row);
639
- const status = metadata.status ?? row.status ?? "available";
640
- return {
641
- id: row.id,
642
- name: row.name,
643
- description: row.description ?? void 0,
644
- price: numberValue(row.price),
645
- cost: numberValue(row.cost),
646
- currency: row.currency ?? "BRL",
647
- sku: row.sku ?? void 0,
648
- imageUrl: row.image_url ?? void 0,
649
- categoryId: row.category_id ?? void 0,
650
- categoryName,
651
- status,
652
- isActive: row.is_active ?? true,
653
- prepTimeMinutes: typeof metadata.prepTimeMinutes === "number" ? metadata.prepTimeMinutes : void 0,
654
- allergens: Array.isArray(metadata.allergens) ? metadata.allergens : void 0,
655
- dietaryTags: Array.isArray(metadata.dietaryTags) ? metadata.dietaryTags : row.tags ?? void 0,
656
- availableForPos: typeof metadata.availableForPos === "boolean" ? metadata.availableForPos : true,
657
- availableForDelivery: typeof metadata.availableForDelivery === "boolean" ? metadata.availableForDelivery : true,
658
- deliveryPrice: typeof metadata.deliveryPrice === "number" ? metadata.deliveryPrice : void 0,
659
- sortOrder: typeof metadata.sortOrder === "number" ? metadata.sortOrder : 0,
660
- isFeatured: typeof metadata.isFeatured === "boolean" ? metadata.isFeatured : false,
661
- modifierGroupIds: Array.isArray(metadata.modifierGroupIds) ? metadata.modifierGroupIds : void 0,
662
- metadata: row.metadata ?? {},
663
- tenantId: row.tenant_id ?? "runtime-tenant",
664
- createdAt: row.created_at ?? nowIso(),
665
- updatedAt: row.updated_at ?? row.created_at ?? nowIso()
666
- };
667
- }
668
- function categoryPayload(input, kind) {
669
- return {
670
- kind,
671
- name: input.name,
672
- slug: slugify(input.name),
673
- parent_id: input.parentId ?? null,
674
- icon: input.icon ?? null,
675
- color: input.color ?? null,
676
- sort_order: input.sortOrder ?? 0,
677
- is_active: true
678
- };
679
- }
680
- function itemMetadata(input, existing) {
681
- return {
682
- ...existing ?? {},
683
- [MENU_METADATA_KEY]: {
684
- ...existing?.[MENU_METADATA_KEY] && typeof existing[MENU_METADATA_KEY] === "object" ? existing[MENU_METADATA_KEY] : {},
685
- status: input.status,
686
- prepTimeMinutes: input.prepTimeMinutes,
687
- allergens: input.allergens,
688
- dietaryTags: input.dietaryTags,
689
- availableForPos: input.availableForPos ?? true,
690
- availableForDelivery: input.availableForDelivery ?? true,
691
- deliveryPrice: input.deliveryPrice,
692
- isFeatured: input.isFeatured ?? false,
693
- modifierGroupIds: input.modifierGroupIds
694
- }
695
- };
696
- }
697
- function itemCreatePayload(input) {
698
- return {
699
- name: input.name,
700
- description: input.description ?? null,
701
- price: input.price,
702
- cost: input.cost ?? null,
703
- currency: "BRL",
704
- image_url: input.imageUrl ?? null,
705
- category_id: input.categoryId ?? null,
706
- status: input.status === "hidden" ? "archived" : "active",
707
- is_active: input.status !== "hidden",
708
- tags: input.dietaryTags ?? [],
709
- metadata: itemMetadata(input)
710
- };
711
- }
712
- function itemUpdatePayload(input, existing) {
713
- const payload = {};
714
- if (input.name !== void 0) payload.name = input.name;
715
- if (input.description !== void 0) payload.description = input.description;
716
- if (input.price !== void 0) payload.price = input.price;
717
- if (input.cost !== void 0) payload.cost = input.cost;
718
- if (input.imageUrl !== void 0) payload.image_url = input.imageUrl;
719
- if (input.categoryId !== void 0) payload.category_id = input.categoryId;
720
- if (input.status !== void 0) {
721
- payload.status = input.status === "hidden" ? "archived" : "active";
722
- payload.is_active = input.status !== "hidden";
723
- }
724
- if (input.dietaryTags !== void 0) payload.tags = input.dietaryTags;
725
- payload.metadata = itemMetadata(input, existing?.metadata);
726
- return payload;
727
- }
728
- function matchesClientQuery(item, query) {
729
- if (query.status) {
730
- const statuses = Array.isArray(query.status) ? query.status : [query.status];
731
- if (!statuses.includes(item.status)) return false;
732
- }
733
- if (query.availableForPos !== void 0 && item.availableForPos !== query.availableForPos) return false;
734
- if (query.availableForDelivery !== void 0 && item.availableForDelivery !== query.availableForDelivery) return false;
735
- if (query.isFeatured !== void 0 && item.isFeatured !== query.isFeatured) return false;
736
- if (query.search) {
737
- const search = query.search.toLowerCase();
738
- return item.name.toLowerCase().includes(search) || (item.description?.toLowerCase().includes(search) ?? false);
739
- }
740
- return true;
741
- }
742
- function paginate2(items, page, pageSize) {
743
- const currentPage = page ?? 1;
744
- const limit = pageSize ?? 50;
745
- const start = (currentPage - 1) * limit;
746
- return { data: items.slice(start, start + limit), total: items.length };
747
- }
748
- function createFayzMenuProvider(options = {}) {
749
- const client = sdk.createFayzClient(options);
750
- const projectId = options.projectId;
751
- const schema = options.schema;
752
- const runtime = options.runtime;
753
- const categoriesTable = options.categoriesTable ?? "categories";
754
- const productsTable = options.productsTable ?? "products";
755
- const categoryKind = options.categoryKind ?? DEFAULT_CATEGORY_KIND;
756
- const tableOptions = { projectId, schema, runtime };
757
- async function getCategoriesRaw() {
758
- const response = await client.data.listRows({
759
- ...tableOptions,
760
- table: categoriesTable,
761
- filters: [
762
- { column: "kind", operator: "eq", value: categoryKind },
763
- { column: "is_active", operator: "eq", value: true }
764
- ],
765
- sortColumn: "sort_order",
766
- sortDirection: "asc",
767
- limit: 500
768
- });
769
- return response.rows;
770
- }
771
- async function getItemsRaw() {
772
- const response = await client.data.listRows({
773
- ...tableOptions,
774
- table: productsTable,
775
- filters: [{ column: "is_active", operator: "eq", value: true }],
776
- sortColumn: "name",
777
- sortDirection: "asc",
778
- limit: 1e3
779
- });
780
- return response.rows.filter((row) => {
781
- const metadata = menuMetadata(row);
782
- return metadata.kind === "menu_item" || metadata.status != null || row.category_id != null;
783
- });
784
- }
785
- async function getCategoryMap() {
786
- const categories = await getCategoriesRaw();
787
- return new Map(categories.map((category) => [category.id, category]));
788
- }
789
- async function getItems() {
790
- const [rows, categoryMap] = await Promise.all([getItemsRaw(), getCategoryMap()]);
791
- return rows.map((row) => rowToItem(row, row.category_id ? categoryMap.get(row.category_id)?.name : void 0)).sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name));
792
- }
793
- return {
794
- async getCategories() {
795
- const [categories, items] = await Promise.all([getCategoriesRaw(), getItemsRaw()]);
796
- const counts = /* @__PURE__ */ new Map();
797
- for (const item of items) {
798
- if (item.category_id) counts.set(item.category_id, (counts.get(item.category_id) ?? 0) + 1);
799
- }
800
- return categories.map((category) => rowToCategory(category, counts.get(category.id) ?? 0));
801
- },
802
- async getCategoryById(id) {
803
- const response = await client.data.listRows({
804
- ...tableOptions,
805
- table: categoriesTable,
806
- filters: [{ column: "id", operator: "eq", value: id }],
807
- limit: 1
808
- });
809
- return response.rows[0] ? rowToCategory(response.rows[0]) : null;
810
- },
811
- async createCategory(input) {
812
- const row = await client.data.createRow({
813
- ...tableOptions,
814
- table: categoriesTable,
815
- row: categoryPayload(input, categoryKind)
816
- });
817
- return rowToCategory(row);
818
- },
819
- async updateCategory(id, data) {
820
- const row = await client.data.updateRow({
821
- ...tableOptions,
822
- table: categoriesTable,
823
- primaryKeys: { id },
824
- row: {
825
- name: data.name,
826
- parent_id: data.parentId,
827
- icon: data.icon,
828
- color: data.color,
829
- sort_order: data.sortOrder,
830
- is_active: data.isActive
831
- }
832
- });
833
- return rowToCategory(row);
834
- },
835
- async deleteCategory(id) {
836
- await client.data.updateRow({
837
- ...tableOptions,
838
- table: categoriesTable,
839
- primaryKeys: { id },
840
- row: { is_active: false }
841
- });
842
- },
843
- async reorderCategories(ids) {
844
- await Promise.all(ids.map((id, sortOrder) => this.updateCategory(id, { sortOrder })));
845
- },
846
- async getMenuItems(query) {
847
- const items = (await getItems()).filter((item) => matchesClientQuery(item, query));
848
- return paginate2(items, query.page, query.pageSize);
849
- },
850
- async getMenuItemById(id) {
851
- const response = await client.data.listRows({
852
- ...tableOptions,
853
- table: productsTable,
854
- filters: [{ column: "id", operator: "eq", value: id }],
855
- limit: 1
856
- });
857
- if (!response.rows[0]) return null;
858
- const categoryMap = await getCategoryMap();
859
- const row = response.rows[0];
860
- return rowToItem(row, row.category_id ? categoryMap.get(row.category_id)?.name : void 0);
861
- },
862
- async createMenuItem(input) {
863
- const row = await client.data.createRow({
864
- ...tableOptions,
865
- table: productsTable,
866
- row: itemCreatePayload(input)
867
- });
868
- const categoryMap = await getCategoryMap();
869
- return rowToItem(row, row.category_id ? categoryMap.get(row.category_id)?.name : void 0);
870
- },
871
- async updateMenuItem(id, data) {
872
- const existing = await this.getMenuItemById(id);
873
- const row = await client.data.updateRow({
874
- ...tableOptions,
875
- table: productsTable,
876
- primaryKeys: { id },
877
- row: itemUpdatePayload(data, existing ?? void 0)
878
- });
879
- const categoryMap = await getCategoryMap();
880
- return rowToItem(row, row.category_id ? categoryMap.get(row.category_id)?.name : void 0);
881
- },
882
- async deleteMenuItem(id) {
883
- await client.data.updateRow({
884
- ...tableOptions,
885
- table: productsTable,
886
- primaryKeys: { id },
887
- row: { is_active: false }
888
- });
889
- },
890
- async toggleAvailability(id, field, value) {
891
- return this.updateMenuItem(id, { [field]: value });
892
- },
893
- async reorderItems(categoryId, ids) {
894
- await Promise.all(ids.map(async (id, sortOrder) => {
895
- const existing = await this.getMenuItemById(id);
896
- await client.data.updateRow({
897
- ...tableOptions,
898
- table: productsTable,
899
- primaryKeys: { id },
900
- row: { metadata: itemMetadata({ }, { ...existing?.metadata, [MENU_METADATA_KEY]: { ...existing?.metadata?.[MENU_METADATA_KEY], sortOrder } }) }
901
- });
902
- }));
903
- },
904
- async getModifierGroups() {
905
- return [];
906
- },
907
- async createModifierGroup() {
908
- throw new Error("Menu modifier groups require a dedicated Fayz table/broker contract.");
909
- },
910
- async updateModifierGroup() {
911
- throw new Error("Menu modifier groups require a dedicated Fayz table/broker contract.");
912
- },
913
- async deleteModifierGroup() {
914
- throw new Error("Menu modifier groups require a dedicated Fayz table/broker contract.");
915
- },
916
- async getSummary() {
917
- const [items, categories] = await Promise.all([getItems(), this.getCategories()]);
918
- const prices = items.filter((item) => item.price > 0).map((item) => item.price);
919
- return {
920
- totalItems: items.length,
921
- availableItems: items.filter((item) => item.status === "available").length,
922
- soldOutItems: items.filter((item) => item.status === "sold_out").length,
923
- hiddenItems: items.filter((item) => item.status === "hidden").length,
924
- categoryCount: categories.length,
925
- averagePrice: prices.length ? prices.reduce((sum, price) => sum + price, 0) / prices.length : 0,
926
- featuredCount: items.filter((item) => item.isFeatured).length
927
- };
928
- }
929
- };
930
- }
931
-
932
- // src/index.ts
933
- var MenuPage = React__default.default.lazy(() => import('./MenuPage-VMI2YAPY.cjs').then((m) => ({ default: m.MenuPage })));
934
- var DEFAULT_LABELS = {
935
- pageTitle: "Menu",
936
- menuManager: "Menu Manager",
937
- categories: "Categories",
938
- newItem: "New Item"
939
- };
940
- var DEFAULT_CURRENCY = { code: "BRL", locale: "pt-BR", symbol: "R$" };
941
- function resolveConfig(options) {
942
- return {
943
- modules: {
944
- modifiers: options?.modules?.modifiers !== false,
945
- deliveryPricing: options?.modules?.deliveryPricing === true
946
- },
947
- labels: { ...DEFAULT_LABELS, ...options?.labels },
948
- currency: { ...DEFAULT_CURRENCY, ...options?.currency }
949
- };
950
- }
951
- function createMenuPlugin(options) {
952
- const config = resolveConfig(options);
953
- core.registerTranslations(menuLocales);
954
- const provider = options?.dataProvider ?? createMockMenuProvider();
955
- const store = createMenuStore(provider);
956
- const PageComponent = () => React__default.default.createElement(
957
- React__default.default.Suspense,
958
- { fallback: null },
959
- React__default.default.createElement(MenuPage, { config, provider, store, registries: menuRegistries })
960
- );
961
- return {
962
- id: "menu",
963
- name: config.labels.pageTitle,
964
- icon: "UtensilsCrossed",
965
- version: "1.0.0",
966
- scope: options?.scope ?? "vertical",
967
- verticalId: options?.verticalId,
968
- // Digital menu serves both internal ops (saas) and online ordering
969
- // (ecommerce) — genuinely dual-surface, low confidence, revisit if a
970
- // real host disagrees.
971
- scaffolds: ["saas", "ecommerce"],
972
- defaultEnabled: true,
973
- dependencies: [],
974
- navigation: [
975
- {
976
- section: options?.navSection ?? "main",
977
- position: options?.navPosition ?? 5,
978
- label: config.labels.pageTitle,
979
- route: "/menu",
980
- icon: "UtensilsCrossed",
981
- permission: { feature: "menu", action: "read" }
982
- }
983
- ],
984
- routes: [
985
- {
986
- path: "/menu",
987
- component: PageComponent,
988
- permission: { feature: "menu", action: "read" }
989
- }
990
- ],
991
- widgets: [],
992
- aiTools: [
993
- {
994
- id: "menu.list-items",
995
- name: "listMenuItems",
996
- description: "Lists menu items, optionally filtered by category or status.",
997
- icon: "UtensilsCrossed",
998
- mode: "read",
999
- category: "Menu",
1000
- parameters: {
1001
- type: "object",
1002
- properties: {
1003
- category: { type: "string", description: "Category name to filter by" },
1004
- status: { type: "string", enum: ["available", "sold_out", "hidden"] }
1005
- }
1006
- },
1007
- suggestions: [
1008
- { label: "What items are on the menu?" },
1009
- { label: "Which items are sold out right now?" }
1010
- ]
1011
- },
1012
- {
1013
- id: "menu.toggle-availability",
1014
- name: "toggleMenuItemAvailability",
1015
- description: "Marks a menu item as available or sold out.",
1016
- icon: "ToggleLeft",
1017
- mode: "persist",
1018
- category: "Menu",
1019
- parameters: {
1020
- type: "object",
1021
- properties: {
1022
- itemName: { type: "string", description: "Menu item name" },
1023
- status: { type: "string", enum: ["available", "sold_out"] }
1024
- },
1025
- required: ["itemName", "status"]
1026
- },
1027
- permission: { feature: "menu", action: "edit" }
1028
- }
1029
- ],
1030
- registries: menuRegistries,
1031
- settings: [
1032
- {
1033
- id: "menu",
1034
- label: config.labels.pageTitle,
1035
- icon: "UtensilsCrossed",
1036
- component: (() => {
1037
- const MenuSettingsTab = () => React__default.default.createElement(saas.PluginSettingsPanel, {
1038
- title: "Menu Settings",
1039
- subtitle: "Categories, allergens, and modifiers",
1040
- registries: menuRegistries,
1041
- routeBase: "/settings/menu"
1042
- });
1043
- MenuSettingsTab.displayName = "MenuSettingsTab";
1044
- return MenuSettingsTab;
1045
- })(),
1046
- order: 20,
1047
- permission: { feature: "menu", action: "read" }
1048
- }
1049
- ],
1050
- locales: menuLocales
1051
- };
1052
- }
1053
-
1054
- exports.createFayzMenuProvider = createFayzMenuProvider;
1055
- exports.createMenuPlugin = createMenuPlugin;
1056
- //# sourceMappingURL=index.cjs.map
1057
- //# sourceMappingURL=index.cjs.map