claudeship 0.2.12 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/apps/server/dist/app.module.js +10 -0
- package/apps/server/dist/app.module.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.js +156 -0
- package/apps/server/dist/chat/prompts/web-system-prompt.js.map +1 -1
- package/apps/server/dist/checkpoint/checkpoint.controller.d.ts +19 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js +93 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.module.d.ts +2 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js +25 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.service.d.ts +41 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js +261 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js.map +1 -0
- package/apps/server/dist/database/database.controller.d.ts +23 -0
- package/apps/server/dist/database/database.controller.js +109 -0
- package/apps/server/dist/database/database.controller.js.map +1 -0
- package/apps/server/dist/database/database.module.d.ts +2 -0
- package/apps/server/dist/database/database.module.js +25 -0
- package/apps/server/dist/database/database.module.js.map +1 -0
- package/apps/server/dist/database/database.service.d.ts +32 -0
- package/apps/server/dist/database/database.service.js +238 -0
- package/apps/server/dist/database/database.service.js.map +1 -0
- package/apps/server/dist/env/env.controller.d.ts +14 -0
- package/apps/server/dist/env/env.controller.js +84 -0
- package/apps/server/dist/env/env.controller.js.map +1 -0
- package/apps/server/dist/env/env.module.d.ts +2 -0
- package/apps/server/dist/env/env.module.js +25 -0
- package/apps/server/dist/env/env.module.js.map +1 -0
- package/apps/server/dist/env/env.service.d.ts +21 -0
- package/apps/server/dist/env/env.service.js +194 -0
- package/apps/server/dist/env/env.service.js.map +1 -0
- package/apps/server/dist/preview/preview.controller.d.ts +5 -0
- package/apps/server/dist/preview/preview.controller.js +41 -0
- package/apps/server/dist/preview/preview.controller.js.map +1 -1
- package/apps/server/dist/preview/preview.service.d.ts +20 -0
- package/apps/server/dist/preview/preview.service.js +51 -2
- package/apps/server/dist/preview/preview.service.js.map +1 -1
- package/apps/server/dist/project/project.controller.d.ts +10 -1
- package/apps/server/dist/project/project.controller.js +57 -0
- package/apps/server/dist/project/project.controller.js.map +1 -1
- package/apps/server/dist/project/project.service.d.ts +15 -0
- package/apps/server/dist/project/project.service.js +111 -0
- package/apps/server/dist/project/project.service.js.map +1 -1
- package/apps/server/dist/project-context/project-context.controller.d.ts +42 -0
- package/apps/server/dist/project-context/project-context.controller.js +127 -0
- package/apps/server/dist/project-context/project-context.controller.js.map +1 -0
- package/apps/server/dist/project-context/project-context.module.d.ts +2 -0
- package/apps/server/dist/project-context/project-context.module.js +25 -0
- package/apps/server/dist/project-context/project-context.module.js.map +1 -0
- package/apps/server/dist/project-context/project-context.service.d.ts +36 -0
- package/apps/server/dist/project-context/project-context.service.js +260 -0
- package/apps/server/dist/project-context/project-context.service.js.map +1 -0
- package/apps/server/dist/testing/testing.controller.d.ts +24 -0
- package/apps/server/dist/testing/testing.controller.js +126 -0
- package/apps/server/dist/testing/testing.controller.js.map +1 -0
- package/apps/server/dist/testing/testing.module.d.ts +2 -0
- package/apps/server/dist/testing/testing.module.js +26 -0
- package/apps/server/dist/testing/testing.module.js.map +1 -0
- package/apps/server/dist/testing/testing.service.d.ts +62 -0
- package/apps/server/dist/testing/testing.service.js +269 -0
- package/apps/server/dist/testing/testing.service.js.map +1 -0
- package/apps/server/dist/tsconfig.tsbuildinfo +1 -1
- package/apps/server/package.json +1 -1
- package/apps/web/.next/BUILD_ID +1 -1
- package/apps/web/.next/app-build-manifest.json +5 -5
- package/apps/web/.next/build-manifest.json +2 -2
- package/apps/web/.next/cache/.previewinfo +1 -1
- package/apps/web/.next/cache/.rscinfo +1 -1
- package/apps/web/.next/cache/.tsbuildinfo +1 -1
- package/apps/web/.next/cache/config.json +3 -3
- package/apps/web/.next/cache/eslint/.cache_j3uhuz +1 -1
- package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
- package/apps/web/.next/prerender-manifest.json +10 -10
- package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/_not-found.html +1 -1
- package/apps/web/.next/server/app/_not-found.rsc +2 -2
- package/apps/web/.next/server/app/index.html +1 -1
- package/apps/web/.next/server/app/index.rsc +3 -3
- package/apps/web/.next/server/app/page.js +2 -2
- package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/project/[id]/page.js +2 -2
- package/apps/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings.html +1 -1
- package/apps/web/.next/server/app/settings.rsc +3 -3
- package/apps/web/.next/server/chunks/392.js +1 -1
- package/apps/web/.next/server/pages/404.html +1 -1
- package/apps/web/.next/server/pages/500.html +1 -1
- package/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/apps/web/.next/static/chunks/574-1fe2bcd6cfb41646.js +1 -0
- package/apps/web/.next/static/chunks/app/page-f19cfa58541ca83d.js +1 -0
- package/apps/web/.next/static/chunks/app/project/[id]/page-dffaa1d02f012216.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/page-d1318c2fd58729a5.js +1 -0
- package/apps/web/.next/static/css/0a24552d9794f8c8.css +3 -0
- package/apps/web/.next/trace +18 -17
- package/apps/web/node_modules/.bin/eslint +2 -2
- package/apps/web/package.json +2 -1
- package/apps/web/src/components/checkpoint/CheckpointPanel.tsx +384 -0
- package/apps/web/src/components/database/DatabasePanel.tsx +405 -0
- package/apps/web/src/components/env/EnvPanel.tsx +356 -0
- package/apps/web/src/components/preview/ConsoleViewer.tsx +270 -0
- package/apps/web/src/components/preview/ErrorOverlay.tsx +189 -0
- package/apps/web/src/components/preview/PreviewPanel.tsx +148 -6
- package/apps/web/src/components/testing/TestRunner.tsx +481 -0
- package/apps/web/src/components/ui/tabs.tsx +55 -0
- package/apps/web/src/components/visual-editor/VisualEditor.tsx +382 -0
- package/apps/web/src/components/workspace/WorkspaceLayout.tsx +66 -4
- package/apps/web/src/lib/api.ts +5 -2
- package/package.json +1 -1
- package/apps/web/.next/static/chunks/298-6f3d6b321c288cd3.js +0 -1
- package/apps/web/.next/static/chunks/app/page-3d093f7f480a8599.js +0 -1
- package/apps/web/.next/static/chunks/app/project/[id]/page-e5cda6f9050b0a52.js +0 -1
- package/apps/web/.next/static/chunks/app/settings/page-92d28565c3d8c755.js +0 -1
- package/apps/web/.next/static/css/8f946046a2047594.css +0 -3
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_buildManifest.js +0 -0
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Database,
|
|
6
|
+
Table,
|
|
7
|
+
RefreshCw,
|
|
8
|
+
ChevronLeft,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
Plus,
|
|
11
|
+
Trash2,
|
|
12
|
+
AlertCircle,
|
|
13
|
+
Play,
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
import { Button } from "@/components/ui/button";
|
|
16
|
+
import { Input } from "@/components/ui/input";
|
|
17
|
+
import { api } from "@/lib/api";
|
|
18
|
+
|
|
19
|
+
interface TableInfo {
|
|
20
|
+
name: string;
|
|
21
|
+
rowCount: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ColumnInfo {
|
|
25
|
+
name: string;
|
|
26
|
+
type: string;
|
|
27
|
+
nullable: boolean;
|
|
28
|
+
primaryKey: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface TableData {
|
|
32
|
+
columns: ColumnInfo[];
|
|
33
|
+
rows: Record<string, unknown>[];
|
|
34
|
+
total: number;
|
|
35
|
+
page: number;
|
|
36
|
+
pageSize: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface DatabasePanelProps {
|
|
40
|
+
projectId: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function DatabasePanel({ projectId }: DatabasePanelProps) {
|
|
44
|
+
const [tables, setTables] = useState<TableInfo[]>([]);
|
|
45
|
+
const [selectedTable, setSelectedTable] = useState<string | null>(null);
|
|
46
|
+
const [tableData, setTableData] = useState<TableData | null>(null);
|
|
47
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
48
|
+
const [error, setError] = useState<string | null>(null);
|
|
49
|
+
const [page, setPage] = useState(1);
|
|
50
|
+
const [queryMode, setQueryMode] = useState(false);
|
|
51
|
+
const [query, setQuery] = useState("");
|
|
52
|
+
const [queryResult, setQueryResult] = useState<Record<string, unknown>[] | null>(null);
|
|
53
|
+
|
|
54
|
+
const pageSize = 50;
|
|
55
|
+
|
|
56
|
+
// Fetch tables
|
|
57
|
+
const fetchTables = useCallback(async () => {
|
|
58
|
+
setIsLoading(true);
|
|
59
|
+
setError(null);
|
|
60
|
+
try {
|
|
61
|
+
const result = await api.get<TableInfo[]>(`/projects/${projectId}/database/tables`);
|
|
62
|
+
setTables(result);
|
|
63
|
+
if (result.length > 0 && !selectedTable) {
|
|
64
|
+
setSelectedTable(result[0].name);
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
setError(e instanceof Error ? e.message : "Failed to load tables");
|
|
68
|
+
} finally {
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
}
|
|
71
|
+
}, [projectId, selectedTable]);
|
|
72
|
+
|
|
73
|
+
// Fetch table data
|
|
74
|
+
const fetchTableData = useCallback(async () => {
|
|
75
|
+
if (!selectedTable) return;
|
|
76
|
+
|
|
77
|
+
setIsLoading(true);
|
|
78
|
+
setError(null);
|
|
79
|
+
try {
|
|
80
|
+
const result = await api.get<TableData>(
|
|
81
|
+
`/projects/${projectId}/database/tables/${selectedTable}/data?page=${page}&pageSize=${pageSize}`
|
|
82
|
+
);
|
|
83
|
+
setTableData(result);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
setError(e instanceof Error ? e.message : "Failed to load table data");
|
|
86
|
+
} finally {
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
}
|
|
89
|
+
}, [projectId, selectedTable, page]);
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
fetchTables();
|
|
93
|
+
}, [fetchTables]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (selectedTable) {
|
|
97
|
+
setPage(1);
|
|
98
|
+
fetchTableData();
|
|
99
|
+
}
|
|
100
|
+
}, [selectedTable]);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (selectedTable) {
|
|
104
|
+
fetchTableData();
|
|
105
|
+
}
|
|
106
|
+
}, [page, fetchTableData]);
|
|
107
|
+
|
|
108
|
+
const handleSelectTable = (tableName: string) => {
|
|
109
|
+
setSelectedTable(tableName);
|
|
110
|
+
setQueryMode(false);
|
|
111
|
+
setQueryResult(null);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const handleDeleteRow = async (row: Record<string, unknown>) => {
|
|
115
|
+
if (!selectedTable || !tableData) return;
|
|
116
|
+
|
|
117
|
+
const primaryKeyCol = tableData.columns.find((c) => c.primaryKey);
|
|
118
|
+
if (!primaryKeyCol) {
|
|
119
|
+
setError("No primary key found for this table");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!confirm("Are you sure you want to delete this row?")) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await api.delete(`/projects/${projectId}/database/tables/${selectedTable}/rows`, {
|
|
129
|
+
primaryKey: primaryKeyCol.name,
|
|
130
|
+
primaryKeyValue: row[primaryKeyCol.name],
|
|
131
|
+
});
|
|
132
|
+
await fetchTableData();
|
|
133
|
+
} catch (e) {
|
|
134
|
+
setError(e instanceof Error ? e.message : "Failed to delete row");
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const handleExecuteQuery = async () => {
|
|
139
|
+
if (!query.trim()) return;
|
|
140
|
+
|
|
141
|
+
setIsLoading(true);
|
|
142
|
+
setError(null);
|
|
143
|
+
try {
|
|
144
|
+
const result = await api.post<Record<string, unknown>[]>(
|
|
145
|
+
`/projects/${projectId}/database/query`,
|
|
146
|
+
{ query }
|
|
147
|
+
);
|
|
148
|
+
setQueryResult(result);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
setError(e instanceof Error ? e.message : "Query execution failed");
|
|
151
|
+
} finally {
|
|
152
|
+
setIsLoading(false);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const totalPages = tableData ? Math.ceil(tableData.total / pageSize) : 0;
|
|
157
|
+
|
|
158
|
+
if (isLoading && tables.length === 0) {
|
|
159
|
+
return (
|
|
160
|
+
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
161
|
+
<RefreshCw className="h-5 w-5 animate-spin mr-2" />
|
|
162
|
+
Loading database...
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (error && tables.length === 0) {
|
|
168
|
+
return (
|
|
169
|
+
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
170
|
+
<div className="text-center">
|
|
171
|
+
<Database className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
|
172
|
+
<p className="text-sm">{error}</p>
|
|
173
|
+
<Button variant="outline" size="sm" onClick={fetchTables} className="mt-2">
|
|
174
|
+
Retry
|
|
175
|
+
</Button>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="flex h-full flex-col">
|
|
183
|
+
{/* Header */}
|
|
184
|
+
<div className="flex items-center justify-between border-b px-4 py-3">
|
|
185
|
+
<h3 className="font-medium flex items-center gap-2">
|
|
186
|
+
<Database className="h-4 w-4" />
|
|
187
|
+
Database
|
|
188
|
+
</h3>
|
|
189
|
+
<div className="flex items-center gap-2">
|
|
190
|
+
<Button
|
|
191
|
+
variant={queryMode ? "secondary" : "outline"}
|
|
192
|
+
size="sm"
|
|
193
|
+
onClick={() => setQueryMode(!queryMode)}
|
|
194
|
+
className="h-8"
|
|
195
|
+
>
|
|
196
|
+
SQL
|
|
197
|
+
</Button>
|
|
198
|
+
<Button
|
|
199
|
+
variant="ghost"
|
|
200
|
+
size="sm"
|
|
201
|
+
onClick={fetchTables}
|
|
202
|
+
disabled={isLoading}
|
|
203
|
+
className="h-8 w-8 p-0"
|
|
204
|
+
>
|
|
205
|
+
<RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} />
|
|
206
|
+
</Button>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Error */}
|
|
211
|
+
{error && (
|
|
212
|
+
<div className="flex items-center gap-2 px-4 py-2 bg-destructive/10 text-destructive text-sm">
|
|
213
|
+
<AlertCircle className="h-4 w-4" />
|
|
214
|
+
{error}
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
|
|
218
|
+
{/* Query Mode */}
|
|
219
|
+
{queryMode && (
|
|
220
|
+
<div className="border-b p-4 space-y-2">
|
|
221
|
+
<div className="flex gap-2">
|
|
222
|
+
<Input
|
|
223
|
+
value={query}
|
|
224
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
225
|
+
placeholder="SELECT * FROM users LIMIT 10"
|
|
226
|
+
className="font-mono text-sm"
|
|
227
|
+
onKeyDown={(e) => {
|
|
228
|
+
if (e.key === "Enter" && e.metaKey) {
|
|
229
|
+
handleExecuteQuery();
|
|
230
|
+
}
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
<Button onClick={handleExecuteQuery} disabled={isLoading} size="sm">
|
|
234
|
+
<Play className="h-4 w-4 mr-1" />
|
|
235
|
+
Run
|
|
236
|
+
</Button>
|
|
237
|
+
</div>
|
|
238
|
+
<p className="text-xs text-muted-foreground">
|
|
239
|
+
Press Cmd+Enter to execute
|
|
240
|
+
</p>
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
|
|
244
|
+
<div className="flex flex-1 overflow-hidden">
|
|
245
|
+
{/* Table List Sidebar */}
|
|
246
|
+
<div className="w-48 border-r overflow-auto">
|
|
247
|
+
<div className="p-2 space-y-1">
|
|
248
|
+
{tables.map((table) => (
|
|
249
|
+
<button
|
|
250
|
+
key={table.name}
|
|
251
|
+
onClick={() => handleSelectTable(table.name)}
|
|
252
|
+
className={`w-full flex items-center justify-between px-3 py-2 text-sm rounded-md transition-colors ${
|
|
253
|
+
selectedTable === table.name
|
|
254
|
+
? "bg-primary/10 text-primary"
|
|
255
|
+
: "hover:bg-muted"
|
|
256
|
+
}`}
|
|
257
|
+
>
|
|
258
|
+
<span className="flex items-center gap-2 truncate">
|
|
259
|
+
<Table className="h-4 w-4 shrink-0" />
|
|
260
|
+
<span className="truncate">{table.name}</span>
|
|
261
|
+
</span>
|
|
262
|
+
<span className="text-xs text-muted-foreground">{table.rowCount}</span>
|
|
263
|
+
</button>
|
|
264
|
+
))}
|
|
265
|
+
{tables.length === 0 && (
|
|
266
|
+
<p className="text-sm text-muted-foreground px-3 py-2">
|
|
267
|
+
No tables found
|
|
268
|
+
</p>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{/* Data Grid */}
|
|
274
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
275
|
+
{queryMode && queryResult ? (
|
|
276
|
+
// Query Results
|
|
277
|
+
<div className="flex-1 overflow-auto">
|
|
278
|
+
{queryResult.length > 0 ? (
|
|
279
|
+
<table className="w-full text-sm">
|
|
280
|
+
<thead className="bg-muted/50 sticky top-0">
|
|
281
|
+
<tr>
|
|
282
|
+
{Object.keys(queryResult[0]).map((key) => (
|
|
283
|
+
<th
|
|
284
|
+
key={key}
|
|
285
|
+
className="px-3 py-2 text-left font-medium text-muted-foreground border-b"
|
|
286
|
+
>
|
|
287
|
+
{key}
|
|
288
|
+
</th>
|
|
289
|
+
))}
|
|
290
|
+
</tr>
|
|
291
|
+
</thead>
|
|
292
|
+
<tbody>
|
|
293
|
+
{queryResult.map((row, i) => (
|
|
294
|
+
<tr key={i} className="border-b hover:bg-muted/30">
|
|
295
|
+
{Object.values(row).map((value, j) => (
|
|
296
|
+
<td key={j} className="px-3 py-2 font-mono">
|
|
297
|
+
{value === null ? (
|
|
298
|
+
<span className="text-muted-foreground">NULL</span>
|
|
299
|
+
) : (
|
|
300
|
+
String(value)
|
|
301
|
+
)}
|
|
302
|
+
</td>
|
|
303
|
+
))}
|
|
304
|
+
</tr>
|
|
305
|
+
))}
|
|
306
|
+
</tbody>
|
|
307
|
+
</table>
|
|
308
|
+
) : (
|
|
309
|
+
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
310
|
+
Query returned no results
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
314
|
+
) : tableData ? (
|
|
315
|
+
// Table Data
|
|
316
|
+
<>
|
|
317
|
+
<div className="flex-1 overflow-auto">
|
|
318
|
+
<table className="w-full text-sm">
|
|
319
|
+
<thead className="bg-muted/50 sticky top-0">
|
|
320
|
+
<tr>
|
|
321
|
+
{tableData.columns.map((col) => (
|
|
322
|
+
<th
|
|
323
|
+
key={col.name}
|
|
324
|
+
className="px-3 py-2 text-left font-medium text-muted-foreground border-b"
|
|
325
|
+
>
|
|
326
|
+
<div className="flex items-center gap-1">
|
|
327
|
+
{col.name}
|
|
328
|
+
{col.primaryKey && (
|
|
329
|
+
<span className="text-xs text-primary">PK</span>
|
|
330
|
+
)}
|
|
331
|
+
</div>
|
|
332
|
+
<span className="text-xs font-normal">{col.type}</span>
|
|
333
|
+
</th>
|
|
334
|
+
))}
|
|
335
|
+
<th className="w-10 border-b"></th>
|
|
336
|
+
</tr>
|
|
337
|
+
</thead>
|
|
338
|
+
<tbody>
|
|
339
|
+
{tableData.rows.map((row, i) => (
|
|
340
|
+
<tr key={i} className="border-b hover:bg-muted/30">
|
|
341
|
+
{tableData.columns.map((col) => (
|
|
342
|
+
<td key={col.name} className="px-3 py-2 font-mono">
|
|
343
|
+
{row[col.name] === null ? (
|
|
344
|
+
<span className="text-muted-foreground">NULL</span>
|
|
345
|
+
) : (
|
|
346
|
+
String(row[col.name])
|
|
347
|
+
)}
|
|
348
|
+
</td>
|
|
349
|
+
))}
|
|
350
|
+
<td className="px-2">
|
|
351
|
+
<Button
|
|
352
|
+
variant="ghost"
|
|
353
|
+
size="sm"
|
|
354
|
+
onClick={() => handleDeleteRow(row)}
|
|
355
|
+
className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive"
|
|
356
|
+
>
|
|
357
|
+
<Trash2 className="h-3 w-3" />
|
|
358
|
+
</Button>
|
|
359
|
+
</td>
|
|
360
|
+
</tr>
|
|
361
|
+
))}
|
|
362
|
+
</tbody>
|
|
363
|
+
</table>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
{/* Pagination */}
|
|
367
|
+
<div className="flex items-center justify-between border-t px-4 py-2">
|
|
368
|
+
<span className="text-sm text-muted-foreground">
|
|
369
|
+
{tableData.total} rows total
|
|
370
|
+
</span>
|
|
371
|
+
<div className="flex items-center gap-2">
|
|
372
|
+
<Button
|
|
373
|
+
variant="outline"
|
|
374
|
+
size="sm"
|
|
375
|
+
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
|
376
|
+
disabled={page === 1}
|
|
377
|
+
className="h-8 w-8 p-0"
|
|
378
|
+
>
|
|
379
|
+
<ChevronLeft className="h-4 w-4" />
|
|
380
|
+
</Button>
|
|
381
|
+
<span className="text-sm">
|
|
382
|
+
{page} / {totalPages || 1}
|
|
383
|
+
</span>
|
|
384
|
+
<Button
|
|
385
|
+
variant="outline"
|
|
386
|
+
size="sm"
|
|
387
|
+
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
|
388
|
+
disabled={page >= totalPages}
|
|
389
|
+
className="h-8 w-8 p-0"
|
|
390
|
+
>
|
|
391
|
+
<ChevronRight className="h-4 w-4" />
|
|
392
|
+
</Button>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</>
|
|
396
|
+
) : (
|
|
397
|
+
<div className="flex h-full items-center justify-center text-muted-foreground">
|
|
398
|
+
Select a table to view data
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
405
|
+
}
|