EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
page.tsx
Go to the documentation of this file.
1'use client';
2
3import { useState } from 'react';
4import { useRouter } from 'next/navigation';
5
6interface Supplier {
7 id: string;
8 name: string;
9}
10
11interface SaleLineItem {
12 id: string;
13 pluCode: string;
14 description: string;
15 quantity: number;
16 total: number;
17 supplierCode?: string;
18 supplierCost?: number;
19 selected: boolean;
20}
21
22interface CustomerDetails {
23 name: string;
24 address1: string;
25 address2: string;
26 address3: string;
27 address4: string;
28 address5: string;
29 address6: string;
30}
31
32interface Sale {
33 saleId: string;
34 date: string;
35 time: string;
36 total: number;
37 physkey: string;
38 customer: CustomerDetails;
39 lines: SaleLineItem[];
40}
41
42export default function DropShippingOrder() {
43 const router = useRouter();
44
45 const [supplierSearch, setSupplierSearch] = useState('');
46 const [supplierId, setSupplierId] = useState('');
47 const [supplierName, setSupplierName] = useState('');
48 const [showSupplierDropdown, setShowSupplierDropdown] = useState(false);
49 const [suppliers, setSuppliers] = useState<Supplier[]>([]);
50
51 const [saleSearch, setSaleSearch] = useState('');
52 const [selectedSale, setSelectedSale] = useState<Sale | null>(null);
53 const [sales, setSales] = useState<Sale[]>([]);
54 const [showSaleList, setShowSaleList] = useState(false);
55 const [searching, setSearching] = useState(false);
56
57 // Mock suppliers
58 const mockSuppliers: Supplier[] = [
59 { id: '1001', name: 'ABC Suppliers Ltd' },
60 { id: '1002', name: 'Best Wholesale Co' },
61 { id: '1003', name: 'Central Distributors' },
62 { id: '1004', name: 'Delta Trading Inc' },
63 ];
64
65 // Mock sales data
66 const mockSales: Sale[] = [
67 {
68 saleId: '50000079',
69 date: '11-Dec-2025',
70 time: '12:17',
71 total: 103.95,
72 physkey: 'KEP3OJYYYBG4MZTDOOS7Q3YAAAEQAAAB',
73 customer: {
74 name: 'John Smith',
75 address1: '123 Main Street',
76 address2: 'Sydney',
77 address3: 'NSW 2000',
78 address4: 'Australia',
79 address5: '',
80 address6: ''
81 },
82 lines: [
83 {
84 id: '1',
85 pluCode: 'SKU001',
86 description: 'Premium Coffee Beans 1kg',
87 quantity: 2,
88 total: 51.00,
89 selected: true
90 },
91 {
92 id: '2',
93 pluCode: 'SKU002',
94 description: 'Coffee Grinder',
95 quantity: 1,
96 total: 52.95,
97 selected: true
98 }
99 ]
100 },
101 {
102 saleId: '50000078',
103 date: '11-Dec-2025',
104 time: '7:57',
105 total: 103.95,
106 physkey: 'KEP3OFQYYBG4MZTDCOPHYIYAAARQAAAB',
107 customer: {
108 name: 'Jane Doe',
109 address1: '456 Oak Ave',
110 address2: 'Melbourne',
111 address3: 'VIC 3000',
112 address4: 'Australia',
113 address5: '',
114 address6: ''
115 },
116 lines: [
117 {
118 id: '1',
119 pluCode: 'SKU003',
120 description: 'Organic Tea Box',
121 quantity: 1,
122 total: 103.95,
123 selected: true
124 }
125 ]
126 },
127 {
128 saleId: '50000077',
129 date: '2-Dec-2025',
130 time: '9:00',
131 total: 261.55,
132 physkey: 'KEP3OAYYYBG4MZTDMOCRBRYAAABAAAAB',
133 customer: {
134 name: 'Bob Johnson',
135 address1: '789 Elm Street',
136 address2: 'Brisbane',
137 address3: 'QLD 4000',
138 address4: 'Australia',
139 address5: '',
140 address6: ''
141 },
142 lines: [
143 {
144 id: '1',
145 pluCode: 'SKU001',
146 description: 'Premium Coffee Beans 1kg',
147 quantity: 3,
148 total: 154.50,
149 selected: true
150 },
151 {
152 id: '2',
153 pluCode: 'SKU004',
154 description: 'Coffee Filters',
155 quantity: 2,
156 total: 107.05,
157 selected: true
158 }
159 ]
160 }
161 ];
162
163 const handleSupplierSearch = (value: string) => {
164 setSupplierSearch(value);
165 if (value.trim()) {
166 const filtered = mockSuppliers.filter(s =>
167 s.name.toLowerCase().includes(value.toLowerCase()) ||
168 s.id.includes(value)
169 );
170 setSuppliers(filtered);
171 } else {
172 setSuppliers(mockSuppliers);
173 }
174 setShowSupplierDropdown(true);
175 };
176
177 const handleSupplierFocus = () => {
178 setShowSupplierDropdown(true);
179 if (suppliers.length === 0) {
180 setSuppliers(mockSuppliers);
181 }
182 };
183
184 const handleSelectSupplier = (supplier: Supplier) => {
185 setSupplierId(supplier.id);
186 setSupplierName(supplier.name);
187 setSupplierSearch(supplier.name);
188 setShowSupplierDropdown(false);
189 };
190
191 const handleSearchSales = (value: string) => {
192 setSaleSearch(value);
193 if (value.trim()) {
194 setSearching(true);
195 // Simulate API call
196 setTimeout(() => {
197 const filtered = mockSales.filter(s =>
198 s.saleId.includes(value) ||
199 s.customer.name.toLowerCase().includes(value.toLowerCase())
200 );
201 setSales(filtered);
202 setShowSaleList(true);
203 setSearching(false);
204 }, 300);
205 } else {
206 setSales([]);
207 setShowSaleList(false);
208 }
209 };
210
211 const handleSaleFocus = () => {
212 if (saleSearch.trim() === '') {
213 // Show recent sales when focused with empty input
214 setSales(mockSales);
215 setShowSaleList(true);
216 }
217 };
218
219 const handleSelectSale = (sale: Sale) => {
220 // Fetch supplier product codes for each item
221 const updatedLines = sale.lines.map(line => ({
222 ...line,
223 supplierCode: `SUPP-${line.pluCode}`,
224 supplierCost: line.total / line.quantity * 0.85 // Mock supplier cost
225 }));
226
227 setSelectedSale({
228 ...sale,
229 lines: updatedLines
230 });
231 setShowSaleList(false);
232 };
233
234 const handleToggleLineItem = (lineId: string) => {
235 if (selectedSale) {
236 setSelectedSale({
237 ...selectedSale,
238 lines: selectedSale.lines.map(line =>
239 line.id === lineId ? { ...line, selected: !line.selected } : line
240 )
241 });
242 }
243 };
244
245 const handleCreateOrder = () => {
246 if (!supplierId) {
247 alert('Please select a supplier');
248 return;
249 }
250
251 if (!selectedSale) {
252 alert('Please select a sale');
253 return;
254 }
255
256 const selectedItems = selectedSale.lines.filter(l => l.selected);
257 if (selectedItems.length === 0) {
258 alert('Please select at least one item');
259 return;
260 }
261
262 const orderTotal = selectedItems.reduce((sum, line) => sum + line.total, 0);
263
264 if (confirm(`Create drop shipping order?\n\nSupplier: ${supplierName}\nCustomer: ${selectedSale.customer.name}\nItems: ${selectedItems.length}\nTotal: $${orderTotal.toFixed(2)}`)) {
265
266 // Build DATI packet
267 const datiPacket = {
268 request: 'save_data',
269 client: 'everydaypos-web',
270 data: {
271 table: 'PurchaseOrders',
272 operation: 'insert_dropship',
273 fields: {
274 f101_E: supplierId,
275 f102_s: supplierName,
276 f103_E: new Date().toISOString().split('T')[0],
277 f104_N: orderTotal,
278 f114_N: 2, // Pre-auth
279 f132_s: 'This is a Drop Shipping Order. Please ship directly to the customers address shown',
280 f200_E: selectedSale.physkey, // Link to sale
281 shipTo: {
282 contact: selectedSale.customer.name,
283 address: [
284 selectedSale.customer.address1,
285 selectedSale.customer.address2,
286 selectedSale.customer.address3,
287 selectedSale.customer.address4,
288 selectedSale.customer.address5,
289 selectedSale.customer.address6
290 ].filter(a => a && a.trim()).join('|')
291 },
292 lines: selectedItems.map((line, index) => ({
293 f1100_E: (index + 1).toString(),
294 f1101_E: line.pluCode,
295 f1102_s: line.description,
296 f1103_N: line.quantity,
297 f1105_N: line.supplierCost || 0,
298 f1106_N: line.total,
299 f1107_s: line.supplierCode || line.pluCode,
300 f1108_N: line.quantity
301 }))
302 }
303 }
304 };
305
306 console.log('Drop Shipping Order DATI packet:', datiPacket);
307
308 alert(`✓ Drop Shipping Order Created!\n\nSupplier: ${supplierName}\nCustomer: ${selectedSale.customer.name}\nOrder Total: $${orderTotal.toFixed(2)}\n\nThe supplier will ship directly to the customer.`);
309
310 router.push('/purchase-orders');
311 }
312 };
313
314 return (
315 <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 overflow-auto">
316 <div className="container mx-auto px-4 py-8">
317 {/* Header */}
318 <div className="mb-6">
319 <div className="flex items-center justify-between mb-2">
320 <h1 className="text-3xl font-bold text-slate-800">Create Drop Shipping Order</h1>
321 <button
322 onClick={() => router.push('/purchase-orders')}
323 className="px-4 py-2 text-slate-600 hover:text-slate-800"
324 >
325 ← Back to Orders
326 </button>
327 </div>
328 <p className="text-slate-600">Create a purchase order that ships directly to your customer</p>
329 </div>
330
331 {/* Supplier Selection */}
332 <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 mb-6">
333 <h2 className="text-lg font-semibold text-slate-800 mb-4">Select Supplier</h2>
334 <div className="relative">
335 <input
336 type="text"
337 value={supplierSearch}
338 onChange={(e) => handleSupplierSearch(e.target.value)}
339 onFocus={handleSupplierFocus}
340 placeholder="Search supplier name or ID..."
341 className="w-full px-4 py-2 pr-10 border border-slate-300 rounded-lg text-sm"
342 />
343 <button
344 onClick={() => setShowSupplierDropdown(true)}
345 className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
346 >
347
348 </button>
349
350 {showSupplierDropdown && suppliers.length > 0 && (
351 <div className="absolute z-10 w-full mt-1 bg-white border border-slate-300 rounded-lg shadow-lg max-h-60 overflow-y-auto">
352 {suppliers.map(supplier => (
353 <div
354 key={supplier.id}
355 onClick={() => handleSelectSupplier(supplier)}
356 className="px-4 py-3 hover:bg-slate-50 cursor-pointer border-b border-slate-100 last:border-b-0"
357 >
358 <div className="font-medium text-slate-800">{supplier.name}</div>
359 <div className="text-sm text-slate-500">ID: {supplier.id}</div>
360 </div>
361 ))}
362 </div>
363 )}
364 </div>
365
366 {supplierId && (
367 <div className="mt-4 p-3 bg-green-50 border border-green-200 rounded-lg">
368 <div className="text-sm text-green-700">✓ Selected: {supplierName}</div>
369 </div>
370 )}
371 </div>
372
373 {/* Sale Selection */}
374 <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 mb-6">
375 <h2 className="text-lg font-semibold text-slate-800 mb-4">Find Sale/Customer Order</h2>
376 <div className="flex gap-2 mb-4">
377 <input
378 type="text"
379 value={saleSearch}
380 onChange={(e) => handleSearchSales(e.target.value)}
381 onFocus={handleSaleFocus}
382 placeholder="Search by sale ID or customer name..."
383 className="flex-1 px-4 py-2 border border-slate-300 rounded-lg text-sm"
384 />
385 {searching && <div className="text-slate-500 py-2">Searching...</div>}
386 </div>
387
388 {showSaleList && sales.length > 0 && (
389 <div className="border border-slate-200 rounded-lg overflow-hidden">
390 <table className="w-full text-sm">
391 <thead className="bg-slate-50 border-b border-slate-200">
392 <tr>
393 <th className="px-4 py-3 text-left font-semibold">Sale ID</th>
394 <th className="px-4 py-3 text-left font-semibold">Date</th>
395 <th className="px-4 py-3 text-left font-semibold">Time</th>
396 <th className="px-4 py-3 text-left font-semibold">Customer</th>
397 <th className="px-4 py-3 text-right font-semibold">Total</th>
398 </tr>
399 </thead>
400 <tbody className="divide-y divide-slate-200">
401 {sales.map(sale => (
402 <tr
403 key={sale.physkey}
404 onClick={() => handleSelectSale(sale)}
405 className="hover:bg-slate-50 cursor-pointer"
406 >
407 <td className="px-4 py-3 font-medium text-blue-600">{sale.saleId}</td>
408 <td className="px-4 py-3">{sale.date}</td>
409 <td className="px-4 py-3">{sale.time}</td>
410 <td className="px-4 py-3">{sale.customer.name}</td>
411 <td className="px-4 py-3 text-right">${sale.total.toFixed(2)}</td>
412 </tr>
413 ))}
414 </tbody>
415 </table>
416 </div>
417 )}
418
419 {sales.length === 0 && saleSearch && (
420 <div className="p-4 text-center text-slate-500">No sales found</div>
421 )}
422 </div>
423
424 {/* Selected Sale Details */}
425 {selectedSale && (
426 <>
427 {/* Customer Details */}
428 <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 mb-6">
429 <h2 className="text-lg font-semibold text-slate-800 mb-4">Customer Details</h2>
430 <div className="grid grid-cols-2 gap-4">
431 <div>
432 <div className="text-sm text-slate-500 mb-1">Customer Name</div>
433 <div className="font-semibold text-slate-800">{selectedSale.customer.name}</div>
434 </div>
435 <div>
436 <div className="text-sm text-slate-500 mb-1">Sale ID</div>
437 <div className="font-semibold text-slate-800">{selectedSale.saleId}</div>
438 </div>
439 </div>
440 <div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
441 <div className="text-sm text-slate-700">
442 <strong>Shipping Address:</strong>
443 <div className="mt-2 whitespace-pre-wrap font-mono text-xs">
444 {selectedSale.customer.name}
445 {selectedSale.customer.address1 && `\n${selectedSale.customer.address1}`}
446 {selectedSale.customer.address2 && `\n${selectedSale.customer.address2}`}
447 {selectedSale.customer.address3 && `\n${selectedSale.customer.address3}`}
448 {selectedSale.customer.address4 && `\n${selectedSale.customer.address4}`}
449 </div>
450 </div>
451 </div>
452 </div>
453
454 {/* Line Items Selection */}
455 <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 mb-6">
456 <h2 className="text-lg font-semibold text-slate-800 mb-4">Select Items to Order</h2>
457 <div className="overflow-x-auto">
458 <table className="w-full text-sm">
459 <thead className="bg-slate-50 border-b border-slate-200">
460 <tr>
461 <th className="px-4 py-3 text-center w-10">
462 <input
463 type="checkbox"
464 checked={selectedSale.lines.every(l => l.selected)}
465 onChange={(e) => {
466 setSelectedSale({
467 ...selectedSale,
468 lines: selectedSale.lines.map(l => ({
469 ...l,
470 selected: e.target.checked
471 }))
472 });
473 }}
474 />
475 </th>
476 <th className="px-4 py-3 text-left font-semibold">Your PLU</th>
477 <th className="px-4 py-3 text-left font-semibold">Description</th>
478 <th className="px-4 py-3 text-right font-semibold">Qty</th>
479 <th className="px-4 py-3 text-right font-semibold">Your Total</th>
480 <th className="px-4 py-3 text-left font-semibold">Supplier PLU</th>
481 <th className="px-4 py-3 text-right font-semibold">Supplier Cost</th>
482 </tr>
483 </thead>
484 <tbody className="divide-y divide-slate-200">
485 {selectedSale.lines.map(line => (
486 <tr key={line.id} className="hover:bg-slate-50">
487 <td className="px-4 py-3 text-center">
488 <input
489 type="checkbox"
490 checked={line.selected}
491 onChange={() => handleToggleLineItem(line.id)}
492 />
493 </td>
494 <td className="px-4 py-3 font-mono text-slate-800">{line.pluCode}</td>
495 <td className="px-4 py-3">{line.description}</td>
496 <td className="px-4 py-3 text-right">{line.quantity}</td>
497 <td className="px-4 py-3 text-right font-semibold">${line.total.toFixed(2)}</td>
498 <td className="px-4 py-3 font-mono text-blue-600">{line.supplierCode}</td>
499 <td className="px-4 py-3 text-right">${(line.supplierCost || 0).toFixed(2)}</td>
500 </tr>
501 ))}
502 </tbody>
503 </table>
504 </div>
505 </div>
506
507 {/* Create Order */}
508 <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
509 <div className="flex items-center justify-between">
510 <div>
511 <div className="text-sm text-slate-500">Selected Items</div>
512 <div className="text-2xl font-bold text-[#00946b]">
513 {selectedSale.lines.filter(l => l.selected).length} item(s)
514 </div>
515 <div className="text-sm text-slate-600 mt-1">
516 Total: ${selectedSale.lines.filter(l => l.selected).reduce((sum, l) => sum + l.total, 0).toFixed(2)}
517 </div>
518 </div>
519 <div className="flex gap-3">
520 <button
521 onClick={() => router.push('/purchase-orders')}
522 className="px-6 py-3 bg-slate-300 text-slate-700 rounded-lg hover:bg-slate-400 font-semibold"
523 >
524 Cancel
525 </button>
526 <button
527 onClick={handleCreateOrder}
528 disabled={!supplierId || selectedSale.lines.filter(l => l.selected).length === 0}
529 className="px-6 py-3 bg-[#00946b] text-white rounded-lg hover:opacity-80 font-semibold disabled:opacity-50 disabled:cursor-not-allowed"
530 >
531 Create Drop Ship Order
532 </button>
533 </div>
534 </div>
535 </div>
536 </>
537 )}
538 </div>
539 </div>
540 );
541}