fix(databases): apply Flowbite colors to data table viewer
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { THEME } from "@/components/project/dashboard-ui";
|
||||
import { Loader2, AlertCircle, Info } from "lucide-react";
|
||||
|
||||
interface PreviewedTable {
|
||||
@@ -37,32 +38,51 @@ export function TableViewer({ projectId, dbUuid, schema, table }: Props) {
|
||||
setError(null);
|
||||
setData(null);
|
||||
|
||||
const url = `/api/projects/${projectId}/databases/${dbUuid}/preview` +
|
||||
const url =
|
||||
`/api/projects/${projectId}/databases/${dbUuid}/preview` +
|
||||
`?schema=${encodeURIComponent(schema)}&table=${encodeURIComponent(table)}`;
|
||||
|
||||
fetch(url, { credentials: "include", signal: ctrl.signal })
|
||||
.then(async r => {
|
||||
.then(async (r) => {
|
||||
let body: unknown = {};
|
||||
try { body = await r.json(); } catch {/* keep {} */}
|
||||
if (!r.ok) throw new Error((body as { error?: string }).error || `HTTP ${r.status}`);
|
||||
try {
|
||||
body = await r.json();
|
||||
} catch {
|
||||
/* keep {} */
|
||||
}
|
||||
if (!r.ok)
|
||||
throw new Error(
|
||||
(body as { error?: string }).error || `HTTP ${r.status}`,
|
||||
);
|
||||
return body as PreviewedTable;
|
||||
})
|
||||
.then(d => { if (!cancelled) setData(d); })
|
||||
.catch(err => {
|
||||
.then((d) => {
|
||||
if (!cancelled) setData(d);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (cancelled) return;
|
||||
if (err?.name === "AbortError") setError("Timed out after 12s.");
|
||||
else setError(err?.message || "Failed to load preview");
|
||||
})
|
||||
.finally(() => { clearTimeout(t); if (!cancelled) setLoading(false); });
|
||||
.finally(() => {
|
||||
clearTimeout(t);
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
|
||||
return () => { cancelled = true; ctrl.abort(); clearTimeout(t); };
|
||||
return () => {
|
||||
cancelled = true;
|
||||
ctrl.abort();
|
||||
clearTimeout(t);
|
||||
};
|
||||
}, [projectId, dbUuid, schema, table]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={center}>
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
<span style={{ marginLeft: 8 }}>Querying {schema}.{table}…</span>
|
||||
<span style={{ marginLeft: 8 }}>
|
||||
Querying {schema}.{table}…
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -88,23 +108,27 @@ export function TableViewer({ projectId, dbUuid, schema, table }: Props) {
|
||||
<div style={wrap}>
|
||||
<div style={meta}>
|
||||
Showing {data.rows.length} row{data.rows.length === 1 ? "" : "s"}
|
||||
{data.truncated && " (truncated to first 50)"} ·{" "}
|
||||
{data.columns.length} column{data.columns.length === 1 ? "" : "s"} ·{" "}
|
||||
<code style={qual}>{schema}.{table}</code>
|
||||
{data.truncated && " (truncated to first 50)"} · {data.columns.length}{" "}
|
||||
column{data.columns.length === 1 ? "" : "s"} ·{" "}
|
||||
<code style={qual}>
|
||||
{schema}.{table}
|
||||
</code>
|
||||
</div>
|
||||
<div style={tableScroll}>
|
||||
<table style={tableEl}>
|
||||
<thead>
|
||||
<tr>
|
||||
{data.columns.map(c => (
|
||||
<th key={c} style={th}>{c}</th>
|
||||
{data.columns.map((c) => (
|
||||
<th key={c} style={th}>
|
||||
{c}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.rows.map((row, i) => (
|
||||
<tr key={i} style={i % 2 === 0 ? trEven : trOdd}>
|
||||
{data.columns.map(c => (
|
||||
{data.columns.map((c) => (
|
||||
<td key={c} style={td} title={row[c]}>
|
||||
{row[c]}
|
||||
</td>
|
||||
@@ -129,52 +153,83 @@ const INK = {
|
||||
} as const;
|
||||
|
||||
const wrap: React.CSSProperties = {
|
||||
display: "flex", flexDirection: "column", gap: 8, minHeight: 0, flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 8,
|
||||
minHeight: 0,
|
||||
flex: 1,
|
||||
};
|
||||
const meta: React.CSSProperties = {
|
||||
fontSize: "0.74rem", color: INK.mid,
|
||||
fontSize: "0.74rem",
|
||||
color: INK.mid,
|
||||
};
|
||||
const qual: React.CSSProperties = {
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
||||
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
||||
color: INK.ink,
|
||||
};
|
||||
const tableScroll: React.CSSProperties = {
|
||||
flex: 1, minHeight: 0, overflow: "auto",
|
||||
border: `1px solid ${INK.borderSoft}`, borderRadius: 6,
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
overflow: "auto",
|
||||
border: `1px solid ${INK.borderSoft}`,
|
||||
borderRadius: 6,
|
||||
};
|
||||
const tableEl: React.CSSProperties = {
|
||||
borderCollapse: "collapse",
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
||||
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
||||
fontSize: "0.76rem",
|
||||
width: "100%",
|
||||
};
|
||||
const th: React.CSSProperties = {
|
||||
position: "sticky", top: 0,
|
||||
textAlign: "left", padding: "6px 10px",
|
||||
background: "#fafaf6", color: INK.ink,
|
||||
fontWeight: 600, fontSize: "0.72rem",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
textAlign: "left",
|
||||
padding: "6px 10px",
|
||||
background: "#fafaf6",
|
||||
color: INK.ink,
|
||||
fontWeight: 600,
|
||||
fontSize: "0.72rem",
|
||||
borderBottom: `1px solid ${INK.border}`,
|
||||
whiteSpace: "nowrap",
|
||||
};
|
||||
const td: React.CSSProperties = {
|
||||
padding: "5px 10px", color: INK.ink,
|
||||
padding: "5px 10px",
|
||||
color: INK.ink,
|
||||
borderBottom: `1px solid ${INK.borderSoft}`,
|
||||
whiteSpace: "nowrap", maxWidth: 360,
|
||||
overflow: "hidden", textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: 360,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
};
|
||||
const trEven: React.CSSProperties = { background: "#fff" };
|
||||
const trOdd: React.CSSProperties = { background: "#fcfaf3" };
|
||||
const center: React.CSSProperties = {
|
||||
flex: 1, display: "flex", alignItems: "center", justifyContent: "center",
|
||||
color: INK.mid, fontSize: "0.85rem",
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: INK.mid,
|
||||
fontSize: "0.85rem",
|
||||
};
|
||||
const errorBox: React.CSSProperties = {
|
||||
display: "flex", alignItems: "center", gap: 6,
|
||||
padding: "10px 12px", fontSize: "0.82rem", color: "#7a1f15",
|
||||
background: "#fbe9e7", border: `1px solid #f4c2bc`, borderRadius: 8,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
padding: "10px 12px",
|
||||
fontSize: "0.82rem",
|
||||
color: "#7a1f15",
|
||||
background: "#fbe9e7",
|
||||
border: `1px solid #f4c2bc`,
|
||||
borderRadius: 8,
|
||||
};
|
||||
const infoBox: React.CSSProperties = {
|
||||
display: "flex", alignItems: "center", gap: 6,
|
||||
padding: "10px 12px", fontSize: "0.82rem", color: INK.mid,
|
||||
background: "#fafaf6", border: `1px dashed ${INK.borderSoft}`, borderRadius: 8,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
padding: "10px 12px",
|
||||
fontSize: "0.82rem",
|
||||
color: INK.mid,
|
||||
background: "#fafaf6",
|
||||
border: `1px dashed ${INK.borderSoft}`,
|
||||
borderRadius: 8,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user