Skip to content

Commit 7157d41

Browse files
committed
fix: migration status card and sheet
1 parent 6140afd commit 7157d41

3 files changed

Lines changed: 118 additions & 101 deletions

File tree

src/features/home/components/MigrationStatusCard.tsx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { useEffect, useState } from "react";
1+
import { useState } from "react";
22
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
33
import { Button } from "@/components/ui/button";
44
import { Badge } from "@/components/ui/badge";
55
import { useImportAnalysis } from "@/features/project/hooks/useImportAnalysis";
6-
import { projectService } from "@/services/bridge/project";
76
import { AlertTriangle, Database, RefreshCw, CheckCircle2 } from "lucide-react";
87
import { SchemaDriftSheet } from "@/features/project/components/SchemaDriftSheet";
98
import { Skeleton } from "@/components/ui/skeleton";
9+
import { cn } from "@/lib/utils";
1010

1111
interface MigrationStatusCardProps {
1212
projectId: string;
@@ -18,10 +18,6 @@ export function MigrationStatusCard({ projectId, databaseId, connectionName }: M
1818
const { analysis, loading, refetch } = useImportAnalysis(projectId, databaseId);
1919
const [driftSheetOpen, setDriftSheetOpen] = useState(false);
2020

21-
useEffect(() => {
22-
// Optionally poll or just rely on manual refresh
23-
}, [projectId]);
24-
2521
if (loading && !analysis) {
2622
return <Skeleton className="h-[200px] w-full rounded-xl" />;
2723
}
@@ -66,17 +62,29 @@ export function MigrationStatusCard({ projectId, databaseId, connectionName }: M
6662
<span className="text-muted-foreground">Pending Migrations</span>
6763
<span className="font-medium">{analysis.migrationCount}</span>
6864
</div>
69-
65+
7066
{isDrifted && analysis.driftDetails && (
7167
<div className="text-xs text-muted-foreground bg-muted p-2 rounded">
7268
Schema drift detected. The live database has modifications not tracked in the schema snapshot.
7369
</div>
7470
)}
7571

72+
{isDrifted && !analysis.driftDetails && (
73+
<div className="text-xs text-muted-foreground bg-muted p-2 rounded">
74+
Drift detected — pending migrations have not been applied to the live database.
75+
</div>
76+
)}
77+
7678
<div className="flex gap-2 pt-2">
77-
<Button variant="outline" size="sm" onClick={refetch} className="flex-1">
78-
<RefreshCw className="h-4 w-4 mr-2" />
79-
Refresh
79+
<Button
80+
variant="outline"
81+
size="sm"
82+
onClick={refetch}
83+
disabled={loading}
84+
className="flex-1"
85+
>
86+
<RefreshCw className={cn("h-4 w-4 mr-2", loading && "animate-spin")} />
87+
{loading ? "Refreshing..." : "Refresh"}
8088
</Button>
8189
{isDrifted && (
8290
<Button size="sm" onClick={() => setDriftSheetOpen(true)} className="flex-1">
@@ -88,16 +96,15 @@ export function MigrationStatusCard({ projectId, databaseId, connectionName }: M
8896
</CardContent>
8997
</Card>
9098

91-
{analysis.driftDetails && (
92-
<SchemaDriftSheet
93-
open={driftSheetOpen}
94-
onOpenChange={setDriftSheetOpen}
95-
projectId={projectId}
96-
connectionName={connectionName}
97-
driftDetails={analysis.driftDetails}
98-
onRefresh={refetch}
99-
/>
100-
)}
99+
{/* Always render the sheet — it manages its own empty-state guard */}
100+
<SchemaDriftSheet
101+
open={driftSheetOpen}
102+
onOpenChange={setDriftSheetOpen}
103+
projectId={projectId}
104+
connectionName={connectionName}
105+
driftDetails={analysis.driftDetails}
106+
onRefresh={refetch}
107+
/>
101108
</>
102109
);
103110
}

src/features/project/components/SchemaDriftSheet.tsx

Lines changed: 91 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -45,98 +45,109 @@ export function SchemaDriftSheet({
4545
}
4646
};
4747

48-
if (!driftDetails) return null;
49-
50-
const hasAdded = driftDetails.tablesAdded.length > 0;
51-
const hasRemoved = driftDetails.tablesRemoved.length > 0;
52-
const hasModified = driftDetails.tablesModified.length > 0;
48+
const hasAdded = (driftDetails?.tablesAdded?.length ?? 0) > 0;
49+
const hasRemoved = (driftDetails?.tablesRemoved?.length ?? 0) > 0;
50+
const hasModified = (driftDetails?.tablesModified?.length ?? 0) > 0;
51+
const hasDriftData = hasAdded || hasRemoved || hasModified;
5352

5453
return (
5554
<Sheet open={open} onOpenChange={onOpenChange}>
5655
<SheetContent className="sm:max-w-xl w-3/4 flex flex-col gap-4">
5756
<SheetHeader>
5857
<SheetTitle>Schema Drift — {connectionName}</SheetTitle>
5958
<SheetDescription>
60-
Live database differs from schema.json{capturedDate ? ` captured ${new Date(capturedDate).toLocaleString()}` : ""}.
59+
Live database differs from schema.json
60+
{capturedDate ? ` captured ${new Date(capturedDate).toLocaleString()}` : ""}.
6161
</SheetDescription>
6262
</SheetHeader>
6363

64-
<ScrollArea className="flex-1 pr-4 -mr-4">
65-
<div className="space-y-4">
66-
{hasAdded && (
67-
<Collapsible defaultOpen className="border rounded-md">
68-
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium hover:bg-muted/50 transition-colors">
69-
<div className="flex items-center gap-2">
70-
<span className="text-green-600">Tables Added</span>
71-
<Badge variant="outline" className="bg-green-600/10 text-green-600 border-green-600/20">
72-
{driftDetails.tablesAdded.length}
73-
</Badge>
74-
</div>
75-
<ChevronDown className="h-4 w-4" />
76-
</CollapsibleTrigger>
77-
<CollapsibleContent className="p-4 pt-0 border-t bg-muted/20">
78-
<ul className="list-disc pl-4 mt-2 text-sm text-green-600">
79-
{driftDetails.tablesAdded.map((t) => <li key={t}>{t}</li>)}
80-
</ul>
81-
</CollapsibleContent>
82-
</Collapsible>
83-
)}
64+
{!hasDriftData ? (
65+
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-center py-12">
66+
<div className="text-4xl">🔍</div>
67+
<p className="text-sm font-medium text-foreground">No detailed drift data available</p>
68+
<p className="text-xs text-muted-foreground max-w-xs">
69+
Structural schema diffing is not yet implemented. Drift was detected from pending
70+
migrations, not a live schema comparison. This is tracked for a future release.
71+
</p>
72+
</div>
73+
) : (
74+
<ScrollArea className="flex-1 pr-4 -mr-4">
75+
<div className="space-y-4">
76+
{hasAdded && (
77+
<Collapsible defaultOpen className="border rounded-md">
78+
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium hover:bg-muted/50 transition-colors">
79+
<div className="flex items-center gap-2">
80+
<span className="text-green-600">Tables Added</span>
81+
<Badge variant="outline" className="bg-green-600/10 text-green-600 border-green-600/20">
82+
{driftDetails!.tablesAdded.length}
83+
</Badge>
84+
</div>
85+
<ChevronDown className="h-4 w-4" />
86+
</CollapsibleTrigger>
87+
<CollapsibleContent className="p-4 pt-0 border-t bg-muted/20">
88+
<ul className="list-disc pl-4 mt-2 text-sm text-green-600">
89+
{driftDetails!.tablesAdded.map((t) => <li key={t}>{t}</li>)}
90+
</ul>
91+
</CollapsibleContent>
92+
</Collapsible>
93+
)}
8494

85-
{hasRemoved && (
86-
<Collapsible defaultOpen className="border rounded-md">
87-
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium hover:bg-muted/50 transition-colors">
88-
<div className="flex items-center gap-2">
89-
<span className="text-destructive">Tables Removed</span>
90-
<Badge variant="destructive" className="bg-destructive/10 text-destructive border-destructive/20">
91-
{driftDetails.tablesRemoved.length}
92-
</Badge>
93-
</div>
94-
<ChevronDown className="h-4 w-4" />
95-
</CollapsibleTrigger>
96-
<CollapsibleContent className="p-4 pt-0 border-t bg-muted/20">
97-
<ul className="list-disc pl-4 mt-2 text-sm text-destructive">
98-
{driftDetails.tablesRemoved.map((t) => <li key={t}>{t}</li>)}
99-
</ul>
100-
</CollapsibleContent>
101-
</Collapsible>
102-
)}
95+
{hasRemoved && (
96+
<Collapsible defaultOpen className="border rounded-md">
97+
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium hover:bg-muted/50 transition-colors">
98+
<div className="flex items-center gap-2">
99+
<span className="text-destructive">Tables Removed</span>
100+
<Badge variant="destructive" className="bg-destructive/10 text-destructive border-destructive/20">
101+
{driftDetails!.tablesRemoved.length}
102+
</Badge>
103+
</div>
104+
<ChevronDown className="h-4 w-4" />
105+
</CollapsibleTrigger>
106+
<CollapsibleContent className="p-4 pt-0 border-t bg-muted/20">
107+
<ul className="list-disc pl-4 mt-2 text-sm text-destructive">
108+
{driftDetails!.tablesRemoved.map((t) => <li key={t}>{t}</li>)}
109+
</ul>
110+
</CollapsibleContent>
111+
</Collapsible>
112+
)}
103113

104-
{hasModified && (
105-
<Collapsible defaultOpen className="border rounded-md">
106-
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium hover:bg-muted/50 transition-colors">
107-
<div className="flex items-center gap-2">
108-
<span className="text-orange-600">Tables Modified</span>
109-
<Badge variant="outline" className="bg-orange-600/10 text-orange-600 border-orange-600/20">
110-
{driftDetails.tablesModified.length}
111-
</Badge>
112-
</div>
113-
<ChevronDown className="h-4 w-4" />
114-
</CollapsibleTrigger>
115-
<CollapsibleContent className="p-4 pt-0 border-t bg-muted/20">
116-
<div className="space-y-4 mt-2">
117-
{driftDetails.tablesModified.map((m) => (
118-
<div key={m.tableName} className="text-sm">
119-
<div className="font-semibold text-foreground mb-1">{m.tableName}</div>
120-
{m.columnsAdded.length > 0 && (
121-
<div className="text-green-600 ml-2">+ {m.columnsAdded.join(", ")}</div>
122-
)}
123-
{m.columnsRemoved.length > 0 && (
124-
<div className="text-destructive ml-2">- {m.columnsRemoved.join(", ")}</div>
125-
)}
126-
{m.columnsChanged.length > 0 && (
127-
<div className="text-orange-600 ml-2">~ {m.columnsChanged.join(", ")}</div>
128-
)}
129-
{m.constraintsChanged.length > 0 && (
130-
<div className="text-blue-600 ml-2">~ {m.constraintsChanged.join(", ")}</div>
131-
)}
132-
</div>
133-
))}
134-
</div>
135-
</CollapsibleContent>
136-
</Collapsible>
137-
)}
138-
</div>
139-
</ScrollArea>
114+
{hasModified && (
115+
<Collapsible defaultOpen className="border rounded-md">
116+
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium hover:bg-muted/50 transition-colors">
117+
<div className="flex items-center gap-2">
118+
<span className="text-orange-600">Tables Modified</span>
119+
<Badge variant="outline" className="bg-orange-600/10 text-orange-600 border-orange-600/20">
120+
{driftDetails!.tablesModified.length}
121+
</Badge>
122+
</div>
123+
<ChevronDown className="h-4 w-4" />
124+
</CollapsibleTrigger>
125+
<CollapsibleContent className="p-4 pt-0 border-t bg-muted/20">
126+
<div className="space-y-4 mt-2">
127+
{driftDetails!.tablesModified.map((m) => (
128+
<div key={m.tableName} className="text-sm">
129+
<div className="font-semibold text-foreground mb-1">{m.tableName}</div>
130+
{m.columnsAdded.length > 0 && (
131+
<div className="text-green-600 ml-2">+ {m.columnsAdded.join(", ")}</div>
132+
)}
133+
{m.columnsRemoved.length > 0 && (
134+
<div className="text-destructive ml-2">- {m.columnsRemoved.join(", ")}</div>
135+
)}
136+
{m.columnsChanged.length > 0 && (
137+
<div className="text-orange-600 ml-2">~ {m.columnsChanged.join(", ")}</div>
138+
)}
139+
{m.constraintsChanged.length > 0 && (
140+
<div className="text-blue-600 ml-2">~ {m.constraintsChanged.join(", ")}</div>
141+
)}
142+
</div>
143+
))}
144+
</div>
145+
</CollapsibleContent>
146+
</Collapsible>
147+
)}
148+
</div>
149+
</ScrollArea>
150+
)}
140151

141152
<div className="flex justify-end gap-2 pt-4 border-t mt-auto">
142153
<Button variant="outline" onClick={() => onOpenChange(false)}>Close</Button>

src/features/project/hooks/useImportAnalysis.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export function useImportAnalysis(projectId?: string, targetDatabaseId?: string)
99

1010
const fetchAnalysis = useCallback(async () => {
1111
if (!projectId) return;
12-
1312
setLoading(true);
1413
setError(null);
1514
try {

0 commit comments

Comments
 (0)