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 { useEffect, useState } from "react";
3import { apiClient } from "@/lib/client/apiClient";
4import { SkeletonCard, SkeletonKPI } from "@/components/LoadingSpinner";
5
6interface SystemHealth {
7 apiStatus: string;
8 locationsCount: number;
9 customersCount: number;
10 productsCount: number;
11 salesApiStatus: string;
12 lastChecked: string;
13}
14
15export default function OperationsMonitoringPage() {
16 const [health, setHealth] = useState<SystemHealth | null>(null);
17 const [isLoading, setIsLoading] = useState(true);
18 const [lastRefresh, setLastRefresh] = useState<Date>(new Date());
19
20 const checkSystemHealth = async () => {
21 try {
22 setIsLoading(true);
23
24 // Run health checks in parallel with full counts
25 const [customersResult, productsResult, locationsResult, salesTotalsResult] = await Promise.allSettled([
26 apiClient.getCustomers({ limit: 1000, source: 'openapi' }),
27 apiClient.getProducts({ limit: 1000, source: 'openapi' }),
28 apiClient.getLocations(),
29 apiClient.getSalesTotals()
30 ]);
31
32 // Check each API endpoint status
33 const customersOk = customersResult.status === 'fulfilled' && customersResult.value.success;
34 const productsOk = productsResult.status === 'fulfilled' && productsResult.value.success;
35 const locationsOk = locationsResult.status === 'fulfilled' && locationsResult.value.success;
36 const salesOk = salesTotalsResult.status === 'fulfilled' && salesTotalsResult.value.success;
37
38 const connectedApis = [customersOk, productsOk, locationsOk, salesOk].filter(Boolean).length;
39
40 let apiStatus = 'Offline';
41 if (connectedApis === 4) {
42 apiStatus = 'Connected';
43 } else if (connectedApis > 0) {
44 apiStatus = `Partial (${connectedApis}/4)`;
45 }
46
47 // Get counts from successful API calls
48 let locationsCount = 0;
49 let customersCount = 0;
50 let productsCount = 0;
51
52 if (locationsOk && locationsResult.status === 'fulfilled') {
53 const locationData = locationsResult.value.data as any;
54 const locations = locationData?.data?.Location || [];
55 locationsCount = Array.isArray(locations) ? locations.length : 0;
56 }
57
58 if (customersOk && customersResult.status === 'fulfilled') {
59 const apiData = customersResult.value.data as any;
60 const customerData = apiData?.data?.Customer || [];
61 customersCount = Array.isArray(customerData) ? customerData.length : 0;
62 }
63
64 if (productsOk && productsResult.status === 'fulfilled') {
65 const apiData = productsResult.value.data as any;
66 const productData = apiData?.data?.Product || [];
67 productsCount = Array.isArray(productData) ? productData.length : 0;
68 }
69
70 setHealth({
71 apiStatus,
72 locationsCount,
73 customersCount,
74 productsCount,
75 salesApiStatus: salesOk ? 'Connected' : 'Error',
76 lastChecked: new Date().toLocaleString()
77 });
78
79 setLastRefresh(new Date());
80 } catch (error) {
81 console.error('Health check error:', error);
82 setHealth({
83 apiStatus: 'Error',
84 locationsCount: 0,
85 customersCount: 0,
86 productsCount: 0,
87 salesApiStatus: 'Error',
88 lastChecked: new Date().toLocaleString()
89 });
90 } finally {
91 setIsLoading(false);
92 }
93 };
94
95 useEffect(() => {
96 checkSystemHealth();
97
98 // Auto-refresh every 2 minutes
99 const interval = setInterval(checkSystemHealth, 2 * 60 * 1000);
100 return () => clearInterval(interval);
101 }, []);
102
103 const getStatusColor = (status: string) => {
104 if (status === 'Connected') return 'text-green-600 bg-green-50 border-green-200';
105 if (status.startsWith('Partial')) return 'text-orange-600 bg-orange-50 border-orange-200';
106 return 'text-red-600 bg-red-50 border-red-200';
107 };
108
109 const getStatusIcon = (status: string) => {
110 if (status === 'Connected') return '✓';
111 if (status.startsWith('Partial')) return '⚠';
112 return '✗';
113 };
114
115 return (
116 <div className="p-8 h-full overflow-y-auto">
117 <div className="max-w-6xl mx-auto">
118 {/* Header */}
119 <div className="mb-6">
120 <div className="flex items-center justify-between">
121 <div>
122 <h1 className="text-3xl font-bold text-text">Operations Monitoring</h1>
123 <p className="text-muted mt-1">Real-time system health and API status</p>
124 </div>
125 <button
126 onClick={checkSystemHealth}
127 disabled={isLoading}
128 className="px-4 py-2 rounded-lg border border-brand bg-brand text-white hover:bg-brand-dark disabled:opacity-50 transition"
129 >
130 {isLoading ? 'Checking...' : 'Refresh'}
131 </button>
132 </div>
133 </div>
134
135 {isLoading && !health ? (
136 <div>
137 {/* Skeleton for status cards */}
138 <div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
139 {[...Array(4)].map((_, i) => (
140 <SkeletonKPI key={i} />
141 ))}
142 </div>
143
144 {/* Skeleton for API status */}
145 <SkeletonCard />
146
147 <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
148 <SkeletonCard />
149 <SkeletonCard />
150 </div>
151 </div>
152 ) : (
153 <>
154 {/* System Status Overview */}
155 <div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
156 <div className={`card border-2 ${getStatusColor(health?.apiStatus || 'Offline')}`}>
157 <div className="text-center">
158 <div className="text-4xl mb-2">{getStatusIcon(health?.apiStatus || 'Offline')}</div>
159 <div className="text-2xl font-bold">{health?.apiStatus || 'Unknown'}</div>
160 <div className="text-sm mt-1">API Status</div>
161 </div>
162 </div>
163
164 <div className="card border border-border">
165 <div className="text-center">
166 <div className="text-3xl font-bold text-text">{health?.locationsCount || 0}</div>
167 <div className="text-sm text-muted mt-1">Store Locations</div>
168 <div className="text-xs text-muted mt-2">Active stores</div>
169 </div>
170 </div>
171
172 <div className="card border border-border">
173 <div className="text-center">
174 <div className="text-3xl font-bold text-text">{health?.customersCount || 0}</div>
175 <div className="text-sm text-muted mt-1">Customer Records</div>
176 <div className="text-xs text-muted mt-2">In database</div>
177 </div>
178 </div>
179
180 <div className="card border border-border">
181 <div className="text-center">
182 <div className="text-3xl font-bold text-text">{health?.productsCount || 0}</div>
183 <div className="text-sm text-muted mt-1">Product Catalog</div>
184 <div className="text-xs text-muted mt-2">Active SKUs</div>
185 </div>
186 </div>
187 </div>
188
189 {/* API Endpoint Status */}
190 <div className="card mb-6">
191 <h2 className="text-xl font-semibold mb-4">API Endpoint Health</h2>
192
193 <div className="space-y-3">
194 <div className="flex items-center justify-between p-4 bg-surface rounded-lg border border-border">
195 <div className="flex items-center gap-3">
196 <div className={`w-3 h-3 rounded-full ${health?.apiStatus === 'Connected' ? 'bg-success' : 'bg-danger'}`}></div>
197 <div>
198 <div className="font-semibold">Fieldpine OpenAPI2</div>
199 <div className="text-sm text-muted">Products, Customers, Locations</div>
200 </div>
201 </div>
202 <div className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(health?.apiStatus || 'Offline')}`}>
203 {health?.apiStatus || 'Unknown'}
204 </div>
205 </div>
206
207 <div className="flex items-center justify-between p-4 bg-surface rounded-lg border border-border">
208 <div className="flex items-center gap-3">
209 <div className={`w-3 h-3 rounded-full ${health?.salesApiStatus === 'Connected' ? 'bg-success' : 'bg-danger'}`}></div>
210 <div>
211 <div className="font-semibold">Sales & Trading API</div>
212 <div className="text-sm text-muted">Sales totals, trading summaries</div>
213 </div>
214 </div>
215 <div className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(health?.salesApiStatus || 'Error')}`}>
216 {health?.salesApiStatus || 'Unknown'}
217 </div>
218 </div>
219
220 <div className="flex items-center justify-between p-4 bg-surface rounded-lg border border-border">
221 <div className="flex items-center gap-3">
222 <div className="w-3 h-3 rounded-full bg-brand"></div>
223 <div>
224 <div className="font-semibold">Next.js Application</div>
225 <div className="text-sm text-muted">Frontend rendering, API routes</div>
226 </div>
227 </div>
228 <div className="px-3 py-1 rounded-full text-sm font-medium text-green-600 bg-green-50 border border-green-200">
229 Running
230 </div>
231 </div>
232 </div>
233 </div>
234
235 {/* System Information */}
236 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
237 <div className="card">
238 <h3 className="text-lg font-semibold mb-3">Connection Details</h3>
239 <div className="space-y-2 text-sm">
240 <div className="flex justify-between">
241 <span className="text-muted">Base URL:</span>
242 <span className="font-mono">https://iig.cwanz.online</span>
243 </div>
244 <div className="flex justify-between">
245 <span className="text-muted">Environment:</span>
246 <span className="font-mono">.env.local</span>
247 </div>
248 <div className="flex justify-between">
249 <span className="text-muted">Auth Method:</span>
250 <span>Cookie (FieldpineApiKey)</span>
251 </div>
252 <div className="flex justify-between">
253 <span className="text-muted">Last Checked:</span>
254 <span>{health?.lastChecked || 'Never'}</span>
255 </div>
256 </div>
257 </div>
258
259 <div className="card">
260 <h3 className="text-lg font-semibold mb-3">Quick Actions</h3>
261 <div className="space-y-2">
262 <a href="/pages/start-here" className="block p-3 bg-surface rounded-lg border border-border hover:shadow-pine transition">
263 <div className="font-semibold">📚 View Documentation</div>
264 <div className="text-sm text-muted">API reference and development guide</div>
265 </a>
266 <a href="/pages/reports/analytics" className="block p-3 bg-surface rounded-lg border border-border hover:shadow-pine transition">
267 <div className="font-semibold">📊 Analytics Dashboard</div>
268 <div className="text-sm text-muted">Sales charts and business metrics</div>
269 </a>
270 <a href="/pages/home" className="block p-3 bg-surface rounded-lg border border-border hover:shadow-pine transition">
271 <div className="font-semibold">🏠 Return to Home</div>
272 <div className="text-sm text-muted">Main dashboard overview</div>
273 </a>
274 </div>
275 </div>
276 </div>
277
278 {/* Footer */}
279 <div className="mt-6 text-center text-sm text-muted">
280 <p>System monitoring refreshes automatically every 2 minutes</p>
281 <p className="mt-1">Last refresh: {lastRefresh.toLocaleTimeString()}</p>
282 </div>
283 </>
284 )}
285 </div>
286 </div>
287 );
288}