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";
2import { useState, useEffect } from "react";
3import { Icon } from '@/contexts/IconContext';
4
5interface AdvisorEvent {
6 f101: number; // Event ID
7 f110: string; // Description
8 f111: string; // Title
9 f120?: string; // Documentation link
10 f121?: string; // Action link
11 [key: string]: any; // Additional fields (f1000-f1009, etc)
12}
13
14interface AdvisorStat {
15 f120: number; // Current progress
16 f121: number; // Total items
17 f122: number; // Errors
18}
19
20interface AdvisorResponse {
21 DATS?: AdvisorEvent[];
22 STAT?: AdvisorStat[];
23}
24
25const categories = [
26 { id: 'accounts', label: 'Accounts', icon: 'account_balance' },
27 { id: 'customers', label: 'Customers', icon: 'group' },
28 { id: 'other', label: 'Other', icon: 'more_horiz' },
29 { id: 'products', label: 'Products', icon: 'inventory_2' },
30 { id: 'staff', label: 'Staff', icon: 'badge' },
31 { id: 'reference', label: 'Reference', icon: 'book' },
32 { id: 'security', label: 'Security', icon: 'security' },
33 { id: 'technical', label: 'Technical', icon: 'settings' },
34];
35
36export default function AdvisorPage() {
37 const [selectedCategories, setSelectedCategories] = useState<string[]>(
38 categories.map(c => c.id)
39 );
40 const [loading, setLoading] = useState(true);
41 const [events, setEvents] = useState<AdvisorEvent[]>([]);
42 const [progress, setProgress] = useState<AdvisorStat | null>(null);
43 const [error, setError] = useState<string | null>(null);
44
45 useEffect(() => {
46 loadAdvisorData();
47 }, [selectedCategories]);
48
49 // Navigate all advisor "Show me" links to reports page
50 const handleReportClick = () => {
51 window.open('/pages/reports?source=advisor', '_blank');
52 };
53
54 const loadAdvisorData = async () => {
55 try {
56 setLoading(true);
57 setError(null);
58
59 const areas = selectedCategories.join(',');
60 const response = await fetch(`/api/v1/elink/advisor?areas=${areas}`);
61 const result = await response.json();
62
63 if (result.success && result.data) {
64 const data: AdvisorResponse = result.data;
65 setEvents(data.DATS || []);
66 setProgress(data.STAT?.[0] || null);
67 } else {
68 setError(result.error || 'Failed to load advisor data');
69 }
70 } catch (err: any) {
71 console.error('Error loading advisor data:', err);
72 setError(err.message || 'An error occurred');
73 } finally {
74 setLoading(false);
75 }
76 };
77
78 const toggleCategory = (categoryId: string) => {
79 setSelectedCategories(prev =>
80 prev.includes(categoryId)
81 ? prev.filter(id => id !== categoryId)
82 : [...prev, categoryId]
83 );
84 };
85
86 const toggleAll = (selectAll: boolean) => {
87 setSelectedCategories(selectAll ? categories.map(c => c.id) : []);
88 };
89
90 const getProgressPercentage = () => {
91 if (!progress || progress.f121 === 0) return 0;
92 return Math.round((progress.f120 / progress.f121) * 100);
93 };
94
95 return (
96 <div className="min-h-screen bg-bg">
97 <div className="max-w-7xl mx-auto p-6">
98 {/* Header */}
99 <div className="mb-8">
100 <div className="flex items-center justify-between mb-4">
101 <div>
102 <h1 className="text-3xl font-bold text-text mb-2 flex items-center gap-3">
103 <Icon name="psychology" size={36} className="text-brand" />
104 Business Advisor
105 </h1>
106 <p className="text-muted">
107 Advisor analyses your retail data and displays potentially interesting tidbits
108 </p>
109 </div>
110
111 {/* Progress Indicator */}
112 {loading && (
113 <div className="flex items-center gap-3 bg-info/10 border border-info/30 rounded-lg px-4 py-3">
114 <div className="relative">
115 <Icon name="progress_activity" size={24} className="text-info animate-spin" />
116 </div>
117 <div className="text-sm">
118 <div className="font-medium text-text">Loading...</div>
119 {progress && (
120 <div className="text-muted">
121 {progress.f120} of {progress.f121} ({getProgressPercentage()}%)
122 </div>
123 )}
124 </div>
125 </div>
126 )}
127
128 {!loading && progress && (
129 <div className="flex items-center gap-3 bg-success/10 border border-success/30 rounded-lg px-4 py-3">
130 <Icon name="check_circle" size={24} className="text-success" />
131 <div className="text-sm">
132 <div className="font-medium text-text">Complete</div>
133 <div className="text-muted">
134 {progress.f121} items analyzed
135 </div>
136 </div>
137 </div>
138 )}
139 </div>
140 </div>
141
142 {/* Category Filters */}
143 <div className="bg-surface rounded-lg shadow-sm border border-border p-6 mb-6">
144 <div className="flex items-center justify-between mb-4">
145 <h2 className="text-lg font-semibold text-text">Filter Categories</h2>
146 <div className="flex gap-2">
147 <button
148 onClick={() => toggleAll(true)}
149 className="px-3 py-1 text-sm bg-brand text-surface rounded hover:bg-brand2 transition-colors"
150 >
151 Select All
152 </button>
153 <button
154 onClick={() => toggleAll(false)}
155 className="px-3 py-1 text-sm bg-surface-2 text-text rounded hover:bg-surface-2/80 transition-colors"
156 >
157 Clear All
158 </button>
159 </div>
160 </div>
161
162 <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
163 {categories.map(category => (
164 <label
165 key={category.id}
166 className={`flex items-center gap-2 px-4 py-3 rounded-lg border-2 cursor-pointer transition-all ${
167 selectedCategories.includes(category.id)
168 ? 'border-brand bg-brand/5 text-brand font-medium'
169 : 'border-border bg-surface text-text hover:border-brand/30'
170 }`}
171 >
172 <input
173 type="checkbox"
174 checked={selectedCategories.includes(category.id)}
175 onChange={() => toggleCategory(category.id)}
176 className="w-4 h-4 text-brand focus:ring-brand focus:ring-2 rounded"
177 />
178 <Icon name={category.icon} size={20} />
179 <span className="text-sm">{category.label}</span>
180 </label>
181 ))}
182 </div>
183 </div>
184
185 {/* Error State */}
186 {error && (
187 <div className="bg-danger/10 border border-danger/30 rounded-lg p-6 mb-6">
188 <div className="flex items-start gap-3">
189 <Icon name="error" size={24} className="text-danger flex-shrink-0" />
190 <div>
191 <h3 className="font-semibold text-text mb-1">Error Loading Data</h3>
192 <p className="text-sm text-muted">{error}</p>
193 <button
194 onClick={loadAdvisorData}
195 className="mt-3 px-4 py-2 bg-danger text-surface rounded hover:bg-danger/80 transition-colors text-sm"
196 >
197 Retry
198 </button>
199 </div>
200 </div>
201 </div>
202 )}
203
204 {/* Loading State */}
205 {loading && !events.length && (
206 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
207 {[1, 2, 3, 4, 5, 6].map(i => (
208 <div key={i} className="bg-surface rounded-lg shadow-sm border border-border p-6 animate-pulse">
209 <div className="h-6 bg-surface-2 rounded w-3/4 mb-3"></div>
210 <div className="h-4 bg-surface-2 rounded w-full mb-2"></div>
211 <div className="h-4 bg-surface-2 rounded w-5/6"></div>
212 </div>
213 ))}
214 </div>
215 )}
216
217 {/* Empty State */}
218 {!loading && events.length === 0 && !error && (
219 <div className="bg-surface rounded-lg shadow-sm border border-border p-12 text-center">
220 <Icon name="check_circle" size={64} className="text-success mx-auto mb-4" />
221 <h3 className="text-xl font-semibold text-text mb-2">No Issues Found</h3>
222 <p className="text-muted">
223 {selectedCategories.length === 0
224 ? 'Select at least one category to analyze your data.'
225 : 'Advisor has analyzed your data and found no issues to report.'}
226 </p>
227 </div>
228 )}
229
230 {/* Events Grid */}
231 {!loading && events.length > 0 && (
232 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
233 {events.map((event, index) => (
234 <div
235 key={`${event.f101}-${index}`}
236 className="bg-surface rounded-lg shadow-sm border border-border hover:border-brand/50 transition-all hover:shadow-md"
237 >
238 <div className="p-6">
239 <div className="flex items-start justify-between mb-3">
240 <h3 className="text-lg font-semibold text-text flex-1">
241 {event.f111}
242 </h3>
243 {event.f121 && (
244 <button
245 onClick={(e) => {
246 e.preventDefault();
247 handleReportClick();
248 }}
249 className="flex items-center gap-1 text-xs bg-brand text-surface px-3 py-1 rounded hover:bg-brand2 transition-colors flex-shrink-0 ml-2"
250 >
251 <Icon name="open_in_new" size={14} />
252 Show me
253 </button>
254 )}
255 </div>
256
257 <p className="text-sm text-text mb-4 leading-relaxed">
258 {event.f110}
259 </p>
260
261 {event.f120 && (
262 <div className="pt-3 border-t border-border">
263 <a
264 href={event.f120}
265 target="_blank"
266 rel="noopener noreferrer"
267 className="text-xs text-info hover:text-info/80 flex items-center gap-1 transition-colors"
268 >
269 <Icon name="help_outline" size={14} />
270 Online help
271 </a>
272 </div>
273 )}
274 </div>
275 </div>
276 ))}
277 </div>
278 )}
279 </div>
280 </div>
281 );
282}