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, useEffect } from 'react';
4import { Icon } from '@/contexts/IconContext';
5
6interface Product {
7 id: number;
8 name: string;
9 price: number;
10 cost: number;
11 salesInfo: string;
12}
13
14interface DuplicatePLU {
15 plu: string;
16 count: number;
17 products: Product[];
18}
19
20export default function DuplicatePLUReport() {
21 const [duplicates, setDuplicates] = useState<DuplicatePLU[]>([]);
22 const [loading, setLoading] = useState(true);
23 const [error, setError] = useState<string | null>(null);
24 const [stats, setStats] = useState({
25 totalProducts: 0,
26 duplicatePLUs: 0,
27 totalDuplicateProducts: 0
28 });
29
30 useEffect(() => {
31 const fetchDuplicates = async () => {
32 try {
33 const response = await fetch('/api/v1/elink/advisor/duplicate-plu');
34 const result = await response.json();
35
36 if (result.success) {
37 setDuplicates(result.data.duplicates || []);
38 setStats({
39 totalProducts: result.data.totalProducts || 0,
40 duplicatePLUs: result.data.duplicatePLUCount || 0,
41 totalDuplicateProducts: result.data.totalDuplicateProducts || 0
42 });
43 } else {
44 throw new Error(result.error || 'Failed to fetch duplicate PLU data');
45 }
46 } catch (err: any) {
47 setError(err.message || 'An error occurred');
48 } finally {
49 setLoading(false);
50 }
51 };
52
53 fetchDuplicates();
54 }, []);
55
56 if (loading) {
57 return (
58 <div className="p-6 min-h-screen bg-bg flex items-center justify-center">
59 <div className="text-center">
60 <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-brand mx-auto mb-4"></div>
61 <p className="text-muted">Analyzing product PLU codes...</p>
62 </div>
63 </div>
64 );
65 }
66
67 if (error) {
68 return (
69 <div className="p-6 min-h-screen bg-bg">
70 <div className="bg-error/10 border border-error/20 rounded-lg p-4 mb-6">
71 <div className="flex items-center gap-2">
72 <Icon name="error" className="text-error" />
73 <span className="text-error font-medium">Error loading duplicate PLU data: {error}</span>
74 </div>
75 </div>
76 <button
77 onClick={() => window.location.reload()}
78 className="px-4 py-2 bg-brand text-white rounded-lg hover:bg-brand2"
79 >
80 Retry
81 </button>
82 </div>
83 );
84 }
85
86 return (
87 <div className="p-6 min-h-screen bg-bg">
88 <div className="mb-6">
89 <div className="flex items-center gap-3 mb-2">
90 <a href="/pages/reports?source=advisor" className="text-muted hover:text-brand">
91 <Icon name="arrow_back" size={24} />
92 </a>
93 <h1 className="text-3xl font-bold text-text">Duplicate PLU Codes</h1>
94 </div>
95 <p className="text-muted ml-9">
96 Multiple products sharing the same PLU/barcode number
97 </p>
98 </div>
99
100 {/* Summary Statistics */}
101 <div className="bg-info/10 border border-info/20 rounded-lg p-4 mb-6">
102 <div className="flex items-center gap-6 flex-wrap">
103 <div className="flex items-center gap-2">
104 <Icon name="inventory" className="text-info" />
105 <span className="text-sm text-muted">
106 Analyzed <span className="font-semibold text-text">{stats.totalProducts}</span> products
107 </span>
108 </div>
109 {stats.duplicatePLUs > 0 && (
110 <>
111 <div className="flex items-center gap-2">
112 <Icon name="warning" className="text-warning" />
113 <span className="text-sm text-muted">
114 Found <span className="font-semibold text-warning">{stats.duplicatePLUs}</span> duplicate PLUs
115 </span>
116 </div>
117 <div className="flex items-center gap-2">
118 <Icon name="error" className="text-error" />
119 <span className="text-sm text-muted">
120 Affecting <span className="font-semibold text-error">{stats.totalDuplicateProducts}</span> products
121 </span>
122 </div>
123 </>
124 )}
125 </div>
126 </div>
127
128 <div className="bg-surface rounded-lg shadow-sm border border-border overflow-hidden">
129 <div className="overflow-x-auto">
130 <table className="w-full">
131 <thead className="bg-[var(--brand)] text-surface">
132 <tr>
133 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
134 PLU Code
135 </th>
136 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
137 Products Using This PLU
138 </th>
139 <th className="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider">
140 Count
141 </th>
142 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
143 Price Range
144 </th>
145 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
146 Actions
147 </th>
148 </tr>
149 </thead>
150 <tbody className="divide-y divide-border">
151 {duplicates.length === 0 ? (
152 <tr>
153 <td colSpan={5} className="px-6 py-12 text-center">
154 <Icon name="check_circle" size={48} className="text-success mx-auto mb-3" />
155 <p className="text-muted">No duplicate PLU codes found</p>
156 </td>
157 </tr>
158 ) : (
159 duplicates.map((dup, idx) => {
160 const prices = dup.products.map(p => p.price);
161 const minPrice = Math.min(...prices);
162 const maxPrice = Math.max(...prices);
163 const priceRange = minPrice === maxPrice
164 ? `$${minPrice.toFixed(2)}`
165 : `$${minPrice.toFixed(2)} - $${maxPrice.toFixed(2)}`;
166
167 return (
168 <tr key={idx} className="hover:bg-surface-2">
169 <td className="px-6 py-4 whitespace-nowrap text-sm font-mono font-medium text-text">
170 {dup.plu}
171 </td>
172 <td className="px-6 py-4 text-sm text-text">
173 <div className="space-y-1">
174 {dup.products.map((product, pIdx) => (
175 <div key={pIdx} className="flex items-center gap-2">
176 <a
177 href={`/pages/products/${product.id}`}
178 className="text-brand hover:text-brand2 hover:underline"
179 >
180 {product.name}
181 </a>
182 <span className="text-xs text-muted">(ID: {product.id})</span>
183 </div>
184 ))}
185 </div>
186 </td>
187 <td className="px-6 py-4 whitespace-nowrap text-center">
188 <span className={`px-2 py-1 text-xs rounded ${
189 dup.count >= 3 ? 'bg-error/20 text-error' : 'bg-warning/20 text-warning'
190 }`}>
191 {dup.count}
192 </span>
193 </td>
194 <td className="px-6 py-4 text-sm text-muted">
195 {priceRange}
196 </td>
197 <td className="px-6 py-4 text-sm">
198 <div className="flex flex-wrap gap-2">
199 {dup.products.map((product, pIdx) => (
200 <a
201 key={pIdx}
202 href={`/pages/products/${product.id}`}
203 className="text-brand hover:text-brand2 hover:underline"
204 >
205 #{product.id}
206 </a>
207 ))}
208 </div>
209 </td>
210 </tr>
211 );
212 })
213 )}
214 </tbody>
215 </table>
216 </div>
217 </div>
218 </div>
219 );
220}