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 } from 'react';
4import { apiClient } from '@/lib/client/apiClient';
5
6interface CustomerData {
7 cid: string;
8 name: string;
9 email?: string;
10 phone?: string;
11}
12
13export default function SalesByCustomerPage() {
14 const [customerData, setCustomerData] = useState<CustomerData[]>([]);
15 const [isLoading, setIsLoading] = useState(false);
16 const [sortColumn, setSortColumn] = useState<keyof CustomerData>('name');
17 const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
18 const [maxRows, setMaxRows] = useState(100);
19
20 const loadReport = async () => {
21 setIsLoading(true);
22 try {
23 const response = await apiClient.getCustomers({ limit: maxRows, source: 'openapi' });
24
25 if (response.success && response.data) {
26 const customers: CustomerData[] = response.data.map((cust: any) => ({
27 cid: String(cust.cid || cust.CustomerId || ''),
28 name: cust.Name || cust.CustomerName || 'Unknown Customer',
29 email: cust.Email || '',
30 phone: cust.Mobile || cust.Phone || ''
31 }));
32
33 setCustomerData(customers);
34 }
35 } catch (error) {
36 console.error('Error loading customers:', error);
37 } finally {
38 setIsLoading(false);
39 }
40 };
41
42 const handleSort = (column: keyof CustomerData) => {
43 if (sortColumn === column) {
44 setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
45 } else {
46 setSortColumn(column);
47 setSortDirection('asc');
48 }
49 };
50
51 const sortedData = [...customerData].sort((a, b) => {
52 const aVal = a[sortColumn];
53 const bVal = b[sortColumn];
54
55 if (aVal === undefined || bVal === undefined) return 0;
56
57 const aStr = String(aVal);
58 const bStr = String(bVal);
59 return sortDirection === 'asc'
60 ? aStr.localeCompare(bStr)
61 : bStr.localeCompare(aStr);
62 });
63
64 const SortIcon = ({ column }: { column: keyof CustomerData }) => {
65 if (sortColumn !== column) return <span className="text-muted/50">⇅</span>;
66 return <span>{sortDirection === 'asc' ? '↑' : '↓'}</span>;
67 };
68
69 return (
70 <div className="p-6">
71 {/* Header */}
72 <div className="mb-6">
73 <h1 className="text-3xl font-bold mb-2">Sales by Customer 👥</h1>
74 <p className="text-sm text-muted mb-2">
75 Customer list and information
76 </p>
77 <div className="text-sm text-muted">
78 <a href="/pages/reports/sales-reports" className="text-brand hover:underline">Sales</a>
79 {' > '}
80 <span>Sales by Customer</span>
81 </div>
82 </div>
83
84 {/* Filters */}
85 <div className="bg-surface rounded-lg shadow p-6 mb-6">
86 <div className="mb-4 p-3 bg-info/10 border-l-4 border-info">
87 <p className="text-sm text-info">
88 <strong>Note:</strong> This report shows customer directory. Sales analytics by customer is not available in the current API.
89 </p>
90 </div>
91 <form
92 onSubmit={(e) => { e.preventDefault(); loadReport(); }}
93 className="flex items-end gap-4"
94 >
95 <div>
96 <label className="block text-sm font-medium text-text mb-2">
97 Max Rows:
98 </label>
99 <select
100 value={maxRows}
101 onChange={(e) => setMaxRows(Number(e.target.value))}
102 className="border rounded px-3 py-2"
103 >
104 <option value="50">50</option>
105 <option value="100">100</option>
106 <option value="200">200</option>
107 <option value="500">500</option>
108 </select>
109 </div>
110 <div>
111 <button
112 type="submit"
113 disabled={isLoading}
114 className="bg-brand text-white px-6 py-2 rounded hover:bg-brand2 disabled:bg-muted/50"
115 >
116 {isLoading ? 'Loading...' : 'Load Customers'}
117 </button>
118 </div>
119 </form>
120 </div>
121
122 {/* Results Table */}
123 <div className="bg-surface rounded-lg shadow overflow-hidden">
124 {isLoading ? (
125 <div className="p-8 text-center">
126 <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-info"></div>
127 <p className="mt-2 text-muted">Loading customers...</p>
128 </div>
129 ) : (
130 <>
131 <div className="overflow-x-auto">
132 <table className="w-full text-sm">
133 <thead className="bg-surface-2 sticky top-0">
134 <tr>
135 <th
136 className="px-4 py-3 text-left font-semibold cursor-pointer hover:bg-surface-2/80"
137 onClick={() => handleSort('cid')}
138 >
139 ID <SortIcon column="cid" />
140 </th>
141 <th
142 className="px-4 py-3 text-left font-semibold cursor-pointer hover:bg-surface-2/80"
143 onClick={() => handleSort('name')}
144 >
145 Name <SortIcon column="name" />
146 </th>
147 <th
148 className="px-4 py-3 text-left font-semibold cursor-pointer hover:bg-surface-2/80"
149 onClick={() => handleSort('email')}
150 >
151 Email <SortIcon column="email" />
152 </th>
153 <th
154 className="px-4 py-3 text-left font-semibold cursor-pointer hover:bg-surface-2/80"
155 onClick={() => handleSort('phone')}
156 >
157 Phone <SortIcon column="phone" />
158 </th>
159 </tr>
160 </thead>
161 <tbody className="divide-y divide-border">
162 {sortedData.length === 0 ? (
163 <tr>
164 <td colSpan={4} className="px-4 py-8 text-center text-muted">
165 {isLoading ? 'Loading...' : 'No customer data found. Click "Load Customers" to fetch data.'}
166 </td>
167 </tr>
168 ) : (
169 sortedData.map((customer, idx) => (
170 <tr key={idx} className="hover:bg-surface-2">
171 <td className="px-4 py-3 font-mono text-sm">
172 {customer.cid}
173 </td>
174 <td className="px-4 py-3 font-medium">
175 {customer.name}
176 </td>
177 <td className="px-4 py-3 text-muted">
178 {customer.email || '-'}
179 </td>
180 <td className="px-4 py-3 text-muted">
181 {customer.phone || '-'}
182 </td>
183 </tr>
184 ))
185 )}
186 </tbody>
187 </table>
188 </div>
189
190 {customerData.length > 0 && (
191 <div className="px-4 py-3 bg-surface-2 border-t">
192 <p className="text-sm text-muted">
193 Showing {customerData.length} customer{customerData.length !== 1 ? 's' : ''}.
194 </p>
195 </div>
196 )}
197 </>
198 )}
199 </div>
200 </div>
201 );
202}