Skip to content

Commit 6069f95

Browse files
committed
feat: enhance the loaders and timestamp func
1 parent bc90a3d commit 6069f95

7 files changed

Lines changed: 86 additions & 60 deletions

File tree

src/components/shared/DataTable.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const DataTable = ({
8585
title={row[column]?.toString() || 'NULL'}
8686
>
8787
{row[column] !== null && row[column] !== undefined ? (
88-
formatCellValue(row[column])
88+
formatCellValue(row[column], column)
8989
) : (
9090
<span className="text-muted-foreground/40 italic text-xs font-sans">null</span>
9191
)}
@@ -147,7 +147,7 @@ export const DataTable = ({
147147
};
148148

149149
// Helper function to format cell values with improved styling
150-
function formatCellValue(value: any): React.ReactNode {
150+
function formatCellValue(value: any, columnName?: string): React.ReactNode {
151151
if (value === null || value === undefined) {
152152
return <span className="text-muted-foreground/40 italic text-xs font-sans">null</span>;
153153
}
@@ -164,6 +164,14 @@ function formatCellValue(value: any): React.ReactNode {
164164
}
165165

166166
if (typeof value === 'number') {
167+
const isLikelyTimestampCol = columnName?.toLowerCase().match(/time|date|created|updated|deleted/);
168+
if (isLikelyTimestampCol) {
169+
if (value > 1e9 && value < 1e10) { // Seconds
170+
return <span className="text-violet-600 dark:text-violet-400">{formatTimestamp(new Date(value * 1000).toISOString())}</span>;
171+
} else if (value > 1e12 && value < 1e13) { // Milliseconds
172+
return <span className="text-violet-600 dark:text-violet-400">{formatTimestamp(new Date(value).toISOString())}</span>;
173+
}
174+
}
167175
return (
168176
<span className="text-indigo-600 dark:text-indigo-400 tabular-nums">
169177
{value.toLocaleString()}
@@ -196,10 +204,22 @@ function formatCellValue(value: any): React.ReactNode {
196204
const strValue = String(value);
197205

198206
// Check if it looks like a date string
199-
if (/^\d{4}-\d{2}-\d{2}/.test(strValue)) {
207+
if (typeof strValue === 'string' && /^\d{4}-\d{2}-\d{2}/.test(strValue)) {
200208
return <span className="text-violet-600 dark:text-violet-400">{formatTimestamp(strValue)}</span>;
201209
}
202210

211+
// Check if it's a UNIX timestamp (seconds or ms) based on column name heuristic
212+
const isLikelyTimestampCol = columnName?.toLowerCase().match(/time|date|created|updated|deleted/);
213+
if (isLikelyTimestampCol) {
214+
// 10 digits (seconds) or 13 digits (ms)
215+
if (/^\d{10}$/.test(strValue)) {
216+
return <span className="text-violet-600 dark:text-violet-400">{formatTimestamp(new Date(Number(strValue) * 1000).toISOString())}</span>;
217+
}
218+
if (/^\d{13}$/.test(strValue)) {
219+
return <span className="text-violet-600 dark:text-violet-400">{formatTimestamp(new Date(Number(strValue)).toISOString())}</span>;
220+
}
221+
}
222+
203223
// Check if it looks like an ID or UUID
204224
if (/^[a-f0-9-]{36}$/i.test(strValue)) {
205225
return <span className="text-slate-500 dark:text-slate-400 text-xs">{strValue}</span>;

src/features/monitoring/components/MonitoringLoadingState.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Card, CardContent, CardHeader } from "@/components/ui/card";
2-
2+
import { Skeleton } from "@/components/ui/skeleton";
33
import { Spinner } from "@/components/ui/spinner";
44

55
export function MonitoringLoadingState() {
@@ -10,16 +10,16 @@ export function MonitoringLoadingState() {
1010
<Card key={index} className="rounded-lg border-border/50 bg-card/65 shadow-sm">
1111
<CardHeader className="flex flex-row items-start justify-between gap-3 pb-2">
1212
<div className="space-y-2">
13-
<div className="h-3 w-16 animate-pulse rounded-full bg-muted/70" />
14-
<div className="h-8 w-24 animate-pulse rounded-lg bg-muted/70" />
13+
<Skeleton className="h-3 w-16 rounded-full bg-muted/70" />
14+
<Skeleton className="h-8 w-24 rounded-lg bg-muted/70" />
1515
</div>
1616
<div className="h-9 w-9 rounded-md border border-border/50 bg-background/60 p-2">
17-
<div className="h-full w-full animate-pulse rounded-sm bg-muted/70" />
17+
<Skeleton className="h-full w-full rounded-sm bg-muted/70" />
1818
</div>
1919
</CardHeader>
2020
<CardContent>
21-
<div className="h-3 w-28 animate-pulse rounded-full bg-muted/70" />
22-
{index === 1 ? <div className="mt-3 h-1.5 w-full animate-pulse rounded-full bg-muted/70" /> : null}
21+
<Skeleton className="h-3 w-28 rounded-full bg-muted/70" />
22+
{index === 1 ? <Skeleton className="mt-3 h-1.5 w-full rounded-full bg-muted/70" /> : null}
2323
</CardContent>
2424
</Card>
2525
))}
@@ -30,10 +30,10 @@ export function MonitoringLoadingState() {
3030
<CardHeader className="border-b border-border/30 pb-3">
3131
<div className="flex items-center justify-between gap-4">
3232
<div className="space-y-2">
33-
<div className="h-4 w-36 animate-pulse rounded-full bg-muted/70" />
34-
<div className="h-3 w-52 animate-pulse rounded-full bg-muted/70" />
33+
<Skeleton className="h-4 w-36 rounded-full bg-muted/70" />
34+
<Skeleton className="h-3 w-52 rounded-full bg-muted/70" />
3535
</div>
36-
<div className="h-6 w-20 animate-pulse rounded-full bg-muted/70" />
36+
<Skeleton className="h-6 w-20 rounded-full bg-muted/70" />
3737
</div>
3838
</CardHeader>
3939
<CardContent className="flex h-72 items-center justify-center p-4">
@@ -47,17 +47,17 @@ export function MonitoringLoadingState() {
4747
<Card className="overflow-hidden rounded-lg border-border/50 bg-card/65 shadow-sm">
4848
<CardHeader className="border-b border-border/30 pb-3">
4949
<div className="space-y-2">
50-
<div className="h-4 w-28 animate-pulse rounded-full bg-muted/70" />
51-
<div className="h-3 w-56 animate-pulse rounded-full bg-muted/70" />
50+
<Skeleton className="h-4 w-28 rounded-full bg-muted/70" />
51+
<Skeleton className="h-3 w-56 rounded-full bg-muted/70" />
5252
</div>
5353
</CardHeader>
5454
<CardContent className="space-y-3 p-4">
5555
{Array.from({ length: 5 }).map((_, index) => (
5656
<div key={index} className="flex items-center gap-3 rounded-md border border-border/30 bg-background/40 px-3 py-3">
57-
<div className="h-3 w-14 animate-pulse rounded-full bg-muted/70" />
58-
<div className="h-3 w-16 animate-pulse rounded-full bg-muted/70" />
59-
<div className="h-5 w-16 animate-pulse rounded-full bg-muted/70" />
60-
<div className="h-3 flex-1 animate-pulse rounded-full bg-muted/70" />
57+
<Skeleton className="h-3 w-14 rounded-full bg-muted/70" />
58+
<Skeleton className="h-3 w-16 rounded-full bg-muted/70" />
59+
<Skeleton className="h-5 w-16 rounded-full bg-muted/70" />
60+
<Skeleton className="h-3 flex-1 rounded-full bg-muted/70" />
6161
</div>
6262
))}
6363
</CardContent>

src/features/query-builder/components/QueryBuilderPanelLoadingState.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Skeleton } from "@/components/ui/skeleton";
2+
import { Card } from "@/components/ui/card";
23

34
export function QueryBuilderPanelLoadingState() {
45
return (
@@ -17,7 +18,7 @@ export function QueryBuilderPanelLoadingState() {
1718
{/* Body */}
1819
<div className="flex-1 flex overflow-hidden p-3 gap-3">
1920
{/* Sidebar skeleton */}
20-
<div className="w-56 shrink-0 rounded-lg border border-border/40 bg-card/50 p-3 space-y-4">
21+
<Card className="w-56 shrink-0 bg-card/50 p-3 space-y-4">
2122
<div className="space-y-2">
2223
<Skeleton className="h-3 w-14 rounded-full" />
2324
{Array.from({ length: 5 }).map((_, i) => (
@@ -33,16 +34,16 @@ export function QueryBuilderPanelLoadingState() {
3334
<Skeleton key={i} className="h-6 w-full" />
3435
))}
3536
</div>
36-
</div>
37+
</Card>
3738
{/* Main canvas + results */}
38-
<div className="flex-1 flex flex-col rounded-lg border border-border/40 bg-card/50 overflow-hidden">
39+
<Card className="flex-1 flex flex-col bg-card/50 overflow-hidden">
3940
{/* Canvas area (diagram) */}
4041
<div className="flex-1 relative p-4">
4142
{/* Fake node cards */}
4243
{[{ top: "20%", left: "15%" }, { top: "25%", left: "55%" }, { top: "60%", left: "35%" }].map((pos, i) => (
43-
<div
44+
<Card
4445
key={i}
45-
className="absolute w-44 rounded-lg border border-border/40 bg-background/70 overflow-hidden"
46+
className="absolute w-44 bg-background/70 overflow-hidden"
4647
style={pos}
4748
>
4849
<div className="h-7 border-b border-border/30 bg-muted/40 px-3 flex items-center gap-2">
@@ -56,7 +57,7 @@ export function QueryBuilderPanelLoadingState() {
5657
</div>
5758
))}
5859
</div>
59-
</div>
60+
</Card>
6061
))}
6162
</div>
6263
{/* SQL results pane */}
@@ -66,7 +67,7 @@ export function QueryBuilderPanelLoadingState() {
6667
<Skeleton key={i} className="h-5 w-full" />
6768
))}
6869
</div>
69-
</div>
70+
</Card>
7071
</div>
7172
{/* Status bar */}
7273
<div className="h-7 border-t border-border/30 bg-background/70 px-4 flex items-center gap-4">

src/features/schema-explorer/components/AnalyzeSchemaButton.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ interface AnalyzeSchemaButtonProps {
2323
}>;
2424
};
2525
databaseType?: string;
26+
dbId?: string;
2627
}
2728

28-
export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaButtonProps) {
29+
export function AnalyzeSchemaButton({ schemaData, databaseType, dbId }: AnalyzeSchemaButtonProps) {
2930
const { settings } = useAISettings();
3031
const [open, setOpen] = useState(false);
3132
const [loading, setLoading] = useState(false);
@@ -41,15 +42,15 @@ export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaB
4142

4243
// Reset all local state when the user switches to a different database
4344
useEffect(() => {
44-
const name = schemaData.name;
45-
if (cachedForRef.current !== undefined && cachedForRef.current !== name) {
45+
const identifier = dbId ?? schemaData.name;
46+
if (cachedForRef.current !== undefined && cachedForRef.current !== identifier) {
4647
setMarkdown(undefined);
4748
setError(null);
4849
setCached(undefined);
4950
setCreatedAt(undefined);
5051
cachedForRef.current = undefined;
5152
}
52-
}, [schemaData.name]);
53+
}, [schemaData.name, dbId]);
5354

5455
const buildInput = (): SchemaAnalysisInput => ({
5556
databaseType,
@@ -70,21 +71,22 @@ export function AnalyzeSchemaButton({ schemaData, databaseType }: AnalyzeSchemaB
7071

7172
const handleAnalyze = async (skipCache = false) => {
7273
setOpen(true);
74+
const identifier = dbId ?? schemaData.name;
7375
// Only reuse cached markdown if it's for THIS database
74-
if (markdown && !skipCache && cachedForRef.current === schemaData.name) return;
76+
if (markdown && !skipCache && cachedForRef.current === identifier) return;
7577
setLoading(true);
7678
setError(null);
7779

7880
try {
7981
const input = buildInput();
8082
const result = await aiService.analyzeSchema(settings, input, {
8183
skipCache,
82-
datasourceName: schemaData.name,
84+
datasourceName: identifier,
8385
});
8486
setMarkdown(result.markdown);
8587
setCached(result.cached);
8688
setCreatedAt(result.createdAt);
87-
cachedForRef.current = schemaData.name; // mark which DB this result belongs to
89+
cachedForRef.current = identifier; // mark which DB this result belongs to
8890
} catch (err: any) {
8991
setError(err?.message ?? String(err));
9092
} finally {

src/features/schema-explorer/components/SchemaExplorerHeader.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const SchemaExplorerHeader = ({ dbId, database, onTableCreated, selectedTable }:
5151
{database.schemas && database.schemas.length > 0 && (
5252
<AnalyzeSchemaButton
5353
schemaData={database as any}
54+
dbId={dbId}
5455
/>
5556
)}
5657

src/features/schema-explorer/components/SchemaExplorerPanelLoadingState.tsx

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
import { Skeleton } from "@/components/ui/skeleton";
2+
13
export function SchemaExplorerPanelLoadingState() {
24
return (
35
<div className="h-full flex flex-col bg-background text-foreground overflow-hidden animate-pulse">
46
{/* ── Header (breadcrumb + actions) ── */}
57
<div className="shrink-0 border-b border-border/40 bg-background/90 px-4 py-2 flex items-center justify-between gap-3">
68
<div className="flex items-center gap-2">
7-
<div className="h-3.5 w-20 rounded-full bg-muted/60" />
8-
<div className="h-3 w-1 rounded-full bg-muted/40" />
9-
<div className="h-3.5 w-28 rounded-full bg-muted/70" />
9+
<Skeleton className="h-3.5 w-20 rounded-full bg-muted/60" />
10+
<Skeleton className="h-3 w-1 rounded-full bg-muted/40" />
11+
<Skeleton className="h-3.5 w-28 rounded-full bg-muted/70" />
1012
</div>
1113
<div className="flex items-center gap-2">
12-
<div className="h-7 w-32 rounded-md bg-muted/50" />
13-
<div className="h-7 w-28 rounded-md bg-primary/20" />
14+
<Skeleton className="h-7 w-32 rounded-md bg-muted/50" />
15+
<Skeleton className="h-7 w-28 rounded-md bg-primary/20" />
1416
</div>
1517
</div>
1618

@@ -22,17 +24,17 @@ export function SchemaExplorerPanelLoadingState() {
2224

2325
{/* Database root node */}
2426
<div className="flex items-center gap-1.5 px-2 py-1.5 rounded-md">
25-
<div className="h-3 w-3 rounded bg-primary/40" />
26-
<div className="h-3 w-24 rounded-full bg-muted/70" />
27-
<div className="ml-auto h-4 w-16 rounded-full bg-muted/40" />
27+
<Skeleton className="h-3 w-3 rounded bg-primary/40" />
28+
<Skeleton className="h-3 w-24 rounded-full bg-muted/70" />
29+
<Skeleton className="ml-auto h-4 w-16 rounded-full bg-muted/40" />
2830
</div>
2931

3032
{/* Schema node (expanded) */}
3133
<div className="flex items-center gap-1.5 px-2 py-1.5 pl-4 rounded-md">
32-
<div className="h-2.5 w-2.5 rounded bg-muted/50" />
33-
<div className="h-3 w-3 rounded bg-primary/30" />
34-
<div className="h-3 w-20 rounded-full bg-muted/65" />
35-
<div className="ml-auto h-4 w-6 rounded-full bg-muted/40" />
34+
<Skeleton className="h-2.5 w-2.5 rounded bg-muted/50" />
35+
<Skeleton className="h-3 w-3 rounded bg-primary/30" />
36+
<Skeleton className="h-3 w-20 rounded-full bg-muted/65" />
37+
<Skeleton className="ml-auto h-4 w-6 rounded-full bg-muted/40" />
3638
</div>
3739

3840
{/* Table rows under schema */}
@@ -42,12 +44,12 @@ export function SchemaExplorerPanelLoadingState() {
4244
"w-22", "w-26", "w-32", "w-18", "w-16",
4345
].map((w, i) => (
4446
<div key={i} className="flex items-center gap-1.5 px-2 py-1 pl-8 rounded-md">
45-
<div className="h-2.5 w-2.5 rounded bg-muted/40 shrink-0" />
46-
<div className="h-2.5 w-3.5 rounded bg-primary/25 shrink-0" />
47-
<div className={`h-2.5 ${w} rounded-full bg-muted/55`} />
47+
<Skeleton className="h-2.5 w-2.5 rounded bg-muted/40 shrink-0" />
48+
<Skeleton className="h-2.5 w-3.5 rounded bg-primary/25 shrink-0" />
49+
<Skeleton className={`h-2.5 ${w} rounded-full bg-muted/55`} />
4850
{/* Occasional FK badge */}
4951
{i === 7 && (
50-
<div className="ml-auto h-3.5 w-7 rounded-full bg-primary/20 text-[9px]" />
52+
<Skeleton className="ml-auto h-3.5 w-7 rounded-full bg-primary/20 text-[9px]" />
5153
)}
5254
</div>
5355
))}
@@ -58,13 +60,12 @@ export function SchemaExplorerPanelLoadingState() {
5860

5961
{/* DB title row */}
6062
<div className="flex items-center gap-3">
61-
<div className="h-8 w-8 rounded-full bg-primary/20 shrink-0" />
63+
<Skeleton className="h-8 w-8 rounded-full bg-primary/20 shrink-0" />
6264
<div className="space-y-1.5">
63-
<div className="h-5 w-32 rounded-full bg-muted/70" />
64-
<div className="h-3 w-16 rounded-full bg-muted/40" />
65+
<Skeleton className="h-5 w-32 rounded-full bg-muted/70" />
66+
<Skeleton className="h-3 w-16 rounded-full bg-muted/40" />
6567
</div>
6668
</div>
67-
6869
{/* 2 × 3 stat cards grid */}
6970
<div className="grid grid-cols-3 gap-3">
7071
{[
@@ -80,9 +81,9 @@ export function SchemaExplorerPanelLoadingState() {
8081
className="rounded-lg border border-border/30 bg-card/50 p-4 space-y-2"
8182
>
8283
{/* Big number */}
83-
<div className={`h-7 ${card.w} rounded-md bg-muted/70`} />
84+
<Skeleton className={`h-7 ${card.w} rounded-md bg-muted/70`} />
8485
{/* Label */}
85-
<div className={`h-2.5 ${card.label} rounded-full bg-muted/40`} />
86+
<Skeleton className={`h-2.5 ${card.label} rounded-full bg-muted/40`} />
8687
</div>
8788
))}
8889
</div>
@@ -92,10 +93,10 @@ export function SchemaExplorerPanelLoadingState() {
9293
{/* ── Footer ── */}
9394
<div className="shrink-0 border-t border-border/40 bg-card/50 px-4 py-1.5 flex items-center justify-between">
9495
<div className="flex items-center gap-3">
95-
<div className="h-3 w-36 rounded-full bg-muted/50" />
96+
<Skeleton className="h-3 w-36 rounded-full bg-muted/50" />
9697
<div className="h-4 w-10 rounded-full bg-emerald-500/25" />
9798
</div>
98-
<div className="h-3 w-64 rounded-full bg-muted/30" />
99+
<Skeleton className="h-3 w-64 rounded-full bg-muted/30" />
99100
</div>
100101
</div>
101102
)

0 commit comments

Comments
 (0)