@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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/TablesContext.d.ts +35 -0
- package/dist/TablesContext.d.ts.map +1 -0
- package/dist/TablesPage-5GCFGSX3.cjs +276 -0
- package/dist/TablesPage-5GCFGSX3.cjs.map +1 -0
- package/dist/TablesPage-IM732NWO.js +274 -0
- package/dist/TablesPage-IM732NWO.js.map +1 -0
- package/dist/TablesPage.d.ts +13 -0
- package/dist/TablesPage.d.ts.map +1 -0
- package/dist/data/fayz.d.ts +15 -0
- package/dist/data/fayz.d.ts.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/types.d.ts +24 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +887 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +880 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +28 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/FloorPlanView.d.ts +3 -0
- package/dist/views/FloorPlanView.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/TablesContext.tsx +41 -0
- package/src/TablesPage.tsx +20 -0
- package/src/data/fayz.ts +348 -0
- package/src/data/mock.ts +322 -0
- package/src/data/types.ts +31 -0
- package/src/index.ts +179 -0
- package/src/locales/en.ts +45 -0
- package/src/locales/index.ts +4 -0
- package/src/locales/pt-BR.ts +45 -0
- package/src/registries.ts +32 -0
- package/src/store.ts +178 -0
- package/src/types.ts +116 -0
- package/src/views/FloorPlanView.tsx +388 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'
|
|
3
|
+
import type { TablesDataProvider } from './data/types'
|
|
4
|
+
import type { TableSession } from './types'
|
|
5
|
+
import { createMockTablesProvider } from './data/mock'
|
|
6
|
+
import { createTablesStore } from './store'
|
|
7
|
+
import { tablesRegistries } from './registries'
|
|
8
|
+
import { tablesLocales } from './locales'
|
|
9
|
+
import { PluginSettingsPanel } from '@fayz-ai/saas'
|
|
10
|
+
|
|
11
|
+
const TablesPage = React.lazy(() => import('./TablesPage').then((m) => ({ default: m.TablesPage })))
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Labels
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export interface TablesPluginLabels {
|
|
18
|
+
pageTitle: string
|
|
19
|
+
floorPlan: string
|
|
20
|
+
zones: string
|
|
21
|
+
sessionHistory: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULT_LABELS: TablesPluginLabels = {
|
|
25
|
+
pageTitle: 'Tables',
|
|
26
|
+
floorPlan: 'Floor Plan',
|
|
27
|
+
zones: 'Zones',
|
|
28
|
+
sessionHistory: 'Session History',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Options
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
export interface TablesPluginOptions {
|
|
36
|
+
modules?: {
|
|
37
|
+
reservations?: boolean
|
|
38
|
+
sessionHistory?: boolean
|
|
39
|
+
}
|
|
40
|
+
labels?: Partial<TablesPluginLabels>
|
|
41
|
+
defaultZones?: Array<{ name: string; color?: string }>
|
|
42
|
+
navPosition?: number
|
|
43
|
+
navSection?: 'main' | 'secondary' | 'settings'
|
|
44
|
+
scope?: PluginScope
|
|
45
|
+
verticalId?: VerticalId
|
|
46
|
+
dataProvider?: TablesDataProvider
|
|
47
|
+
onTableSeated?: (session: TableSession) => Promise<string | undefined>
|
|
48
|
+
onTableClosed?: (session: TableSession) => Promise<void>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Config resolver
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
function resolveConfig(options?: TablesPluginOptions) {
|
|
56
|
+
return {
|
|
57
|
+
modules: {
|
|
58
|
+
reservations: options?.modules?.reservations === true,
|
|
59
|
+
sessionHistory: options?.modules?.sessionHistory !== false,
|
|
60
|
+
},
|
|
61
|
+
labels: { ...DEFAULT_LABELS, ...options?.labels } as any,
|
|
62
|
+
defaultZones: options?.defaultZones ?? [
|
|
63
|
+
{ name: 'Indoor', color: '#3b82f6' },
|
|
64
|
+
{ name: 'Outdoor', color: '#22c55e' },
|
|
65
|
+
{ name: 'Bar', color: '#f59e0b' },
|
|
66
|
+
],
|
|
67
|
+
onTableSeated: options?.onTableSeated,
|
|
68
|
+
onTableClosed: options?.onTableClosed,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Factory
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
export function createTablesPlugin(options?: TablesPluginOptions): PluginManifest {
|
|
77
|
+
const config = resolveConfig(options)
|
|
78
|
+
const provider = options?.dataProvider ?? createMockTablesProvider()
|
|
79
|
+
const store = createTablesStore(provider)
|
|
80
|
+
|
|
81
|
+
const PageComponent: React.FC<any> = () =>
|
|
82
|
+
React.createElement(React.Suspense, { fallback: null },
|
|
83
|
+
React.createElement(TablesPage, { config, provider, store, registries: tablesRegistries })
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
id: 'tables',
|
|
88
|
+
name: config.labels.pageTitle,
|
|
89
|
+
icon: 'MapPin',
|
|
90
|
+
version: '1.0.0',
|
|
91
|
+
scope: options?.scope ?? 'vertical',
|
|
92
|
+
verticalId: options?.verticalId,
|
|
93
|
+
defaultEnabled: true,
|
|
94
|
+
dependencies: [],
|
|
95
|
+
navigation: [
|
|
96
|
+
{
|
|
97
|
+
section: options?.navSection ?? 'main',
|
|
98
|
+
position: options?.navPosition ?? 4,
|
|
99
|
+
label: config.labels.pageTitle,
|
|
100
|
+
route: '/tables',
|
|
101
|
+
icon: 'MapPin',
|
|
102
|
+
permission: { feature: 'tables', action: 'read' as const },
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
routes: [
|
|
106
|
+
{
|
|
107
|
+
path: '/tables',
|
|
108
|
+
component: PageComponent,
|
|
109
|
+
permission: { feature: 'tables', action: 'read' as const },
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
widgets: [],
|
|
113
|
+
aiTools: [
|
|
114
|
+
{
|
|
115
|
+
id: 'tables.availability',
|
|
116
|
+
name: 'getTableAvailability',
|
|
117
|
+
description: 'Returns which tables are available, occupied, reserved, or being cleaned.',
|
|
118
|
+
icon: 'MapPin',
|
|
119
|
+
mode: 'read' as const,
|
|
120
|
+
category: 'Tables',
|
|
121
|
+
parameters: {
|
|
122
|
+
type: 'object' as const,
|
|
123
|
+
properties: {
|
|
124
|
+
zone: { type: 'string' as const, description: 'Filter by zone name' },
|
|
125
|
+
status: { type: 'string' as const, enum: ['available', 'occupied', 'reserved', 'cleaning'] },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
suggestions: [
|
|
129
|
+
{ label: 'Which tables are available right now?' },
|
|
130
|
+
{ label: 'How many tables are occupied?' },
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'tables.seat-guests',
|
|
135
|
+
name: 'seatGuests',
|
|
136
|
+
description: 'Seats guests at a specific table.',
|
|
137
|
+
icon: 'UserPlus',
|
|
138
|
+
mode: 'persist' as const,
|
|
139
|
+
category: 'Tables',
|
|
140
|
+
parameters: {
|
|
141
|
+
type: 'object' as const,
|
|
142
|
+
properties: {
|
|
143
|
+
tableNumber: { type: 'number' as const, description: 'Table number' },
|
|
144
|
+
guests: { type: 'number' as const, description: 'Number of guests' },
|
|
145
|
+
},
|
|
146
|
+
required: ['tableNumber', 'guests'],
|
|
147
|
+
},
|
|
148
|
+
permission: { feature: 'tables', action: 'edit' as const },
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
registries: tablesRegistries,
|
|
152
|
+
settings: [
|
|
153
|
+
{
|
|
154
|
+
id: 'tables',
|
|
155
|
+
label: config.labels.pageTitle,
|
|
156
|
+
icon: 'MapPin',
|
|
157
|
+
component: (() => {
|
|
158
|
+
const TablesSettingsTab: React.ComponentType<unknown> = () =>
|
|
159
|
+
React.createElement(PluginSettingsPanel, {
|
|
160
|
+
title: 'Tables Settings',
|
|
161
|
+
subtitle: 'Zones and floor plan configuration',
|
|
162
|
+
registries: tablesRegistries,
|
|
163
|
+
routeBase: '/settings/tables',
|
|
164
|
+
})
|
|
165
|
+
TablesSettingsTab.displayName = 'TablesSettingsTab'
|
|
166
|
+
return TablesSettingsTab
|
|
167
|
+
})(),
|
|
168
|
+
order: 21,
|
|
169
|
+
permission: { feature: 'tables', action: 'read' as const },
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
locales: tablesLocales,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type { TablesDataProvider } from './data/types'
|
|
177
|
+
export { createFayzTablesProvider } from './data/fayz'
|
|
178
|
+
export type { FayzTablesProviderOptions } from './data/fayz'
|
|
179
|
+
export type { ResolvedTablesConfig } from './TablesContext'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const en: Record<string, string> = {
|
|
2
|
+
'tables.title': 'Tables',
|
|
3
|
+
'tables.nav.floorPlan': 'Floor Plan',
|
|
4
|
+
'tables.nav.zones': 'Zones',
|
|
5
|
+
'tables.nav.history': 'Session History',
|
|
6
|
+
'tables.floorPlan.title': 'Floor Plan',
|
|
7
|
+
'tables.floorPlan.subtitle': 'Manage table layout and seating',
|
|
8
|
+
'tables.floorPlan.available': 'Available',
|
|
9
|
+
'tables.floorPlan.occupied': 'Occupied',
|
|
10
|
+
'tables.floorPlan.reserved': 'Reserved',
|
|
11
|
+
'tables.floorPlan.cleaning': 'Cleaning',
|
|
12
|
+
'tables.floorPlan.seats': 'seats',
|
|
13
|
+
'tables.floorPlan.noTables': 'No tables configured',
|
|
14
|
+
'tables.floorPlan.noTablesDesc': 'Add your first table to get started',
|
|
15
|
+
'tables.floorPlan.addTable': 'Add Table',
|
|
16
|
+
'tables.floorPlan.occupiedOf': '{occupied} occupied, {available} available of {total} tables',
|
|
17
|
+
'tables.floorPlan.selectTable': 'Select a table',
|
|
18
|
+
'tables.floorPlan.selectTableDesc': 'Click on any table to see details and actions',
|
|
19
|
+
'tables.floorPlan.min': 'min',
|
|
20
|
+
'tables.floorPlan.zone': 'Zone',
|
|
21
|
+
'tables.floorPlan.order': 'Order',
|
|
22
|
+
'tables.floorPlan.checkIn': 'Check In',
|
|
23
|
+
'tables.detail.currentSession': 'Current Session',
|
|
24
|
+
'tables.detail.guests': 'Guests',
|
|
25
|
+
'tables.detail.waiter': 'Waiter',
|
|
26
|
+
'tables.detail.elapsed': 'Elapsed',
|
|
27
|
+
'tables.detail.total': 'Running Total',
|
|
28
|
+
'tables.detail.viewOrder': 'View Order',
|
|
29
|
+
'tables.detail.seatGuests': 'Seat Guests',
|
|
30
|
+
'tables.detail.closeTable': 'Close Table',
|
|
31
|
+
'tables.detail.markClean': 'Mark Ready',
|
|
32
|
+
'tables.detail.cancel': 'Cancel',
|
|
33
|
+
'tables.seat.title': 'Seat Guests',
|
|
34
|
+
'tables.seat.guestCount': 'Number of guests',
|
|
35
|
+
'tables.seat.waiter': 'Waiter',
|
|
36
|
+
'tables.seat.notes': 'Notes',
|
|
37
|
+
'tables.seat.confirm': 'Seat',
|
|
38
|
+
'tables.history.title': 'Session History',
|
|
39
|
+
'tables.history.table': 'Table',
|
|
40
|
+
'tables.history.guests': 'Guests',
|
|
41
|
+
'tables.history.waiter': 'Waiter',
|
|
42
|
+
'tables.history.duration': 'Duration',
|
|
43
|
+
'tables.history.noSessions': 'No session history yet',
|
|
44
|
+
'tables.settings.title': 'Tables Settings',
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const ptBR: Record<string, string> = {
|
|
2
|
+
'tables.title': 'Mesas',
|
|
3
|
+
'tables.nav.floorPlan': 'Mapa de Mesas',
|
|
4
|
+
'tables.nav.zones': 'Áreas',
|
|
5
|
+
'tables.nav.history': 'Histórico',
|
|
6
|
+
'tables.floorPlan.title': 'Mapa de Mesas',
|
|
7
|
+
'tables.floorPlan.subtitle': 'Gerencie o layout e a ocupação das mesas',
|
|
8
|
+
'tables.floorPlan.available': 'Disponível',
|
|
9
|
+
'tables.floorPlan.occupied': 'Ocupada',
|
|
10
|
+
'tables.floorPlan.reserved': 'Reservada',
|
|
11
|
+
'tables.floorPlan.cleaning': 'Limpeza',
|
|
12
|
+
'tables.floorPlan.seats': 'lugares',
|
|
13
|
+
'tables.floorPlan.noTables': 'Nenhuma mesa configurada',
|
|
14
|
+
'tables.floorPlan.noTablesDesc': 'Adicione a primeira mesa para começar',
|
|
15
|
+
'tables.floorPlan.addTable': 'Adicionar Mesa',
|
|
16
|
+
'tables.floorPlan.occupiedOf': '{occupied} ocupadas, {available} disponíveis de {total} mesas',
|
|
17
|
+
'tables.floorPlan.selectTable': 'Selecione uma mesa',
|
|
18
|
+
'tables.floorPlan.selectTableDesc': 'Clique em uma mesa para ver detalhes e ações',
|
|
19
|
+
'tables.floorPlan.min': 'min',
|
|
20
|
+
'tables.floorPlan.zone': 'Área',
|
|
21
|
+
'tables.floorPlan.order': 'Pedido',
|
|
22
|
+
'tables.floorPlan.checkIn': 'Check-in',
|
|
23
|
+
'tables.detail.currentSession': 'Sessão Atual',
|
|
24
|
+
'tables.detail.guests': 'Pessoas',
|
|
25
|
+
'tables.detail.waiter': 'Garçom',
|
|
26
|
+
'tables.detail.elapsed': 'Tempo',
|
|
27
|
+
'tables.detail.total': 'Total Parcial',
|
|
28
|
+
'tables.detail.viewOrder': 'Ver Pedido',
|
|
29
|
+
'tables.detail.seatGuests': 'Sentar Clientes',
|
|
30
|
+
'tables.detail.closeTable': 'Fechar Mesa',
|
|
31
|
+
'tables.detail.markClean': 'Marcar Pronta',
|
|
32
|
+
'tables.detail.cancel': 'Cancelar',
|
|
33
|
+
'tables.seat.title': 'Sentar Clientes',
|
|
34
|
+
'tables.seat.guestCount': 'Número de pessoas',
|
|
35
|
+
'tables.seat.waiter': 'Garçom',
|
|
36
|
+
'tables.seat.notes': 'Observações',
|
|
37
|
+
'tables.seat.confirm': 'Sentar',
|
|
38
|
+
'tables.history.title': 'Histórico de Sessões',
|
|
39
|
+
'tables.history.table': 'Mesa',
|
|
40
|
+
'tables.history.guests': 'Pessoas',
|
|
41
|
+
'tables.history.waiter': 'Garçom',
|
|
42
|
+
'tables.history.duration': 'Duração',
|
|
43
|
+
'tables.history.noSessions': 'Nenhum histórico de sessões',
|
|
44
|
+
'tables.settings.title': 'Configurações de Mesas',
|
|
45
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PluginRegistryDef } from '@fayz-ai/core'
|
|
2
|
+
import type { EntityDef } from '@fayz-ai/core'
|
|
3
|
+
|
|
4
|
+
const zoneEntity: EntityDef = {
|
|
5
|
+
name: 'Zone',
|
|
6
|
+
namePlural: 'Zones',
|
|
7
|
+
icon: 'MapPin',
|
|
8
|
+
displayField: 'name',
|
|
9
|
+
defaultSort: 'sortOrder',
|
|
10
|
+
fields: [
|
|
11
|
+
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true },
|
|
12
|
+
{ key: 'color', label: 'Color', type: 'text', showInTable: true },
|
|
13
|
+
{ key: 'sortOrder', label: 'Order', type: 'number', showInTable: true, defaultValue: 0 },
|
|
14
|
+
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
15
|
+
],
|
|
16
|
+
data: { table: 'restaurant_zones', tenantScoped: true },
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const tablesRegistries: PluginRegistryDef[] = [
|
|
20
|
+
{
|
|
21
|
+
id: 'zones',
|
|
22
|
+
entity: zoneEntity,
|
|
23
|
+
icon: 'MapPin',
|
|
24
|
+
description: 'Floor plan zones (indoor, outdoor, bar, etc.)',
|
|
25
|
+
display: 'table',
|
|
26
|
+
seedData: [
|
|
27
|
+
{ id: 'z-indoor', name: 'Indoor', color: '#3b82f6', sortOrder: 0, isActive: true },
|
|
28
|
+
{ id: 'z-outdoor', name: 'Outdoor', color: '#22c55e', sortOrder: 1, isActive: true },
|
|
29
|
+
{ id: 'z-bar', name: 'Bar', color: '#f59e0b', sortOrder: 2, isActive: true },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
]
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createStore, type StoreApi } from 'zustand/vanilla'
|
|
2
|
+
import { dedup } from '@fayz-ai/saas'
|
|
3
|
+
import { toast } from 'sonner'
|
|
4
|
+
import type { TablesDataProvider } from './data/types'
|
|
5
|
+
import type {
|
|
6
|
+
RestaurantTable, TableSession, Zone,
|
|
7
|
+
CreateTableInput, SeatGuestsInput, UpdateTableStatusInput,
|
|
8
|
+
TableQuery, TablesSummary,
|
|
9
|
+
} from './types'
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Store state
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface TablesUIState {
|
|
16
|
+
// Data cache
|
|
17
|
+
tables: RestaurantTable[]
|
|
18
|
+
tablesLoading: boolean
|
|
19
|
+
|
|
20
|
+
zones: Zone[]
|
|
21
|
+
zonesLoading: boolean
|
|
22
|
+
|
|
23
|
+
activeSessions: TableSession[]
|
|
24
|
+
sessionsLoading: boolean
|
|
25
|
+
|
|
26
|
+
sessionHistory: TableSession[]
|
|
27
|
+
historyLoading: boolean
|
|
28
|
+
|
|
29
|
+
summary: TablesSummary | null
|
|
30
|
+
summaryLoading: boolean
|
|
31
|
+
|
|
32
|
+
selectedTableId: string | null
|
|
33
|
+
|
|
34
|
+
// Actions
|
|
35
|
+
fetchTables(query?: TableQuery): Promise<void>
|
|
36
|
+
fetchZones(): Promise<void>
|
|
37
|
+
fetchActiveSessions(): Promise<void>
|
|
38
|
+
fetchSessionHistory(tableId?: string): Promise<void>
|
|
39
|
+
fetchSummary(): Promise<void>
|
|
40
|
+
selectTable(id: string | null): void
|
|
41
|
+
seatGuests(input: SeatGuestsInput): Promise<TableSession>
|
|
42
|
+
closeSession(sessionId: string): Promise<void>
|
|
43
|
+
updateTableStatus(input: UpdateTableStatusInput): Promise<void>
|
|
44
|
+
createTable(input: CreateTableInput): Promise<RestaurantTable>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Store factory
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
export function createTablesStore(provider: TablesDataProvider): StoreApi<TablesUIState> {
|
|
52
|
+
return createStore<TablesUIState>((set, get) => ({
|
|
53
|
+
tables: [],
|
|
54
|
+
tablesLoading: false,
|
|
55
|
+
|
|
56
|
+
zones: [],
|
|
57
|
+
zonesLoading: false,
|
|
58
|
+
|
|
59
|
+
activeSessions: [],
|
|
60
|
+
sessionsLoading: false,
|
|
61
|
+
|
|
62
|
+
sessionHistory: [],
|
|
63
|
+
historyLoading: false,
|
|
64
|
+
|
|
65
|
+
summary: null,
|
|
66
|
+
summaryLoading: false,
|
|
67
|
+
|
|
68
|
+
selectedTableId: null,
|
|
69
|
+
|
|
70
|
+
async fetchTables(query) {
|
|
71
|
+
return dedup('tables:tables:' + JSON.stringify(query), async () => {
|
|
72
|
+
set({ tablesLoading: true })
|
|
73
|
+
const tables = await provider.getTables(query)
|
|
74
|
+
set({ tables, tablesLoading: false })
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
async fetchZones() {
|
|
79
|
+
return dedup('tables:zones', async () => {
|
|
80
|
+
set({ zonesLoading: true })
|
|
81
|
+
const zones = await provider.getZones()
|
|
82
|
+
set({ zones, zonesLoading: false })
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async fetchActiveSessions() {
|
|
87
|
+
return dedup('tables:activeSessions', async () => {
|
|
88
|
+
set({ sessionsLoading: true })
|
|
89
|
+
const activeSessions = await provider.getActiveSessions()
|
|
90
|
+
set({ activeSessions, sessionsLoading: false })
|
|
91
|
+
})
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async fetchSessionHistory(tableId) {
|
|
95
|
+
return dedup('tables:history:' + (tableId ?? ''), async () => {
|
|
96
|
+
set({ historyLoading: true })
|
|
97
|
+
const sessionHistory = await provider.getSessionHistory(tableId)
|
|
98
|
+
set({ sessionHistory, historyLoading: false })
|
|
99
|
+
})
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async fetchSummary() {
|
|
103
|
+
return dedup('tables:summary', async () => {
|
|
104
|
+
set({ summaryLoading: true })
|
|
105
|
+
const summary = await provider.getSummary()
|
|
106
|
+
set({ summary, summaryLoading: false })
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
selectTable(id) {
|
|
111
|
+
set({ selectedTableId: id })
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async seatGuests(input) {
|
|
115
|
+
try {
|
|
116
|
+
const session = await provider.seatGuests(input)
|
|
117
|
+
const [tables, activeSessions, summary] = await Promise.all([
|
|
118
|
+
provider.getTables(),
|
|
119
|
+
provider.getActiveSessions(),
|
|
120
|
+
provider.getSummary(),
|
|
121
|
+
])
|
|
122
|
+
set({ tables, activeSessions, summary })
|
|
123
|
+
toast.success('Guests seated')
|
|
124
|
+
return session
|
|
125
|
+
} catch (err: any) {
|
|
126
|
+
toast.error('Failed to seat guests', { description: err?.message })
|
|
127
|
+
throw err
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async closeSession(sessionId) {
|
|
132
|
+
try {
|
|
133
|
+
await provider.closeSession(sessionId)
|
|
134
|
+
const [tables, activeSessions, summary] = await Promise.all([
|
|
135
|
+
provider.getTables(),
|
|
136
|
+
provider.getActiveSessions(),
|
|
137
|
+
provider.getSummary(),
|
|
138
|
+
])
|
|
139
|
+
set({ tables, activeSessions, summary })
|
|
140
|
+
toast.success('Session closed')
|
|
141
|
+
} catch (err: any) {
|
|
142
|
+
toast.error('Failed to close session', { description: err?.message })
|
|
143
|
+
throw err
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
async updateTableStatus(input) {
|
|
148
|
+
try {
|
|
149
|
+
await provider.updateTableStatus(input)
|
|
150
|
+
const [tables, summary] = await Promise.all([
|
|
151
|
+
provider.getTables(),
|
|
152
|
+
provider.getSummary(),
|
|
153
|
+
])
|
|
154
|
+
set({ tables, summary })
|
|
155
|
+
toast.success('Table status updated')
|
|
156
|
+
} catch (err: any) {
|
|
157
|
+
toast.error('Failed to update table status', { description: err?.message })
|
|
158
|
+
throw err
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
async createTable(input) {
|
|
163
|
+
try {
|
|
164
|
+
const table = await provider.createTable(input)
|
|
165
|
+
const [tables, summary] = await Promise.all([
|
|
166
|
+
provider.getTables(),
|
|
167
|
+
provider.getSummary(),
|
|
168
|
+
])
|
|
169
|
+
set({ tables, summary })
|
|
170
|
+
toast.success('Table created')
|
|
171
|
+
return table
|
|
172
|
+
} catch (err: any) {
|
|
173
|
+
toast.error('Failed to create table', { description: err?.message })
|
|
174
|
+
throw err
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
}))
|
|
178
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Tables Plugin — Pure TypeScript types
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
// ============================================================
|
|
6
|
+
// ENUMS / LITERALS
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
export type TableStatus = 'available' | 'occupied' | 'reserved' | 'cleaning'
|
|
10
|
+
export type TableShape = 'square' | 'round' | 'rectangle' | 'bar'
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// CORE ENTITIES
|
|
14
|
+
// ============================================================
|
|
15
|
+
|
|
16
|
+
export interface RestaurantTable {
|
|
17
|
+
id: string
|
|
18
|
+
name: string
|
|
19
|
+
number: number
|
|
20
|
+
seats: number
|
|
21
|
+
status: TableStatus
|
|
22
|
+
zone: string
|
|
23
|
+
zoneName?: string
|
|
24
|
+
shape: TableShape
|
|
25
|
+
gridCol: number
|
|
26
|
+
gridRow: number
|
|
27
|
+
isActive: boolean
|
|
28
|
+
// Current session (denormalized for floor plan)
|
|
29
|
+
currentSessionId?: string
|
|
30
|
+
currentOrderId?: string
|
|
31
|
+
currentGuests?: number
|
|
32
|
+
currentWaiterName?: string
|
|
33
|
+
currentElapsedMinutes?: number
|
|
34
|
+
currentTotal?: number
|
|
35
|
+
metadata?: Record<string, unknown>
|
|
36
|
+
tenantId: string
|
|
37
|
+
createdAt: string
|
|
38
|
+
updatedAt: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TableSession {
|
|
42
|
+
id: string
|
|
43
|
+
tableId: string
|
|
44
|
+
tableName?: string
|
|
45
|
+
orderId?: string
|
|
46
|
+
guests: number
|
|
47
|
+
waiterId?: string
|
|
48
|
+
waiterName?: string
|
|
49
|
+
seatedAt: string
|
|
50
|
+
closedAt?: string
|
|
51
|
+
status: 'active' | 'closed'
|
|
52
|
+
notes?: string
|
|
53
|
+
tenantId: string
|
|
54
|
+
createdAt: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Zone {
|
|
58
|
+
id: string
|
|
59
|
+
name: string
|
|
60
|
+
color?: string
|
|
61
|
+
sortOrder: number
|
|
62
|
+
isActive: boolean
|
|
63
|
+
tenantId: string
|
|
64
|
+
createdAt: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================
|
|
68
|
+
// INPUT TYPES
|
|
69
|
+
// ============================================================
|
|
70
|
+
|
|
71
|
+
export interface CreateTableInput {
|
|
72
|
+
name: string
|
|
73
|
+
number: number
|
|
74
|
+
seats: number
|
|
75
|
+
zone: string
|
|
76
|
+
shape?: TableShape
|
|
77
|
+
gridCol?: number
|
|
78
|
+
gridRow?: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface SeatGuestsInput {
|
|
82
|
+
tableId: string
|
|
83
|
+
guests: number
|
|
84
|
+
waiterId?: string
|
|
85
|
+
notes?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface UpdateTableStatusInput {
|
|
89
|
+
tableId: string
|
|
90
|
+
status: TableStatus
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================
|
|
94
|
+
// QUERY TYPES
|
|
95
|
+
// ============================================================
|
|
96
|
+
|
|
97
|
+
export interface TableQuery {
|
|
98
|
+
zone?: string
|
|
99
|
+
status?: TableStatus | TableStatus[]
|
|
100
|
+
search?: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================
|
|
104
|
+
// AGGREGATION
|
|
105
|
+
// ============================================================
|
|
106
|
+
|
|
107
|
+
export interface TablesSummary {
|
|
108
|
+
totalTables: number
|
|
109
|
+
availableCount: number
|
|
110
|
+
occupiedCount: number
|
|
111
|
+
reservedCount: number
|
|
112
|
+
cleaningCount: number
|
|
113
|
+
totalSeats: number
|
|
114
|
+
occupiedSeats: number
|
|
115
|
+
averageSessionMinutes: number
|
|
116
|
+
}
|