Files
ember-market-frontend/components/admin/SystemStatusCard.tsx
g 0176f89cb7 Add CSV export for orders and update UI symbols
Introduces an exportOrdersToCSV function in lib/api-client.ts to allow exporting orders by status as a CSV file. Updates various UI components to use the '•' (bullet) symbol instead of '·' (middle dot) and replaces some emoji/unicode characters for improved consistency and compatibility. Also normalizes the 'use client' directive to include a BOM in many files.
2025-12-15 17:57:18 +00:00

154 lines
5.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { fetchClient } from "@/lib/api-client";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
interface Status {
uptimeSeconds: number;
memory: Record<string, number>;
versions: Record<string, string>;
counts: { vendors: number; orders: number; products: number; chats: number };
}
function formatDuration(seconds: number) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${h}h ${m}m ${s}s`;
}
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 Bytes';
if (bytes < 0) return '0 Bytes'; // Handle negative values
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.max(0, Math.floor(Math.log(bytes) / Math.log(k))); // Ensure i is at least 0
// Clamp i to valid array index
const clampedI = Math.min(i, sizes.length - 1);
return Math.round(bytes / Math.pow(k, clampedI) * 100) / 100 + ' ' + sizes[clampedI];
}
export default function SystemStatusCard() {
const [data, setData] = useState<Status | null>(null);
const [error, setError] = useState<string | null>(null);
const [showDebug, setShowDebug] = useState(false);
useEffect(() => {
let mounted = true;
(async () => {
try {
const res = await fetchClient<Status>("/admin/system-status");
if (mounted) setData(res);
} catch (e: any) {
if (mounted) setError(e?.message || "Failed to load status");
}
})();
return () => { mounted = false; };
}, []);
return (
<div className="space-y-4">
<div className="rounded-lg border border-border/60 bg-background p-4 h-full min-h-[200px]">
<div className="flex items-start justify-between">
<div>
<h2 className="font-medium">System status</h2>
<p className="text-sm text-muted-foreground mt-1">Uptime, versions, environment</p>
</div>
<div className="flex items-center gap-2">
<span className="text-xs px-2 py-0.5 rounded bg-emerald-500/15 text-emerald-400">OK</span>
<Button
variant="outline"
size="sm"
onClick={() => setShowDebug(!showDebug)}
>
{showDebug ? 'Hide' : 'Show'} Debug
</Button>
</div>
</div>
{error && <p className="text-sm text-muted-foreground mt-3">{error}</p>}
{data && (
<div className="mt-3 grid grid-cols-2 gap-3 text-sm">
<div className="space-y-1">
<div className="text-muted-foreground">Uptime</div>
<div>{formatDuration(data.uptimeSeconds)}</div>
</div>
<div className="space-y-1">
<div className="text-muted-foreground">Node</div>
<div>{data.versions?.node}</div>
</div>
<div className="space-y-1">
<div className="text-muted-foreground">Vendors</div>
<div>{data.counts?.vendors}</div>
</div>
<div className="space-y-1">
<div className="text-muted-foreground">Orders</div>
<div>{data.counts?.orders}</div>
</div>
</div>
)}
</div>
{showDebug && data && (
<Card>
<CardHeader>
<CardTitle>Debug: Raw System Status Data</CardTitle>
<CardDescription>Complete system status response from backend</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4 text-xs font-mono">
<div>
<div className="font-semibold mb-2">Memory Usage:</div>
<div className="pl-4 space-y-1">
<div>RSS (Resident Set Size): {formatBytes(data.memory?.rss || 0)}</div>
<div>Heap Total: {formatBytes(data.memory?.heapTotal || 0)}</div>
<div>Heap Used: {formatBytes(data.memory?.heapUsed || 0)}</div>
<div>External: {formatBytes(data.memory?.external || 0)}</div>
<div>Array Buffers: {formatBytes(data.memory?.arrayBuffers || 0)}</div>
</div>
</div>
<div>
<div className="font-semibold mb-2">Versions:</div>
<div className="pl-4 space-y-1">
{Object.entries(data.versions || {}).map(([key, value]) => (
<div key={key}>{key}: {value}</div>
))}
</div>
</div>
<div>
<div className="font-semibold mb-2">Counts:</div>
<div className="pl-4 space-y-1">
<div>Vendors: {data.counts?.vendors || 0}</div>
<div>Orders: {data.counts?.orders || 0}</div>
<div>Products: {data.counts?.products || 0}</div>
<div>Chats: {data.counts?.chats || 0}</div>
</div>
</div>
<div>
<div className="font-semibold mb-2">Uptime:</div>
<div className="pl-4">
{data.uptimeSeconds} seconds ({formatDuration(data.uptimeSeconds)})
</div>
</div>
<details className="mt-4">
<summary className="font-semibold cursor-pointer">Full JSON Response</summary>
<pre className="mt-2 bg-muted p-4 rounded overflow-auto max-h-96 text-[10px]">
{JSON.stringify(data, null, 2)}
</pre>
</details>
</div>
</CardContent>
</Card>
)}
</div>
);
}