Fix/enhance data management page

This commit is contained in:
2025-03-27 17:09:06 -04:00
parent 957c7b5eb1
commit 087ec710f6
2 changed files with 209 additions and 79 deletions

View File

@@ -79,6 +79,18 @@ interface TableStatus {
last_sync_timestamp: string;
}
interface TableCount {
table_name: string;
count: number | null;
error?: string;
}
interface GroupedTableCounts {
core: TableCount[];
metrics: TableCount[];
config: TableCount[];
}
export function DataManagement() {
const [isUpdating, setIsUpdating] = useState(false);
const [isResetting, setIsResetting] = useState(false);
@@ -90,6 +102,7 @@ export function DataManagement() {
const [tableStatus, setTableStatus] = useState<TableStatus[]>([]);
const [scriptOutput, setScriptOutput] = useState<string[]>([]);
const [eventSource, setEventSource] = useState<EventSource | null>(null);
const [tableCounts, setTableCounts] = useState<GroupedTableCounts | null>(null);
// Add useRef for scroll handling
const terminalRef = useRef<HTMLDivElement>(null);
@@ -187,6 +200,7 @@ export function DataManagement() {
const handleFullUpdate = async () => {
setIsUpdating(true);
setScriptOutput([]);
fetchHistory(); // Refresh at start
try {
const source = new EventSource(`${config.apiUrl}/csv/update/progress`, {
@@ -207,10 +221,10 @@ export function DataManagement() {
source.close();
setEventSource(null);
setIsUpdating(false);
fetchHistory(); // Refresh at end
if (data.status === 'complete') {
toast.success("Update completed successfully");
fetchHistory();
} else if (data.status === 'error') {
toast.error(`Update failed: ${data.error || 'Unknown error'}`);
} else {
@@ -257,6 +271,7 @@ export function DataManagement() {
const handleFullReset = async () => {
setIsResetting(true);
setScriptOutput([]);
fetchHistory(); // Refresh at start
try {
const source = new EventSource(`${config.apiUrl}/csv/reset/progress`, {
@@ -277,10 +292,10 @@ export function DataManagement() {
source.close();
setEventSource(null);
setIsResetting(false);
fetchHistory(); // Refresh at end
if (data.status === 'complete') {
toast.success("Reset completed successfully");
fetchHistory();
} else if (data.status === 'error') {
toast.error(`Reset failed: ${data.error || 'Unknown error'}`);
} else {
@@ -359,26 +374,29 @@ export function DataManagement() {
};
const fetchHistory = async () => {
let shouldSetLoading = !importHistory.length || !calculateHistory.length;
try {
setIsLoading(true);
if (shouldSetLoading) setIsLoading(true);
setHasError(false);
const [importRes, calcRes, moduleRes, tableRes] = await Promise.all([
const [importRes, calcRes, moduleRes, tableRes, tableCountsRes] = await Promise.all([
fetch(`${config.apiUrl}/csv/history/import`, { credentials: 'include' }),
fetch(`${config.apiUrl}/csv/history/calculate`, { credentials: 'include' }),
fetch(`${config.apiUrl}/csv/status/modules`, { credentials: 'include' }),
fetch(`${config.apiUrl}/csv/status/tables`, { credentials: 'include' }),
fetch(`${config.apiUrl}/csv/status/table-counts`, { credentials: 'include' }),
]);
if (!importRes.ok || !calcRes.ok || !moduleRes.ok || !tableRes.ok) {
if (!importRes.ok || !calcRes.ok || !moduleRes.ok || !tableRes.ok || !tableCountsRes.ok) {
throw new Error('One or more requests failed');
}
const [importData, calcData, moduleData, tableData] = await Promise.all([
const [importData, calcData, moduleData, tableData, tableCountsData] = await Promise.all([
importRes.json(),
calcRes.json(),
moduleRes.json(),
tableRes.json(),
tableCountsRes.json(),
]);
// Process import history to add duration_minutes if it doesn't exist
@@ -405,17 +423,19 @@ export function DataManagement() {
setCalculateHistory(processedCalcData);
setModuleStatus(moduleData || []);
setTableStatus(tableData || []);
setTableCounts(tableCountsData);
setHasError(false);
} catch (error) {
console.error("Error fetching history:", error);
console.error("Error fetching data:", error);
setHasError(true);
toast.error("Failed to load data. Please try again.");
setImportHistory([]);
setCalculateHistory([]);
setModuleStatus([]);
setTableStatus([]);
setTableCounts(null);
} finally {
setIsLoading(false);
if (shouldSetLoading) setIsLoading(false);
}
};
@@ -528,6 +548,57 @@ export function DataManagement() {
);
};
// Add formatNumber helper
const formatNumber = (num: number | null) => {
if (num === null) return 'N/A';
return new Intl.NumberFormat().format(num);
};
// Update renderTableCountsSection to match other cards' styling
const renderTableCountsSection = () => {
if (!tableCounts) return null;
const renderTableGroup = (title: string, tables: TableCount[]) => (
<div className="mt-0 border-t first:border-t-0 first:mt-0">
<div>
{tables.map((table, index) => (
<div
key={table.table_name}
className="flex justify-between text-sm items-center py-2 border-b last:border-0"
>
<span className="font-medium">{table.table_name}</span>
{table.error ? (
<span className="text-red-600 text-sm">{table.error}</span>
) : (
<span className="text-sm text-gray-600">{formatNumber(table.count)}</span>
)}
</div>
))}
</div>
</div>
);
return (
<Card className="md:col-start-2 md:row-span-2 h-[670px]">
<CardHeader className="pb-3">
<CardTitle>Table Record Counts</CardTitle>
</CardHeader>
<CardContent>
{isLoading && !tableCounts.core.length ? (
<div className="flex justify-center py-4">
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
</div>
) : (
<div>
<div className="bg-sky-50/50 rounded-t-md px-2">{renderTableGroup('Core Tables', tableCounts.core)}</div>
<div className="bg-green-50/50 rounded-b-md px-2">{renderTableGroup('Metrics Tables', tableCounts.metrics)}</div>
</div>
)}
</CardContent>
</Card>
);
};
return (
<div className="space-y-8 max-w-4xl">
<div className="grid gap-4 md:grid-cols-2">
@@ -632,7 +703,7 @@ export function DataManagement() {
{(isUpdating || isResetting) && renderTerminal()}
{/* History Section */}
<div className="space-y-6">
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold">History & Status</h2>
<Button
@@ -652,76 +723,81 @@ export function DataManagement() {
<div className="grid gap-4 md:grid-cols-2">
{/* Table Status */}
<Card className="">
<CardHeader className="pb-3">
<CardTitle>Last Import Times</CardTitle>
</CardHeader>
<CardContent>
<div className="">
{isLoading ? (
<div className="flex justify-center py-4">
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
</div>
) : tableStatus.length > 0 ? (
tableStatus.map((table) => (
<div
key={table.table_name}
className="flex justify-between text-sm items-center py-2 border-b last:border-0"
>
<span className="font-medium">{table.table_name}</span>
<span className="text-sm text-gray-600">
{formatStatusTime(table.last_sync_timestamp)}
</span>
<div className="space-y-4 flex flex-col h-[670px]">
<Card className="flex-1">
<CardHeader className="pb-3">
<CardTitle>Last Import Times</CardTitle>
</CardHeader>
<CardContent className="h-[calc(50%)]">
<div className="">
{isLoading && !tableStatus.length ? (
<div className="flex justify-center py-4">
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
</div>
))
) : (
<div className="text-sm text-muted-foreground py-4 text-center">
{hasError ? (
"Failed to load data. Please try refreshing."
) : (
<>No imports have been performed yet.<br/>Run a full update or reset to import data.</>
)}
</div>
)}
</div>
</CardContent>
</Card>
{/* Module Status */}
<Card>
<CardHeader className="pb-3">
<CardTitle>Last Calculation Times</CardTitle>
</CardHeader>
<CardContent>
<div className="">
{isLoading ? (
<div className="flex justify-center py-4">
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
</div>
) : moduleStatus.length > 0 ? (
moduleStatus.map((module) => (
<div
key={module.module_name}
className="flex justify-between text-sm items-center py-2 border-b last:border-0"
>
<span className="font-medium">{module.module_name}</span>
<span className="text-sm text-gray-600">
{formatStatusTime(module.last_calculation_timestamp)}
</span>
) : tableStatus.length > 0 ? (
tableStatus.map((table) => (
<div
key={table.table_name}
className="flex justify-between text-sm items-center py-2 border-b last:border-0"
>
<span className="font-medium">{table.table_name}</span>
<span className="text-sm text-gray-600">
{formatStatusTime(table.last_sync_timestamp)}
</span>
</div>
))
) : (
<div className="text-sm text-muted-foreground py-4 text-center">
{hasError ? (
"Failed to load data. Please try refreshing."
) : (
<>No imports have been performed yet.<br/>Run a full update or reset to import data.</>
)}
</div>
))
) : (
<div className="text-sm text-muted-foreground py-4 text-center">
{hasError ? (
"Failed to load data. Please try refreshing."
) : (
<>No metrics have been calculated yet.<br/>Run a full update or reset to calculate metrics.</>
)}
</div>
)}
</div>
</CardContent>
</Card>
)}
</div>
</CardContent>
</Card>
{/* Module Status */}
<Card className="flex-1">
<CardHeader className="pb-3">
<CardTitle>Last Calculation Times</CardTitle>
</CardHeader>
<CardContent className="h-[calc(50%)]">
<div className="">
{isLoading && !moduleStatus.length ? (
<div className="flex justify-center py-4">
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
</div>
) : moduleStatus.length > 0 ? (
moduleStatus.map((module) => (
<div
key={module.module_name}
className="flex justify-between text-sm items-center py-2 border-b last:border-0"
>
<span className="font-medium">{module.module_name}</span>
<span className="text-sm text-gray-600">
{formatStatusTime(module.last_calculation_timestamp)}
</span>
</div>
))
) : (
<div className="text-sm text-muted-foreground py-4 text-center">
{hasError ? (
"Failed to load data. Please try refreshing."
) : (
<>No metrics have been calculated yet.<br/>Run a full update or reset to calculate metrics.</>
)}
</div>
)}
</div>
</CardContent>
</Card>
</div>
{/* Add Table Counts section here */}
{renderTableCountsSection()}
</div>
{/* Recent Import History */}
@@ -732,7 +808,7 @@ export function DataManagement() {
<CardContent className="px-4 mb-4 max-h-[300px] overflow-y-auto">
<Table>
<TableBody>
{isLoading ? (
{isLoading && !importHistory.length ? (
<TableRow>
<TableCell className="text-center py-8">
<div className="flex flex-col items-center justify-center">
@@ -843,7 +919,7 @@ export function DataManagement() {
<CardContent className="px-4 mb-4 max-h-[300px] overflow-y-auto">
<Table>
<TableBody>
{isLoading ? (
{isLoading && !calculateHistory.length ? (
<TableRow>
<TableCell className="text-center py-8">
<div className="flex flex-col items-center justify-center">