RafaelJaime commited on
Commit
efaacf2
·
verified ·
1 Parent(s): cf4cfbc

Update src/App.js

Browse files
Files changed (1) hide show
  1. src/App.js +97 -97
src/App.js CHANGED
@@ -47,49 +47,49 @@ const useStore = () => {
47
 
48
  const mockExtractTextFromImage = async (file) => {
49
  await new Promise(resolve => setTimeout(resolve, 2000));
50
- return `SUPERMARKET MERCADONA
51
- Address: Calle Mayor 123, Madrid
52
  Date: ${new Date().toLocaleDateString()}
53
  ----------------------------------------
54
- Whole Milk 1L 2.50
55
- Whole Wheat Bread 500g 1.80
56
- Tomatoes 1kg 3.20
57
- Chicken Fillets 500g 4.50
58
- Olive Oil 500ml 5.80
59
- Dozen Eggs 2.90
60
  ----------------------------------------
61
- TOTAL: 20.70`;
62
  };
63
 
64
  const mockProcessReceiptText = async (text) => {
65
  await new Promise(resolve => setTimeout(resolve, 1500));
66
  const lines = text.split('\n');
67
  const products = [];
68
- let supermercado = '';
69
- let direccion = '';
70
- let fecha = '';
71
 
72
  lines.forEach(line => {
73
- if (line.includes('SUPERMARKET') || line.includes('MERCADONA') || line.includes('CARREFOUR')) {
74
- supermercado = line.replace('SUPERMARKET ', '').trim();
75
  }
76
  if (line.includes('Address:')) {
77
- direccion = line.replace('Address:', '').trim();
78
  }
79
  if (line.includes('Date:')) {
80
- fecha = line.replace('Date:', '').trim();
81
  }
82
 
83
- const priceMatch = line.match(/(.+?)\s+(\d+[,.]?\d*)$/);
84
  if (priceMatch && !line.includes('TOTAL') && !line.includes('---')) {
85
- const nombre = priceMatch[1].trim();
86
- const precio = parseFloat(priceMatch[2].replace('', '').replace(',', '.'));
87
- if (nombre && precio > 0) {
88
  products.push({
89
- nombre,
90
- precio,
91
- supermercado: { nombre: supermercado, direccion },
92
- fecha
93
  });
94
  }
95
  }
@@ -139,36 +139,36 @@ const Sidebar = ({ activeTab, setActiveTab }) => {
139
  const HomeTab = () => {
140
  const { products } = useStore();
141
 
142
- const totalGastado = products.reduce((sum, p) => sum + p.precio, 0);
143
 
144
- const productsBySupermercado = products.reduce((acc, p) => {
145
- const key = p.supermercado.nombre;
146
  if (!acc[key]) acc[key] = [];
147
  acc[key].push(p);
148
  return acc;
149
  }, {});
150
 
151
- const variacionPrecios = products.reduce((acc, p) => {
152
- if (!acc[p.nombre]) acc[p.nombre] = [];
153
- acc[p.nombre].push(p);
154
  return acc;
155
  }, {});
156
 
157
- const productosConVariacion = Object.entries(variacionPrecios)
158
  .filter(([_, prods]) => prods.length > 1)
159
- .map(([nombre, prods]) => ({
160
- nombre,
161
- minPrecio: Math.min(...prods.map(p => p.precio)),
162
- maxPrecio: Math.max(...prods.map(p => p.precio)),
163
- diferencia: Math.max(...prods.map(p => p.precio)) - Math.min(...prods.map(p => p.precio))
164
  }))
165
- .sort((a, b) => b.diferencia - a.diferencia)
166
  .slice(0, 5);
167
 
168
- const gastoPorSupermercado = Object.entries(productsBySupermercado).map(([nombre, prods]) => ({
169
- nombre,
170
- gasto: prods.reduce((sum, p) => sum + p.precio, 0),
171
- productos: prods.length
172
  }));
173
 
174
  const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'];
@@ -179,77 +179,77 @@ const HomeTab = () => {
179
 
180
  <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
181
  <div className="bg-white rounded-lg shadow p-6">
182
- <h3 className="text-lg font-semibold text-gray-700 mb-2">Total Gastado</h3>
183
- <p className="text-3xl font-bold text-blue-600">{totalGastado.toFixed(2)}</p>
184
  </div>
185
 
186
  <div className="bg-white rounded-lg shadow p-6">
187
- <h3 className="text-lg font-semibold text-gray-700 mb-2">Productos Registrados</h3>
188
  <p className="text-3xl font-bold text-green-600">{products.length}</p>
189
  </div>
190
 
191
  <div className="bg-white rounded-lg shadow p-6">
192
- <h3 className="text-lg font-semibold text-gray-700 mb-2">Supermercados</h3>
193
- <p className="text-3xl font-bold text-purple-600">{Object.keys(productsBySupermercado).length}</p>
194
  </div>
195
  </div>
196
 
197
- {gastoPorSupermercado.length > 0 && (
198
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
199
  <div className="bg-white rounded-lg shadow p-6">
200
- <h3 className="text-lg font-semibold text-gray-700 mb-4">Gasto por Supermercado</h3>
201
  <ResponsiveContainer width="100%" height={300}>
202
- <BarChart data={gastoPorSupermercado}>
203
  <CartesianGrid strokeDasharray="3 3" />
204
- <XAxis dataKey="nombre" />
205
  <YAxis />
206
- <Tooltip formatter={(value) => [`${value.toFixed(2)}`, 'Gasto']} />
207
- <Bar dataKey="gasto" fill="#3b82f6" />
208
  </BarChart>
209
  </ResponsiveContainer>
210
  </div>
211
 
212
  <div className="bg-white rounded-lg shadow p-6">
213
- <h3 className="text-lg font-semibold text-gray-700 mb-4">Distribución de Gastos</h3>
214
  <ResponsiveContainer width="100%" height={300}>
215
  <PieChart>
216
  <Pie
217
- data={gastoPorSupermercado}
218
  cx="50%"
219
  cy="50%"
220
  labelLine={false}
221
- label={({ nombre, percent }) => `${nombre} ${(percent * 100).toFixed(0)}%`}
222
  outerRadius={80}
223
  fill="#8884d8"
224
- dataKey="gasto"
225
  >
226
- {gastoPorSupermercado.map((entry, index) => (
227
  <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
228
  ))}
229
  </Pie>
230
- <Tooltip formatter={(value) => [`${value.toFixed(2)}`, 'Gasto']} />
231
  </PieChart>
232
  </ResponsiveContainer>
233
  </div>
234
  </div>
235
  )}
236
 
237
- {productosConVariacion.length > 0 && (
238
  <div className="bg-white rounded-lg shadow p-6">
239
  <h3 className="text-lg font-semibold text-gray-700 mb-4 flex items-center">
240
  <TrendingUp className="mr-2" />
241
- Productos con Mayor Variación de Precio
242
  </h3>
243
  <div className="space-y-3">
244
- {productosConVariacion.map((producto, index) => (
245
  <div key={index} className="flex justify-between items-center p-3 bg-gray-50 rounded">
246
- <span className="font-medium">{producto.nombre}</span>
247
  <div className="text-right">
248
  <span className="text-sm text-gray-500">
249
- {producto.minPrecio.toFixed(2)} - {producto.maxPrecio.toFixed(2)}
250
  </span>
251
  <span className="ml-2 text-red-600 font-semibold">
252
- +{producto.diferencia.toFixed(2)}
253
  </span>
254
  </div>
255
  </div>
@@ -293,7 +293,7 @@ const UploadTab = () => {
293
  setStep('review');
294
  } catch (error) {
295
  console.error('Error processing receipt:', error);
296
- alert('Error procesando la factura');
297
  } finally {
298
  setLoading(false);
299
  }
@@ -305,12 +305,12 @@ const UploadTab = () => {
305
  setExtractedText('');
306
  setProcessedProducts([]);
307
  setStep('upload');
308
- alert('Productos guardados exitosamente');
309
  };
310
 
311
  return (
312
  <div className="p-6">
313
- <h2 className="text-3xl font-bold text-gray-800 mb-6">Subir Factura</h2>
314
 
315
  <div className="bg-white rounded-lg shadow p-6">
316
  {step === 'upload' && (
@@ -319,7 +319,7 @@ const UploadTab = () => {
319
  <Upload className="mx-auto h-12 w-12 text-gray-400 mb-4" />
320
  <label className="cursor-pointer">
321
  <span className="text-lg font-medium text-gray-700">
322
- Selecciona una imagen de factura
323
  </span>
324
  <input
325
  type="file"
@@ -330,7 +330,7 @@ const UploadTab = () => {
330
  </label>
331
  {file && (
332
  <p className="mt-2 text-sm text-gray-500">
333
- Archivo seleccionado: {file.name}
334
  </p>
335
  )}
336
  </div>
@@ -341,7 +341,7 @@ const UploadTab = () => {
341
  disabled={loading}
342
  className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 disabled:opacity-50"
343
  >
344
- {loading ? 'Procesando...' : 'Procesar Factura'}
345
  </button>
346
  )}
347
  </div>
@@ -350,30 +350,30 @@ const UploadTab = () => {
350
  {step === 'extracting' && (
351
  <div className="text-center py-8">
352
  <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
353
- <p className="text-lg font-medium text-gray-700">Extrayendo texto de la imagen...</p>
354
  </div>
355
  )}
356
 
357
  {step === 'processing' && (
358
  <div className="text-center py-8">
359
  <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto mb-4"></div>
360
- <p className="text-lg font-medium text-gray-700">Procesando productos...</p>
361
  </div>
362
  )}
363
 
364
  {step === 'review' && (
365
  <div className="space-y-4">
366
- <h3 className="text-xl font-semibold text-gray-800">Productos Detectados</h3>
367
  <div className="bg-gray-50 rounded-lg p-4 max-h-96 overflow-y-auto">
368
  {processedProducts.map((product, index) => (
369
  <div key={index} className="flex justify-between items-center py-2 border-b border-gray-200 last:border-b-0">
370
  <div>
371
- <span className="font-medium">{product.nombre}</span>
372
  <span className="text-sm text-gray-500 ml-2">
373
- ({product.supermercado.nombre})
374
  </span>
375
  </div>
376
- <span className="font-bold text-green-600">{product.precio.toFixed(2)}</span>
377
  </div>
378
  ))}
379
  </div>
@@ -383,13 +383,13 @@ const UploadTab = () => {
383
  onClick={handleSaveProducts}
384
  className="flex-1 bg-green-600 text-white py-3 px-4 rounded-lg hover:bg-green-700"
385
  >
386
- Guardar Productos
387
  </button>
388
  <button
389
  onClick={() => setStep('upload')}
390
  className="flex-1 bg-gray-600 text-white py-3 px-4 rounded-lg hover:bg-gray-700"
391
  >
392
- Cancelar
393
  </button>
394
  </div>
395
  </div>
@@ -421,14 +421,14 @@ const ListTab = () => {
421
  };
422
 
423
  const handleDelete = (id) => {
424
- if (window.confirm('¿Estás seguro de que quieres eliminar este producto?')) {
425
  deleteProduct(id);
426
  }
427
  };
428
 
429
  return (
430
  <div className="p-6">
431
- <h2 className="text-3xl font-bold text-gray-800 mb-6">Listado de Productos</h2>
432
 
433
  <div className="bg-white rounded-lg shadow overflow-hidden">
434
  <div className="overflow-x-auto">
@@ -436,19 +436,19 @@ const ListTab = () => {
436
  <thead className="bg-gray-50">
437
  <tr>
438
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
439
- Producto
440
  </th>
441
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
442
- Precio
443
  </th>
444
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
445
- Supermercado
446
  </th>
447
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
448
- Fecha
449
  </th>
450
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
451
- Acciones
452
  </th>
453
  </tr>
454
  </thead>
@@ -459,12 +459,12 @@ const ListTab = () => {
459
  {editingId === product.id ? (
460
  <input
461
  type="text"
462
- value={editForm.nombre}
463
- onChange={(e) => setEditForm({...editForm, nombre: e.target.value})}
464
  className="w-full px-3 py-1 border border-gray-300 rounded"
465
  />
466
  ) : (
467
- <div className="text-sm font-medium text-gray-900">{product.nombre}</div>
468
  )}
469
  </td>
470
  <td className="px-6 py-4 whitespace-nowrap">
@@ -472,28 +472,28 @@ const ListTab = () => {
472
  <input
473
  type="number"
474
  step="0.01"
475
- value={editForm.precio}
476
- onChange={(e) => setEditForm({...editForm, precio: parseFloat(e.target.value)})}
477
  className="w-full px-3 py-1 border border-gray-300 rounded"
478
  />
479
  ) : (
480
- <div className="text-sm text-gray-900">{product.precio.toFixed(2)}</div>
481
  )}
482
  </td>
483
  <td className="px-6 py-4 whitespace-nowrap">
484
- <div className="text-sm text-gray-900">{product.supermercado.nombre}</div>
485
- <div className="text-sm text-gray-500">{product.supermercado.direccion}</div>
486
  </td>
487
  <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
488
  {editingId === product.id ? (
489
  <input
490
  type="date"
491
- value={editForm.fecha}
492
- onChange={(e) => setEditForm({...editForm, fecha: e.target.value})}
493
  className="w-full px-3 py-1 border border-gray-300 rounded"
494
  />
495
  ) : (
496
- product.fecha
497
  )}
498
  </td>
499
  <td className="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
@@ -536,7 +536,7 @@ const ListTab = () => {
536
 
537
  {products.length === 0 && (
538
  <div className="text-center py-8">
539
- <p className="text-gray-500">No hay productos registrados</p>
540
  </div>
541
  )}
542
  </div>
 
47
 
48
  const mockExtractTextFromImage = async (file) => {
49
  await new Promise(resolve => setTimeout(resolve, 2000));
50
+ return `SUPERMARKET WALMART
51
+ Address: 123 Main Street, New York
52
  Date: ${new Date().toLocaleDateString()}
53
  ----------------------------------------
54
+ Whole Milk 1L $2.50
55
+ Whole Wheat Bread 500g $1.80
56
+ Tomatoes 1kg $3.20
57
+ Chicken Fillets 500g $4.50
58
+ Olive Oil 500ml $5.80
59
+ Dozen Eggs $2.90
60
  ----------------------------------------
61
+ TOTAL: $20.70`;
62
  };
63
 
64
  const mockProcessReceiptText = async (text) => {
65
  await new Promise(resolve => setTimeout(resolve, 1500));
66
  const lines = text.split('\n');
67
  const products = [];
68
+ let store = '';
69
+ let address = '';
70
+ let date = '';
71
 
72
  lines.forEach(line => {
73
+ if (line.includes('SUPERMARKET') || line.includes('WALMART') || line.includes('TARGET')) {
74
+ store = line.replace('SUPERMARKET ', '').trim();
75
  }
76
  if (line.includes('Address:')) {
77
+ address = line.replace('Address:', '').trim();
78
  }
79
  if (line.includes('Date:')) {
80
+ date = line.replace('Date:', '').trim();
81
  }
82
 
83
+ const priceMatch = line.match(/(.+?)\s+(\$\d+[,.]?\d*)$/);
84
  if (priceMatch && !line.includes('TOTAL') && !line.includes('---')) {
85
+ const name = priceMatch[1].trim();
86
+ const price = parseFloat(priceMatch[2].replace('$', '').replace(',', '.'));
87
+ if (name && price > 0) {
88
  products.push({
89
+ name,
90
+ price,
91
+ store: { name: store, address },
92
+ date
93
  });
94
  }
95
  }
 
139
  const HomeTab = () => {
140
  const { products } = useStore();
141
 
142
+ const totalSpent = products.reduce((sum, p) => sum + p.price, 0);
143
 
144
+ const productsByStore = products.reduce((acc, p) => {
145
+ const key = p.store.name;
146
  if (!acc[key]) acc[key] = [];
147
  acc[key].push(p);
148
  return acc;
149
  }, {});
150
 
151
+ const priceVariations = products.reduce((acc, p) => {
152
+ if (!acc[p.name]) acc[p.name] = [];
153
+ acc[p.name].push(p);
154
  return acc;
155
  }, {});
156
 
157
+ const productsWithVariation = Object.entries(priceVariations)
158
  .filter(([_, prods]) => prods.length > 1)
159
+ .map(([name, prods]) => ({
160
+ name,
161
+ minPrice: Math.min(...prods.map(p => p.price)),
162
+ maxPrice: Math.max(...prods.map(p => p.price)),
163
+ difference: Math.max(...prods.map(p => p.price)) - Math.min(...prods.map(p => p.price))
164
  }))
165
+ .sort((a, b) => b.difference - a.difference)
166
  .slice(0, 5);
167
 
168
+ const spendingByStore = Object.entries(productsByStore).map(([name, prods]) => ({
169
+ name,
170
+ spending: prods.reduce((sum, p) => sum + p.price, 0),
171
+ products: prods.length
172
  }));
173
 
174
  const COLORS = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'];
 
179
 
180
  <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
181
  <div className="bg-white rounded-lg shadow p-6">
182
+ <h3 className="text-lg font-semibold text-gray-700 mb-2">Total Spent</h3>
183
+ <p className="text-3xl font-bold text-blue-600">${totalSpent.toFixed(2)}</p>
184
  </div>
185
 
186
  <div className="bg-white rounded-lg shadow p-6">
187
+ <h3 className="text-lg font-semibold text-gray-700 mb-2">Registered Products</h3>
188
  <p className="text-3xl font-bold text-green-600">{products.length}</p>
189
  </div>
190
 
191
  <div className="bg-white rounded-lg shadow p-6">
192
+ <h3 className="text-lg font-semibold text-gray-700 mb-2">Stores</h3>
193
+ <p className="text-3xl font-bold text-purple-600">{Object.keys(productsByStore).length}</p>
194
  </div>
195
  </div>
196
 
197
+ {spendingByStore.length > 0 && (
198
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
199
  <div className="bg-white rounded-lg shadow p-6">
200
+ <h3 className="text-lg font-semibold text-gray-700 mb-4">Spending by Store</h3>
201
  <ResponsiveContainer width="100%" height={300}>
202
+ <BarChart data={spendingByStore}>
203
  <CartesianGrid strokeDasharray="3 3" />
204
+ <XAxis dataKey="name" />
205
  <YAxis />
206
+ <Tooltip formatter={(value) => [`$${value.toFixed(2)}`, 'Spending']} />
207
+ <Bar dataKey="spending" fill="#3b82f6" />
208
  </BarChart>
209
  </ResponsiveContainer>
210
  </div>
211
 
212
  <div className="bg-white rounded-lg shadow p-6">
213
+ <h3 className="text-lg font-semibold text-gray-700 mb-4">Spending Distribution</h3>
214
  <ResponsiveContainer width="100%" height={300}>
215
  <PieChart>
216
  <Pie
217
+ data={spendingByStore}
218
  cx="50%"
219
  cy="50%"
220
  labelLine={false}
221
+ label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
222
  outerRadius={80}
223
  fill="#8884d8"
224
+ dataKey="spending"
225
  >
226
+ {spendingByStore.map((entry, index) => (
227
  <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
228
  ))}
229
  </Pie>
230
+ <Tooltip formatter={(value) => [`$${value.toFixed(2)}`, 'Spending']} />
231
  </PieChart>
232
  </ResponsiveContainer>
233
  </div>
234
  </div>
235
  )}
236
 
237
+ {productsWithVariation.length > 0 && (
238
  <div className="bg-white rounded-lg shadow p-6">
239
  <h3 className="text-lg font-semibold text-gray-700 mb-4 flex items-center">
240
  <TrendingUp className="mr-2" />
241
+ Products with Highest Price Variation
242
  </h3>
243
  <div className="space-y-3">
244
+ {productsWithVariation.map((product, index) => (
245
  <div key={index} className="flex justify-between items-center p-3 bg-gray-50 rounded">
246
+ <span className="font-medium">{product.name}</span>
247
  <div className="text-right">
248
  <span className="text-sm text-gray-500">
249
+ ${product.minPrice.toFixed(2)} - ${product.maxPrice.toFixed(2)}
250
  </span>
251
  <span className="ml-2 text-red-600 font-semibold">
252
+ +${product.difference.toFixed(2)}
253
  </span>
254
  </div>
255
  </div>
 
293
  setStep('review');
294
  } catch (error) {
295
  console.error('Error processing receipt:', error);
296
+ alert('Error processing receipt');
297
  } finally {
298
  setLoading(false);
299
  }
 
305
  setExtractedText('');
306
  setProcessedProducts([]);
307
  setStep('upload');
308
+ alert('Products saved successfully');
309
  };
310
 
311
  return (
312
  <div className="p-6">
313
+ <h2 className="text-3xl font-bold text-gray-800 mb-6">Upload Receipt</h2>
314
 
315
  <div className="bg-white rounded-lg shadow p-6">
316
  {step === 'upload' && (
 
319
  <Upload className="mx-auto h-12 w-12 text-gray-400 mb-4" />
320
  <label className="cursor-pointer">
321
  <span className="text-lg font-medium text-gray-700">
322
+ Select a receipt image
323
  </span>
324
  <input
325
  type="file"
 
330
  </label>
331
  {file && (
332
  <p className="mt-2 text-sm text-gray-500">
333
+ Selected file: {file.name}
334
  </p>
335
  )}
336
  </div>
 
341
  disabled={loading}
342
  className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 disabled:opacity-50"
343
  >
344
+ {loading ? 'Processing...' : 'Process Receipt'}
345
  </button>
346
  )}
347
  </div>
 
350
  {step === 'extracting' && (
351
  <div className="text-center py-8">
352
  <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
353
+ <p className="text-lg font-medium text-gray-700">Extracting text from image...</p>
354
  </div>
355
  )}
356
 
357
  {step === 'processing' && (
358
  <div className="text-center py-8">
359
  <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto mb-4"></div>
360
+ <p className="text-lg font-medium text-gray-700">Processing products...</p>
361
  </div>
362
  )}
363
 
364
  {step === 'review' && (
365
  <div className="space-y-4">
366
+ <h3 className="text-xl font-semibold text-gray-800">Detected Products</h3>
367
  <div className="bg-gray-50 rounded-lg p-4 max-h-96 overflow-y-auto">
368
  {processedProducts.map((product, index) => (
369
  <div key={index} className="flex justify-between items-center py-2 border-b border-gray-200 last:border-b-0">
370
  <div>
371
+ <span className="font-medium">{product.name}</span>
372
  <span className="text-sm text-gray-500 ml-2">
373
+ ({product.store.name})
374
  </span>
375
  </div>
376
+ <span className="font-bold text-green-600">${product.price.toFixed(2)}</span>
377
  </div>
378
  ))}
379
  </div>
 
383
  onClick={handleSaveProducts}
384
  className="flex-1 bg-green-600 text-white py-3 px-4 rounded-lg hover:bg-green-700"
385
  >
386
+ Save Products
387
  </button>
388
  <button
389
  onClick={() => setStep('upload')}
390
  className="flex-1 bg-gray-600 text-white py-3 px-4 rounded-lg hover:bg-gray-700"
391
  >
392
+ Cancel
393
  </button>
394
  </div>
395
  </div>
 
421
  };
422
 
423
  const handleDelete = (id) => {
424
+ if (window.confirm('Are you sure you want to delete this product?')) {
425
  deleteProduct(id);
426
  }
427
  };
428
 
429
  return (
430
  <div className="p-6">
431
+ <h2 className="text-3xl font-bold text-gray-800 mb-6">Product List</h2>
432
 
433
  <div className="bg-white rounded-lg shadow overflow-hidden">
434
  <div className="overflow-x-auto">
 
436
  <thead className="bg-gray-50">
437
  <tr>
438
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
439
+ Product
440
  </th>
441
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
442
+ Price
443
  </th>
444
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
445
+ Store
446
  </th>
447
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
448
+ Date
449
  </th>
450
  <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
451
+ Actions
452
  </th>
453
  </tr>
454
  </thead>
 
459
  {editingId === product.id ? (
460
  <input
461
  type="text"
462
+ value={editForm.name}
463
+ onChange={(e) => setEditForm({...editForm, name: e.target.value})}
464
  className="w-full px-3 py-1 border border-gray-300 rounded"
465
  />
466
  ) : (
467
+ <div className="text-sm font-medium text-gray-900">{product.name}</div>
468
  )}
469
  </td>
470
  <td className="px-6 py-4 whitespace-nowrap">
 
472
  <input
473
  type="number"
474
  step="0.01"
475
+ value={editForm.price}
476
+ onChange={(e) => setEditForm({...editForm, price: parseFloat(e.target.value)})}
477
  className="w-full px-3 py-1 border border-gray-300 rounded"
478
  />
479
  ) : (
480
+ <div className="text-sm text-gray-900">${product.price.toFixed(2)}</div>
481
  )}
482
  </td>
483
  <td className="px-6 py-4 whitespace-nowrap">
484
+ <div className="text-sm text-gray-900">{product.store.name}</div>
485
+ <div className="text-sm text-gray-500">{product.store.address}</div>
486
  </td>
487
  <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
488
  {editingId === product.id ? (
489
  <input
490
  type="date"
491
+ value={editForm.date}
492
+ onChange={(e) => setEditForm({...editForm, date: e.target.value})}
493
  className="w-full px-3 py-1 border border-gray-300 rounded"
494
  />
495
  ) : (
496
+ product.date
497
  )}
498
  </td>
499
  <td className="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
 
536
 
537
  {products.length === 0 && (
538
  <div className="text-center py-8">
539
+ <p className="text-gray-500">No products registered</p>
540
  </div>
541
  )}
542
  </div>