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 DuplicateMatch {
7 id1: number;
8 id2: number;
9 physkey1: string;
10 physkey2: string;
11 name1: string;
12 name2: string;
13 phone1: string;
14 phone2: string;
15 email1: string;
16 email2: string;
17 similarity: number;
18 matchReason: string;
19}
20
21export default function DuplicateCustomersReport() {
22 const [duplicates, setDuplicates] = useState<DuplicateMatch[]>([]);
23 const [loading, setLoading] = useState(true);
24 const [error, setError] = useState<string | null>(null);
25 const [totalCustomers, setTotalCustomers] = useState(0);
26
27 useEffect(() => {
28 fetchDuplicates();
29 }, []);
30
31 const fetchDuplicates = async () => {
32 try {
33 setLoading(true);
34 setError(null);
35
36 const response = await fetch('/api/v1/elink/advisor/duplicate-customers');
37 const data = await response.json();
38
39 if (!response.ok) {
40 throw new Error(data.error || 'Failed to fetch duplicates');
41 }
42
43 setDuplicates(data.duplicates || []);
44 setTotalCustomers(data.totalCustomers || 0);
45 } catch (err: any) {
46 console.error('Error fetching duplicates:', err);
47 setError(err.message);
48 } finally {
49 setLoading(false);
50 }
51 };
52
53 if (loading) {
54 return (
55 <div className="p-6 min-h-screen bg-bg">
56 <div className="mb-6">
57 <div className="flex items-center gap-3 mb-2">
58 <a href="/pages/reports?source=advisor" className="text-muted hover:text-brand">
59 <Icon name="arrow_back" size={24} />
60 </a>
61 <h1 className="text-3xl font-bold text-text">Possible Duplicate Customers</h1>
62 </div>
63 <p className="text-muted ml-9">
64 Review potential duplicate customer records that may need to be merged
65 </p>
66 </div>
67 <div className="flex items-center justify-center py-12">
68 <div className="text-center">
69 <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-brand mx-auto mb-4"></div>
70 <p className="text-muted">Analyzing customer records...</p>
71 </div>
72 </div>
73 </div>
74 );
75 }
76
77 if (error) {
78 return (
79 <div className="p-6 min-h-screen bg-bg">
80 <div className="mb-6">
81 <div className="flex items-center gap-3 mb-2">
82 <a href="/pages/reports?source=advisor" className="text-muted hover:text-brand">
83 <Icon name="arrow_back" size={24} />
84 </a>
85 <h1 className="text-3xl font-bold text-text">Possible Duplicate Customers</h1>
86 </div>
87 </div>
88 <div className="bg-surface rounded-lg shadow-sm border border-border p-6">
89 <div className="flex items-center gap-3 text-error mb-4">
90 <Icon name="error" size={24} />
91 <span className="font-medium">Error loading duplicates</span>
92 </div>
93 <p className="text-muted mb-4">{error}</p>
94 <button
95 onClick={fetchDuplicates}
96 className="px-4 py-2 bg-brand text-white rounded hover:bg-brand2"
97 >
98 Retry
99 </button>
100 </div>
101 </div>
102 );
103 }
104
105 return (
106 <div className="p-6 min-h-screen bg-bg">
107 <div className="mb-6">
108 <div className="flex items-center gap-3 mb-2">
109 <a href="/pages/reports?source=advisor" className="text-muted hover:text-brand">
110 <Icon name="arrow_back" size={24} />
111 </a>
112 <h1 className="text-3xl font-bold text-text">Possible Duplicate Customers</h1>
113 </div>
114 <p className="text-muted ml-9">
115 Review potential duplicate customer records that may need to be merged
116 </p>
117 {totalCustomers > 0 && (
118 <p className="text-muted text-sm ml-9 mt-1">
119 Analyzed {totalCustomers} customers • Found {duplicates.length} potential duplicate{duplicates.length !== 1 ? 's' : ''}
120 </p>
121 )}
122 </div>
123
124 <div className="bg-surface rounded-lg shadow-sm border border-border overflow-hidden">
125 <div className="overflow-x-auto">
126 <table className="w-full">
127 <thead className="bg-[var(--brand)] text-surface">
128 <tr>
129 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
130 Customer Name 1
131 </th>
132 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
133 Customer Name 2
134 </th>
135 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
136 Contact Info
137 </th>
138 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
139 Match Reason
140 </th>
141 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
142 Match %
143 </th>
144 <th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
145 Actions
146 </th>
147 </tr>
148 </thead>
149 <tbody className="divide-y divide-border">
150 {duplicates.length === 0 ? (
151 <tr>
152 <td colSpan={6} className="px-6 py-12 text-center">
153 <Icon name="check_circle" size={48} className="text-success mx-auto mb-3" />
154 <p className="text-muted">No duplicate customers found</p>
155 <p className="text-muted text-sm mt-2">All customer records appear to be unique</p>
156 </td>
157 </tr>
158 ) : (
159 duplicates.map((dup, index) => (
160 <tr key={`${dup.id1}-${dup.id2}`} className="hover:bg-surface-2">
161 <td className="px-6 py-4 text-sm">
162 <div className="text-text font-medium">{dup.name1}</div>
163 <div className="text-muted text-xs">ID: {dup.id1}</div>
164 </td>
165 <td className="px-6 py-4 text-sm">
166 <div className="text-text font-medium">{dup.name2}</div>
167 <div className="text-muted text-xs">ID: {dup.id2}</div>
168 </td>
169 <td className="px-6 py-4 text-sm text-muted">
170 {dup.phone1 && (
171 <div className="flex items-center gap-1">
172 <Icon name="phone" size={14} />
173 <span>{dup.phone1}</span>
174 </div>
175 )}
176 {dup.email1 && (
177 <div className="flex items-center gap-1 mt-1">
178 <Icon name="mail" size={14} />
179 <span className="truncate max-w-[200px]">{dup.email1}</span>
180 </div>
181 )}
182 </td>
183 <td className="px-6 py-4 text-sm text-muted">
184 {dup.matchReason}
185 </td>
186 <td className="px-6 py-4 whitespace-nowrap">
187 <span className={`px-2 py-1 text-xs font-medium rounded ${
188 dup.similarity >= 95 ? 'bg-error/20 text-error' :
189 dup.similarity >= 85 ? 'bg-warning/20 text-warning' :
190 'bg-surface-2 text-muted'
191 }`}>
192 {dup.similarity}%
193 </span>
194 </td>
195 <td className="px-6 py-4 whitespace-nowrap text-sm space-x-3">
196 <a
197 href={`/pages/customers/${dup.id1}`}
198 className="text-brand hover:text-brand2"
199 >
200 View 1
201 </a>
202 <a
203 href={`/pages/customers/${dup.id2}`}
204 className="text-brand hover:text-brand2"
205 >
206 View 2
207 </a>
208 </td>
209 </tr>
210 ))
211 )}
212 </tbody>
213 </table>
214 </div>
215 </div>
216 </div>
217 );
218}