2import { useEffect, useState } from "react";
3import { apiClient } from "@/lib/client/apiClient";
4import { SkeletonCard, SkeletonKPI } from "@/components/LoadingSpinner";
6interface SystemHealth {
8 locationsCount: number;
9 customersCount: number;
10 productsCount: number;
11 salesApiStatus: string;
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());
20 const checkSystemHealth = async () => {
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()
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;
38 const connectedApis = [customersOk, productsOk, locationsOk, salesOk].filter(Boolean).length;
40 let apiStatus = 'Offline';
41 if (connectedApis === 4) {
42 apiStatus = 'Connected';
43 } else if (connectedApis > 0) {
44 apiStatus = `Partial (${connectedApis}/4)`;
47 // Get counts from successful API calls
48 let locationsCount = 0;
49 let customersCount = 0;
50 let productsCount = 0;
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;
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;
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;
75 salesApiStatus: salesOk ? 'Connected' : 'Error',
76 lastChecked: new Date().toLocaleString()
79 setLastRefresh(new Date());
81 console.error('Health check error:', error);
87 salesApiStatus: 'Error',
88 lastChecked: new Date().toLocaleString()
98 // Auto-refresh every 2 minutes
99 const interval = setInterval(checkSystemHealth, 2 * 60 * 1000);
100 return () => clearInterval(interval);
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';
109 const getStatusIcon = (status: string) => {
110 if (status === 'Connected') return '✓';
111 if (status.startsWith('Partial')) return '⚠';
116 <div className="p-8 h-full overflow-y-auto">
117 <div className="max-w-6xl mx-auto">
119 <div className="mb-6">
120 <div className="flex items-center justify-between">
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>
126 onClick={checkSystemHealth}
128 className="px-4 py-2 rounded-lg border border-brand bg-brand text-white hover:bg-brand-dark disabled:opacity-50 transition"
130 {isLoading ? 'Checking...' : 'Refresh'}
135 {isLoading && !health ? (
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} />
144 {/* Skeleton for API status */}
147 <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
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>
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>
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>
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>
189 {/* API Endpoint Status */}
190 <div className="card mb-6">
191 <h2 className="text-xl font-semibold mb-4">API Endpoint Health</h2>
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>
198 <div className="font-semibold">Fieldpine OpenAPI2</div>
199 <div className="text-sm text-muted">Products, Customers, Locations</div>
202 <div className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(health?.apiStatus || 'Offline')}`}>
203 {health?.apiStatus || 'Unknown'}
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>
211 <div className="font-semibold">Sales & Trading API</div>
212 <div className="text-sm text-muted">Sales totals, trading summaries</div>
215 <div className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(health?.salesApiStatus || 'Error')}`}>
216 {health?.salesApiStatus || 'Unknown'}
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>
224 <div className="font-semibold">Next.js Application</div>
225 <div className="text-sm text-muted">Frontend rendering, API routes</div>
228 <div className="px-3 py-1 rounded-full text-sm font-medium text-green-600 bg-green-50 border border-green-200">
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>
244 <div className="flex justify-between">
245 <span className="text-muted">Environment:</span>
246 <span className="font-mono">.env.local</span>
248 <div className="flex justify-between">
249 <span className="text-muted">Auth Method:</span>
250 <span>Cookie (FieldpineApiKey)</span>
252 <div className="flex justify-between">
253 <span className="text-muted">Last Checked:</span>
254 <span>{health?.lastChecked || 'Never'}</span>
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>
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>
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>
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>