@fayz-ai/plugin-tables 0.2.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/TablesContext.d.ts +35 -0
  4. package/dist/TablesContext.d.ts.map +1 -0
  5. package/dist/TablesPage-5GCFGSX3.cjs +276 -0
  6. package/dist/TablesPage-5GCFGSX3.cjs.map +1 -0
  7. package/dist/TablesPage-IM732NWO.js +274 -0
  8. package/dist/TablesPage-IM732NWO.js.map +1 -0
  9. package/dist/TablesPage.d.ts +13 -0
  10. package/dist/TablesPage.d.ts.map +1 -0
  11. package/dist/data/fayz.d.ts +15 -0
  12. package/dist/data/fayz.d.ts.map +1 -0
  13. package/dist/data/mock.d.ts +3 -0
  14. package/dist/data/mock.d.ts.map +1 -0
  15. package/dist/data/types.d.ts +24 -0
  16. package/dist/data/types.d.ts.map +1 -0
  17. package/dist/index.cjs +887 -0
  18. package/dist/index.cjs.map +1 -0
  19. package/dist/index.d.ts +33 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +880 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/locales/en.d.ts +2 -0
  24. package/dist/locales/en.d.ts.map +1 -0
  25. package/dist/locales/index.d.ts +2 -0
  26. package/dist/locales/index.d.ts.map +1 -0
  27. package/dist/locales/pt-BR.d.ts +2 -0
  28. package/dist/locales/pt-BR.d.ts.map +1 -0
  29. package/dist/registries.d.ts +3 -0
  30. package/dist/registries.d.ts.map +1 -0
  31. package/dist/store.d.ts +28 -0
  32. package/dist/store.d.ts.map +1 -0
  33. package/dist/types.d.ts +84 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/views/FloorPlanView.d.ts +3 -0
  36. package/dist/views/FloorPlanView.d.ts.map +1 -0
  37. package/package.json +55 -0
  38. package/src/TablesContext.tsx +41 -0
  39. package/src/TablesPage.tsx +20 -0
  40. package/src/data/fayz.ts +348 -0
  41. package/src/data/mock.ts +322 -0
  42. package/src/data/types.ts +31 -0
  43. package/src/index.ts +179 -0
  44. package/src/locales/en.ts +45 -0
  45. package/src/locales/index.ts +4 -0
  46. package/src/locales/pt-BR.ts +45 -0
  47. package/src/registries.ts +32 -0
  48. package/src/store.ts +178 -0
  49. package/src/types.ts +116 -0
  50. package/src/views/FloorPlanView.tsx +388 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Faya Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @fayz-ai/plugin-tables
