@fayz-ai/plugin-orders 0.12.28 → 0.14.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/dist/OrderDetailRoute-LTPDQXQU.js +193 -0
- package/dist/OrderDetailRoute-LTPDQXQU.js.map +1 -0
- package/dist/OrdersPage-5326V62H.js +291 -0
- package/dist/OrdersPage-5326V62H.js.map +1 -0
- package/dist/chunk-YVC7R552.js +6 -0
- package/dist/chunk-YVC7R552.js.map +1 -0
- package/dist/context.d.ts +0 -46
- package/dist/context.d.ts.map +1 -1
- package/dist/data/core-supabase.d.ts.map +1 -1
- package/dist/index.d.ts +4 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1108 -934
- package/dist/index.js.map +1 -1
- package/dist/lib/detail-slot.d.ts +13 -0
- package/dist/lib/detail-slot.d.ts.map +1 -0
- package/dist/lib/list-query.d.ts +21 -0
- package/dist/lib/list-query.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +48 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/views/OrderDetailRoute.d.ts +12 -0
- package/dist/views/OrderDetailRoute.d.ts.map +1 -0
- package/dist/views/OrdersPage.d.ts +1 -7
- package/dist/views/OrdersPage.d.ts.map +1 -1
- package/dist/views/OrdersScreen.d.ts.map +1 -1
- package/package.json +12 -9
- package/dist/OrdersPage-QV2YWARF.js +0 -483
- package/dist/OrdersPage-QV2YWARF.js.map +0 -1
- package/dist/chunk-TYS3LCKX.js +0 -57
- package/dist/chunk-TYS3LCKX.js.map +0 -1
- package/dist/lib/floor-plan-id.d.ts +0 -13
- package/dist/lib/floor-plan-id.d.ts.map +0 -1
- package/dist/views/CommercialOrdersScreen.d.ts +0 -3
- package/dist/views/CommercialOrdersScreen.d.ts.map +0 -1
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
import { mapToKanbanColumn, getNextStatus, ADVANCE_VERB, KANBAN_COLUMNS } from './chunk-TYS3LCKX.js';
|
|
2
|
-
import { createPluginContext, formatCurrency, orderKindColumn, orderChannelColumn, OrderListView, useOrdersPaymentStatus, PaymentStatusBadge, OrderAmount, OrderDetailRail, PageActions, adminNavigateTo, RecordPickerModal } from '@fayz-ai/admin';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { Clock, Bike, MapPin, LayoutGrid, Settings, Plus } from 'lucide-react';
|
|
5
|
-
import { Button } from '@fayz-ai/ui';
|
|
6
|
-
import { useTranslation, getComponent } from '@fayz-ai/core';
|
|
7
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
-
|
|
9
|
-
var ctx = createPluginContext("OrdersPage");
|
|
10
|
-
var OrdersContextProvider = ctx.ContextProvider;
|
|
11
|
-
var useOrdersConfig = ctx.useConfig;
|
|
12
|
-
ctx.useProvider;
|
|
13
|
-
var useOrdersStore = ctx.useStore;
|
|
14
|
-
|
|
15
|
-
// src/lib/floor-plan-id.ts
|
|
16
|
-
var FLOOR_PLAN_COMPONENT_ID = "tables.floor-plan";
|
|
17
|
-
function AddItemsBlock({ orderId, onDone }) {
|
|
18
|
-
const t = useTranslation();
|
|
19
|
-
const config = useOrdersConfig();
|
|
20
|
-
const addOrderItem = useOrdersStore((s) => s.addOrderItem);
|
|
21
|
-
const money = React.useCallback(
|
|
22
|
-
(v) => formatCurrency(v, config.currency),
|
|
23
|
-
[config.currency]
|
|
24
|
-
);
|
|
25
|
-
const [open, setOpen] = React.useState(false);
|
|
26
|
-
const lookup = config.menuItemLookup;
|
|
27
|
-
if (!lookup) return null;
|
|
28
|
-
const search = async (term) => term ? lookup.search(term) : await lookup.list?.() ?? lookup.search("");
|
|
29
|
-
return /* @__PURE__ */ jsxs("div", { className: "border-t pt-3", children: [
|
|
30
|
-
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "w-full", onClick: () => setOpen(true), children: [
|
|
31
|
-
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
32
|
-
" ",
|
|
33
|
-
t("orders.detail.addItem")
|
|
34
|
-
] }),
|
|
35
|
-
/* @__PURE__ */ jsx(
|
|
36
|
-
RecordPickerModal,
|
|
37
|
-
{
|
|
38
|
-
open,
|
|
39
|
-
onClose: () => setOpen(false),
|
|
40
|
-
title: t("orders.detail.addItem"),
|
|
41
|
-
placeholder: t("orders.detail.searchMenu"),
|
|
42
|
-
confirmLabel: t("orders.detail.addItem"),
|
|
43
|
-
search,
|
|
44
|
-
rowKey: (row) => row.id,
|
|
45
|
-
renderRow: (row) => ({
|
|
46
|
-
title: row.label,
|
|
47
|
-
subtitle: row.subtitle,
|
|
48
|
-
value: row.price != null ? money(row.price) : void 0
|
|
49
|
-
}),
|
|
50
|
-
onConfirm: (rows) => {
|
|
51
|
-
void (async () => {
|
|
52
|
-
for (const row of rows) {
|
|
53
|
-
await addOrderItem(orderId, {
|
|
54
|
-
productId: row.id,
|
|
55
|
-
name: row.label,
|
|
56
|
-
quantity: 1,
|
|
57
|
-
unitPrice: row.price ?? 0
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
onDone();
|
|
61
|
-
})();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
)
|
|
65
|
-
] });
|
|
66
|
-
}
|
|
67
|
-
function serviceOf(order) {
|
|
68
|
-
return order.kind === "dine_in" ? "salao" : "delivery";
|
|
69
|
-
}
|
|
70
|
-
var COLUMN_COLOR = {
|
|
71
|
-
new: "bg-violet-500",
|
|
72
|
-
confirmed: "bg-blue-500",
|
|
73
|
-
preparing: "bg-amber-500",
|
|
74
|
-
ready: "bg-emerald-500",
|
|
75
|
-
out: "bg-cyan-500",
|
|
76
|
-
completed: "bg-gray-400",
|
|
77
|
-
cancelled: "bg-red-500"
|
|
78
|
-
};
|
|
79
|
-
function labelOfColumn(id, service, t) {
|
|
80
|
-
if (service === "salao") {
|
|
81
|
-
if (id === "out") return t("orders.kanban.column.served");
|
|
82
|
-
if (id === "confirmed") {
|
|
83
|
-
const open = t("orders.kanban.column.open");
|
|
84
|
-
return open === "orders.kanban.column.open" ? t("orders.kanban.column.confirmed") : open;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return t(`orders.kanban.column.${id}`);
|
|
88
|
-
}
|
|
89
|
-
function timeAgo(dateStr) {
|
|
90
|
-
const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 6e4);
|
|
91
|
-
if (diff < 1) return "agora";
|
|
92
|
-
if (diff === 1) return "1min";
|
|
93
|
-
return `${diff}min`;
|
|
94
|
-
}
|
|
95
|
-
function OrdersScreen() {
|
|
96
|
-
const t = useTranslation();
|
|
97
|
-
const config = useOrdersConfig();
|
|
98
|
-
const activeOrders = useOrdersStore((s) => s.activeOrders);
|
|
99
|
-
const loading = useOrdersStore((s) => s.activeOrdersLoading);
|
|
100
|
-
const fetchActiveOrders = useOrdersStore((s) => s.fetchActiveOrders);
|
|
101
|
-
const advanceOrderStatus = useOrdersStore((s) => s.advanceOrderStatus);
|
|
102
|
-
const selectOrder = useOrdersStore((s) => s.selectOrder);
|
|
103
|
-
const selectedOrderId = useOrdersStore((s) => s.selectedOrderId);
|
|
104
|
-
const cancelOrder = useOrdersStore((s) => s.cancelOrder);
|
|
105
|
-
React.useEffect(() => {
|
|
106
|
-
void fetchActiveOrders();
|
|
107
|
-
}, [fetchActiveOrders]);
|
|
108
|
-
const orderIds = React.useMemo(() => activeOrders.map((o) => o.id), [activeOrders]);
|
|
109
|
-
const { byOrder: paymentByOrder } = useOrdersPaymentStatus(orderIds);
|
|
110
|
-
const translated = t("orders.kanban.newOrder");
|
|
111
|
-
const createLabel = !translated || translated === "orders.kanban.newOrder" ? "Criar pedido" : translated;
|
|
112
|
-
const statusLabel = React.useCallback((status) => {
|
|
113
|
-
const key = `orders.kanban.column.${mapToKanbanColumn(status)}`;
|
|
114
|
-
const label = t(key);
|
|
115
|
-
return label === key ? status : label;
|
|
116
|
-
}, [t]);
|
|
117
|
-
const money = React.useCallback(
|
|
118
|
-
(value) => formatCurrency(value, config.currency),
|
|
119
|
-
[config.currency]
|
|
120
|
-
);
|
|
121
|
-
const columns = React.useMemo(() => [
|
|
122
|
-
{
|
|
123
|
-
key: "reference",
|
|
124
|
-
header: t("orders.list.column.order"),
|
|
125
|
-
render: (o) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: o.referenceNumber })
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
key: "customer",
|
|
129
|
-
header: t("orders.list.column.customer"),
|
|
130
|
-
render: (o) => o.partyName ?? o.tableName ?? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" })
|
|
131
|
-
},
|
|
132
|
-
// A lista responde "quanto" e nao respondia "recebido?" — que e a coluna
|
|
133
|
-
// que o caixa procura ao conferir o dia.
|
|
134
|
-
{
|
|
135
|
-
key: "payment",
|
|
136
|
-
header: "Recebimento",
|
|
137
|
-
render: (o) => {
|
|
138
|
-
const p = paymentByOrder.get(o.id);
|
|
139
|
-
if (!p || p.status === "none") return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
140
|
-
return /* @__PURE__ */ jsx(
|
|
141
|
-
PaymentStatusBadge,
|
|
142
|
-
{
|
|
143
|
-
status: p.status,
|
|
144
|
-
totalAmount: p.amount,
|
|
145
|
-
paidAmount: p.paid,
|
|
146
|
-
formatCurrency: money
|
|
147
|
-
}
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
// `Canal` is #324's orders.channel where the archetype already carries it;
|
|
152
|
-
// until the app's extension is migrated it is the order's `source`, which is
|
|
153
|
-
// the same fact under the old name.
|
|
154
|
-
orderChannelColumn((o) => o.channel ?? o.source),
|
|
155
|
-
{
|
|
156
|
-
key: "total",
|
|
157
|
-
header: t("orders.list.column.total"),
|
|
158
|
-
align: "right",
|
|
159
|
-
render: (o) => money(o.total)
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
key: "status",
|
|
163
|
-
header: t("orders.list.column.status"),
|
|
164
|
-
render: (o) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-sm", children: [
|
|
165
|
-
/* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${COLUMN_COLOR[mapToKanbanColumn(o.status)] ?? "bg-muted-foreground/40"}` }),
|
|
166
|
-
t(`orders.kanban.column.${mapToKanbanColumn(o.status)}`)
|
|
167
|
-
] })
|
|
168
|
-
}
|
|
169
|
-
], [t, money, paymentByOrder]);
|
|
170
|
-
const makeBoard = React.useCallback((service) => ({
|
|
171
|
-
// O salão não tem "Novo": ninguém aceita uma comanda. A entrega recebe um
|
|
172
|
-
// pedido de fora e a primeira decisão é confirmá-lo; no salão a comanda
|
|
173
|
-
// nasce do garçom que já a abriu, e a coluna Novo ficava vazia entre nove
|
|
174
|
-
// comandas confirmadas — uma coluna que só ocupa a largura das outras.
|
|
175
|
-
columns: (service === "salao" ? KANBAN_COLUMNS.filter((id) => id !== "new") : KANBAN_COLUMNS).map((id) => ({
|
|
176
|
-
id,
|
|
177
|
-
label: labelOfColumn(id, service, t),
|
|
178
|
-
color: COLUMN_COLOR[id]
|
|
179
|
-
})),
|
|
180
|
-
// The status is DERIVED from the document status plus the lines' execution
|
|
181
|
-
// state, and it is not stored. columnOf receives the already-derived value.
|
|
182
|
-
columnOf: (o) => {
|
|
183
|
-
if (serviceOf(o) !== service) return null;
|
|
184
|
-
const column = mapToKanbanColumn(o.status);
|
|
185
|
-
return service === "salao" && column === "new" ? "confirmed" : column;
|
|
186
|
-
},
|
|
187
|
-
canAdvance: (o) => getNextStatus(o.kind, o.status) !== null,
|
|
188
|
-
// O destino do cartão enquanto a escrita está no ar. O padrão do quadro é a
|
|
189
|
-
// próxima coluna DECLARADA, e aqui isso mentiria: o salão não tem a coluna
|
|
190
|
-
// Novo, então quem sai de Aberta iria parar em Preparando por engano de
|
|
191
|
-
// contagem. Quem sabe o próximo estado é o fluxo, e ele é daqui.
|
|
192
|
-
nextColumnOf: (o) => {
|
|
193
|
-
const next = getNextStatus(o.kind, o.status);
|
|
194
|
-
if (!next) return null;
|
|
195
|
-
const column = mapToKanbanColumn(next);
|
|
196
|
-
return service === "salao" && column === "new" ? "confirmed" : column;
|
|
197
|
-
},
|
|
198
|
-
onAdvance: (o) => advanceOrderStatus(o.id),
|
|
199
|
-
// O verbo do PASSO, não o do quadro. "Avançar" descreve o movimento no
|
|
200
|
-
// diagrama; quem está no salão confirma, prepara, serve ou despacha — e é
|
|
201
|
-
// essa palavra que decide o clique sem abrir o pedido. A chave por estado
|
|
202
|
-
// vem primeiro para o app traduzir; o `ADVANCE_VERB` é o padrão, e o
|
|
203
|
-
// "Avançar" antigo continua sendo o último recurso de um estado que nenhum
|
|
204
|
-
// dos dois nomeia.
|
|
205
|
-
advanceLabel: (o) => {
|
|
206
|
-
const next = getNextStatus(o.kind, o.status);
|
|
207
|
-
if (!next) return t("orders.kanban.advance");
|
|
208
|
-
const key = `orders.advance.${next}`;
|
|
209
|
-
const translated2 = t(key);
|
|
210
|
-
return translated2 !== key ? translated2 : ADVANCE_VERB[next] ?? t("orders.kanban.advance");
|
|
211
|
-
},
|
|
212
|
-
emptyColumn: t("orders.kanban.noOrders"),
|
|
213
|
-
card: (o) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
214
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
215
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold", children: o.referenceNumber }),
|
|
216
|
-
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium", children: t(`orders.source.${o.source}`) !== `orders.source.${o.source}` ? t(`orders.source.${o.source}`) : o.source })
|
|
217
|
-
] }),
|
|
218
|
-
o.partyName && /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: o.partyName }),
|
|
219
|
-
o.tableName && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: o.tableName }),
|
|
220
|
-
o.items.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-0.5", children: o.items.map((item) => /* @__PURE__ */ jsxs("p", { className: "truncate text-xs text-muted-foreground", children: [
|
|
221
|
-
item.quantity,
|
|
222
|
-
"x ",
|
|
223
|
-
item.name
|
|
224
|
-
] }, item.id)) }),
|
|
225
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between gap-2 border-t pt-2", children: [
|
|
226
|
-
/* @__PURE__ */ jsx(OrderAmount, { value: o.total, summary: paymentByOrder.get(o.id), formatCurrency: money }),
|
|
227
|
-
/* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-0.5 text-[10px] text-muted-foreground", children: [
|
|
228
|
-
/* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
|
|
229
|
-
timeAgo(o.createdAt)
|
|
230
|
-
] })
|
|
231
|
-
] })
|
|
232
|
-
] })
|
|
233
|
-
}), [t, money, advanceOrderStatus, paymentByOrder]);
|
|
234
|
-
const salaoBoard = React.useMemo(() => makeBoard("salao"), [makeBoard]);
|
|
235
|
-
const deliveryBoard = React.useMemo(() => makeBoard("delivery"), [makeBoard]);
|
|
236
|
-
const FloorPlan = getComponent(FLOOR_PLAN_COMPONENT_ID);
|
|
237
|
-
const showFloor = !!FloorPlan && config.floorView !== false;
|
|
238
|
-
const inspect = config.detailRail === "inspect";
|
|
239
|
-
const showDelivery = !config.deliveryRoute && config.modules.delivery !== false;
|
|
240
|
-
const isDelivery = config.pinnedView === "delivery";
|
|
241
|
-
const offeredViews = [
|
|
242
|
-
"list",
|
|
243
|
-
...config.boardView !== false ? ["quadro"] : [],
|
|
244
|
-
...showFloor ? ["salao"] : [],
|
|
245
|
-
...showDelivery ? ["delivery"] : []
|
|
246
|
-
];
|
|
247
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
248
|
-
/* @__PURE__ */ jsx(
|
|
249
|
-
OrderDetailRail,
|
|
250
|
-
{
|
|
251
|
-
orderId: selectedOrderId,
|
|
252
|
-
onClose: () => selectOrder(null),
|
|
253
|
-
fullPageHref: config.orderRoute,
|
|
254
|
-
onActionDone: () => {
|
|
255
|
-
void fetchActiveOrders();
|
|
256
|
-
},
|
|
257
|
-
children: inspect ? void 0 : (order, reload) => (
|
|
258
|
-
// Uma comanda fechada ou cancelada é decisão já tomada: nada mais entra
|
|
259
|
-
// nela. É a mesma regra que governa o botão de editar.
|
|
260
|
-
config.canEditOrder?.({ status: order.status }) === false ? null : /* @__PURE__ */ jsx(AddItemsBlock, { orderId: order.id, onDone: () => {
|
|
261
|
-
reload();
|
|
262
|
-
void fetchActiveOrders();
|
|
263
|
-
} })
|
|
264
|
-
),
|
|
265
|
-
actions: inspect ? void 0 : (order) => {
|
|
266
|
-
const verbs = [];
|
|
267
|
-
const row = activeOrders.find((o) => o.id === order.id);
|
|
268
|
-
const next = row ? getNextStatus(row.kind, row.status) : null;
|
|
269
|
-
if (next) {
|
|
270
|
-
verbs.push({
|
|
271
|
-
id: "advance",
|
|
272
|
-
label: `${t("orders.kanban.advance")} \xB7 ${statusLabel(next)}`,
|
|
273
|
-
variant: "default",
|
|
274
|
-
// The panel stays open and re-reads the order: advancing is usually
|
|
275
|
-
// done twice in a row, and it is the only way to see that the state
|
|
276
|
-
// you asked for is the state it now has.
|
|
277
|
-
keepOpen: true,
|
|
278
|
-
onSelect: (o) => advanceOrderStatus(o.id)
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (order.status && !["cancelled", "completed"].includes(order.status)) {
|
|
282
|
-
verbs.push({
|
|
283
|
-
id: "cancel",
|
|
284
|
-
label: t("orders.detail.cancel"),
|
|
285
|
-
variant: "destructive",
|
|
286
|
-
confirm: t("orders.detail.cancelConfirm").replace("{reference}", order.referenceNumber),
|
|
287
|
-
onSelect: (o) => cancelOrder(o.id)
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
return verbs;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
),
|
|
294
|
-
/* @__PURE__ */ jsx(
|
|
295
|
-
OrderListView,
|
|
296
|
-
{
|
|
297
|
-
title: t("orders.kanban.title"),
|
|
298
|
-
viewKey: config.pinnedView ? void 0 : "orders",
|
|
299
|
-
views: config.pinnedView ? [config.pinnedView] : offeredViews,
|
|
300
|
-
defaultView: config.pinnedView ?? "list",
|
|
301
|
-
boards: {
|
|
302
|
-
quadro: { label: "Quadro", config: salaoBoard, icon: LayoutGrid },
|
|
303
|
-
...showFloor && FloorPlan ? { salao: { label: "Sal\xE3o", render: () => /* @__PURE__ */ jsx(FloorPlan, {}), icon: MapPin } } : {},
|
|
304
|
-
...showDelivery || config.pinnedView === "delivery" ? { delivery: { label: "Delivery", config: deliveryBoard, icon: Bike } } : {}
|
|
305
|
-
},
|
|
306
|
-
columns,
|
|
307
|
-
rows: activeOrders,
|
|
308
|
-
loading,
|
|
309
|
-
empty: t("orders.kanban.noOrders"),
|
|
310
|
-
searchText: (o) => [o.referenceNumber, o.partyName, o.tableName, o.source].filter(Boolean).join(" "),
|
|
311
|
-
searchPlaceholder: t("orders.list.searchPlaceholder"),
|
|
312
|
-
pageSize: 25,
|
|
313
|
-
onRowClick: (o) => selectOrder(o.id),
|
|
314
|
-
selectedRowId: selectedOrderId,
|
|
315
|
-
actions: config.createRoute || isDelivery ? /* @__PURE__ */ jsx(
|
|
316
|
-
PageActions,
|
|
317
|
-
{
|
|
318
|
-
actions: [
|
|
319
|
-
// `utility`: a engrenagem é botão PRÓPRIO ao lado do "Novo Pedido",
|
|
320
|
-
// não item dentro do caret dele — a mesma forma que o Financeiro e o
|
|
321
|
-
// CRM usam (ModuleActionBar.settingsPath). Configurar não é uma
|
|
322
|
-
// maneira de criar pedido, e escondê-la sob o botão de criar era
|
|
323
|
-
// dizer que é.
|
|
324
|
-
...isDelivery ? [{
|
|
325
|
-
id: "delivery-settings",
|
|
326
|
-
label: t("orders.delivery.settings"),
|
|
327
|
-
icon: Settings,
|
|
328
|
-
intent: "utility",
|
|
329
|
-
testId: "delivery-settings",
|
|
330
|
-
onSelect: () => adminNavigateTo("/settings/orders-delivery")
|
|
331
|
-
}] : [],
|
|
332
|
-
...config.createRoute ? [{
|
|
333
|
-
id: "new-order",
|
|
334
|
-
label: createLabel,
|
|
335
|
-
icon: Plus,
|
|
336
|
-
intent: "primary",
|
|
337
|
-
creates: true,
|
|
338
|
-
testId: "orders-create",
|
|
339
|
-
onSelect: () => adminNavigateTo(config.createRoute)
|
|
340
|
-
}] : []
|
|
341
|
-
]
|
|
342
|
-
}
|
|
343
|
-
) : void 0
|
|
344
|
-
}
|
|
345
|
-
)
|
|
346
|
-
] });
|
|
347
|
-
}
|
|
348
|
-
function paymentOf(order) {
|
|
349
|
-
return order.metadata?.paymentStatus ?? "none";
|
|
350
|
-
}
|
|
351
|
-
var PAYMENT_LABEL = {
|
|
352
|
-
none: "Sem t\xEDtulo",
|
|
353
|
-
pending: "Em aberto",
|
|
354
|
-
partial: "Parcial",
|
|
355
|
-
paid: "Pago",
|
|
356
|
-
overdue: "Vencido",
|
|
357
|
-
cancelled: "Cancelado"
|
|
358
|
-
};
|
|
359
|
-
var PAYMENT_TONE = {
|
|
360
|
-
// "Sem título" is the one that has to catch the eye: it is the gap.
|
|
361
|
-
none: "bg-red-100 text-red-800",
|
|
362
|
-
pending: "bg-amber-100 text-amber-800",
|
|
363
|
-
partial: "bg-blue-100 text-blue-800",
|
|
364
|
-
paid: "bg-emerald-100 text-emerald-800",
|
|
365
|
-
overdue: "bg-red-100 text-red-800",
|
|
366
|
-
cancelled: "bg-gray-100 text-gray-700"
|
|
367
|
-
};
|
|
368
|
-
var STATUS_LABEL = {
|
|
369
|
-
draft: "Rascunho",
|
|
370
|
-
scheduled: "Agendado",
|
|
371
|
-
confirmed: "Confirmado",
|
|
372
|
-
in_progress: "Em andamento",
|
|
373
|
-
completed: "Conclu\xEDdo",
|
|
374
|
-
cancelled: "Cancelado",
|
|
375
|
-
no_show: "N\xE3o compareceu",
|
|
376
|
-
invoiced: "Faturado",
|
|
377
|
-
paid: "Pago",
|
|
378
|
-
open: "Aberto"
|
|
379
|
-
};
|
|
380
|
-
function CommercialOrdersScreen() {
|
|
381
|
-
const config = useOrdersConfig();
|
|
382
|
-
const orders = useOrdersStore((s) => s.orders);
|
|
383
|
-
const loading = useOrdersStore((s) => s.ordersLoading);
|
|
384
|
-
const fetchOrders = useOrdersStore((s) => s.fetchOrders);
|
|
385
|
-
const selectOrder = useOrdersStore((s) => s.selectOrder);
|
|
386
|
-
React.useEffect(() => {
|
|
387
|
-
void fetchOrders({ limit: 200, offset: 0 });
|
|
388
|
-
}, [fetchOrders]);
|
|
389
|
-
const money = React.useCallback(
|
|
390
|
-
(value) => formatCurrency(value, config.currency),
|
|
391
|
-
[config.currency]
|
|
392
|
-
);
|
|
393
|
-
const hasAssignee = React.useMemo(
|
|
394
|
-
() => (orders ?? []).some((o) => Boolean(o.assigneeName)),
|
|
395
|
-
[orders]
|
|
396
|
-
);
|
|
397
|
-
const columns = React.useMemo(() => [
|
|
398
|
-
{
|
|
399
|
-
key: "document",
|
|
400
|
-
header: "Documento",
|
|
401
|
-
render: (o) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: o.referenceNumber })
|
|
402
|
-
},
|
|
403
|
-
orderKindColumn((o) => o.kind),
|
|
404
|
-
{
|
|
405
|
-
key: "party",
|
|
406
|
-
header: "Cliente",
|
|
407
|
-
render: (o) => o.partyName ?? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" })
|
|
408
|
-
},
|
|
409
|
-
// Só quando ALGUÉM é: "Profissional" é vocabulário de salão, e num produto
|
|
410
|
-
// onde ninguém atende a venda (bilheteria, loja) a coluna existia para
|
|
411
|
-
// mostrar um traço em toda linha. Uma coluna que nunca tem valor não é uma
|
|
412
|
-
// informação faltando — é largura roubada das que têm.
|
|
413
|
-
...hasAssignee ? [{
|
|
414
|
-
key: "assignee",
|
|
415
|
-
header: "Profissional",
|
|
416
|
-
render: (o) => o.assigneeName ?? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" })
|
|
417
|
-
}] : [],
|
|
418
|
-
orderChannelColumn((o) => o.metadata?.channel),
|
|
419
|
-
{ key: "total", header: "Total", align: "right", render: (o) => money(o.total) },
|
|
420
|
-
{
|
|
421
|
-
key: "status",
|
|
422
|
-
header: "Estado",
|
|
423
|
-
render: (o) => STATUS_LABEL[o.status] ?? o.status
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
key: "invoice",
|
|
427
|
-
header: "T\xEDtulo",
|
|
428
|
-
render: (o) => {
|
|
429
|
-
const state = paymentOf(o);
|
|
430
|
-
return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${PAYMENT_TONE[state]}`, children: PAYMENT_LABEL[state] });
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
], [money, hasAssignee]);
|
|
434
|
-
return /* @__PURE__ */ jsx(
|
|
435
|
-
OrderListView,
|
|
436
|
-
{
|
|
437
|
-
title: config.labels.pageTitle,
|
|
438
|
-
viewKey: "commercial-orders",
|
|
439
|
-
views: ["list", "board"],
|
|
440
|
-
defaultView: "list",
|
|
441
|
-
board: {
|
|
442
|
-
columns: [
|
|
443
|
-
{ id: "open", label: "Em aberto", color: "bg-blue-500" },
|
|
444
|
-
{ id: "no_invoice", label: "Sem t\xEDtulo", color: "bg-red-500" },
|
|
445
|
-
{ id: "paid", label: "Pago", color: "bg-emerald-500" },
|
|
446
|
-
{ id: "cancelled", label: "Cancelado", color: "bg-gray-400" }
|
|
447
|
-
],
|
|
448
|
-
columnOf: (o) => {
|
|
449
|
-
if (o.status === "cancelled") return "cancelled";
|
|
450
|
-
const state = paymentOf(o);
|
|
451
|
-
if (state === "paid") return "paid";
|
|
452
|
-
if (state === "none") return "no_invoice";
|
|
453
|
-
return "open";
|
|
454
|
-
},
|
|
455
|
-
emptyColumn: "Nada aqui",
|
|
456
|
-
card: (o) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
457
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
458
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold", children: o.referenceNumber }),
|
|
459
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: new Date(o.createdAt).toLocaleDateString("pt-BR") })
|
|
460
|
-
] }),
|
|
461
|
-
o.partyName && /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: o.partyName }),
|
|
462
|
-
o.assigneeName && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: o.assigneeName }),
|
|
463
|
-
/* @__PURE__ */ jsx("div", { className: "mt-3 border-t pt-2 text-sm font-bold", children: money(o.total) })
|
|
464
|
-
] })
|
|
465
|
-
},
|
|
466
|
-
columns,
|
|
467
|
-
rows: orders,
|
|
468
|
-
loading,
|
|
469
|
-
empty: "Nenhum pedido ainda.",
|
|
470
|
-
searchText: (o) => [o.referenceNumber, o.partyName, o.assigneeName].filter(Boolean).join(" "),
|
|
471
|
-
searchPlaceholder: hasAssignee ? "Buscar por documento, cliente ou profissional\u2026" : "Buscar por documento ou cliente\u2026",
|
|
472
|
-
pageSize: 25,
|
|
473
|
-
onRowClick: (o) => selectOrder(o.id)
|
|
474
|
-
}
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
function OrdersPage({ config, provider, store, variant = "restaurant" }) {
|
|
478
|
-
return /* @__PURE__ */ jsx(OrdersContextProvider, { config, provider, store, children: variant === "commercial" ? /* @__PURE__ */ jsx(CommercialOrdersScreen, {}) : /* @__PURE__ */ jsx(OrdersScreen, {}) });
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
export { OrdersPage };
|
|
482
|
-
//# sourceMappingURL=OrdersPage-QV2YWARF.js.map
|
|
483
|
-
//# sourceMappingURL=OrdersPage-QV2YWARF.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.tsx","../src/lib/floor-plan-id.ts","../src/views/OrdersScreen.tsx","../src/views/CommercialOrdersScreen.tsx","../src/views/OrdersPage.tsx"],"names":["translated","React","jsx","orderChannelColumn","OrderListView","jsxs","Fragment"],"mappings":"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","file":"OrdersPage-QV2YWARF.js","sourcesContent":["import { createPluginContext, formatCurrency, type CurrencyConfig, type EntityLookup } from '@fayz-ai/admin'\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 onOrderCancelled?: (order: RestaurantOrder) => Promise<void>\n /** Route the list's create action opens. Unset = the list offers none. */\n createRoute?: string\n orderRoute?: (orderId: string) => string\n editRoute?: (orderId: string) => string\n /** Whether this order still takes changes. Unset = it always does. */\n canEditOrder?: (order: { status?: string }) => boolean\n /**\n * Whether the floor-plan tab is offered when one is registered.\n *\n * A restaurant that runs its salão on an operator surface (ChefControl's\n * Balcão) already answers \"onde a comanda está sentada\" there, and the tab\n * becomes a second, colder copy of the same room inside the back office.\n */\n floorView: boolean\n /**\n * Whether the QUADRO tab — the during-service board — is offered.\n *\n * It was the one view no app could turn off, because every app that mounted\n * this plugin was a restaurant. A commercial order is a document: it has a\n * customer, a total and a state, and no kitchen advancing it column by\n * column.\n */\n boardView: boolean\n /**\n * This screen, opened straight onto one board and with no tabs.\n *\n * For an app that gives a board its own rail entry — \"Operação · Delivery\" is\n * a place, not a tab you have to know to click. The tabs go because the\n * destination IS the board: leaving them turns one rail row into three.\n */\n pinnedView?: 'quadro' | 'salao' | 'delivery'\n /**\n * The delivery board has a rail entry of its own (see `deliveryRoute`).\n *\n * When it does, the tab on /orders is the same board reachable a second way,\n * one click deeper — and the rail row stops being the way in. Same reasoning\n * as `floorView`: a board that became a place stops being a tab.\n */\n deliveryRoute?: string\n /**\n * What the detail panel is FOR.\n *\n * `operate` (default) is the panel that runs the order: add items, advance\n * the status, cancel. `inspect` only shows it.\n *\n * The difference is which surface owns the service. An app with an operator\n * screen (ChefControl's Balcão) already has one place where a comanda gains\n * an item and gets settled; a second, colder copy of those verbs inside the\n * back office is how the same comanda ends up written from two hands with\n * two different sets of rules.\n */\n detailRail: 'operate' | 'inspect'\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","/**\n * O id sob o qual a planta do salão se anuncia no registro de componentes.\n *\n * Uma cópia da constante, não um import de `@fayz-ai/plugin-tables`: os dois\n * plugins são independentes de propósito — uma lanchonete de balcão tem pedido\n * e não tem salão — e um import aqui faria o plugin de mesas virar dependência\n * de todo app que mostra pedidos. O acoplamento é a string, e ela é combinada\n * em dois lugares porque nenhum dos dois pode ser dono do outro.\n *\n * Mantido em par com `FLOOR_PLAN_COMPONENT_ID` de `@fayz-ai/plugin-tables`.\n */\nexport const FLOOR_PLAN_COMPONENT_ID = 'tables.floor-plan'\n","import React from 'react'\nimport { Bike, Clock, LayoutGrid, MapPin, Plus, Settings, Utensils } from 'lucide-react'\nimport {\n OrderDetailRail, OrderListView, orderChannelColumn, PageActions, adminNavigateTo,\n PaymentStatusBadge, OrderAmount, useOrdersPaymentStatus, RecordPickerModal,\n type OrderRailAction, type EntityLookupResult,\n type OrderBoardConfig, type OrderListColumn,\n} from '@fayz-ai/admin'\nimport { Button } from '@fayz-ai/ui'\nimport { useTranslation, getComponent } from '@fayz-ai/core'\nimport { useOrdersConfig, useOrdersStore, formatCurrency } from '../context'\n// Só a CONSTANTE — o id sob o qual a planta se anuncia. Importar o componente\n// tornaria `plugin-tables` dependência obrigatória de todo app com pedidos.\nimport { FLOOR_PLAN_COMPONENT_ID } from '../lib/floor-plan-id'\nimport {\n KANBAN_COLUMNS, mapToKanbanColumn, getNextStatus, ADVANCE_VERB,\n type OrderStatus, type RestaurantOrder,\n} from '../types'\n\n/**\n * Lançar item numa comanda já aberta.\n *\n * O quadro e o painel sabiam abrir a mesa, avançar o estado e cancelar — e não\n * sabiam pôr comida nela. Quem abrisse a comanda por `/orders` chegava a uma\n * conta que só podia olhar; o único lugar com o cardápio era o painel da mesa,\n * em `/tables`. Duas telas para a mesma comanda, e a que o rail leva primeiro\n * era a que não deixava trabalhar.\n *\n * É o MESMO `RecordPickerModal` do painel da mesa e da nova comanda, de\n * propósito: um garçom não pode encontrar três jeitos de escolher do mesmo\n * cardápio conforme a tela em que estiver.\n */\nfunction AddItemsBlock({ orderId, onDone }: { orderId: string; onDone: () => void }) {\n const t = useTranslation()\n const config = useOrdersConfig()\n const addOrderItem = useOrdersStore((s) => s.addOrderItem)\n const money = React.useCallback(\n (v: number) => formatCurrency(v, config.currency),\n [config.currency],\n )\n const [open, setOpen] = React.useState(false)\n\n const lookup = config.menuItemLookup\n // Sem cardápio ligado não há o que oferecer, e um botão que abre uma lista\n // sempre vazia é pior do que botão nenhum.\n if (!lookup) return null\n\n const search = async (term: string) =>\n term ? lookup.search(term) : ((await lookup.list?.()) ?? lookup.search(''))\n\n return (\n <div className=\"border-t pt-3\">\n <Button variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={() => setOpen(true)}>\n <Plus className=\"mr-1.5 h-3.5 w-3.5\" /> {t('orders.detail.addItem')}\n </Button>\n\n <RecordPickerModal<EntityLookupResult>\n open={open}\n onClose={() => setOpen(false)}\n title={t('orders.detail.addItem')}\n placeholder={t('orders.detail.searchMenu')}\n confirmLabel={t('orders.detail.addItem')}\n search={search}\n rowKey={(row) => row.id}\n // O subtítulo vai SOB o nome, onde a linha o corta em uma linha só. Na\n // coluna `meta` (24 pontos de largura) a descrição do produto — que no\n // catálogo de Artorius tem duas frases — empilhava letra por letra e\n // fazia cada linha do cardápio ter 250px de altura.\n renderRow={(row) => ({\n title: row.label,\n subtitle: row.subtitle,\n value: row.price != null ? money(row.price) : undefined,\n })}\n // Em série, não em paralelo: cada linha recalcula o total da comanda, e\n // três dessas correndo juntas deixam como total o que a última gravar.\n onConfirm={(rows) => { void (async () => {\n for (const row of rows) {\n await addOrderItem(orderId, {\n productId: row.id,\n name: row.label,\n quantity: 1,\n unitPrice: row.price ?? 0,\n })\n }\n onDone()\n })() }}\n />\n </div>\n )\n}\n\n/**\n * Que serviço é este pedido — o eixo que separa os dois quadros, e o MESMO eixo\n * que separa quando se paga: delivery e balcão são pagos antes ou na entrega, o\n * salão é pago depois do consumo. É por isso que os dois fluxos não cabem numa\n * tira de colunas só.\n */\nfunction serviceOf(order: RestaurantOrder): 'salao' | 'delivery' {\n return order.kind === 'dine_in' ? 'salao' : 'delivery'\n}\n\n// ---------------------------------------------------------------------------\n// The orders screen (#334). This plugin was the only order-bearing one that\n// never adopted the shared archetype: it shipped a board and nothing else, so\n// on cluster-restaurant-br-01 the 27 comandas could be watched during service\n// and never looked up afterwards — no search, no date range, no way to open one\n// and read it.\n//\n// It opens on the LIST, here as everywhere: a board is a during-service view,\n// and a list is how an order is looked up, audited and reconciled. The board is\n// one click away and the choice is remembered per user.\n//\n// The board's columns are the restaurant's, and they arrive through\n// `board.columnOf` — the archetype knows none of these words (#333).\n// ---------------------------------------------------------------------------\n\nconst COLUMN_COLOR: Record<string, string> = {\n new: 'bg-violet-500',\n confirmed: 'bg-blue-500',\n preparing: 'bg-amber-500',\n ready: 'bg-emerald-500',\n out: 'bg-cyan-500',\n completed: 'bg-gray-400',\n cancelled: 'bg-red-500',\n}\n\n/**\n * Duas colunas mudam de nome no salão, e mudam porque nomeiam coisas diferentes:\n * `out` é `served` (a comida foi à mesa, a comanda continua viva), e a primeira\n * coluna é \"Aberta\" e não \"Confirmado\" — ela guarda a comanda desde que o\n * garçom a abre, sem passo de aceite pelo meio.\n */\nfunction labelOfColumn(\n id: string,\n service: 'salao' | 'delivery',\n t: (key: string) => string,\n): string {\n if (service === 'salao') {\n if (id === 'out') return t('orders.kanban.column.served')\n // O fallback importa: um host com bundle de locale mais velho que esta\n // chave mostraria a própria chave no cabeçalho da coluna.\n if (id === 'confirmed') {\n const open = t('orders.kanban.column.open')\n return open === 'orders.kanban.column.open' ? t('orders.kanban.column.confirmed') : open\n }\n }\n return t(`orders.kanban.column.${id}`)\n}\n\nfunction timeAgo(dateStr: string): string {\n const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 60000)\n if (diff < 1) return 'agora'\n if (diff === 1) return '1min'\n return `${diff}min`\n}\n\nexport function OrdersScreen() {\n const t = useTranslation()\n const config = useOrdersConfig()\n\n const activeOrders = useOrdersStore((s) => s.activeOrders)\n const loading = 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 // `selectOrder` already existed and set an id nothing drew — clicking a\n // comanda was a no-op. The rail is what it was always selecting FOR.\n const selectedOrderId = useOrdersStore((s) => s.selectedOrderId)\n const cancelOrder = useOrdersStore((s) => s.cancelOrder)\n\n React.useEffect(() => { void fetchActiveOrders() }, [fetchActiveOrders])\n\n // \"Ja foi pago?\" e uma pergunta do QUADRO, nao so da tela de detalhe: quem\n // esta no salao decide fechar a mesa por ela. Uma consulta para a lista\n // inteira — ver useOrdersPaymentStatus sobre por que nao e um hook por card.\n const orderIds = React.useMemo(() => activeOrders.map((o) => o.id), [activeOrders])\n const { byOrder: paymentByOrder } = useOrdersPaymentStatus(orderIds)\n\n // `orders.kanban.newOrder` is the plugin's own key; the literal is the\n // fallback for a host whose locale bundle predates it. NOT config.labels —\n // this plugin ships TWO divergent OrdersPluginLabels (index.ts vs context.tsx,\n // reconciled with an `as any` in resolveConfig), and adding a field to the\n // wrong one is how that divergence grew.\n const translated = t('orders.kanban.newOrder')\n const createLabel = !translated || translated === 'orders.kanban.newOrder' ? 'Criar pedido' : translated\n\n // The board already names every state; the verb borrows those words rather\n // than inventing a second vocabulary for the same seven statuses.\n const statusLabel = React.useCallback((status: string) => {\n const key = `orders.kanban.column.${mapToKanbanColumn(status as OrderStatus)}`\n const label = t(key)\n return label === key ? status : label\n }, [t])\n\n const money = React.useCallback(\n (value: number) => formatCurrency(value, config.currency),\n [config.currency],\n )\n\n const columns: OrderListColumn<RestaurantOrder>[] = React.useMemo(() => [\n {\n key: 'reference',\n header: t('orders.list.column.order'),\n render: (o) => <span className=\"font-medium\">{o.referenceNumber}</span>,\n },\n {\n key: 'customer',\n header: t('orders.list.column.customer'),\n render: (o) => o.partyName ?? o.tableName ?? <span className=\"text-muted-foreground\">—</span>,\n },\n // A lista responde \"quanto\" e nao respondia \"recebido?\" — que e a coluna\n // que o caixa procura ao conferir o dia.\n {\n key: 'payment',\n header: 'Recebimento',\n render: (o) => {\n const p = paymentByOrder.get(o.id)\n if (!p || p.status === 'none') return <span className=\"text-muted-foreground\">—</span>\n return (\n <PaymentStatusBadge\n status={p.status}\n totalAmount={p.amount}\n paidAmount={p.paid}\n formatCurrency={money}\n />\n )\n },\n },\n // `Canal` is #324's orders.channel where the archetype already carries it;\n // until the app's extension is migrated it is the order's `source`, which is\n // the same fact under the old name.\n orderChannelColumn<RestaurantOrder>((o) => (o as { channel?: string }).channel ?? o.source),\n {\n key: 'total',\n header: t('orders.list.column.total'),\n align: 'right',\n render: (o) => money(o.total),\n },\n {\n key: 'status',\n header: t('orders.list.column.status'),\n render: (o) => (\n <span className=\"inline-flex items-center gap-1.5 text-sm\">\n <span className={`h-2 w-2 rounded-full ${COLUMN_COLOR[mapToKanbanColumn(o.status)] ?? 'bg-muted-foreground/40'}`} />\n {t(`orders.kanban.column.${mapToKanbanColumn(o.status)}`)}\n </span>\n ),\n },\n ], [t, money, paymentByOrder])\n\n // Um construtor, dois quadros. A diferença é o RABO do fluxo e a palavra da\n // penúltima coluna: no salão a comida é servida e a comanda continua viva até\n // a conta; na entrega ela sai e acabou. Forçar as duas na mesma tira punha\n // toda comanda sob \"Saiu\" — uma coluna de logística para um pedido que nunca\n // sai pela porta.\n //\n // `columnOf` devolvendo null é o que mantém cada quadro só com o que é dele:\n // o mesmo filtro que escolhe as colunas escolhe as linhas.\n const makeBoard = React.useCallback((service: 'salao' | 'delivery'): OrderBoardConfig<RestaurantOrder> => ({\n // O salão não tem \"Novo\": ninguém aceita uma comanda. A entrega recebe um\n // pedido de fora e a primeira decisão é confirmá-lo; no salão a comanda\n // nasce do garçom que já a abriu, e a coluna Novo ficava vazia entre nove\n // comandas confirmadas — uma coluna que só ocupa a largura das outras.\n columns: (service === 'salao'\n ? KANBAN_COLUMNS.filter((id) => id !== 'new')\n : KANBAN_COLUMNS\n ).map((id) => ({\n id,\n label: labelOfColumn(id, service, t),\n color: COLUMN_COLOR[id],\n })),\n // The status is DERIVED from the document status plus the lines' execution\n // state, and it is not stored. columnOf receives the already-derived value.\n columnOf: (o) => {\n if (serviceOf(o) !== service) return null\n const column = mapToKanbanColumn(o.status)\n // Sem coluna Novo, uma comanda que ainda não foi confirmada some do\n // quadro — e uma comanda aberta que não aparece é pior do que a coluna\n // que se foi. Ela entra onde o salão já a lê: aberta.\n return service === 'salao' && column === 'new' ? 'confirmed' : column\n },\n canAdvance: (o) => getNextStatus(o.kind, o.status) !== null,\n // O destino do cartão enquanto a escrita está no ar. O padrão do quadro é a\n // próxima coluna DECLARADA, e aqui isso mentiria: o salão não tem a coluna\n // Novo, então quem sai de Aberta iria parar em Preparando por engano de\n // contagem. Quem sabe o próximo estado é o fluxo, e ele é daqui.\n nextColumnOf: (o) => {\n const next = getNextStatus(o.kind, o.status)\n if (!next) return null\n const column = mapToKanbanColumn(next)\n return service === 'salao' && column === 'new' ? 'confirmed' : column\n },\n onAdvance: (o) => advanceOrderStatus(o.id),\n // O verbo do PASSO, não o do quadro. \"Avançar\" descreve o movimento no\n // diagrama; quem está no salão confirma, prepara, serve ou despacha — e é\n // essa palavra que decide o clique sem abrir o pedido. A chave por estado\n // vem primeiro para o app traduzir; o `ADVANCE_VERB` é o padrão, e o\n // \"Avançar\" antigo continua sendo o último recurso de um estado que nenhum\n // dos dois nomeia.\n advanceLabel: (o) => {\n const next = getNextStatus(o.kind, o.status)\n if (!next) return t('orders.kanban.advance')\n const key = `orders.advance.${next}`\n const translated = t(key)\n return translated !== key ? translated : (ADVANCE_VERB[next] ?? t('orders.kanban.advance'))\n },\n emptyColumn: t('orders.kanban.noOrders'),\n card: (o) => (\n <>\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-sm font-bold\">{o.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 {t(`orders.source.${o.source}`) !== `orders.source.${o.source}` ? t(`orders.source.${o.source}`) : o.source}\n </span>\n </div>\n {o.partyName && <p className=\"truncate text-sm font-medium\">{o.partyName}</p>}\n {o.tableName && <p className=\"text-xs text-muted-foreground\">{o.tableName}</p>}\n {o.items.length > 0 && (\n <div className=\"mt-2 space-y-0.5\">\n {o.items.map((item) => (\n <p key={item.id} className=\"truncate text-xs text-muted-foreground\">\n {item.quantity}x {item.name}\n </p>\n ))}\n </div>\n )}\n {/* O selo ao lado do VALOR, e não numa linha própria embaixo. \"Quanto\"\n e \"já pagou?\" são a mesma pergunta feita duas vezes, e separá-las em\n duas linhas fazia o cartão crescer para dizer metade de cada. O\n relógio continua na direita, que é o outro assunto do rodapé. */}\n <div className=\"mt-3 flex items-center justify-between gap-2 border-t pt-2\">\n <OrderAmount value={o.total} summary={paymentByOrder.get(o.id)} formatCurrency={money} />\n <span className=\"inline-flex shrink-0 items-center gap-0.5 text-[10px] text-muted-foreground\">\n <Clock className=\"h-3 w-3\" />\n {timeAgo(o.createdAt)}\n </span>\n </div>\n </>\n ),\n }), [t, money, advanceOrderStatus, paymentByOrder])\n\n const salaoBoard = React.useMemo(() => makeBoard('salao'), [makeBoard])\n const deliveryBoard = React.useMemo(() => makeBoard('delivery'), [makeBoard])\n\n // A planta do salão, se alguém a anunciou. `plugin-tables` a registra ao\n // montar (`tables.floor-plan`); numa casa só de balcão ninguém registra e a\n // aba não existe — em vez de existir vazia.\n const FloorPlan = getComponent(FLOOR_PLAN_COMPONENT_ID) as React.ComponentType<unknown> | undefined\n const showFloor = !!FloorPlan && config.floorView !== false\n /** O painel só mostra o pedido — quem o opera é outra superfície. */\n const inspect = config.detailRail === 'inspect'\n // Delivery só é aba enquanto NÃO for lugar: com `deliveryRoute` o app deu ao\n // quadro uma linha no rail, e mantê-lo aqui é o mesmo destino por dois\n // caminhos — um deles um clique mais fundo. Mesma regra do salão.\n // E só enquanto a casa ENTREGAR. `modules.delivery` desligado já tira a\n // configuração e as ferramentas do agente; a aba continuava, e uma aba de\n // entrega numa casa que não entrega é um quadro que nunca recebe cartão.\n const showDelivery = !config.deliveryRoute && config.modules.delivery !== false\n /** Esta montagem É o quadro de entregas (a rota dedicada), não a lista. */\n const isDelivery = config.pinnedView === 'delivery'\n const offeredViews = ([\n 'list',\n ...(config.boardView !== false ? ['quadro' as const] : []),\n ...(showFloor ? ['salao' as const] : []),\n ...(showDelivery ? ['delivery' as const] : []),\n ]) as ('list' | 'quadro' | 'salao' | 'delivery')[]\n\n return (\n <>\n <OrderDetailRail\n orderId={selectedOrderId}\n onClose={() => selectOrder(null)}\n fullPageHref={config.orderRoute}\n onActionDone={() => { void fetchActiveOrders() }}\n // O bloco que ESCREVE precisa do `reload`: ele muda as próprias linhas e\n // o total desenhados logo acima dele. Em `inspect` ele não existe.\n children={inspect ? undefined : (order, reload) => (\n // Uma comanda fechada ou cancelada é decisão já tomada: nada mais entra\n // nela. É a mesma regra que governa o botão de editar.\n config.canEditOrder?.({ status: order.status }) === false\n ? null\n : <AddItemsBlock orderId={order.id} onDone={() => { reload(); void fetchActiveOrders() }} />\n )}\n // Only what the order's state actually allows: a cancelled comanda has\n // nothing left to cancel, and a button that is always disabled is a\n // button that should not have been drawn.\n actions={inspect ? undefined : (order) => {\n const verbs: OrderRailAction[] = []\n // What comes NEXT, which is what the panel is open for during service.\n //\n // Read from the ROW, not from the panel: `OrderDetailRailOrder.status`\n // is `orders.status` as stored, and a host may derive the status it\n // shows from more than that column. resto-saas does exactly this — its\n // board status `new` is document status `draft`, because the kitchen\n // state lives on the lines — so advancing from the stored word computed\n // `draft → new`, wrote `new`, and the provider mapped it straight back\n // to `draft`. The button worked, the toast fired, and nothing moved.\n //\n // The flow itself is this plugin's (`STATUS_FLOW`), not the archetype's:\n // the database says which statuses are legal and permits any of them,\n // and only the restaurant knows they happen in an order.\n const row = activeOrders.find((o) => o.id === order.id)\n const next = row ? getNextStatus(row.kind, row.status) : null\n if (next) {\n verbs.push({\n id: 'advance',\n label: `${t('orders.kanban.advance')} · ${statusLabel(next)}`,\n variant: 'default',\n // The panel stays open and re-reads the order: advancing is usually\n // done twice in a row, and it is the only way to see that the state\n // you asked for is the state it now has.\n keepOpen: true,\n onSelect: (o) => advanceOrderStatus(o.id),\n })\n }\n if (order.status && !['cancelled', 'completed'].includes(order.status)) {\n verbs.push({\n id: 'cancel',\n label: t('orders.detail.cancel'),\n variant: 'destructive',\n confirm: t('orders.detail.cancelConfirm').replace('{reference}', order.referenceNumber),\n onSelect: (o) => cancelOrder(o.id),\n })\n }\n return verbs\n }}\n />\n <OrderListView<RestaurantOrder>\n title={t('orders.kanban.title')}\n viewKey={config.pinnedView ? undefined : 'orders'}\n // Respostas diferentes para a mesma pergunta: a LISTA procura um pedido; o\n // QUADRO mostra em que estado a comanda está; o SALÃO mostra onde ela está\n // sentada; o DELIVERY tem outro rabo (sai e é entregue) e por isso não\n // cabe nas colunas do salão.\n //\n // `pinnedView` deixa UMA delas — a rota é o destino, e a tira de abas\n // viraria três rotas disfarçadas de uma. `floorView: false` tira o salão\n // de um app que já o desenha numa superfície de operação.\n views={config.pinnedView ? [config.pinnedView] : offeredViews}\n defaultView={config.pinnedView ?? 'list'}\n boards={{\n quadro: { label: 'Quadro', config: salaoBoard, icon: LayoutGrid },\n ...(showFloor && FloorPlan ? { salao: { label: 'Salão', render: () => <FloorPlan />, icon: MapPin } } : {}),\n ...(showDelivery || config.pinnedView === 'delivery'\n ? { delivery: { label: 'Delivery', config: deliveryBoard, icon: Bike } }\n : {}),\n }}\n columns={columns}\n rows={activeOrders}\n loading={loading}\n empty={t('orders.kanban.noOrders')}\n searchText={(o) => [o.referenceNumber, o.partyName, o.tableName, o.source].filter(Boolean).join(' ')}\n searchPlaceholder={t('orders.list.searchPlaceholder')}\n pageSize={25}\n onRowClick={(o) => selectOrder(o.id)}\n // A comanda aberta no painel continua acesa no quadro: o painel some\n // quando se rola de volta, e sem isso não há como saber em qual delas se\n // estava.\n selectedRowId={selectedOrderId}\n // Declared, not drawn: PageActions is what turns a create action into the\n // thumb's \"+\" circle on a phone and a primary button on a desktop, and it\n // is the same construct the shop's order list already uses — so the two\n // products keep one answer to \"where do I open an order\".\n // O quadro de despacho é a tela em que se passa o turno, e a taxa, o\n // mínimo e o raio são decisões que se acertam OLHANDO para ele — sem a\n // porta aqui, o caminho é sair, abrir Configurações e procurar entre\n // catorze telas qual delas fala de entrega.\n actions={(config.createRoute || isDelivery) ? (\n <PageActions\n actions={[\n // `utility`: a engrenagem é botão PRÓPRIO ao lado do \"Novo Pedido\",\n // não item dentro do caret dele — a mesma forma que o Financeiro e o\n // CRM usam (ModuleActionBar.settingsPath). Configurar não é uma\n // maneira de criar pedido, e escondê-la sob o botão de criar era\n // dizer que é.\n ...(isDelivery ? [{\n id: 'delivery-settings',\n label: t('orders.delivery.settings'),\n icon: Settings,\n intent: 'utility' as const,\n testId: 'delivery-settings',\n onSelect: () => adminNavigateTo('/settings/orders-delivery'),\n }] : []),\n ...(config.createRoute ? [{\n id: 'new-order',\n label: createLabel,\n icon: Plus,\n intent: 'primary' as const,\n creates: true,\n testId: 'orders-create',\n onSelect: () => adminNavigateTo(config.createRoute as string),\n }] : []),\n ]}\n />\n ) : undefined}\n />\n </>\n )\n}\n","import React from 'react'\nimport { OrderListView, orderKindColumn, orderChannelColumn, type OrderListColumn } from '@fayz-ai/admin'\nimport { useOrdersConfig, useOrdersStore, formatCurrency } from '../context'\nimport type { RestaurantOrder } from '../types'\n\n// ---------------------------------------------------------------------------\n// The commercial orders screen (#336) — the salon's vocabulary over the core\n// archetype.\n//\n// Columns: Documento · Cliente · Profissional · Total · Estado · Título. The\n// last one is the point. Whether a Venda produced a Título is one join, and it\n// is what turns \"367 of 370 completed appointments have neither\" from an audit\n// finding into something the salon owner notices on a Tuesday.\n//\n// List by default and a board available: a salon books ahead, so the list is\n// even more clearly right here than in the restaurant.\n// ---------------------------------------------------------------------------\n\ntype PaymentState = 'none' | 'pending' | 'partial' | 'paid' | 'overdue' | 'cancelled'\n\nfunction paymentOf(order: RestaurantOrder): PaymentState {\n return ((order.metadata as Record<string, unknown> | undefined)?.paymentStatus as PaymentState) ?? 'none'\n}\n\nconst PAYMENT_LABEL: Record<PaymentState, string> = {\n none: 'Sem título',\n pending: 'Em aberto',\n partial: 'Parcial',\n paid: 'Pago',\n overdue: 'Vencido',\n cancelled: 'Cancelado',\n}\n\nconst PAYMENT_TONE: Record<PaymentState, string> = {\n // \"Sem título\" is the one that has to catch the eye: it is the gap.\n none: 'bg-red-100 text-red-800',\n pending: 'bg-amber-100 text-amber-800',\n partial: 'bg-blue-100 text-blue-800',\n paid: 'bg-emerald-100 text-emerald-800',\n overdue: 'bg-red-100 text-red-800',\n cancelled: 'bg-gray-100 text-gray-700',\n}\n\nconst STATUS_LABEL: Record<string, string> = {\n draft: 'Rascunho',\n scheduled: 'Agendado',\n confirmed: 'Confirmado',\n in_progress: 'Em andamento',\n completed: 'Concluído',\n cancelled: 'Cancelado',\n no_show: 'Não compareceu',\n invoiced: 'Faturado',\n paid: 'Pago',\n open: 'Aberto',\n}\n\nexport function CommercialOrdersScreen() {\n const config = useOrdersConfig()\n const orders = useOrdersStore((s) => s.orders)\n const loading = useOrdersStore((s) => s.ordersLoading)\n const fetchOrders = useOrdersStore((s) => s.fetchOrders)\n const selectOrder = useOrdersStore((s) => s.selectOrder)\n\n React.useEffect(() => { void fetchOrders({ limit: 200, offset: 0 } as never) }, [fetchOrders])\n\n const money = React.useCallback(\n (value: number) => formatCurrency(value, config.currency),\n [config.currency],\n )\n\n const hasAssignee = React.useMemo(\n () => (orders ?? []).some((o) => Boolean(o.assigneeName)),\n [orders],\n )\n\n const columns: OrderListColumn<RestaurantOrder>[] = React.useMemo(() => [\n {\n key: 'document',\n header: 'Documento',\n render: (o) => <span className=\"font-medium\">{o.referenceNumber}</span>,\n },\n orderKindColumn<RestaurantOrder>((o) => o.kind as string),\n {\n key: 'party',\n header: 'Cliente',\n render: (o) => o.partyName ?? <span className=\"text-muted-foreground\">—</span>,\n },\n // Só quando ALGUÉM é: \"Profissional\" é vocabulário de salão, e num produto\n // onde ninguém atende a venda (bilheteria, loja) a coluna existia para\n // mostrar um traço em toda linha. Uma coluna que nunca tem valor não é uma\n // informação faltando — é largura roubada das que têm.\n ...(hasAssignee\n ? [{\n key: 'assignee',\n header: 'Profissional',\n render: (o: RestaurantOrder) => o.assigneeName ?? <span className=\"text-muted-foreground\">—</span>,\n } as OrderListColumn<RestaurantOrder>]\n : []),\n orderChannelColumn<RestaurantOrder>((o) => (o.metadata as Record<string, unknown> | undefined)?.channel as string | undefined),\n { key: 'total', header: 'Total', align: 'right', render: (o) => money(o.total) },\n {\n key: 'status',\n header: 'Estado',\n render: (o) => STATUS_LABEL[o.status as string] ?? o.status,\n },\n {\n key: 'invoice',\n header: 'Título',\n render: (o) => {\n const state = paymentOf(o)\n return (\n <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${PAYMENT_TONE[state]}`}>\n {PAYMENT_LABEL[state]}\n </span>\n )\n },\n },\n ], [money, hasAssignee])\n\n return (\n <OrderListView<RestaurantOrder>\n // O rótulo do HOST, não um literal. O app que monta este plugin já diz\n // como chama a tela (`labels.pageTitle`) — \"Vendas\" no TicketControl — e\n // ignorá-lo dava dois nomes para o mesmo lugar: um no rail e outro no\n // cabeçalho da página que ele abre.\n title={config.labels.pageTitle}\n viewKey=\"commercial-orders\"\n views={['list', 'board']}\n defaultView=\"list\"\n board={{\n columns: [\n { id: 'open', label: 'Em aberto', color: 'bg-blue-500' },\n { id: 'no_invoice', label: 'Sem título', color: 'bg-red-500' },\n { id: 'paid', label: 'Pago', color: 'bg-emerald-500' },\n { id: 'cancelled', label: 'Cancelado', color: 'bg-gray-400' },\n ],\n columnOf: (o) => {\n if (o.status === 'cancelled') return 'cancelled'\n const state = paymentOf(o)\n if (state === 'paid') return 'paid'\n if (state === 'none') return 'no_invoice'\n return 'open'\n },\n emptyColumn: 'Nada aqui',\n card: (o) => (\n <>\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-sm font-bold\">{o.referenceNumber}</span>\n <span className=\"text-[10px] text-muted-foreground\">\n {new Date(o.createdAt).toLocaleDateString('pt-BR')}\n </span>\n </div>\n {o.partyName && <p className=\"truncate text-sm font-medium\">{o.partyName}</p>}\n {o.assigneeName && <p className=\"truncate text-xs text-muted-foreground\">{o.assigneeName}</p>}\n <div className=\"mt-3 border-t pt-2 text-sm font-bold\">{money(o.total)}</div>\n </>\n ),\n }}\n columns={columns}\n rows={orders}\n loading={loading}\n empty=\"Nenhum pedido ainda.\"\n searchText={(o) => [o.referenceNumber, o.partyName, o.assigneeName].filter(Boolean).join(' ')}\n // Segue a coluna: sem profissional na tabela, oferecê-lo na busca promete\n // um filtro que não existe.\n searchPlaceholder={hasAssignee ? 'Buscar por documento, cliente ou profissional…' : 'Buscar por documento ou cliente…'}\n pageSize={25}\n onRowClick={(o) => selectOrder(o.id)}\n />\n )\n}\n","import React from 'react'\nimport type { StoreApi } from 'zustand/vanilla'\nimport { OrdersContextProvider, type ResolvedOrdersConfig } from '../context'\nimport { OrdersScreen } from './OrdersScreen'\nimport { CommercialOrdersScreen } from './CommercialOrdersScreen'\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, variant = 'restaurant' }: {\n config: ResolvedOrdersConfig\n provider: OrdersDataProvider\n store: StoreApi<OrdersUIState>\n registries?: PluginRegistryDef[]\n /**\n * Which vocabulary the screen speaks (#334, #336). The restaurant watches a\n * service in progress; a salon or a clinic looks a Venda up and asks whether\n * it produced a Título. Same archetype, same board, different columns.\n */\n variant?: 'restaurant' | 'commercial'\n}) {\n return (\n <OrdersContextProvider config={config} provider={provider} store={store}>\n {variant === 'commercial' ? <CommercialOrdersScreen /> : <OrdersScreen />}\n </OrdersContextProvider>\n )\n}\n"]}
|