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 { Icon } from '@/contexts/IconContext';
4import { useEffect, useState } from 'react';
5
6interface Product {
7 id: number;
8 sku: string;
9 noSell?: boolean;
10 hidden?: boolean;
11}
12
13interface Duplicate {
14 description: string;
15 products: Product[];
16 count: number;
17}
18
19export default function DuplicateDescriptionsReport() {
20 const [duplicates, setDuplicates] = useState<Duplicate[]>([]);
21 const [loading, setLoading] = useState(true);
22 const [error, setError] = useState<string | null>(null);
23
24 useEffect(() => {
25 async function fetchDuplicates() {
26 try {
27 setLoading(true);
28 setError(null);
29
30 const response = await fetch('/api/v1/reports/advisor/duplicate-descriptions');
31 const result = await response.json();
32
33 if (!response.ok || !result.success) {
34 throw new Error(result.error || 'Failed to fetch duplicate descriptions');
35 }
36
37 setDuplicates(result.data.duplicates || []);
38 } catch (err: any) {
39 console.error('Error fetching duplicates:', err);
40 setError(err.message || 'Failed to load duplicate descriptions');
41 } finally {
42 setLoading(false);
43 }
44 }
45
46 fetchDuplicates();
47 }, []);
48
49 return (
50 <div className="p-6 min-h-screen bg-bg">
51 <div className="mb-6">
52 <div className="flex items-center gap-3 mb-2">
53 <a href="/pages/reports?source=advisor" className="text-muted hover:text-brand">
54 <Icon name="arrow_back" size={24} />
55 </a>
56 <h1 className="text-3xl font-bold text-text">Duplicate Product Descriptions</h1>
57 </div>
58 <p className="text-muted ml-9">
59 Products with identical or very similar descriptions
60 </p>
61 </div>
62
63 {loading ? (
64 <div className="bg-surface rounded-lg shadow-sm border border-border p-12 text-center">
65 <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-brand mb-4"></div>
66 <p className="text-muted">Loading duplicate descriptions...</p>
67 </div>
68 ) : error ? (
69 <div className="bg-surface rounded-lg shadow-sm border border-border p-8">
70 <div className="flex items-start gap-3">
71 <Icon name="error" size={24} className="text-error flex-shrink-0 mt-1" />
72 <div>
73 <h3 className="text-lg font-semibold text-text mb-1">Error Loading Data</h3>
74 <p className="text-muted">{error}</p>
75 </div>
76 </div>
77 </div>
78 ) : (
79 <div className="bg-surface rounded-lg shadow-sm border border-border overflow-hidden">
80 <div className="overflow-x-auto">
81 <table className="w-full">
82 <thead className="bg-[var(--brand)] text-surface">
83 <tr>
84 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
85 Description
86 </th>
87 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
88 Product SKUs
89 </th>
90 <th className="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider">
91 Duplicate Count
92 </th>
93 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
94 Actions
95 </th>
96 </tr>
97 </thead>
98 <tbody className="divide-y divide-border">
99 {duplicates.length === 0 ? (
100 <tr>
101 <td colSpan={4} className="px-6 py-12 text-center">
102 <Icon name="check_circle" size={48} className="text-success mx-auto mb-3" />
103 <p className="text-muted">No duplicate descriptions found</p>
104 </td>
105 </tr>
106 ) : (
107 duplicates.map((dup, idx) => (
108 <tr key={idx} className="hover:bg-surface-2">
109 <td className="px-6 py-4 text-sm font-medium text-text">
110 {dup.description}
111 </td>
112 <td className="px-6 py-4 text-sm text-muted">
113 <div className="space-y-1">
114 {dup.products.map((product, pIdx) => (
115 <div key={pIdx} className="flex items-center gap-2">
116 <a
117 href={`/pages/products/${product.id}`}
118 className="text-brand hover:text-brand2 hover:underline"
119 >
120 {product.sku}
121 </a>
122 <span className="text-xs text-muted">(ID: {product.id})</span>
123 {product.noSell && (
124 <span className="text-xs px-1.5 py-0.5 rounded bg-error/20 text-error" title="Cannot be sold">NoSell</span>
125 )}
126 {product.hidden && (
127 <span className="text-xs px-1.5 py-0.5 rounded bg-warning/20 text-warning" title="Hidden from display">Hidden</span>
128 )}
129 </div>
130 ))}
131 </div>
132 </td>
133 <td className="px-6 py-4 whitespace-nowrap text-center">
134 <span className={`px-2 py-1 text-xs rounded ${
135 dup.count >= 3 ? 'bg-error/20 text-error' : 'bg-warning/20 text-warning'
136 }`}>
137 {dup.count}
138 </span>
139 </td>
140 <td className="px-6 py-4 text-sm">
141 <div className="flex flex-wrap gap-2">
142 {dup.products.map((product, pIdx) => (
143 <a
144 key={pIdx}
145 href={`/pages/products/${product.id}`}
146 className="text-brand hover:text-brand2 hover:underline"
147 >
148 #{product.id}
149 </a>
150 ))}
151 </div>
152 </td>
153 </tr>
154 ))
155 )}
156 </tbody>
157 </table>
158 </div>
159 </div>
160 )}
161 </div>
162 );
163}