2
+
3
+ > A live floor plan for restaurants — seat, track, and turn tables in real time.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@fayz-ai/plugin-tables.svg)](https://www.npmjs.com/package/@fayz-ai/plugin-tables)
6
+ [![license](https://img.shields.io/npm/l/@fayz-ai/plugin-tables.svg)](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
7
+
8
+ Restaurants don't run on records — they run on the room. plugin-tables gives the food vertical a real floor plan: zones, tables, and live status (available, occupied, reserved, cleaning), with seat and close flows wired to your own backend. It's the difference between a generic CRM and software a host can actually work a Friday night on.
9
+
10
+ It snaps into a `defineSaas` app as the vertical surface for food. Configure zones, toggle reservations and session history, and hand the AI assistant the ability to answer "which tables are free?" or seat guests by table number — all permission-gated, all in the same app as bookings and payments.
11
+
12
+ ## What's inside
13
+ - **`/tables` route + nav entry** (icon `MapPin`) with a live floor plan view
14
+ - **Zones** — configurable defaults (Indoor, Outdoor, Bar) via `defaultZones`
15
+ - **Optional modules** — `reservations` (off by default) and `sessionHistory` (on by default)
16
+ - **Lifecycle hooks** — `onTableSeated` and `onTableClosed` to bridge sessions into your orders/payments
17
+ - **AI tools** — `getTableAvailability` (read) and `seatGuests` (persist, `tables:edit`)
18
+ - **Settings tab** — zone and floor-plan configuration via `PluginSettingsPanel`
19
+ - **Pluggable data layer** — `dataProvider` option, with `createFayzTablesProvider` exported, or a built-in mock provider for local dev
20
+ - **Vertical scope** — defaults to `scope: 'vertical'`, with i18n locales included
21
+
22
+ ## Install
23
+ ```bash
24
+ npm install @fayz-ai/plugin-tables
25
+ ```
26
+ Peer deps: `react`, `react-dom`. Runtime deps: `@fayz-ai/core`, `@fayz-ai/ui`, `@fayz-ai/saas`, `@fayz-ai/sdk`.
27
+
28
+ ## Usage
29
+ ```tsx
30
+ import { defineSaas } from '@fayz-ai/saas'
31
+ import { createTablesPlugin } from '@fayz-ai/plugin-tables'
32
+
33
+ export const app = defineSaas({
34
+ // ...
35
+ plugins: [
36
+ createTablesPlugin({
37
+ modules: { reservations: true, sessionHistory: true },
38
+ defaultZones: [
39
+ { name: 'Indoor', color: '#3b82f6' },
40
+ { name: 'Patio', color: '#22c55e' },
41
+ ],
42
+ onTableSeated: async (session) => createOrderFor(session),
43
+ onTableClosed: async (session) => closeOrderFor(session),
44
+ }),
45
+ ],
46
+ })
47
+ ```
48
+
49
+ ## Part of the Fayz SDK
50
+ One of the composable plugins for `@fayz-ai/saas` — this is the food vertical's room-management surface.
51
+
52
+ ## Roadmap & contributing
53
+ Built and evolving in the open. See the [Fayz SDK roadmap](../../docs/ROADMAP.md#plugin-tables) for current gaps, missing features, and good first issues.
@@ -0,0 +1,35 @@
1
+ import type { TablesDataProvider } from './data/types';
2
+ import type { TablesUIState } from './store';
3
+ import type { TableSession } from './types';
4
+ export interface TablesPluginLabels {
5
+ pageTitle: string;
6
+ pageSubtitle: string;
7
+ tableSingular: string;
8
+ tablePlural: string;
9
+ sessionSingular: string;
10
+ sessionPlural: string;
11
+ }
12
+ export interface TablesModules {
13
+ reservations: boolean;
14
+ sessionHistory: boolean;
15
+ }
16
+ export interface ResolvedTablesConfig {
17
+ modules: TablesModules;
18
+ labels: TablesPluginLabels;
19
+ defaultZones: Array<{
20
+ name: string;
21
+ color?: string;
22
+ }>;
23
+ onTableSeated?: (session: TableSession) => Promise<string | undefined>;
24
+ onTableClosed?: (session: TableSession) => Promise<void>;
25
+ }
26
+ export declare const TablesContextProvider: import("react").FC<{
27
+ config: ResolvedTablesConfig;
28
+ provider: TablesDataProvider;
29
+ store: import("zustand").StoreApi<TablesUIState>;
30
+ children?: React.ReactNode;
31
+ }>;
32
+ export declare const useTablesConfig: () => ResolvedTablesConfig;
33
+ export declare const useTablesProvider: () => TablesDataProvider;
34
+ export declare const useTablesStore: <T>(selector: (state: TablesUIState) => T) => T;
35
+ //# sourceMappingURL=TablesContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TablesContext.d.ts","sourceRoot":"","sources":["../src/TablesContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAM3C,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;CACtB;AAMD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACrD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACtE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACzD;AAID,eAAO,MAAM,qBAAqB;;;;;EAAsB,CAAA;AACxD,eAAO,MAAM,eAAe,4BAAgB,CAAA;AAC5C,eAAO,MAAM,iBAAiB,0BAAkB,CAAA;AAChD,eAAO,MAAM,cAAc,iDAAe,CAAA"}
@@ -0,0 +1,276 @@
1
+ 'use strict';
2
+
3
+ var saas = require('@fayz-ai/saas');
4
+ var react = require('react');
5
+ var lucideReact = require('lucide-react');
6
+ var core = require('@fayz-ai/core');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ // src/TablesContext.tsx
10
+ var ctx = saas.createPluginContext("TablesPage");
11
+ var TablesContextProvider = ctx.ContextProvider;
12
+ var useTablesConfig = ctx.useConfig;
13
+ ctx.useProvider;
14
+ var useTablesStore = ctx.useStore;
15
+ var statusConfig = {
16
+ available: { color: "text-success", bg: "bg-success/10 border-success/30 hover:bg-success/20" },
17
+ occupied: { color: "text-primary", bg: "bg-primary/10 border-primary/30 hover:bg-primary/20" },
18
+ reserved: { color: "text-accent", bg: "bg-accent/10 border-accent/30 hover:bg-accent/20" },
19
+ cleaning: { color: "text-muted-foreground", bg: "bg-muted border-border hover:bg-muted/80" }
20
+ };
21
+ function formatElapsed(minutes, minLabel) {
22
+ if (minutes == null) return "";
23
+ if (minutes < 60) return `${minutes}${minLabel}`;
24
+ const h = Math.floor(minutes / 60);
25
+ const m = minutes % 60;
26
+ return m > 0 ? `${h}h ${m}${minLabel}` : `${h}h`;
27
+ }
28
+ function formatCurrency(value) {
29
+ if (value == null) return "";
30
+ return new Intl.NumberFormat(void 0, { style: "currency", currency: "BRL" }).format(value);
31
+ }
32
+ function TableCard({ table, isSelected, onClick, t }) {
33
+ const config = statusConfig[table.status];
34
+ const isLarge = table.seats >= 6;
35
+ const minLabel = t("tables.floorPlan.min");
36
+ return /* @__PURE__ */ jsxRuntime.jsxs(
37
+ "button",
38
+ {
39
+ onClick,
40
+ className: `
41
+ relative flex flex-col items-center justify-center rounded-lg border-2 p-3 transition-all cursor-pointer
42
+ ${config.bg}
43
+ ${isLarge ? "col-span-2" : ""}
44
+ ${isSelected ? "ring-2 ring-primary ring-offset-2" : ""}
45
+ `,
46
+ style: { minHeight: "120px" },
47
+ children: [
48
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-2xl font-bold ${config.color}`, children: table.number }),
49
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground mt-1", children: [
50
+ table.seats,
51
+ " ",
52
+ t("tables.floorPlan.seats")
53
+ ] }),
54
+ table.status === "occupied" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
55
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium mt-1", children: [
56
+ table.currentGuests,
57
+ " ",
58
+ t("tables.detail.guests").toLowerCase()
59
+ ] }),
60
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: formatElapsed(table.currentElapsedMinutes, minLabel) })
61
+ ] }),
62
+ /* @__PURE__ */ jsxRuntime.jsx(
63
+ "span",
64
+ {
65
+ className: `
66
+ absolute -top-2 -right-2 text-[10px] px-1.5 py-0.5 rounded-full font-medium border
67
+ ${table.status === "occupied" ? "bg-primary text-primary-foreground border-primary" : "bg-secondary text-secondary-foreground border-border"}
68
+ `,
69
+ children: t(`tables.floorPlan.${table.status}`)
70
+ }
71
+ )
72
+ ]
73
+ }
74
+ );
75
+ }
76
+ function DetailPanel({ table, t }) {
77
+ const updateTableStatus = useTablesStore((s) => s.updateTableStatus);
78
+ const seatGuests = useTablesStore((s) => s.seatGuests);
79
+ const closeSession = useTablesStore((s) => s.closeSession);
80
+ const minLabel = t("tables.floorPlan.min");
81
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card p-5 sticky top-6", children: [
82
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-4", children: [
83
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold", children: table.name || `#${table.number}` }),
84
+ /* @__PURE__ */ jsxRuntime.jsx(
85
+ "span",
86
+ {
87
+ className: `
88
+ text-xs px-2 py-1 rounded-full font-medium border
89
+ ${table.status === "occupied" ? "bg-primary text-primary-foreground border-primary" : "bg-secondary text-secondary-foreground border-border"}
90
+ `,
91
+ children: t(`tables.floorPlan.${table.status}`)
92
+ }
93
+ )
94
+ ] }),
95
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "space-y-3 text-sm", children: [
96
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
97
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: t("tables.floorPlan.seats") }),
98
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "font-medium", children: table.seats })
99
+ ] }),
100
+ table.zoneName && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
101
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: t("tables.floorPlan.zone") }),
102
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "font-medium", children: table.zoneName })
103
+ ] }),
104
+ table.status === "occupied" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
105
+ table.currentGuests != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
106
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: t("tables.detail.guests") }),
107
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "font-medium", children: table.currentGuests })
108
+ ] }),
109
+ table.currentWaiterName && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
110
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: t("tables.detail.waiter") }),
111
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "font-medium", children: table.currentWaiterName })
112
+ ] }),
113
+ table.currentOrderId && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
114
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: t("tables.floorPlan.order") }),
115
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "font-medium", children: table.currentOrderId })
116
+ ] }),
117
+ table.currentTotal != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: t("tables.detail.total") }),
119
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "font-bold text-base", children: formatCurrency(table.currentTotal) })
120
+ ] }),
121
+ table.currentElapsedMinutes != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
122
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: t("tables.detail.elapsed") }),
123
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "font-medium", children: formatElapsed(table.currentElapsedMinutes, minLabel) })
124
+ ] })
125
+ ] })
126
+ ] }),
127
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 grid grid-cols-2 gap-2", children: [
128
+ table.status === "available" && /* @__PURE__ */ jsxRuntime.jsx(
129
+ "button",
130
+ {
131
+ className: "col-span-2 inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
132
+ onClick: () => seatGuests({ tableId: table.id, guests: 2 }),
133
+ children: t("tables.detail.seatGuests")
134
+ }
135
+ ),
136
+ table.status === "occupied" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
137
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors", children: t("tables.detail.viewOrder") }),
138
+ /* @__PURE__ */ jsxRuntime.jsx(
139
+ "button",
140
+ {
141
+ className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
142
+ onClick: () => {
143
+ if (table.currentSessionId) closeSession(table.currentSessionId);
144
+ },
145
+ children: t("tables.detail.closeTable")
146
+ }
147
+ )
148
+ ] }),
149
+ table.status === "cleaning" && /* @__PURE__ */ jsxRuntime.jsx(
150
+ "button",
151
+ {
152
+ className: "col-span-2 inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
153
+ onClick: () => updateTableStatus({ tableId: table.id, status: "available" }),
154
+ children: t("tables.detail.markClean")
155
+ }
156
+ ),
157
+ table.status === "reserved" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
158
+ /* @__PURE__ */ jsxRuntime.jsx(
159
+ "button",
160
+ {
161
+ className: "inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors",
162
+ onClick: () => updateTableStatus({ tableId: table.id, status: "available" }),
163
+ children: t("tables.detail.cancel")
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsxRuntime.jsx(
167
+ "button",
168
+ {
169
+ className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
170
+ onClick: () => seatGuests({ tableId: table.id, guests: 2 }),
171
+ children: t("tables.floorPlan.checkIn")
172
+ }
173
+ )
174
+ ] })
175
+ ] })
176
+ ] });
177
+ }
178
+ function FloorPlanSkeleton() {
179
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
180
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-20 rounded bg-muted animate-pulse" }, i)) }),
181
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid lg:grid-cols-[1fr_320px] gap-6", children: [
182
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6", children: Array.from({ length: 2 }).map((_, z) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card p-5", children: [
183
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-24 rounded bg-muted animate-pulse mb-4" }),
184
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-3", children: Array.from({ length: 4 }).map((_2, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[120px] rounded-lg bg-muted animate-pulse" }, i)) })
185
+ ] }, z)) }),
186
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[300px] rounded-lg bg-muted animate-pulse" })
187
+ ] })
188
+ ] });
189
+ }
190
+ function FloorPlanView() {
191
+ const t = core.useTranslation();
192
+ useTablesConfig();
193
+ const tables = useTablesStore((s) => s.tables);
194
+ const zones = useTablesStore((s) => s.zones);
195
+ const tablesLoading = useTablesStore((s) => s.tablesLoading);
196
+ const fetchTables = useTablesStore((s) => s.fetchTables);
197
+ const fetchZones = useTablesStore((s) => s.fetchZones);
198
+ const selectedTableId = useTablesStore((s) => s.selectedTableId);
199
+ const selectTable = useTablesStore((s) => s.selectTable);
200
+ react.useEffect(() => {
201
+ fetchTables();
202
+ fetchZones();
203
+ }, [fetchTables, fetchZones]);
204
+ const tablesByZone = react.useMemo(() => {
205
+ const map = /* @__PURE__ */ new Map();
206
+ for (const zone of zones) {
207
+ map.set(zone.id, { zoneName: zone.name, color: zone.color, tables: [] });
208
+ }
209
+ for (const table of tables) {
210
+ const zoneId = table.zone;
211
+ if (!map.has(zoneId)) {
212
+ map.set(zoneId, { zoneName: table.zoneName || zoneId, tables: [] });
213
+ }
214
+ map.get(zoneId).tables.push(table);
215
+ }
216
+ return Array.from(map.entries()).filter(([, v]) => v.tables.length > 0).map(([id, v]) => ({ id, ...v }));
217
+ }, [tables, zones]);
218
+ const occupiedCount = tables.filter((t2) => t2.status === "occupied").length;
219
+ const availableCount = tables.filter((t2) => t2.status === "available").length;
220
+ const selectedTable = selectedTableId ? tables.find((t2) => t2.id === selectedTableId) ?? null : null;
221
+ if (tablesLoading && tables.length === 0) {
222
+ return /* @__PURE__ */ jsxRuntime.jsx(FloorPlanSkeleton, {});
223
+ }
224
+ if (!tablesLoading && tables.length === 0) {
225
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-20 text-center", children: [
226
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UtensilsCrossed, { className: "h-12 w-12 text-muted-foreground/40 mb-4" }),
227
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold", children: t("tables.floorPlan.noTables") }),
228
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-1", children: t("tables.floorPlan.noTablesDesc") })
229
+ ] });
230
+ }
231
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
232
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between flex-wrap gap-4", children: [
233
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm", children: t("tables.floorPlan.occupiedOf").replace("{occupied}", String(occupiedCount)).replace("{available}", String(availableCount)).replace("{total}", String(tables.length)) }) }),
234
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-3 flex-wrap", children: Object.keys(statusConfig).map((status) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [
235
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `h-3 w-3 rounded-full ${statusConfig[status].bg} border` }),
236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t(`tables.floorPlan.${status}`) })
237
+ ] }, status)) })
238
+ ] }),
239
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid lg:grid-cols-[1fr_320px] gap-6", children: [
240
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6", children: tablesByZone.map((zone) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card p-5", children: [
241
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-4", children: [
242
+ zone.color && /* @__PURE__ */ jsxRuntime.jsx(
243
+ "div",
244
+ {
245
+ className: "h-3 w-3 rounded-full border",
246
+ style: { backgroundColor: zone.color }
247
+ }
248
+ ),
249
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-muted-foreground uppercase tracking-wider", children: zone.zoneName })
250
+ ] }),
251
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3", children: zone.tables.map((table) => /* @__PURE__ */ jsxRuntime.jsx(
252
+ TableCard,
253
+ {
254
+ table,
255
+ isSelected: table.id === selectedTableId,
256
+ onClick: () => selectTable(table.id === selectedTableId ? null : table.id),
257
+ t
258
+ },
259
+ table.id
260
+ )) })
261
+ ] }, zone.id)) }),
262
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: selectedTable ? /* @__PURE__ */ jsxRuntime.jsx(DetailPanel, { table: selectedTable, t }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card p-8 text-center text-muted-foreground", children: [
263
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "h-10 w-10 mx-auto mb-3 text-muted-foreground/40" }),
264
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: t("tables.floorPlan.selectTable") }),
265
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm mt-1", children: t("tables.floorPlan.selectTableDesc") })
266
+ ] }) })
267
+ ] })
268
+ ] });
269
+ }
270
+ function TablesPage({ config, provider, store, registries }) {
271
+ return /* @__PURE__ */ jsxRuntime.jsx(TablesContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsx(FloorPlanView, {}) });
272
+ }
273
+
274
+ exports.TablesPage = TablesPage;
275
+ //# sourceMappingURL=TablesPage-5GCFGSX3.cjs.map
276
+ //# sourceMappingURL=TablesPage-5GCFGSX3.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/TablesContext.tsx","../src/views/FloorPlanView.tsx","../src/TablesPage.tsx"],"names":["createPluginContext","jsxs","jsx","Fragment","_","useTranslation","useEffect","useMemo","t","UtensilsCrossed","MapPin"],"mappings":"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","file":"TablesPage-5GCFGSX3.cjs","sourcesContent":["import { createPluginContext } from '@fayz-ai/saas'\nimport type { TablesDataProvider } from './data/types'\nimport type { TablesUIState } from './store'\nimport type { TableSession } from './types'\n\n// ---------------------------------------------------------------------------\n// Plugin labels\n// ---------------------------------------------------------------------------\n\nexport interface TablesPluginLabels {\n pageTitle: string\n pageSubtitle: string\n tableSingular: string\n tablePlural: string\n sessionSingular: string\n sessionPlural: string\n}\n\n// ---------------------------------------------------------------------------\n// Resolved config — fully merged, no optionals\n// ---------------------------------------------------------------------------\n\nexport interface TablesModules {\n reservations: boolean\n sessionHistory: boolean\n}\n\nexport interface ResolvedTablesConfig {\n modules: TablesModules\n labels: TablesPluginLabels\n defaultZones: Array<{ name: string; color?: string }>\n onTableSeated?: (session: TableSession) => Promise<string | undefined>\n onTableClosed?: (session: TableSession) => Promise<void>\n}\n\nconst ctx = createPluginContext<ResolvedTablesConfig, TablesDataProvider, TablesUIState>('TablesPage')\n\nexport const TablesContextProvider = ctx.ContextProvider\nexport const useTablesConfig = ctx.useConfig\nexport const useTablesProvider = ctx.useProvider\nexport const useTablesStore = ctx.useStore\n","import React, { useEffect, useMemo } from 'react'\nimport { MapPin, Users, Clock, UtensilsCrossed, Loader2 } from 'lucide-react'\nimport { useTablesConfig, useTablesStore } from '../TablesContext'\nimport { useTranslation } from '@fayz-ai/core'\nimport type { RestaurantTable, TableStatus } from '../types'\n\n// ---------------------------------------------------------------------------\n// Status config\n// ---------------------------------------------------------------------------\n\nconst statusConfig: Record<TableStatus, { color: string; bg: string }> = {\n available: { color: 'text-success', bg: 'bg-success/10 border-success/30 hover:bg-success/20' },\n occupied: { color: 'text-primary', bg: 'bg-primary/10 border-primary/30 hover:bg-primary/20' },\n reserved: { color: 'text-accent', bg: 'bg-accent/10 border-accent/30 hover:bg-accent/20' },\n cleaning: { color: 'text-muted-foreground', bg: 'bg-muted border-border hover:bg-muted/80' },\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatElapsed(minutes: number | undefined, minLabel: string): string {\n if (minutes == null) return ''\n if (minutes < 60) return `${minutes}${minLabel}`\n const h = Math.floor(minutes / 60)\n const m = minutes % 60\n return m > 0 ? `${h}h ${m}${minLabel}` : `${h}h`\n}\n\nfunction formatCurrency(value: number | undefined): string {\n if (value == null) return ''\n return new Intl.NumberFormat(undefined, { style: 'currency', currency: 'BRL' }).format(value)\n}\n\n// ---------------------------------------------------------------------------\n// Table card\n// ---------------------------------------------------------------------------\n\nfunction TableCard({ table, isSelected, onClick, t }: {\n table: RestaurantTable\n isSelected: boolean\n onClick: () => void\n t: (key: string) => string\n}) {\n const config = statusConfig[table.status]\n const isLarge = table.seats >= 6\n const minLabel = t('tables.floorPlan.min')\n\n return (\n <button\n onClick={onClick}\n className={`\n relative flex flex-col items-center justify-center rounded-lg border-2 p-3 transition-all cursor-pointer\n ${config.bg}\n ${isLarge ? 'col-span-2' : ''}\n ${isSelected ? 'ring-2 ring-primary ring-offset-2' : ''}\n `}\n style={{ minHeight: '120px' }}\n >\n <span className={`text-2xl font-bold ${config.color}`}>{table.number}</span>\n <span className=\"text-xs text-muted-foreground mt-1\">\n {table.seats} {t('tables.floorPlan.seats')}\n </span>\n\n {table.status === 'occupied' && (\n <>\n <span className=\"text-xs font-medium mt-1\">\n {table.currentGuests} {t('tables.detail.guests').toLowerCase()}\n </span>\n <span className=\"text-[10px] text-muted-foreground\">\n {formatElapsed(table.currentElapsedMinutes, minLabel)}\n </span>\n </>\n )}\n\n <span\n className={`\n absolute -top-2 -right-2 text-[10px] px-1.5 py-0.5 rounded-full font-medium border\n ${table.status === 'occupied'\n ? 'bg-primary text-primary-foreground border-primary'\n : 'bg-secondary text-secondary-foreground border-border'}\n `}\n >\n {t(`tables.floorPlan.${table.status}`)}\n </span>\n </button>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Detail sidebar\n// ---------------------------------------------------------------------------\n\nfunction DetailPanel({ table, t }: { table: RestaurantTable; t: (key: string) => string }) {\n const updateTableStatus = useTablesStore(s => s.updateTableStatus)\n const seatGuests = useTablesStore(s => s.seatGuests)\n const closeSession = useTablesStore(s => s.closeSession)\n const minLabel = t('tables.floorPlan.min')\n\n return (\n <div className=\"rounded-lg border bg-card p-5 sticky top-6\">\n <div className=\"flex items-center justify-between mb-4\">\n <h3 className=\"text-lg font-bold\">\n {table.name || `#${table.number}`}\n </h3>\n <span\n className={`\n text-xs px-2 py-1 rounded-full font-medium border\n ${table.status === 'occupied'\n ? 'bg-primary text-primary-foreground border-primary'\n : 'bg-secondary text-secondary-foreground border-border'}\n `}\n >\n {t(`tables.floorPlan.${table.status}`)}\n </span>\n </div>\n\n <dl className=\"space-y-3 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('tables.floorPlan.seats')}</dt>\n <dd className=\"font-medium\">{table.seats}</dd>\n </div>\n {table.zoneName && (\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('tables.floorPlan.zone')}</dt>\n <dd className=\"font-medium\">{table.zoneName}</dd>\n </div>\n )}\n {table.status === 'occupied' && (\n <>\n {table.currentGuests != null && (\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('tables.detail.guests')}</dt>\n <dd className=\"font-medium\">{table.currentGuests}</dd>\n </div>\n )}\n {table.currentWaiterName && (\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('tables.detail.waiter')}</dt>\n <dd className=\"font-medium\">{table.currentWaiterName}</dd>\n </div>\n )}\n {table.currentOrderId && (\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('tables.floorPlan.order')}</dt>\n <dd className=\"font-medium\">{table.currentOrderId}</dd>\n </div>\n )}\n {table.currentTotal != null && (\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('tables.detail.total')}</dt>\n <dd className=\"font-bold text-base\">{formatCurrency(table.currentTotal)}</dd>\n </div>\n )}\n {table.currentElapsedMinutes != null && (\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">{t('tables.detail.elapsed')}</dt>\n <dd className=\"font-medium\">{formatElapsed(table.currentElapsedMinutes, minLabel)}</dd>\n </div>\n )}\n </>\n )}\n </dl>\n\n <div className=\"mt-5 grid grid-cols-2 gap-2\">\n {table.status === 'available' && (\n <button\n className=\"col-span-2 inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n onClick={() => seatGuests({ tableId: table.id, guests: 2 })}\n >\n {t('tables.detail.seatGuests')}\n </button>\n )}\n {table.status === 'occupied' && (\n <>\n <button className=\"inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors\">\n {t('tables.detail.viewOrder')}\n </button>\n <button\n className=\"inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n onClick={() => {\n if (table.currentSessionId) closeSession(table.currentSessionId)\n }}\n >\n {t('tables.detail.closeTable')}\n </button>\n </>\n )}\n {table.status === 'cleaning' && (\n <button\n className=\"col-span-2 inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n onClick={() => updateTableStatus({ tableId: table.id, status: 'available' })}\n >\n {t('tables.detail.markClean')}\n </button>\n )}\n {table.status === 'reserved' && (\n <>\n <button\n className=\"inline-flex items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors\"\n onClick={() => updateTableStatus({ tableId: table.id, status: 'available' })}\n >\n {t('tables.detail.cancel')}\n </button>\n <button\n className=\"inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n onClick={() => seatGuests({ tableId: table.id, guests: 2 })}\n >\n {t('tables.floorPlan.checkIn')}\n </button>\n </>\n )}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Loading skeleton\n// ---------------------------------------------------------------------------\n\nfunction FloorPlanSkeleton() {\n return (\n <div className=\"space-y-6\">\n <div className=\"flex items-center gap-3\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-4 w-20 rounded bg-muted animate-pulse\" />\n ))}\n </div>\n <div className=\"grid lg:grid-cols-[1fr_320px] gap-6\">\n <div className=\"space-y-6\">\n {Array.from({ length: 2 }).map((_, z) => (\n <div key={z} className=\"rounded-lg border bg-card p-5\">\n <div className=\"h-4 w-24 rounded bg-muted animate-pulse mb-4\" />\n <div className=\"grid grid-cols-4 gap-3\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-[120px] rounded-lg bg-muted animate-pulse\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n <div className=\"h-[300px] rounded-lg bg-muted animate-pulse\" />\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Main view\n// ---------------------------------------------------------------------------\n\nexport function FloorPlanView() {\n const t = useTranslation()\n const config = useTablesConfig()\n\n const tables = useTablesStore(s => s.tables)\n const zones = useTablesStore(s => s.zones)\n const tablesLoading = useTablesStore(s => s.tablesLoading)\n const fetchTables = useTablesStore(s => s.fetchTables)\n const fetchZones = useTablesStore(s => s.fetchZones)\n const selectedTableId = useTablesStore(s => s.selectedTableId)\n const selectTable = useTablesStore(s => s.selectTable)\n\n // Fetch on mount\n useEffect(() => {\n fetchTables()\n fetchZones()\n }, [fetchTables, fetchZones])\n\n // Group tables by zone\n const tablesByZone = useMemo(() => {\n const map = new Map<string, { zoneName: string; color?: string; tables: RestaurantTable[] }>()\n\n // Seed zone order from store zones\n for (const zone of zones) {\n map.set(zone.id, { zoneName: zone.name, color: zone.color, tables: [] })\n }\n\n for (const table of tables) {\n const zoneId = table.zone\n if (!map.has(zoneId)) {\n map.set(zoneId, { zoneName: table.zoneName || zoneId, tables: [] })\n }\n map.get(zoneId)!.tables.push(table)\n }\n\n // Filter out empty zones\n return Array.from(map.entries())\n .filter(([, v]) => v.tables.length > 0)\n .map(([id, v]) => ({ id, ...v }))\n }, [tables, zones])\n\n // Summary counts\n const occupiedCount = tables.filter(t => t.status === 'occupied').length\n const availableCount = tables.filter(t => t.status === 'available').length\n\n // Selected table\n const selectedTable = selectedTableId\n ? tables.find(t => t.id === selectedTableId) ?? null\n : null\n\n // Loading state\n if (tablesLoading && tables.length === 0) {\n return <FloorPlanSkeleton />\n }\n\n // Empty state\n if (!tablesLoading && tables.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20 text-center\">\n <UtensilsCrossed className=\"h-12 w-12 text-muted-foreground/40 mb-4\" />\n <h3 className=\"text-lg font-semibold\">{t('tables.floorPlan.noTables')}</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">{t('tables.floorPlan.noTablesDesc')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Header: summary + status legend */}\n <div className=\"flex items-center justify-between flex-wrap gap-4\">\n <div>\n <p className=\"text-muted-foreground text-sm\">\n {t('tables.floorPlan.occupiedOf')\n .replace('{occupied}', String(occupiedCount))\n .replace('{available}', String(availableCount))\n .replace('{total}', String(tables.length))}\n </p>\n </div>\n <div className=\"flex gap-3 flex-wrap\">\n {(Object.keys(statusConfig) as TableStatus[]).map(status => (\n <div key={status} className=\"flex items-center gap-1.5 text-xs\">\n <div className={`h-3 w-3 rounded-full ${statusConfig[status].bg} border`} />\n <span className=\"text-muted-foreground\">{t(`tables.floorPlan.${status}`)}</span>\n </div>\n ))}\n </div>\n </div>\n\n {/* Floor plan grid + detail panel */}\n <div className=\"grid lg:grid-cols-[1fr_320px] gap-6\">\n {/* Zone sections */}\n <div className=\"space-y-6\">\n {tablesByZone.map(zone => (\n <div key={zone.id} className=\"rounded-lg border bg-card p-5\">\n <div className=\"flex items-center gap-2 mb-4\">\n {zone.color && (\n <div\n className=\"h-3 w-3 rounded-full border\"\n style={{ backgroundColor: zone.color }}\n />\n )}\n <h3 className=\"text-sm font-semibold text-muted-foreground uppercase tracking-wider\">\n {zone.zoneName}\n </h3>\n </div>\n <div className=\"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3\">\n {zone.tables.map(table => (\n <TableCard\n key={table.id}\n table={table}\n isSelected={table.id === selectedTableId}\n onClick={() => selectTable(table.id === selectedTableId ? null : table.id)}\n t={t}\n />\n ))}\n </div>\n </div>\n ))}\n </div>\n\n {/* Detail panel / sidebar */}\n <div>\n {selectedTable ? (\n <DetailPanel table={selectedTable} t={t} />\n ) : (\n <div className=\"rounded-lg border bg-card p-8 text-center text-muted-foreground\">\n <MapPin className=\"h-10 w-10 mx-auto mb-3 text-muted-foreground/40\" />\n <p className=\"font-medium\">{t('tables.floorPlan.selectTable')}</p>\n <p className=\"text-sm mt-1\">{t('tables.floorPlan.selectTableDesc')}</p>\n </div>\n )}\n </div>\n </div>\n </div>\n )\n}\n","import React from 'react'\nimport type { StoreApi } from 'zustand/vanilla'\nimport { TablesContextProvider, type ResolvedTablesConfig } from './TablesContext'\nimport { FloorPlanView } from './views/FloorPlanView'\nimport type { TablesDataProvider } from './data/types'\nimport type { TablesUIState } from './store'\nimport type { PluginRegistryDef } from '@fayz-ai/core'\n\nexport function TablesPage({ config, provider, store, registries }: {\n config: ResolvedTablesConfig\n provider: TablesDataProvider\n store: StoreApi<TablesUIState>\n registries?: PluginRegistryDef[]\n}) {\n return (\n <TablesContextProvider config={config} provider={provider} store={store}>\n <FloorPlanView />\n </TablesContextProvider>\n )\n}\n"]}