3import { useState, useEffect } from 'react';
4import { Icon } from '@/contexts/IconContext';
14interface DuplicatePLU {
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({
27 totalDuplicateProducts: 0
31 const fetchDuplicates = async () => {
33 const response = await fetch('/api/v1/elink/advisor/duplicate-plu');
34 const result = await response.json();
37 setDuplicates(result.data.duplicates || []);
39 totalProducts: result.data.totalProducts || 0,
40 duplicatePLUs: result.data.duplicatePLUCount || 0,
41 totalDuplicateProducts: result.data.totalDuplicateProducts || 0
44 throw new Error(result.error || 'Failed to fetch duplicate PLU data');
47 setError(err.message || 'An error occurred');
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>
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>
77 onClick={() => window.location.reload()}
78 className="px-4 py-2 bg-brand text-white rounded-lg hover:bg-brand2"
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} />
93 <h1 className="text-3xl font-bold text-text">Duplicate PLU Codes</h1>
95 <p className="text-muted ml-9">
96 Multiple products sharing the same PLU/barcode number
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
109 {stats.duplicatePLUs > 0 && (
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
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
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">
133 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
136 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
137 Products Using This PLU
139 <th className="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider">
142 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
145 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
150 <tbody className="divide-y divide-border">
151 {duplicates.length === 0 ? (
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>
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)}`;
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">
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">
177 href={`/pages/products/${product.id}`}
178 className="text-brand hover:text-brand2 hover:underline"
182 <span className="text-xs text-muted">(ID: {product.id})</span>
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'
194 <td className="px-6 py-4 text-sm text-muted">
197 <td className="px-6 py-4 text-sm">
198 <div className="flex flex-wrap gap-2">
199 {dup.products.map((product, pIdx) => (
202 href={`/pages/products/${product.id}`}
203 className="text-brand hover:text-brand2 hover:underline"