@fayz-ai/plugin-orders 0.9.0 → 0.9.2

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.
@@ -1,240 +0,0 @@
1
- import React, { useEffect, useMemo } from 'react'
2
- import { ChevronRight, Clock, Loader2, ClipboardList } from 'lucide-react'
3
- import { useOrdersConfig, useOrdersStore, formatCurrency } from '../OrdersContext'
4
- import { useTranslation } from '@fayz-ai/core'
5
- import {
6
- KANBAN_COLUMNS,
7
- mapToKanbanColumn,
8
- getNextStatus,
9
- type KanbanColumn,
10
- type RestaurantOrder,
11
- } from '../types'
12
-
13
- // ---------------------------------------------------------------------------
14
- // Column config
15
- // ---------------------------------------------------------------------------
16
-
17
- const COLUMN_CONFIG: Record<KanbanColumn, { color: string }> = {
18
- new: { color: 'bg-violet-500' },
19
- confirmed: { color: 'bg-blue-500' },
20
- preparing: { color: 'bg-amber-500' },
21
- ready: { color: 'bg-emerald-500' },
22
- out: { color: 'bg-cyan-500' },
23
- completed: { color: 'bg-gray-400' },
24
- cancelled: { color: 'bg-red-500' },
25
- }
26
-
27
- // ---------------------------------------------------------------------------
28
- // Helpers
29
- // ---------------------------------------------------------------------------
30
-
31
- function timeAgo(dateStr: string): string {
32
- const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 60000)
33
- if (diff < 1) return 'just now'
34
- if (diff === 1) return '1min'
35
- return `${diff}min`
36
- }
37
-
38
- // ---------------------------------------------------------------------------
39
- // Order Card
40
- // ---------------------------------------------------------------------------
41
-
42
- function OrderCard({
43
- order,
44
- onAdvance,
45
- onSelect,
46
- currency,
47
- }: {
48
- order: RestaurantOrder
49
- onAdvance?: () => void
50
- onSelect: () => void
51
- currency: { code: string; locale: string; symbol: string }
52
- }) {
53
- const t = useTranslation()
54
- const sourceLabel = t(`orders.source.${order.source}`) !== `orders.source.${order.source}`
55
- ? t(`orders.source.${order.source}`)
56
- : order.source
57
-
58
- return (
59
- <div
60
- className="rounded-lg border bg-card p-3 hover:shadow-md transition-shadow cursor-pointer"
61
- onClick={onSelect}
62
- >
63
- {/* Header: order number + source badge */}
64
- <div className="flex items-center justify-between mb-2">
65
- <span className="text-sm font-bold">{order.referenceNumber}</span>
66
- <span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium">
67
- {sourceLabel}
68
- </span>
69
- </div>
70
-
71
- {/* Customer + table */}
72
- {order.partyName && (
73
- <p className="text-sm font-medium truncate">{order.partyName}</p>
74
- )}
75
- {order.tableName && (
76
- <p className="text-xs text-muted-foreground">{order.tableName}</p>
77
- )}
78
-
79
- {/* Items */}
80
- {order.items.length > 0 && (
81
- <div className="mt-2 space-y-0.5">
82
- {order.items.map((item) => (
83
- <p key={item.id} className="text-xs text-muted-foreground truncate">
84
- {item.quantity}x {item.name}
85
- </p>
86
- ))}
87
- </div>
88
- )}
89
-
90
- {/* Total + time */}
91
- <div className="flex items-center justify-between mt-3 pt-2 border-t">
92
- <span className="text-sm font-bold">
93
- {formatCurrency(order.total, currency)}
94
- </span>
95
- <span className="inline-flex items-center gap-0.5 text-[10px] text-muted-foreground">
96
- <Clock className="h-3 w-3" />
97
- {timeAgo(order.createdAt)}
98
- </span>
99
- </div>
100
-
101
- {/* Advance button */}
102
- {onAdvance && (
103
- <button
104
- type="button"
105
- 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"
106
- onClick={(e) => {
107
- e.stopPropagation()
108
- onAdvance()
109
- }}
110
- >
111
- {t('orders.kanban.advance')}
112
- <ChevronRight className="h-3 w-3" />
113
- </button>
114
- )}
115
- </div>
116
- )
117
- }
118
-
119
- // ---------------------------------------------------------------------------
120
- // Skeleton column (loading state)
121
- // ---------------------------------------------------------------------------
122
-
123
- function SkeletonColumn() {
124
- return (
125
- <div className="flex-shrink-0 w-72">
126
- <div className="h-9 rounded-lg bg-muted animate-pulse mb-3" />
127
- <div className="space-y-3">
128
- {[1, 2, 3].map((i) => (
129
- <div key={i} className="h-32 rounded-lg bg-muted/60 animate-pulse" />
130
- ))}
131
- </div>
132
- </div>
133
- )
134
- }
135
-
136
- // ---------------------------------------------------------------------------
137
- // OrderKanbanView
138
- // ---------------------------------------------------------------------------
139
-
140
- export function OrderKanbanView() {
141
- const t = useTranslation()
142
- const config = useOrdersConfig()
143
-
144
- const activeOrders = useOrdersStore((s) => s.activeOrders)
145
- const activeOrdersLoading = useOrdersStore((s) => s.activeOrdersLoading)
146
- const fetchActiveOrders = useOrdersStore((s) => s.fetchActiveOrders)
147
- const advanceOrderStatus = useOrdersStore((s) => s.advanceOrderStatus)
148
- const selectOrder = useOrdersStore((s) => s.selectOrder)
149
-
150
- useEffect(() => {
151
- fetchActiveOrders()
152
- }, [fetchActiveOrders])
153
-
154
- // Group orders by kanban column
155
- const grouped = useMemo(() => {
156
- const map: Record<KanbanColumn, RestaurantOrder[]> = {
157
- new: [],
158
- confirmed: [],
159
- preparing: [],
160
- ready: [],
161
- out: [],
162
- completed: [],
163
- cancelled: [],
164
- }
165
- for (const order of activeOrders) {
166
- const col = mapToKanbanColumn(order.status)
167
- map[col].push(order)
168
- }
169
- return map
170
- }, [activeOrders])
171
-
172
- // Loading state
173
- if (activeOrdersLoading && activeOrders.length === 0) {
174
- return (
175
- <div className="flex gap-4 overflow-x-auto pb-4">
176
- {KANBAN_COLUMNS.map((col) => (
177
- <SkeletonColumn key={col} />
178
- ))}
179
- </div>
180
- )
181
- }
182
-
183
- // Empty state
184
- if (!activeOrdersLoading && activeOrders.length === 0) {
185
- return (
186
- <div className="flex flex-col items-center justify-center h-64 text-center">
187
- <ClipboardList className="h-12 w-12 text-muted-foreground/40 mb-3" />
188
- <p className="text-sm font-medium text-muted-foreground">
189
- {t('orders.kanban.noOrders')}
190
- </p>
191
- <p className="text-xs text-muted-foreground/70 mt-1">
192
- {t('orders.kanban.noOrdersDesc')}
193
- </p>
194
- </div>
195
- )
196
- }
197
-
198
- return (
199
- <div className="flex gap-4 overflow-x-auto pb-4">
200
- {KANBAN_COLUMNS.map((col) => {
201
- const columnOrders = grouped[col]
202
- const colConfig = COLUMN_CONFIG[col]
203
- const label = t(`orders.kanban.column.${col}`)
204
- const hasNext = (order: RestaurantOrder) =>
205
- getNextStatus(order.kind, order.status) !== null
206
-
207
- return (
208
- <div key={col} className="flex-shrink-0 w-72">
209
- {/* Column header */}
210
- <div className="flex items-center gap-2 px-3 py-2 rounded-lg mb-3">
211
- <span className={`h-2.5 w-2.5 rounded-full ${colConfig.color}`} />
212
- <span className="text-sm font-semibold">{label}</span>
213
- <span className="ml-auto inline-flex items-center justify-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium">
214
- {columnOrders.length}
215
- </span>
216
- </div>
217
-
218
- {/* Cards */}
219
- <div className="space-y-3 max-h-[calc(100vh-16rem)] overflow-y-auto pr-1">
220
- {columnOrders.map((order) => (
221
- <OrderCard
222
- key={order.id}
223
- order={order}
224
- currency={config.currency}
225
- onSelect={() => selectOrder(order.id)}
226
- onAdvance={hasNext(order) ? () => advanceOrderStatus(order.id) : undefined}
227
- />
228
- ))}
229
- {columnOrders.length === 0 && (
230
- <div className="text-center py-8 text-sm text-muted-foreground rounded-lg border border-dashed">
231
- {t('orders.kanban.noOrders')}
232
- </div>
233
- )}
234
- </div>
235
- </div>
236
- )
237
- })}
238
- </div>
239
- )
240
- }
File without changes