@fayz-ai/plugin-orders 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 +50 -0
- package/dist/OrdersContext.d.ts +40 -0
- package/dist/OrdersContext.d.ts.map +1 -0
- package/dist/OrdersPage-2IXARHDR.cjs +157 -0
- package/dist/OrdersPage-2IXARHDR.cjs.map +1 -0
- package/dist/OrdersPage-LJIEIZYE.js +155 -0
- package/dist/OrdersPage-LJIEIZYE.js.map +1 -0
- package/dist/OrdersPage.d.ts +13 -0
- package/dist/OrdersPage.d.ts.map +1 -0
- package/dist/chunk-7LFVFZKZ.js +23 -0
- package/dist/chunk-7LFVFZKZ.js.map +1 -0
- package/dist/chunk-WBGECNYU.cjs +28 -0
- package/dist/chunk-WBGECNYU.cjs.map +1 -0
- package/dist/data/fayz.d.ts +18 -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 +17 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +1185 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1178 -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 +29 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/OrderKanbanView.d.ts +3 -0
- package/dist/views/OrderKanbanView.d.ts.map +1 -0
- package/package.json +54 -0
- package/src/OrdersContext.tsx +47 -0
- package/src/OrdersPage.tsx +20 -0
- package/src/data/fayz.ts +456 -0
- package/src/data/mock.ts +512 -0
- package/src/data/types.ts +23 -0
- package/src/index.ts +217 -0
- package/src/locales/en.ts +53 -0
- package/src/locales/index.ts +4 -0
- package/src/locales/pt-BR.ts +53 -0
- package/src/registries.ts +32 -0
- package/src/store.ts +191 -0
- package/src/types.ts +190 -0
- package/src/views/OrderKanbanView.tsx +240 -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,50 @@
|
|
|
1
|
+
# @fayz-ai/plugin-orders
|
|
2
|
+
|
|
3
|
+
> A live order board for restaurants — kanban, history, and AI that knows what's cooking.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@fayz-ai/plugin-orders)
|
|
6
|
+
[](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Orders are the heartbeat of an operations business. `plugin-orders` gives a restaurant — or any food vertical — a real-time order board: dine-in, takeout, and delivery flowing across a kanban, with history behind it. It's the surface the floor staff actually live in.
|
|
9
|
+
|
|
10
|
+
It's built provider-first: a `MenuDataProvider`-aware orders flow that ships with a mock provider for instant demos and a `createFayzOrdersProvider` for real data. Order sources, currency, and labels are all configurable, and the host AI assistant can read today's summary, list active orders, and even create new ones. The point isn't a one-off POS — it's a plugin you snap into a `defineSaas` app next to menu and inventory to compose a complete restaurant SaaS.
|
|
11
|
+
|
|
12
|
+
## What's inside
|
|
13
|
+
- **Navigation + page** — an `/orders` surface with a kanban board (`OrderKanbanView`) and order history
|
|
14
|
+
- **Pluggable data** — inject an `OrdersDataProvider`, use `createFayzOrdersProvider`, or fall back to the bundled mock provider
|
|
15
|
+
- **Configurable order sources** — defaults to dine-in and takeout; delivery/takeout modules toggle on or off
|
|
16
|
+
- **AI tools** — `getOrdersSummary` (count, revenue, average ticket), `getActiveOrders` (filter by kind/status), and `createOrder` (persist a new order with items)
|
|
17
|
+
- **Settings + i18n** — a settings panel for order sources and preferences, plus bundled locales
|
|
18
|
+
- **Hooks** — `onOrderCompleted` callback, plus `menuItemLookup` / `staffLookup` entity lookups
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
```bash
|
|
22
|
+
npm install @fayz-ai/plugin-orders
|
|
23
|
+
```
|
|
24
|
+
Peer deps: `@fayz-ai/core`, `@fayz-ai/saas`, `@fayz-ai/sdk` (+ react, react-dom). Declares a dependency on the `menu` plugin.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
```tsx
|
|
28
|
+
import { defineSaas } from '@fayz-ai/saas'
|
|
29
|
+
import { createOrdersPlugin, createFayzOrdersProvider } from '@fayz-ai/plugin-orders'
|
|
30
|
+
|
|
31
|
+
export const app = defineSaas({
|
|
32
|
+
// ...
|
|
33
|
+
plugins: [
|
|
34
|
+
createOrdersPlugin({
|
|
35
|
+
modules: { delivery: true, takeout: true },
|
|
36
|
+
currency: { code: 'BRL', locale: 'pt-BR', symbol: 'R$' },
|
|
37
|
+
dataProvider: createFayzOrdersProvider({ /* ... */ }),
|
|
38
|
+
onOrderCompleted: async (order) => { /* fire a webhook, print, etc. */ },
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Options: `modules`, `labels`, `currency`, `navPosition`, `navSection`, `scope`, `verticalId`, `dataProvider`, `menuItemLookup`, `staffLookup`, `onOrderCompleted`, `orderSources`.
|
|
45
|
+
|
|
46
|
+
## Part of the Fayz SDK
|
|
47
|
+
A composable plugin for the Fayz SDK. Depends on `@fayz-ai/plugin-menu` for items, and pairs with `@fayz-ai/plugin-inventory` and `@fayz-ai/plugin-shop` for a full food-to-cash flow.
|
|
48
|
+
|
|
49
|
+
## Roadmap & contributing
|
|
50
|
+
Built and evolving in the open. See the [Fayz SDK roadmap](../../docs/ROADMAP.md#plugin-orders) for current gaps, missing features, and good first issues.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { formatCurrency, type CurrencyConfig, type EntityLookup } from '@fayz-ai/saas';
|
|
2
|
+
import type { OrdersDataProvider } from './data/types';
|
|
3
|
+
import type { OrdersUIState } from './store';
|
|
4
|
+
import type { RestaurantOrder } from './types';
|
|
5
|
+
export interface OrdersModules {
|
|
6
|
+
delivery: boolean;
|
|
7
|
+
takeout: boolean;
|
|
8
|
+
}
|
|
9
|
+
export type OrdersCurrency = CurrencyConfig;
|
|
10
|
+
export interface OrderSourceOption {
|
|
11
|
+
value: string;
|
|
12
|
+
label: string;
|
|
13
|
+
icon?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface OrdersPluginLabels {
|
|
16
|
+
pageTitle: string;
|
|
17
|
+
pageSubtitle: string;
|
|
18
|
+
orderSingular: string;
|
|
19
|
+
orderPlural: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ResolvedOrdersConfig {
|
|
22
|
+
modules: OrdersModules;
|
|
23
|
+
labels: OrdersPluginLabels;
|
|
24
|
+
currency: OrdersCurrency;
|
|
25
|
+
orderSources: OrderSourceOption[];
|
|
26
|
+
menuItemLookup?: EntityLookup;
|
|
27
|
+
staffLookup?: EntityLookup;
|
|
28
|
+
onOrderCompleted?: (order: RestaurantOrder) => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export declare const OrdersContextProvider: import("react").FC<{
|
|
31
|
+
config: ResolvedOrdersConfig;
|
|
32
|
+
provider: OrdersDataProvider;
|
|
33
|
+
store: import("zustand").StoreApi<OrdersUIState>;
|
|
34
|
+
children?: React.ReactNode;
|
|
35
|
+
}>;
|
|
36
|
+
export declare const useOrdersConfig: () => ResolvedOrdersConfig;
|
|
37
|
+
export declare const useOrdersProvider: () => OrdersDataProvider;
|
|
38
|
+
export declare const useOrdersStore: <T>(selector: (state: OrdersUIState) => T) => T;
|
|
39
|
+
export { formatCurrency };
|
|
40
|
+
//# sourceMappingURL=OrdersContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OrdersContext.d.ts","sourceRoot":"","sources":["../src/OrdersContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAC3G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM9C,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,CAAA;AAE3C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,QAAQ,EAAE,cAAc,CAAA;IACxB,YAAY,EAAE,iBAAiB,EAAE,CAAA;IACjC,cAAc,CAAC,EAAE,YAAY,CAAA;IAC7B,WAAW,CAAC,EAAE,YAAY,CAAA;IAC1B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7D;AAID,eAAO,MAAM,qBAAqB;;;;;EAAsB,CAAA;AACxD,eAAO,MAAM,eAAe,4BAAgB,CAAA;AAC5C,eAAO,MAAM,iBAAiB,0BAAkB,CAAA;AAChD,eAAO,MAAM,cAAc,iDAAe,CAAA;AAE1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkWBGECNYU_cjs = require('./chunk-WBGECNYU.cjs');
|
|
4
|
+
var saas = require('@fayz-ai/saas');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var lucideReact = require('lucide-react');
|
|
7
|
+
var core = require('@fayz-ai/core');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
|
|
10
|
+
var ctx = saas.createPluginContext("OrdersPage");
|
|
11
|
+
var OrdersContextProvider = ctx.ContextProvider;
|
|
12
|
+
var useOrdersConfig = ctx.useConfig;
|
|
13
|
+
ctx.useProvider;
|
|
14
|
+
var useOrdersStore = ctx.useStore;
|
|
15
|
+
var COLUMN_CONFIG = {
|
|
16
|
+
new: { color: "bg-violet-500" },
|
|
17
|
+
confirmed: { color: "bg-blue-500" },
|
|
18
|
+
preparing: { color: "bg-amber-500" },
|
|
19
|
+
ready: { color: "bg-emerald-500" },
|
|
20
|
+
out: { color: "bg-cyan-500" },
|
|
21
|
+
completed: { color: "bg-gray-400" },
|
|
22
|
+
cancelled: { color: "bg-red-500" }
|
|
23
|
+
};
|
|
24
|
+
function timeAgo(dateStr) {
|
|
25
|
+
const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 6e4);
|
|
26
|
+
if (diff < 1) return "just now";
|
|
27
|
+
if (diff === 1) return "1min";
|
|
28
|
+
return `${diff}min`;
|
|
29
|
+
}
|
|
30
|
+
function OrderCard({
|
|
31
|
+
order,
|
|
32
|
+
onAdvance,
|
|
33
|
+
onSelect,
|
|
34
|
+
currency
|
|
35
|
+
}) {
|
|
36
|
+
const t = core.useTranslation();
|
|
37
|
+
const sourceLabel = t(`orders.source.${order.source}`) !== `orders.source.${order.source}` ? t(`orders.source.${order.source}`) : order.source;
|
|
38
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
39
|
+
"div",
|
|
40
|
+
{
|
|
41
|
+
className: "rounded-lg border bg-card p-3 hover:shadow-md transition-shadow cursor-pointer",
|
|
42
|
+
onClick: onSelect,
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
45
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold", children: order.referenceNumber }),
|
|
46
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium", children: sourceLabel })
|
|
47
|
+
] }),
|
|
48
|
+
order.partyName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: order.partyName }),
|
|
49
|
+
order.tableName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: order.tableName }),
|
|
50
|
+
order.items.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 space-y-0.5", children: order.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground truncate", children: [
|
|
51
|
+
item.quantity,
|
|
52
|
+
"x ",
|
|
53
|
+
item.name
|
|
54
|
+
] }, item.id)) }),
|
|
55
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mt-3 pt-2 border-t", children: [
|
|
56
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold", children: saas.formatCurrency(order.total, currency) }),
|
|
57
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-muted-foreground", children: [
|
|
58
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
59
|
+
timeAgo(order.createdAt)
|
|
60
|
+
] })
|
|
61
|
+
] }),
|
|
62
|
+
onAdvance && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
type: "button",
|
|
66
|
+
className: "mt-2 flex w-full items-center justify-center gap-1 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
|
|
67
|
+
onClick: (e) => {
|
|
68
|
+
e.stopPropagation();
|
|
69
|
+
onAdvance();
|
|
70
|
+
},
|
|
71
|
+
children: [
|
|
72
|
+
t("orders.kanban.advance"),
|
|
73
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-3 w-3" })
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function SkeletonColumn() {
|
|
82
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 w-72", children: [
|
|
83
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-9 rounded-lg bg-muted animate-pulse mb-3" }),
|
|
84
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-32 rounded-lg bg-muted/60 animate-pulse" }, i)) })
|
|
85
|
+
] });
|
|
86
|
+
}
|
|
87
|
+
function OrderKanbanView() {
|
|
88
|
+
const t = core.useTranslation();
|
|
89
|
+
const config = useOrdersConfig();
|
|
90
|
+
const activeOrders = useOrdersStore((s) => s.activeOrders);
|
|
91
|
+
const activeOrdersLoading = useOrdersStore((s) => s.activeOrdersLoading);
|
|
92
|
+
const fetchActiveOrders = useOrdersStore((s) => s.fetchActiveOrders);
|
|
93
|
+
const advanceOrderStatus = useOrdersStore((s) => s.advanceOrderStatus);
|
|
94
|
+
const selectOrder = useOrdersStore((s) => s.selectOrder);
|
|
95
|
+
react.useEffect(() => {
|
|
96
|
+
fetchActiveOrders();
|
|
97
|
+
}, [fetchActiveOrders]);
|
|
98
|
+
const grouped = react.useMemo(() => {
|
|
99
|
+
const map = {
|
|
100
|
+
new: [],
|
|
101
|
+
confirmed: [],
|
|
102
|
+
preparing: [],
|
|
103
|
+
ready: [],
|
|
104
|
+
out: [],
|
|
105
|
+
completed: [],
|
|
106
|
+
cancelled: []
|
|
107
|
+
};
|
|
108
|
+
for (const order of activeOrders) {
|
|
109
|
+
const col = chunkWBGECNYU_cjs.mapToKanbanColumn(order.status);
|
|
110
|
+
map[col].push(order);
|
|
111
|
+
}
|
|
112
|
+
return map;
|
|
113
|
+
}, [activeOrders]);
|
|
114
|
+
if (activeOrdersLoading && activeOrders.length === 0) {
|
|
115
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-4 overflow-x-auto pb-4", children: chunkWBGECNYU_cjs.KANBAN_COLUMNS.map((col) => /* @__PURE__ */ jsxRuntime.jsx(SkeletonColumn, {}, col)) });
|
|
116
|
+
}
|
|
117
|
+
if (!activeOrdersLoading && activeOrders.length === 0) {
|
|
118
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center h-64 text-center", children: [
|
|
119
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ClipboardList, { className: "h-12 w-12 text-muted-foreground/40 mb-3" }),
|
|
120
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-muted-foreground", children: t("orders.kanban.noOrders") }),
|
|
121
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground/70 mt-1", children: t("orders.kanban.noOrdersDesc") })
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-4 overflow-x-auto pb-4", children: chunkWBGECNYU_cjs.KANBAN_COLUMNS.map((col) => {
|
|
125
|
+
const columnOrders = grouped[col];
|
|
126
|
+
const colConfig = COLUMN_CONFIG[col];
|
|
127
|
+
const label = t(`orders.kanban.column.${col}`);
|
|
128
|
+
const hasNext = (order) => chunkWBGECNYU_cjs.getNextStatus(order.kind, order.status) !== null;
|
|
129
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 w-72", children: [
|
|
130
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg mb-3", children: [
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `h-2.5 w-2.5 rounded-full ${colConfig.color}` }),
|
|
132
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: label }),
|
|
133
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto inline-flex items-center justify-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium", children: columnOrders.length })
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 max-h-[calc(100vh-16rem)] overflow-y-auto pr-1", children: [
|
|
136
|
+
columnOrders.map((order) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
137
|
+
OrderCard,
|
|
138
|
+
{
|
|
139
|
+
order,
|
|
140
|
+
currency: config.currency,
|
|
141
|
+
onSelect: () => selectOrder(order.id),
|
|
142
|
+
onAdvance: hasNext(order) ? () => advanceOrderStatus(order.id) : void 0
|
|
143
|
+
},
|
|
144
|
+
order.id
|
|
145
|
+
)),
|
|
146
|
+
columnOrders.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-8 text-sm text-muted-foreground rounded-lg border border-dashed", children: t("orders.kanban.noOrders") })
|
|
147
|
+
] })
|
|
148
|
+
] }, col);
|
|
149
|
+
}) });
|
|
150
|
+
}
|
|
151
|
+
function OrdersPage({ config, provider, store, registries }) {
|
|
152
|
+
return /* @__PURE__ */ jsxRuntime.jsx(OrdersContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsx(OrderKanbanView, {}) });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
exports.OrdersPage = OrdersPage;
|
|
156
|
+
//# sourceMappingURL=OrdersPage-2IXARHDR.cjs.map
|
|
157
|
+
//# sourceMappingURL=OrdersPage-2IXARHDR.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/OrdersContext.tsx","../src/views/OrderKanbanView.tsx","../src/OrdersPage.tsx"],"names":["createPluginContext","useTranslation","jsxs","jsx","Clock","ChevronRight","useEffect","useMemo","mapToKanbanColumn","KANBAN_COLUMNS","ClipboardList","getNextStatus"],"mappings":"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","file":"OrdersPage-2IXARHDR.cjs","sourcesContent":["import { createPluginContext, formatCurrency, type CurrencyConfig, type EntityLookup } from '@fayz-ai/saas'\nimport type { OrdersDataProvider } from './data/types'\nimport type { OrdersUIState } from './store'\nimport type { RestaurantOrder } from './types'\n\n// ---------------------------------------------------------------------------\n// Resolved config — fully merged, no optionals\n// ---------------------------------------------------------------------------\n\nexport interface OrdersModules {\n delivery: boolean\n takeout: boolean\n}\n\nexport type OrdersCurrency = CurrencyConfig\n\nexport interface OrderSourceOption {\n value: string\n label: string\n icon?: string\n}\n\nexport interface OrdersPluginLabels {\n pageTitle: string\n pageSubtitle: string\n orderSingular: string\n orderPlural: string\n}\n\nexport interface ResolvedOrdersConfig {\n modules: OrdersModules\n labels: OrdersPluginLabels\n currency: OrdersCurrency\n orderSources: OrderSourceOption[]\n menuItemLookup?: EntityLookup\n staffLookup?: EntityLookup\n onOrderCompleted?: (order: RestaurantOrder) => Promise<void>\n}\n\nconst ctx = createPluginContext<ResolvedOrdersConfig, OrdersDataProvider, OrdersUIState>('OrdersPage')\n\nexport const OrdersContextProvider = ctx.ContextProvider\nexport const useOrdersConfig = ctx.useConfig\nexport const useOrdersProvider = ctx.useProvider\nexport const useOrdersStore = ctx.useStore\n\nexport { formatCurrency }\n","import React, { useEffect, useMemo } from 'react'\nimport { ChevronRight, Clock, Loader2, ClipboardList } from 'lucide-react'\nimport { useOrdersConfig, useOrdersStore, formatCurrency } from '../OrdersContext'\nimport { useTranslation } from '@fayz-ai/core'\nimport {\n KANBAN_COLUMNS,\n mapToKanbanColumn,\n getNextStatus,\n type KanbanColumn,\n type RestaurantOrder,\n} from '../types'\n\n// ---------------------------------------------------------------------------\n// Column config\n// ---------------------------------------------------------------------------\n\nconst COLUMN_CONFIG: Record<KanbanColumn, { color: string }> = {\n new: { color: 'bg-violet-500' },\n confirmed: { color: 'bg-blue-500' },\n preparing: { color: 'bg-amber-500' },\n ready: { color: 'bg-emerald-500' },\n out: { color: 'bg-cyan-500' },\n completed: { color: 'bg-gray-400' },\n cancelled: { color: 'bg-red-500' },\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction timeAgo(dateStr: string): string {\n const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 60000)\n if (diff < 1) return 'just now'\n if (diff === 1) return '1min'\n return `${diff}min`\n}\n\n// ---------------------------------------------------------------------------\n// Order Card\n// ---------------------------------------------------------------------------\n\nfunction OrderCard({\n order,\n onAdvance,\n onSelect,\n currency,\n}: {\n order: RestaurantOrder\n onAdvance?: () => void\n onSelect: () => void\n currency: { code: string; locale: string; symbol: string }\n}) {\n const t = useTranslation()\n const sourceLabel = t(`orders.source.${order.source}`) !== `orders.source.${order.source}`\n ? t(`orders.source.${order.source}`)\n : order.source\n\n return (\n <div\n className=\"rounded-lg border bg-card p-3 hover:shadow-md transition-shadow cursor-pointer\"\n onClick={onSelect}\n >\n {/* Header: order number + source badge */}\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-bold\">{order.referenceNumber}</span>\n <span className=\"inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium\">\n {sourceLabel}\n </span>\n </div>\n\n {/* Customer + table */}\n {order.partyName && (\n <p className=\"text-sm font-medium truncate\">{order.partyName}</p>\n )}\n {order.tableName && (\n <p className=\"text-xs text-muted-foreground\">{order.tableName}</p>\n )}\n\n {/* Items */}\n {order.items.length > 0 && (\n <div className=\"mt-2 space-y-0.5\">\n {order.items.map((item) => (\n <p key={item.id} className=\"text-xs text-muted-foreground truncate\">\n {item.quantity}x {item.name}\n </p>\n ))}\n </div>\n )}\n\n {/* Total + time */}\n <div className=\"flex items-center justify-between mt-3 pt-2 border-t\">\n <span className=\"text-sm font-bold\">\n {formatCurrency(order.total, currency)}\n </span>\n <span className=\"inline-flex items-center gap-0.5 text-[10px] text-muted-foreground\">\n <Clock className=\"h-3 w-3\" />\n {timeAgo(order.createdAt)}\n </span>\n </div>\n\n {/* Advance button */}\n {onAdvance && (\n <button\n type=\"button\"\n className=\"mt-2 flex w-full items-center justify-center gap-1 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n onClick={(e) => {\n e.stopPropagation()\n onAdvance()\n }}\n >\n {t('orders.kanban.advance')}\n <ChevronRight className=\"h-3 w-3\" />\n </button>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton column (loading state)\n// ---------------------------------------------------------------------------\n\nfunction SkeletonColumn() {\n return (\n <div className=\"flex-shrink-0 w-72\">\n <div className=\"h-9 rounded-lg bg-muted animate-pulse mb-3\" />\n <div className=\"space-y-3\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-32 rounded-lg bg-muted/60 animate-pulse\" />\n ))}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// OrderKanbanView\n// ---------------------------------------------------------------------------\n\nexport function OrderKanbanView() {\n const t = useTranslation()\n const config = useOrdersConfig()\n\n const activeOrders = useOrdersStore((s) => s.activeOrders)\n const activeOrdersLoading = useOrdersStore((s) => s.activeOrdersLoading)\n const fetchActiveOrders = useOrdersStore((s) => s.fetchActiveOrders)\n const advanceOrderStatus = useOrdersStore((s) => s.advanceOrderStatus)\n const selectOrder = useOrdersStore((s) => s.selectOrder)\n\n useEffect(() => {\n fetchActiveOrders()\n }, [fetchActiveOrders])\n\n // Group orders by kanban column\n const grouped = useMemo(() => {\n const map: Record<KanbanColumn, RestaurantOrder[]> = {\n new: [],\n confirmed: [],\n preparing: [],\n ready: [],\n out: [],\n completed: [],\n cancelled: [],\n }\n for (const order of activeOrders) {\n const col = mapToKanbanColumn(order.status)\n map[col].push(order)\n }\n return map\n }, [activeOrders])\n\n // Loading state\n if (activeOrdersLoading && activeOrders.length === 0) {\n return (\n <div className=\"flex gap-4 overflow-x-auto pb-4\">\n {KANBAN_COLUMNS.map((col) => (\n <SkeletonColumn key={col} />\n ))}\n </div>\n )\n }\n\n // Empty state\n if (!activeOrdersLoading && activeOrders.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center h-64 text-center\">\n <ClipboardList className=\"h-12 w-12 text-muted-foreground/40 mb-3\" />\n <p className=\"text-sm font-medium text-muted-foreground\">\n {t('orders.kanban.noOrders')}\n </p>\n <p className=\"text-xs text-muted-foreground/70 mt-1\">\n {t('orders.kanban.noOrdersDesc')}\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"flex gap-4 overflow-x-auto pb-4\">\n {KANBAN_COLUMNS.map((col) => {\n const columnOrders = grouped[col]\n const colConfig = COLUMN_CONFIG[col]\n const label = t(`orders.kanban.column.${col}`)\n const hasNext = (order: RestaurantOrder) =>\n getNextStatus(order.kind, order.status) !== null\n\n return (\n <div key={col} className=\"flex-shrink-0 w-72\">\n {/* Column header */}\n <div className=\"flex items-center gap-2 px-3 py-2 rounded-lg mb-3\">\n <span className={`h-2.5 w-2.5 rounded-full ${colConfig.color}`} />\n <span className=\"text-sm font-semibold\">{label}</span>\n <span className=\"ml-auto inline-flex items-center justify-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium\">\n {columnOrders.length}\n </span>\n </div>\n\n {/* Cards */}\n <div className=\"space-y-3 max-h-[calc(100vh-16rem)] overflow-y-auto pr-1\">\n {columnOrders.map((order) => (\n <OrderCard\n key={order.id}\n order={order}\n currency={config.currency}\n onSelect={() => selectOrder(order.id)}\n onAdvance={hasNext(order) ? () => advanceOrderStatus(order.id) : undefined}\n />\n ))}\n {columnOrders.length === 0 && (\n <div className=\"text-center py-8 text-sm text-muted-foreground rounded-lg border border-dashed\">\n {t('orders.kanban.noOrders')}\n </div>\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n","import React from 'react'\nimport type { StoreApi } from 'zustand/vanilla'\nimport { OrdersContextProvider, type ResolvedOrdersConfig } from './OrdersContext'\nimport { OrderKanbanView } from './views/OrderKanbanView'\nimport type { OrdersDataProvider } from './data/types'\nimport type { OrdersUIState } from './store'\nimport type { PluginRegistryDef } from '@fayz-ai/core'\n\nexport function OrdersPage({ config, provider, store, registries }: {\n config: ResolvedOrdersConfig\n provider: OrdersDataProvider\n store: StoreApi<OrdersUIState>\n registries?: PluginRegistryDef[]\n}) {\n return (\n <OrdersContextProvider config={config} provider={provider} store={store}>\n <OrderKanbanView />\n </OrdersContextProvider>\n )\n}\n"]}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { mapToKanbanColumn, KANBAN_COLUMNS, getNextStatus } from './chunk-7LFVFZKZ.js';
|
|
2
|
+
import { createPluginContext, formatCurrency } from '@fayz-ai/saas';
|
|
3
|
+
import { useEffect, useMemo } from 'react';
|
|
4
|
+
import { ClipboardList, Clock, ChevronRight } from 'lucide-react';
|
|
5
|
+
import { useTranslation } from '@fayz-ai/core';
|
|
6
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var ctx = createPluginContext("OrdersPage");
|
|
9
|
+
var OrdersContextProvider = ctx.ContextProvider;
|
|
10
|
+
var useOrdersConfig = ctx.useConfig;
|
|
11
|
+
ctx.useProvider;
|
|
12
|
+
var useOrdersStore = ctx.useStore;
|
|
13
|
+
var COLUMN_CONFIG = {
|
|
14
|
+
new: { color: "bg-violet-500" },
|
|
15
|
+
confirmed: { color: "bg-blue-500" },
|
|
16
|
+
preparing: { color: "bg-amber-500" },
|
|
17
|
+
ready: { color: "bg-emerald-500" },
|
|
18
|
+
out: { color: "bg-cyan-500" },
|
|
19
|
+
completed: { color: "bg-gray-400" },
|
|
20
|
+
cancelled: { color: "bg-red-500" }
|
|
21
|
+
};
|
|
22
|
+
function timeAgo(dateStr) {
|
|
23
|
+
const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 6e4);
|
|
24
|
+
if (diff < 1) return "just now";
|
|
25
|
+
if (diff === 1) return "1min";
|
|
26
|
+
return `${diff}min`;
|
|
27
|
+
}
|
|
28
|
+
function OrderCard({
|
|
29
|
+
order,
|
|
30
|
+
onAdvance,
|
|
31
|
+
onSelect,
|
|
32
|
+
currency
|
|
33
|
+
}) {
|
|
34
|
+
const t = useTranslation();
|
|
35
|
+
const sourceLabel = t(`orders.source.${order.source}`) !== `orders.source.${order.source}` ? t(`orders.source.${order.source}`) : order.source;
|
|
36
|
+
return /* @__PURE__ */ jsxs(
|
|
37
|
+
"div",
|
|
38
|
+
{
|
|
39
|
+
className: "rounded-lg border bg-card p-3 hover:shadow-md transition-shadow cursor-pointer",
|
|
40
|
+
onClick: onSelect,
|
|
41
|
+
children: [
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold", children: order.referenceNumber }),
|
|
44
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium", children: sourceLabel })
|
|
45
|
+
] }),
|
|
46
|
+
order.partyName && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: order.partyName }),
|
|
47
|
+
order.tableName && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: order.tableName }),
|
|
48
|
+
order.items.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-0.5", children: order.items.map((item) => /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground truncate", children: [
|
|
49
|
+
item.quantity,
|
|
50
|
+
"x ",
|
|
51
|
+
item.name
|
|
52
|
+
] }, item.id)) }),
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-3 pt-2 border-t", children: [
|
|
54
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold", children: formatCurrency(order.total, currency) }),
|
|
55
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-muted-foreground", children: [
|
|
56
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
|
|
57
|
+
timeAgo(order.createdAt)
|
|
58
|
+
] })
|
|
59
|
+
] }),
|
|
60
|
+
onAdvance && /* @__PURE__ */ jsxs(
|
|
61
|
+
"button",
|
|
62
|
+
{
|
|
63
|
+
type: "button",
|
|
64
|
+
className: "mt-2 flex w-full items-center justify-center gap-1 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
|
|
65
|
+
onClick: (e) => {
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
onAdvance();
|
|
68
|
+
},
|
|
69
|
+
children: [
|
|
70
|
+
t("orders.kanban.advance"),
|
|
71
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3" })
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
function SkeletonColumn() {
|
|
80
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 w-72", children: [
|
|
81
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 rounded-lg bg-muted animate-pulse mb-3" }),
|
|
82
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-3", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx("div", { className: "h-32 rounded-lg bg-muted/60 animate-pulse" }, i)) })
|
|
83
|
+
] });
|
|
84
|
+
}
|
|
85
|
+
function OrderKanbanView() {
|
|
86
|
+
const t = useTranslation();
|
|
87
|
+
const config = useOrdersConfig();
|
|
88
|
+
const activeOrders = useOrdersStore((s) => s.activeOrders);
|
|
89
|
+
const activeOrdersLoading = useOrdersStore((s) => s.activeOrdersLoading);
|
|
90
|
+
const fetchActiveOrders = useOrdersStore((s) => s.fetchActiveOrders);
|
|
91
|
+
const advanceOrderStatus = useOrdersStore((s) => s.advanceOrderStatus);
|
|
92
|
+
const selectOrder = useOrdersStore((s) => s.selectOrder);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
fetchActiveOrders();
|
|
95
|
+
}, [fetchActiveOrders]);
|
|
96
|
+
const grouped = useMemo(() => {
|
|
97
|
+
const map = {
|
|
98
|
+
new: [],
|
|
99
|
+
confirmed: [],
|
|
100
|
+
preparing: [],
|
|
101
|
+
ready: [],
|
|
102
|
+
out: [],
|
|
103
|
+
completed: [],
|
|
104
|
+
cancelled: []
|
|
105
|
+
};
|
|
106
|
+
for (const order of activeOrders) {
|
|
107
|
+
const col = mapToKanbanColumn(order.status);
|
|
108
|
+
map[col].push(order);
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}, [activeOrders]);
|
|
112
|
+
if (activeOrdersLoading && activeOrders.length === 0) {
|
|
113
|
+
return /* @__PURE__ */ jsx("div", { className: "flex gap-4 overflow-x-auto pb-4", children: KANBAN_COLUMNS.map((col) => /* @__PURE__ */ jsx(SkeletonColumn, {}, col)) });
|
|
114
|
+
}
|
|
115
|
+
if (!activeOrdersLoading && activeOrders.length === 0) {
|
|
116
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-64 text-center", children: [
|
|
117
|
+
/* @__PURE__ */ jsx(ClipboardList, { className: "h-12 w-12 text-muted-foreground/40 mb-3" }),
|
|
118
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-muted-foreground", children: t("orders.kanban.noOrders") }),
|
|
119
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground/70 mt-1", children: t("orders.kanban.noOrdersDesc") })
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
return /* @__PURE__ */ jsx("div", { className: "flex gap-4 overflow-x-auto pb-4", children: KANBAN_COLUMNS.map((col) => {
|
|
123
|
+
const columnOrders = grouped[col];
|
|
124
|
+
const colConfig = COLUMN_CONFIG[col];
|
|
125
|
+
const label = t(`orders.kanban.column.${col}`);
|
|
126
|
+
const hasNext = (order) => getNextStatus(order.kind, order.status) !== null;
|
|
127
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 w-72", children: [
|
|
128
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg mb-3", children: [
|
|
129
|
+
/* @__PURE__ */ jsx("span", { className: `h-2.5 w-2.5 rounded-full ${colConfig.color}` }),
|
|
130
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: label }),
|
|
131
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto inline-flex items-center justify-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium", children: columnOrders.length })
|
|
132
|
+
] }),
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 max-h-[calc(100vh-16rem)] overflow-y-auto pr-1", children: [
|
|
134
|
+
columnOrders.map((order) => /* @__PURE__ */ jsx(
|
|
135
|
+
OrderCard,
|
|
136
|
+
{
|
|
137
|
+
order,
|
|
138
|
+
currency: config.currency,
|
|
139
|
+
onSelect: () => selectOrder(order.id),
|
|
140
|
+
onAdvance: hasNext(order) ? () => advanceOrderStatus(order.id) : void 0
|
|
141
|
+
},
|
|
142
|
+
order.id
|
|
143
|
+
)),
|
|
144
|
+
columnOrders.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-8 text-sm text-muted-foreground rounded-lg border border-dashed", children: t("orders.kanban.noOrders") })
|
|
145
|
+
] })
|
|
146
|
+
] }, col);
|
|
147
|
+
}) });
|
|
148
|
+
}
|
|
149
|
+
function OrdersPage({ config, provider, store, registries }) {
|
|
150
|
+
return /* @__PURE__ */ jsx(OrdersContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(OrderKanbanView, {}) });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { OrdersPage };
|
|
154
|
+
//# sourceMappingURL=OrdersPage-LJIEIZYE.js.map
|
|
155
|
+
//# sourceMappingURL=OrdersPage-LJIEIZYE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/OrdersContext.tsx","../src/views/OrderKanbanView.tsx","../src/OrdersPage.tsx"],"names":["jsx"],"mappings":";;;;;;;AAuCA,IAAM,GAAA,GAAM,oBAA6E,YAAY,CAAA;AAE9F,IAAM,wBAAwB,GAAA,CAAI,eAAA;AAClC,IAAM,kBAAkB,GAAA,CAAI,SAAA;AACF,GAAA,CAAI;AAC9B,IAAM,iBAAiB,GAAA,CAAI,QAAA;AC5BlC,IAAM,aAAA,GAAyD;AAAA,EAC7D,GAAA,EAAK,EAAE,KAAA,EAAO,eAAA,EAAgB;AAAA,EAC9B,SAAA,EAAW,EAAE,KAAA,EAAO,aAAA,EAAc;AAAA,EAClC,SAAA,EAAW,EAAE,KAAA,EAAO,cAAA,EAAe;AAAA,EACnC,KAAA,EAAO,EAAE,KAAA,EAAO,gBAAA,EAAiB;AAAA,EACjC,GAAA,EAAK,EAAE,KAAA,EAAO,aAAA,EAAc;AAAA,EAC5B,SAAA,EAAW,EAAE,KAAA,EAAO,aAAA,EAAc;AAAA,EAClC,SAAA,EAAW,EAAE,KAAA,EAAO,YAAA;AACtB,CAAA;AAMA,SAAS,QAAQ,OAAA,EAAyB;AACxC,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAA,CAAO,IAAA,CAAK,GAAA,EAAI,GAAI,IAAI,IAAA,CAAK,OAAO,CAAA,CAAE,OAAA,EAAQ,IAAK,GAAK,CAAA;AAC1E,EAAA,IAAI,IAAA,GAAO,GAAG,OAAO,UAAA;AACrB,EAAA,IAAI,IAAA,KAAS,GAAG,OAAO,MAAA;AACvB,EAAA,OAAO,GAAG,IAAI,CAAA,GAAA,CAAA;AAChB;AAMA,SAAS,SAAA,CAAU;AAAA,EACjB,KAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAKG;AACD,EAAA,MAAM,IAAI,cAAA,EAAe;AACzB,EAAA,MAAM,cAAc,CAAA,CAAE,CAAA,cAAA,EAAiB,KAAA,CAAM,MAAM,EAAE,CAAA,KAAM,CAAA,cAAA,EAAiB,KAAA,CAAM,MAAM,KACpF,CAAA,CAAE,CAAA,cAAA,EAAiB,MAAM,MAAM,CAAA,CAAE,IACjC,KAAA,CAAM,MAAA;AAEV,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,gFAAA;AAAA,MACV,OAAA,EAAS,QAAA;AAAA,MAGT,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mBAAA,EAAqB,QAAA,EAAA,KAAA,CAAM,eAAA,EAAgB,CAAA;AAAA,0BAC3D,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0FAAA,EACb,QAAA,EAAA,WAAA,EACH;AAAA,SAAA,EACF,CAAA;AAAA,QAGC,MAAM,SAAA,oBACL,GAAA,CAAC,OAAE,SAAA,EAAU,8BAAA,EAAgC,gBAAM,SAAA,EAAU,CAAA;AAAA,QAE9D,MAAM,SAAA,oBACL,GAAA,CAAC,OAAE,SAAA,EAAU,+BAAA,EAAiC,gBAAM,SAAA,EAAU,CAAA;AAAA,QAI/D,MAAM,KAAA,CAAM,MAAA,GAAS,CAAA,oBACpB,GAAA,CAAC,SAAI,SAAA,EAAU,kBAAA,EACZ,QAAA,EAAA,KAAA,CAAM,KAAA,CAAM,IAAI,CAAC,IAAA,qBAChB,IAAA,CAAC,GAAA,EAAA,EAAgB,WAAU,wCAAA,EACxB,QAAA,EAAA;AAAA,UAAA,IAAA,CAAK,QAAA;AAAA,UAAS,IAAA;AAAA,UAAG,IAAA,CAAK;AAAA,SAAA,EAAA,EADjB,IAAA,CAAK,EAEb,CACD,CAAA,EACH,CAAA;AAAA,wBAIF,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,sDAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,UAAK,SAAA,EAAU,mBAAA,EACb,yBAAe,KAAA,CAAM,KAAA,EAAO,QAAQ,CAAA,EACvC,CAAA;AAAA,0BACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oEAAA,EACd,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,KAAA,EAAA,EAAM,WAAU,SAAA,EAAU,CAAA;AAAA,YAC1B,OAAA,CAAQ,MAAM,SAAS;AAAA,WAAA,EAC1B;AAAA,SAAA,EACF,CAAA;AAAA,QAGC,SAAA,oBACC,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,SAAA,EAAU,wKAAA;AAAA,YACV,OAAA,EAAS,CAAC,CAAA,KAAM;AACd,cAAA,CAAA,CAAE,eAAA,EAAgB;AAClB,cAAA,SAAA,EAAU;AAAA,YACZ,CAAA;AAAA,YAEC,QAAA,EAAA;AAAA,cAAA,CAAA,CAAE,uBAAuB,CAAA;AAAA,8BAC1B,GAAA,CAAC,YAAA,EAAA,EAAa,SAAA,EAAU,SAAA,EAAU;AAAA;AAAA;AAAA;AACpC;AAAA;AAAA,GAEJ;AAEJ;AAMA,SAAS,cAAA,GAAiB;AACxB,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oBAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,4CAAA,EAA6C,CAAA;AAAA,wBAC3D,KAAA,EAAA,EAAI,SAAA,EAAU,aACZ,QAAA,EAAA,CAAC,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,sBACd,GAAA,CAAC,KAAA,EAAA,EAAY,WAAU,2CAAA,EAAA,EAAb,CAAyD,CACpE,CAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAMO,SAAS,eAAA,GAAkB;AAChC,EAAA,MAAM,IAAI,cAAA,EAAe;AACzB,EAAA,MAAM,SAAS,eAAA,EAAgB;AAE/B,EAAA,MAAM,YAAA,GAAe,cAAA,CAAe,CAAC,CAAA,KAAM,EAAE,YAAY,CAAA;AACzD,EAAA,MAAM,mBAAA,GAAsB,cAAA,CAAe,CAAC,CAAA,KAAM,EAAE,mBAAmB,CAAA;AACvE,EAAA,MAAM,iBAAA,GAAoB,cAAA,CAAe,CAAC,CAAA,KAAM,EAAE,iBAAiB,CAAA;AACnE,EAAA,MAAM,kBAAA,GAAqB,cAAA,CAAe,CAAC,CAAA,KAAM,EAAE,kBAAkB,CAAA;AACrE,EAAA,MAAM,WAAA,GAAc,cAAA,CAAe,CAAC,CAAA,KAAM,EAAE,WAAW,CAAA;AAEvD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,iBAAA,EAAkB;AAAA,EACpB,CAAA,EAAG,CAAC,iBAAiB,CAAC,CAAA;AAGtB,EAAA,MAAM,OAAA,GAAU,QAAQ,MAAM;AAC5B,IAAA,MAAM,GAAA,GAA+C;AAAA,MACnD,KAAK,EAAC;AAAA,MACN,WAAW,EAAC;AAAA,MACZ,WAAW,EAAC;AAAA,MACZ,OAAO,EAAC;AAAA,MACR,KAAK,EAAC;AAAA,MACN,WAAW,EAAC;AAAA,MACZ,WAAW;AAAC,KACd;AACA,IAAA,KAAA,MAAW,SAAS,YAAA,EAAc;AAChC,MAAA,MAAM,GAAA,GAAM,iBAAA,CAAkB,KAAA,CAAM,MAAM,CAAA;AAC1C,MAAA,GAAA,CAAI,GAAG,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA;AAAA,IACrB;AACA,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAGjB,EAAA,IAAI,mBAAA,IAAuB,YAAA,CAAa,MAAA,KAAW,CAAA,EAAG;AACpD,IAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iCAAA,EACZ,QAAA,EAAA,cAAA,CAAe,GAAA,CAAI,CAAC,GAAA,qBACnB,GAAA,CAAC,cAAA,EAAA,EAAA,EAAoB,GAAK,CAC3B,CAAA,EACH,CAAA;AAAA,EAEJ;AAGA,EAAA,IAAI,CAAC,mBAAA,IAAuB,YAAA,CAAa,MAAA,KAAW,CAAA,EAAG;AACrD,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,4DAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,WAAU,yCAAA,EAA0C,CAAA;AAAA,0BAClE,GAAA,EAAA,EAAE,SAAA,EAAU,2CAAA,EACV,QAAA,EAAA,CAAA,CAAE,wBAAwB,CAAA,EAC7B,CAAA;AAAA,0BACC,GAAA,EAAA,EAAE,SAAA,EAAU,uCAAA,EACV,QAAA,EAAA,CAAA,CAAE,4BAA4B,CAAA,EACjC;AAAA,KAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,2BACG,KAAA,EAAA,EAAI,SAAA,EAAU,mCACZ,QAAA,EAAA,cAAA,CAAe,GAAA,CAAI,CAAC,GAAA,KAAQ;AAC3B,IAAA,MAAM,YAAA,GAAe,QAAQ,GAAG,CAAA;AAChC,IAAA,MAAM,SAAA,GAAY,cAAc,GAAG,CAAA;AACnC,IAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,CAAA,qBAAA,EAAwB,GAAG,CAAA,CAAE,CAAA;AAC7C,IAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KACf,aAAA,CAAc,MAAM,IAAA,EAAM,KAAA,CAAM,MAAM,CAAA,KAAM,IAAA;AAE9C,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAc,SAAA,EAAU,oBAAA,EAEvB,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,mDAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,CAAA,yBAAA,EAA4B,SAAA,CAAU,KAAK,CAAA,CAAA,EAAI,CAAA;AAAA,wBAChE,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAyB,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,wBAC/C,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uGAAA,EACb,uBAAa,MAAA,EAChB;AAAA,OAAA,EACF,CAAA;AAAA,sBAGA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0DAAA,EACZ,QAAA,EAAA;AAAA,QAAA,YAAA,CAAa,GAAA,CAAI,CAAC,KAAA,qBACjB,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YAEC,KAAA;AAAA,YACA,UAAU,MAAA,CAAO,QAAA;AAAA,YACjB,QAAA,EAAU,MAAM,WAAA,CAAY,KAAA,CAAM,EAAE,CAAA;AAAA,YACpC,SAAA,EAAW,QAAQ,KAAK,CAAA,GAAI,MAAM,kBAAA,CAAmB,KAAA,CAAM,EAAE,CAAA,GAAI;AAAA,WAAA;AAAA,UAJ5D,KAAA,CAAM;AAAA,SAMd,CAAA;AAAA,QACA,YAAA,CAAa,WAAW,CAAA,oBACvB,GAAA,CAAC,SAAI,SAAA,EAAU,gFAAA,EACZ,QAAA,EAAA,CAAA,CAAE,wBAAwB,CAAA,EAC7B;AAAA,OAAA,EAEJ;AAAA,KAAA,EAAA,EA1BQ,GA2BV,CAAA;AAAA,EAEJ,CAAC,CAAA,EACH,CAAA;AAEJ;ACvOO,SAAS,WAAW,EAAE,MAAA,EAAQ,QAAA,EAAU,KAAA,EAAO,YAAW,EAK9D;AACD,EAAA,uBACEA,IAAC,qBAAA,EAAA,EAAsB,MAAA,EAAgB,UAAoB,KAAA,EACzD,QAAA,kBAAAA,GAAAA,CAAC,eAAA,EAAA,EAAgB,CAAA,EACnB,CAAA;AAEJ","file":"OrdersPage-LJIEIZYE.js","sourcesContent":["import { createPluginContext, formatCurrency, type CurrencyConfig, type EntityLookup } from '@fayz-ai/saas'\nimport type { OrdersDataProvider } from './data/types'\nimport type { OrdersUIState } from './store'\nimport type { RestaurantOrder } from './types'\n\n// ---------------------------------------------------------------------------\n// Resolved config — fully merged, no optionals\n// ---------------------------------------------------------------------------\n\nexport interface OrdersModules {\n delivery: boolean\n takeout: boolean\n}\n\nexport type OrdersCurrency = CurrencyConfig\n\nexport interface OrderSourceOption {\n value: string\n label: string\n icon?: string\n}\n\nexport interface OrdersPluginLabels {\n pageTitle: string\n pageSubtitle: string\n orderSingular: string\n orderPlural: string\n}\n\nexport interface ResolvedOrdersConfig {\n modules: OrdersModules\n labels: OrdersPluginLabels\n currency: OrdersCurrency\n orderSources: OrderSourceOption[]\n menuItemLookup?: EntityLookup\n staffLookup?: EntityLookup\n onOrderCompleted?: (order: RestaurantOrder) => Promise<void>\n}\n\nconst ctx = createPluginContext<ResolvedOrdersConfig, OrdersDataProvider, OrdersUIState>('OrdersPage')\n\nexport const OrdersContextProvider = ctx.ContextProvider\nexport const useOrdersConfig = ctx.useConfig\nexport const useOrdersProvider = ctx.useProvider\nexport const useOrdersStore = ctx.useStore\n\nexport { formatCurrency }\n","import React, { useEffect, useMemo } from 'react'\nimport { ChevronRight, Clock, Loader2, ClipboardList } from 'lucide-react'\nimport { useOrdersConfig, useOrdersStore, formatCurrency } from '../OrdersContext'\nimport { useTranslation } from '@fayz-ai/core'\nimport {\n KANBAN_COLUMNS,\n mapToKanbanColumn,\n getNextStatus,\n type KanbanColumn,\n type RestaurantOrder,\n} from '../types'\n\n// ---------------------------------------------------------------------------\n// Column config\n// ---------------------------------------------------------------------------\n\nconst COLUMN_CONFIG: Record<KanbanColumn, { color: string }> = {\n new: { color: 'bg-violet-500' },\n confirmed: { color: 'bg-blue-500' },\n preparing: { color: 'bg-amber-500' },\n ready: { color: 'bg-emerald-500' },\n out: { color: 'bg-cyan-500' },\n completed: { color: 'bg-gray-400' },\n cancelled: { color: 'bg-red-500' },\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction timeAgo(dateStr: string): string {\n const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 60000)\n if (diff < 1) return 'just now'\n if (diff === 1) return '1min'\n return `${diff}min`\n}\n\n// ---------------------------------------------------------------------------\n// Order Card\n// ---------------------------------------------------------------------------\n\nfunction OrderCard({\n order,\n onAdvance,\n onSelect,\n currency,\n}: {\n order: RestaurantOrder\n onAdvance?: () => void\n onSelect: () => void\n currency: { code: string; locale: string; symbol: string }\n}) {\n const t = useTranslation()\n const sourceLabel = t(`orders.source.${order.source}`) !== `orders.source.${order.source}`\n ? t(`orders.source.${order.source}`)\n : order.source\n\n return (\n <div\n className=\"rounded-lg border bg-card p-3 hover:shadow-md transition-shadow cursor-pointer\"\n onClick={onSelect}\n >\n {/* Header: order number + source badge */}\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-bold\">{order.referenceNumber}</span>\n <span className=\"inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium\">\n {sourceLabel}\n </span>\n </div>\n\n {/* Customer + table */}\n {order.partyName && (\n <p className=\"text-sm font-medium truncate\">{order.partyName}</p>\n )}\n {order.tableName && (\n <p className=\"text-xs text-muted-foreground\">{order.tableName}</p>\n )}\n\n {/* Items */}\n {order.items.length > 0 && (\n <div className=\"mt-2 space-y-0.5\">\n {order.items.map((item) => (\n <p key={item.id} className=\"text-xs text-muted-foreground truncate\">\n {item.quantity}x {item.name}\n </p>\n ))}\n </div>\n )}\n\n {/* Total + time */}\n <div className=\"flex items-center justify-between mt-3 pt-2 border-t\">\n <span className=\"text-sm font-bold\">\n {formatCurrency(order.total, currency)}\n </span>\n <span className=\"inline-flex items-center gap-0.5 text-[10px] text-muted-foreground\">\n <Clock className=\"h-3 w-3\" />\n {timeAgo(order.createdAt)}\n </span>\n </div>\n\n {/* Advance button */}\n {onAdvance && (\n <button\n type=\"button\"\n className=\"mt-2 flex w-full items-center justify-center gap-1 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 transition-colors\"\n onClick={(e) => {\n e.stopPropagation()\n onAdvance()\n }}\n >\n {t('orders.kanban.advance')}\n <ChevronRight className=\"h-3 w-3\" />\n </button>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton column (loading state)\n// ---------------------------------------------------------------------------\n\nfunction SkeletonColumn() {\n return (\n <div className=\"flex-shrink-0 w-72\">\n <div className=\"h-9 rounded-lg bg-muted animate-pulse mb-3\" />\n <div className=\"space-y-3\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-32 rounded-lg bg-muted/60 animate-pulse\" />\n ))}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// OrderKanbanView\n// ---------------------------------------------------------------------------\n\nexport function OrderKanbanView() {\n const t = useTranslation()\n const config = useOrdersConfig()\n\n const activeOrders = useOrdersStore((s) => s.activeOrders)\n const activeOrdersLoading = useOrdersStore((s) => s.activeOrdersLoading)\n const fetchActiveOrders = useOrdersStore((s) => s.fetchActiveOrders)\n const advanceOrderStatus = useOrdersStore((s) => s.advanceOrderStatus)\n const selectOrder = useOrdersStore((s) => s.selectOrder)\n\n useEffect(() => {\n fetchActiveOrders()\n }, [fetchActiveOrders])\n\n // Group orders by kanban column\n const grouped = useMemo(() => {\n const map: Record<KanbanColumn, RestaurantOrder[]> = {\n new: [],\n confirmed: [],\n preparing: [],\n ready: [],\n out: [],\n completed: [],\n cancelled: [],\n }\n for (const order of activeOrders) {\n const col = mapToKanbanColumn(order.status)\n map[col].push(order)\n }\n return map\n }, [activeOrders])\n\n // Loading state\n if (activeOrdersLoading && activeOrders.length === 0) {\n return (\n <div className=\"flex gap-4 overflow-x-auto pb-4\">\n {KANBAN_COLUMNS.map((col) => (\n <SkeletonColumn key={col} />\n ))}\n </div>\n )\n }\n\n // Empty state\n if (!activeOrdersLoading && activeOrders.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center h-64 text-center\">\n <ClipboardList className=\"h-12 w-12 text-muted-foreground/40 mb-3\" />\n <p className=\"text-sm font-medium text-muted-foreground\">\n {t('orders.kanban.noOrders')}\n </p>\n <p className=\"text-xs text-muted-foreground/70 mt-1\">\n {t('orders.kanban.noOrdersDesc')}\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"flex gap-4 overflow-x-auto pb-4\">\n {KANBAN_COLUMNS.map((col) => {\n const columnOrders = grouped[col]\n const colConfig = COLUMN_CONFIG[col]\n const label = t(`orders.kanban.column.${col}`)\n const hasNext = (order: RestaurantOrder) =>\n getNextStatus(order.kind, order.status) !== null\n\n return (\n <div key={col} className=\"flex-shrink-0 w-72\">\n {/* Column header */}\n <div className=\"flex items-center gap-2 px-3 py-2 rounded-lg mb-3\">\n <span className={`h-2.5 w-2.5 rounded-full ${colConfig.color}`} />\n <span className=\"text-sm font-semibold\">{label}</span>\n <span className=\"ml-auto inline-flex items-center justify-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium\">\n {columnOrders.length}\n </span>\n </div>\n\n {/* Cards */}\n <div className=\"space-y-3 max-h-[calc(100vh-16rem)] overflow-y-auto pr-1\">\n {columnOrders.map((order) => (\n <OrderCard\n key={order.id}\n order={order}\n currency={config.currency}\n onSelect={() => selectOrder(order.id)}\n onAdvance={hasNext(order) ? () => advanceOrderStatus(order.id) : undefined}\n />\n ))}\n {columnOrders.length === 0 && (\n <div className=\"text-center py-8 text-sm text-muted-foreground rounded-lg border border-dashed\">\n {t('orders.kanban.noOrders')}\n </div>\n )}\n </div>\n </div>\n )\n })}\n </div>\n )\n}\n","import React from 'react'\nimport type { StoreApi } from 'zustand/vanilla'\nimport { OrdersContextProvider, type ResolvedOrdersConfig } from './OrdersContext'\nimport { OrderKanbanView } from './views/OrderKanbanView'\nimport type { OrdersDataProvider } from './data/types'\nimport type { OrdersUIState } from './store'\nimport type { PluginRegistryDef } from '@fayz-ai/core'\n\nexport function OrdersPage({ config, provider, store, registries }: {\n config: ResolvedOrdersConfig\n provider: OrdersDataProvider\n store: StoreApi<OrdersUIState>\n registries?: PluginRegistryDef[]\n}) {\n return (\n <OrdersContextProvider config={config} provider={provider} store={store}>\n <OrderKanbanView />\n </OrdersContextProvider>\n )\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { StoreApi } from 'zustand/vanilla';
|
|
3
|
+
import { type ResolvedOrdersConfig } from './OrdersContext';
|
|
4
|
+
import type { OrdersDataProvider } from './data/types';
|
|
5
|
+
import type { OrdersUIState } from './store';
|
|
6
|
+
import type { PluginRegistryDef } from '@fayz-ai/core';
|
|
7
|
+
export declare function OrdersPage({ config, provider, store, registries }: {
|
|
8
|
+
config: ResolvedOrdersConfig;
|
|
9
|
+
provider: OrdersDataProvider;
|
|
10
|
+
store: StoreApi<OrdersUIState>;
|
|
11
|
+
registries?: PluginRegistryDef[];
|
|
12
|
+
}): React.JSX.Element;
|
|
13
|
+
//# sourceMappingURL=OrdersPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OrdersPage.d.ts","sourceRoot":"","sources":["../src/OrdersPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAyB,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAElF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEtD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;IAClE,MAAM,EAAE,oBAAoB,CAAA;IAC5B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAA;IAC9B,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAA;CACjC,qBAMA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var STATUS_FLOW = {
|
|
3
|
+
dine_in: ["new", "confirmed", "preparing", "ready", "served", "completed"],
|
|
4
|
+
takeout: ["new", "confirmed", "preparing", "ready", "picked_up", "completed"],
|
|
5
|
+
delivery: ["new", "confirmed", "preparing", "ready", "dispatched", "delivered", "completed"]
|
|
6
|
+
};
|
|
7
|
+
function getNextStatus(kind, current) {
|
|
8
|
+
const flow = STATUS_FLOW[kind];
|
|
9
|
+
const idx = flow.indexOf(current);
|
|
10
|
+
return idx >= 0 && idx < flow.length - 1 ? flow[idx + 1] : null;
|
|
11
|
+
}
|
|
12
|
+
function mapToKanbanColumn(status) {
|
|
13
|
+
if (["served", "picked_up", "dispatched", "delivered"].includes(status)) return "out";
|
|
14
|
+
if (["new", "confirmed", "preparing", "ready", "completed", "cancelled"].includes(status)) {
|
|
15
|
+
return status;
|
|
16
|
+
}
|
|
17
|
+
return "new";
|
|
18
|
+
}
|
|
19
|
+
var KANBAN_COLUMNS = ["new", "confirmed", "preparing", "ready", "out", "completed"];
|
|
20
|
+
|
|
21
|
+
export { KANBAN_COLUMNS, STATUS_FLOW, getNextStatus, mapToKanbanColumn };
|
|
22
|
+
//# sourceMappingURL=chunk-7LFVFZKZ.js.map
|
|
23
|
+
//# sourceMappingURL=chunk-7LFVFZKZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";AAsBO,IAAM,WAAA,GAAgD;AAAA,EAC3D,SAAS,CAAC,KAAA,EAAO,aAAa,WAAA,EAAa,OAAA,EAAS,UAAU,WAAW,CAAA;AAAA,EACzE,SAAS,CAAC,KAAA,EAAO,aAAa,WAAA,EAAa,OAAA,EAAS,aAAa,WAAW,CAAA;AAAA,EAC5E,QAAA,EAAU,CAAC,KAAA,EAAO,WAAA,EAAa,aAAa,OAAA,EAAS,YAAA,EAAc,aAAa,WAAW;AAC7F;AAEO,SAAS,aAAA,CAAc,MAAiB,OAAA,EAA0C;AACvF,EAAA,MAAM,IAAA,GAAO,YAAY,IAAI,CAAA;AAC7B,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAChC,EAAA,OAAO,GAAA,IAAO,KAAK,GAAA,GAAM,IAAA,CAAK,SAAS,CAAA,GAAI,IAAA,CAAK,GAAA,GAAM,CAAC,CAAA,GAAI,IAAA;AAC7D;AAQO,SAAS,kBAAkB,MAAA,EAAmC;AACnE,EAAA,IAAI,CAAC,UAAU,WAAA,EAAa,YAAA,EAAc,WAAW,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,KAAA;AAChF,EAAA,IAAI,CAAC,KAAA,EAAO,WAAA,EAAa,WAAA,EAAa,OAAA,EAAS,aAAa,WAAW,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG;AACzF,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,iBAAiC,CAAC,KAAA,EAAO,aAAa,WAAA,EAAa,OAAA,EAAS,OAAO,WAAW","file":"chunk-7LFVFZKZ.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Orders Plugin — Pure TypeScript types\n// ---------------------------------------------------------------------------\n\n// ============================================================\n// ENUMS / LITERALS\n// ============================================================\n\nexport type OrderKind = 'dine_in' | 'takeout' | 'delivery'\n\nexport type DineInStatus = 'new' | 'confirmed' | 'preparing' | 'ready' | 'served' | 'completed' | 'cancelled'\nexport type TakeoutStatus = 'new' | 'confirmed' | 'preparing' | 'ready' | 'picked_up' | 'completed' | 'cancelled'\nexport type DeliveryStatus = 'new' | 'confirmed' | 'preparing' | 'ready' | 'dispatched' | 'delivered' | 'completed' | 'cancelled'\nexport type OrderStatus = DineInStatus | TakeoutStatus | DeliveryStatus\n\n/** Unified columns for the kanban board */\nexport type KanbanColumn = 'new' | 'confirmed' | 'preparing' | 'ready' | 'out' | 'completed' | 'cancelled'\n\n// ============================================================\n// STATUS FLOW\n// ============================================================\n\nexport const STATUS_FLOW: Record<OrderKind, OrderStatus[]> = {\n dine_in: ['new', 'confirmed', 'preparing', 'ready', 'served', 'completed'],\n takeout: ['new', 'confirmed', 'preparing', 'ready', 'picked_up', 'completed'],\n delivery: ['new', 'confirmed', 'preparing', 'ready', 'dispatched', 'delivered', 'completed'],\n}\n\nexport function getNextStatus(kind: OrderKind, current: OrderStatus): OrderStatus | null {\n const flow = STATUS_FLOW[kind]\n const idx = flow.indexOf(current)\n return idx >= 0 && idx < flow.length - 1 ? flow[idx + 1] : null\n}\n\nexport function getPreviousStatus(kind: OrderKind, current: OrderStatus): OrderStatus | null {\n const flow = STATUS_FLOW[kind]\n const idx = flow.indexOf(current)\n return idx > 0 ? flow[idx - 1] : null\n}\n\nexport function mapToKanbanColumn(status: OrderStatus): KanbanColumn {\n if (['served', 'picked_up', 'dispatched', 'delivered'].includes(status)) return 'out'\n if (['new', 'confirmed', 'preparing', 'ready', 'completed', 'cancelled'].includes(status)) {\n return status as KanbanColumn\n }\n return 'new'\n}\n\nexport const KANBAN_COLUMNS: KanbanColumn[] = ['new', 'confirmed', 'preparing', 'ready', 'out', 'completed']\n\n// ============================================================\n// CORE ENTITIES\n// ============================================================\n\n/** Restaurant order — saas_core.orders + public.orders extension */\nexport interface RestaurantOrder {\n id: string\n referenceNumber: string\n kind: OrderKind\n status: OrderStatus\n partyId?: string\n partyName?: string\n assigneeId?: string\n assigneeName?: string\n locationId?: string\n subtotal: number\n discount: number\n tax: number\n total: number\n currency: string\n notes?: string\n tags: string[]\n // Extension fields\n source: string\n tableId?: string\n tableName?: string\n estimatedMinutes?: number\n items: RestaurantOrderItem[]\n metadata?: Record<string, unknown>\n tenantId: string\n createdAt: string\n updatedAt: string\n completedAt?: string\n}\n\nexport interface RestaurantOrderItem {\n id: string\n orderId: string\n productId?: string\n name: string\n description?: string\n quantity: number\n unitPrice: number\n discount: number\n total: number\n sortOrder: number\n modifiers?: string\n kitchenNotes?: string\n metadata?: Record<string, unknown>\n createdAt: string\n}\n\n// ============================================================\n// INPUT TYPES\n// ============================================================\n\nexport interface CreateOrderInput {\n kind: OrderKind\n source?: string\n tableId?: string\n partyId?: string\n assigneeId?: string\n notes?: string\n estimatedMinutes?: number\n items: CreateOrderItemInput[]\n}\n\nexport interface CreateOrderItemInput {\n productId?: string\n name: string\n quantity: number\n unitPrice: number\n discount?: number\n modifiers?: string\n kitchenNotes?: string\n}\n\nexport interface UpdateOrderStatusInput {\n orderId: string\n status: OrderStatus\n}\n\n// ============================================================\n// QUERY TYPES\n// ============================================================\n\nexport interface OrderQuery {\n kind?: OrderKind | OrderKind[]\n status?: OrderStatus | OrderStatus[]\n source?: string\n tableId?: string\n assigneeId?: string\n dateRange?: { from: string; to: string }\n search?: string\n page?: number\n pageSize?: number\n}\n\nexport interface PaginatedResult<T> {\n data: T[]\n total: number\n}\n\n// ============================================================\n// AGGREGATION\n// ============================================================\n\nexport interface OrdersSummary {\n todayOrderCount: number\n todayRevenue: number\n averageTicket: number\n activeOrderCount: number\n ordersByKind: Record<OrderKind, number>\n ordersByStatus: Record<string, number>\n topItems: Array<{ name: string; quantity: number; revenue: number }>\n}\n\n// ============================================================\n// STATUS CONFIG\n// ============================================================\n\nexport interface OrderStatusConfig {\n value: string\n label: string\n color: string\n icon?: string\n}\n\nexport const DEFAULT_STATUS_CONFIGS: OrderStatusConfig[] = [\n { value: 'new', label: 'New', color: '#6366f1', icon: 'PlusCircle' },\n { value: 'confirmed', label: 'Confirmed', color: '#3b82f6', icon: 'CheckCircle' },\n { value: 'preparing', label: 'Preparing', color: '#f59e0b', icon: 'ChefHat' },\n { value: 'ready', label: 'Ready', color: '#10b981', icon: 'Bell' },\n { value: 'served', label: 'Served', color: '#06b6d4', icon: 'Utensils' },\n { value: 'picked_up', label: 'Picked Up', color: '#06b6d4', icon: 'ShoppingBag' },\n { value: 'dispatched', label: 'Dispatched', color: '#8b5cf6', icon: 'Truck' },\n { value: 'delivered', label: 'Delivered', color: '#06b6d4', icon: 'PackageCheck' },\n { value: 'completed', label: 'Completed', color: '#22c55e', icon: 'CircleCheck' },\n { value: 'cancelled', label: 'Cancelled', color: '#ef4444', icon: 'XCircle' },\n]\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/types.ts
|
|
4
|
+
var STATUS_FLOW = {
|
|
5
|
+
dine_in: ["new", "confirmed", "preparing", "ready", "served", "completed"],
|
|
6
|
+
takeout: ["new", "confirmed", "preparing", "ready", "picked_up", "completed"],
|
|
7
|
+
delivery: ["new", "confirmed", "preparing", "ready", "dispatched", "delivered", "completed"]
|
|
8
|
+
};
|
|
9
|
+
function getNextStatus(kind, current) {
|
|
10
|
+
const flow = STATUS_FLOW[kind];
|
|
11
|
+
const idx = flow.indexOf(current);
|
|
12
|
+
return idx >= 0 && idx < flow.length - 1 ? flow[idx + 1] : null;
|
|
13
|
+
}
|
|
14
|
+
function mapToKanbanColumn(status) {
|
|
15
|
+
if (["served", "picked_up", "dispatched", "delivered"].includes(status)) return "out";
|
|
16
|
+
if (["new", "confirmed", "preparing", "ready", "completed", "cancelled"].includes(status)) {
|
|
17
|
+
return status;
|
|
18
|
+
}
|
|
19
|
+
return "new";
|
|
20
|
+
}
|
|
21
|
+
var KANBAN_COLUMNS = ["new", "confirmed", "preparing", "ready", "out", "completed"];
|
|
22
|
+
|
|
23
|
+
exports.KANBAN_COLUMNS = KANBAN_COLUMNS;
|
|
24
|
+
exports.STATUS_FLOW = STATUS_FLOW;
|
|
25
|
+
exports.getNextStatus = getNextStatus;
|
|
26
|
+
exports.mapToKanbanColumn = mapToKanbanColumn;
|
|
27
|
+
//# sourceMappingURL=chunk-WBGECNYU.cjs.map
|
|
28
|
+
//# sourceMappingURL=chunk-WBGECNYU.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";;;AAsBO,IAAM,WAAA,GAAgD;AAAA,EAC3D,SAAS,CAAC,KAAA,EAAO,aAAa,WAAA,EAAa,OAAA,EAAS,UAAU,WAAW,CAAA;AAAA,EACzE,SAAS,CAAC,KAAA,EAAO,aAAa,WAAA,EAAa,OAAA,EAAS,aAAa,WAAW,CAAA;AAAA,EAC5E,QAAA,EAAU,CAAC,KAAA,EAAO,WAAA,EAAa,aAAa,OAAA,EAAS,YAAA,EAAc,aAAa,WAAW;AAC7F;AAEO,SAAS,aAAA,CAAc,MAAiB,OAAA,EAA0C;AACvF,EAAA,MAAM,IAAA,GAAO,YAAY,IAAI,CAAA;AAC7B,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAChC,EAAA,OAAO,GAAA,IAAO,KAAK,GAAA,GAAM,IAAA,CAAK,SAAS,CAAA,GAAI,IAAA,CAAK,GAAA,GAAM,CAAC,CAAA,GAAI,IAAA;AAC7D;AAQO,SAAS,kBAAkB,MAAA,EAAmC;AACnE,EAAA,IAAI,CAAC,UAAU,WAAA,EAAa,YAAA,EAAc,WAAW,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,KAAA;AAChF,EAAA,IAAI,CAAC,KAAA,EAAO,WAAA,EAAa,WAAA,EAAa,OAAA,EAAS,aAAa,WAAW,CAAA,CAAE,QAAA,CAAS,MAAM,CAAA,EAAG;AACzF,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,iBAAiC,CAAC,KAAA,EAAO,aAAa,WAAA,EAAa,OAAA,EAAS,OAAO,WAAW","file":"chunk-WBGECNYU.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Orders Plugin — Pure TypeScript types\n// ---------------------------------------------------------------------------\n\n// ============================================================\n// ENUMS / LITERALS\n// ============================================================\n\nexport type OrderKind = 'dine_in' | 'takeout' | 'delivery'\n\nexport type DineInStatus = 'new' | 'confirmed' | 'preparing' | 'ready' | 'served' | 'completed' | 'cancelled'\nexport type TakeoutStatus = 'new' | 'confirmed' | 'preparing' | 'ready' | 'picked_up' | 'completed' | 'cancelled'\nexport type DeliveryStatus = 'new' | 'confirmed' | 'preparing' | 'ready' | 'dispatched' | 'delivered' | 'completed' | 'cancelled'\nexport type OrderStatus = DineInStatus | TakeoutStatus | DeliveryStatus\n\n/** Unified columns for the kanban board */\nexport type KanbanColumn = 'new' | 'confirmed' | 'preparing' | 'ready' | 'out' | 'completed' | 'cancelled'\n\n// ============================================================\n// STATUS FLOW\n// ============================================================\n\nexport const STATUS_FLOW: Record<OrderKind, OrderStatus[]> = {\n dine_in: ['new', 'confirmed', 'preparing', 'ready', 'served', 'completed'],\n takeout: ['new', 'confirmed', 'preparing', 'ready', 'picked_up', 'completed'],\n delivery: ['new', 'confirmed', 'preparing', 'ready', 'dispatched', 'delivered', 'completed'],\n}\n\nexport function getNextStatus(kind: OrderKind, current: OrderStatus): OrderStatus | null {\n const flow = STATUS_FLOW[kind]\n const idx = flow.indexOf(current)\n return idx >= 0 && idx < flow.length - 1 ? flow[idx + 1] : null\n}\n\nexport function getPreviousStatus(kind: OrderKind, current: OrderStatus): OrderStatus | null {\n const flow = STATUS_FLOW[kind]\n const idx = flow.indexOf(current)\n return idx > 0 ? flow[idx - 1] : null\n}\n\nexport function mapToKanbanColumn(status: OrderStatus): KanbanColumn {\n if (['served', 'picked_up', 'dispatched', 'delivered'].includes(status)) return 'out'\n if (['new', 'confirmed', 'preparing', 'ready', 'completed', 'cancelled'].includes(status)) {\n return status as KanbanColumn\n }\n return 'new'\n}\n\nexport const KANBAN_COLUMNS: KanbanColumn[] = ['new', 'confirmed', 'preparing', 'ready', 'out', 'completed']\n\n// ============================================================\n// CORE ENTITIES\n// ============================================================\n\n/** Restaurant order — saas_core.orders + public.orders extension */\nexport interface RestaurantOrder {\n id: string\n referenceNumber: string\n kind: OrderKind\n status: OrderStatus\n partyId?: string\n partyName?: string\n assigneeId?: string\n assigneeName?: string\n locationId?: string\n subtotal: number\n discount: number\n tax: number\n total: number\n currency: string\n notes?: string\n tags: string[]\n // Extension fields\n source: string\n tableId?: string\n tableName?: string\n estimatedMinutes?: number\n items: RestaurantOrderItem[]\n metadata?: Record<string, unknown>\n tenantId: string\n createdAt: string\n updatedAt: string\n completedAt?: string\n}\n\nexport interface RestaurantOrderItem {\n id: string\n orderId: string\n productId?: string\n name: string\n description?: string\n quantity: number\n unitPrice: number\n discount: number\n total: number\n sortOrder: number\n modifiers?: string\n kitchenNotes?: string\n metadata?: Record<string, unknown>\n createdAt: string\n}\n\n// ============================================================\n// INPUT TYPES\n// ============================================================\n\nexport interface CreateOrderInput {\n kind: OrderKind\n source?: string\n tableId?: string\n partyId?: string\n assigneeId?: string\n notes?: string\n estimatedMinutes?: number\n items: CreateOrderItemInput[]\n}\n\nexport interface CreateOrderItemInput {\n productId?: string\n name: string\n quantity: number\n unitPrice: number\n discount?: number\n modifiers?: string\n kitchenNotes?: string\n}\n\nexport interface UpdateOrderStatusInput {\n orderId: string\n status: OrderStatus\n}\n\n// ============================================================\n// QUERY TYPES\n// ============================================================\n\nexport interface OrderQuery {\n kind?: OrderKind | OrderKind[]\n status?: OrderStatus | OrderStatus[]\n source?: string\n tableId?: string\n assigneeId?: string\n dateRange?: { from: string; to: string }\n search?: string\n page?: number\n pageSize?: number\n}\n\nexport interface PaginatedResult<T> {\n data: T[]\n total: number\n}\n\n// ============================================================\n// AGGREGATION\n// ============================================================\n\nexport interface OrdersSummary {\n todayOrderCount: number\n todayRevenue: number\n averageTicket: number\n activeOrderCount: number\n ordersByKind: Record<OrderKind, number>\n ordersByStatus: Record<string, number>\n topItems: Array<{ name: string; quantity: number; revenue: number }>\n}\n\n// ============================================================\n// STATUS CONFIG\n// ============================================================\n\nexport interface OrderStatusConfig {\n value: string\n label: string\n color: string\n icon?: string\n}\n\nexport const DEFAULT_STATUS_CONFIGS: OrderStatusConfig[] = [\n { value: 'new', label: 'New', color: '#6366f1', icon: 'PlusCircle' },\n { value: 'confirmed', label: 'Confirmed', color: '#3b82f6', icon: 'CheckCircle' },\n { value: 'preparing', label: 'Preparing', color: '#f59e0b', icon: 'ChefHat' },\n { value: 'ready', label: 'Ready', color: '#10b981', icon: 'Bell' },\n { value: 'served', label: 'Served', color: '#06b6d4', icon: 'Utensils' },\n { value: 'picked_up', label: 'Picked Up', color: '#06b6d4', icon: 'ShoppingBag' },\n { value: 'dispatched', label: 'Dispatched', color: '#8b5cf6', icon: 'Truck' },\n { value: 'delivered', label: 'Delivered', color: '#06b6d4', icon: 'PackageCheck' },\n { value: 'completed', label: 'Completed', color: '#22c55e', icon: 'CircleCheck' },\n { value: 'cancelled', label: 'Cancelled', color: '#ef4444', icon: 'XCircle' },\n]\n"]}
|