
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
Visual database schema inspector and SQL query previewer with cluster health header, table navigation sidebar with row counts, columns metadata table (data types, nullability, defaults, PK/FK indicators), foreign keys diagram, B-Tree indexes, and syntax-styled DDL preview with copy actions.
Also available for Vue ->$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/database-schema-viewer.json$npx shadcn@latest add https://uipkge.dev/r/react/database-schema-viewer.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/database-schema-viewer.json$bunx shadcn@latest add https://uipkge.dev/r/react/database-schema-viewer.jsonnpx shadcn@latest add @uipkge-react/database-schema-viewerInstalls to:components/blocks/| Name | Type / Values | Default | Required |
|---|---|---|---|
className | string | — | optional |
Type aliases exported from this item's source. Use these to shape the data you pass in.
ColumnDefinitioninterface ColumnDefinition {
name: string
type: string
nullable: boolean
defaultValue: string | null
isPrimaryKey?: boolean
isForeignKey?: boolean
foreignKeyRef?: string
description?: string
}ForeignKeyDefinitioninterface ForeignKeyDefinition {
name: string
column: string
foreignTable: string
foreignColumn: string
onDelete: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION'
onUpdate: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION'
}IndexDefinitioninterface IndexDefinition {
name: string
type: 'B-Tree' | 'GIN' | 'GiST' | 'Hash'
columns: string[]
size: string
isUnique?: boolean
isPrimary?: boolean
}TableSchemainterface TableSchema {
name: string
schema: string
rowCount: number
rowCountFormatted: string
size: string
primaryKey: string
lastAnalyzed: string
description: string
columns: ColumnDefinition[]
foreignKeys: ForeignKeyDefinition[]
indexes: IndexDefinition[]
ddl: string
}'use client'
import * as React from 'react'
import {
ArrowRight,
Check,
Clock,
Code2,
Copy,
Database,
Download,
HardDrive,
Key,
Layers,
Link2,
Search,
Table2,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Separator } from '@/components/ui/separator'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
export interface ColumnDefinition {
name: string
type: string
nullable: boolean
defaultValue: string | null
isPrimaryKey?: boolean
isForeignKey?: boolean
foreignKeyRef?: string
description?: string
}
export interface ForeignKeyDefinition {
name: string
column: string
foreignTable: string
foreignColumn: string
onDelete: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION'
onUpdate: 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'NO ACTION'
}
export interface IndexDefinition {
name: string
type: 'B-Tree' | 'GIN' | 'GiST' | 'Hash'
columns: string[]
size: string
isUnique?: boolean
isPrimary?: boolean
}
export interface TableSchema {
name: string
schema: string
rowCount: number
rowCountFormatted: string
size: string
primaryKey: string
lastAnalyzed: string
description: string
columns: ColumnDefinition[]
foreignKeys: ForeignKeyDefinition[]
indexes: IndexDefinition[]
ddl: string
}
export interface DatabaseSchemaViewerProps {
className?: string
}
const tables: TableSchema[] = [
{
name: 'users',
schema: 'public',
rowCount: 42850,
rowCountFormatted: '42,850 rows',
size: '18.4 MB',
primaryKey: 'id (UUID)',
lastAnalyzed: '12 mins ago',
description: 'User accounts, authentication identities, role assignments, and profile metadata.',
columns: [
{
name: 'id',
type: 'uuid',
nullable: false,
defaultValue: 'gen_random_uuid()',
isPrimaryKey: true,
description: 'Unique identifier for the user',
},
{
name: 'email',
type: 'varchar(255)',
nullable: false,
defaultValue: null,
description: 'Verified primary contact email',
},
{
name: 'encrypted_password',
type: 'varchar(255)',
nullable: false,
defaultValue: null,
description: 'Bcrypt hashed password digest',
},
{
name: 'full_name',
type: 'varchar(100)',
nullable: true,
defaultValue: null,
description: 'Display name or legal identity',
},
{
name: 'role',
type: 'varchar(32)',
nullable: false,
defaultValue: "'member'",
description: 'System authorization role',
},
{
name: 'avatar_url',
type: 'text',
nullable: true,
defaultValue: null,
description: 'Remote profile avatar image URL',
},
{
name: 'is_verified',
type: 'boolean',
nullable: false,
defaultValue: 'false',
description: 'Email verification status flag',
},
{
name: 'created_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Account creation timestamp',
},
{
name: 'updated_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Last record modification timestamp',
},
],
foreignKeys: [],
indexes: [
{ name: 'users_pkey', type: 'B-Tree', columns: ['id'], size: '1.2 MB', isPrimary: true, isUnique: true },
{ name: 'idx_users_email', type: 'B-Tree', columns: ['email'], size: '940 KB', isUnique: true },
{ name: 'idx_users_role_created', type: 'B-Tree', columns: ['role', 'created_at DESC'], size: '1.8 MB' },
],
ddl: `CREATE TABLE public.users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email varchar(255) NOT NULL UNIQUE,
encrypted_password varchar(255) NOT NULL,
full_name varchar(100),
role varchar(32) NOT NULL DEFAULT 'member',
avatar_url text,
is_verified boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_users_role CHECK (role IN ('admin', 'member', 'viewer'))
);`,
},
{
name: 'orders',
schema: 'public',
rowCount: 148290,
rowCountFormatted: '148,290 rows',
size: '64.2 MB',
primaryKey: 'id (UUID)',
lastAnalyzed: '2 mins ago',
description: 'Customer transactional purchase orders, fulfillment status, and payment totals.',
columns: [
{
name: 'id',
type: 'uuid',
nullable: false,
defaultValue: 'gen_random_uuid()',
isPrimaryKey: true,
description: 'Unique order identifier',
},
{
name: 'order_number',
type: 'varchar(48)',
nullable: false,
defaultValue: null,
description: 'Human-readable sequential invoice reference',
},
{
name: 'user_id',
type: 'uuid',
nullable: false,
defaultValue: null,
isForeignKey: true,
foreignKeyRef: 'users.id',
description: 'Purchaser account reference',
},
{
name: 'status',
type: 'varchar(32)',
nullable: false,
defaultValue: "'pending'",
description: 'Order lifecycle stage',
},
{
name: 'total_amount',
type: 'numeric(10,2)',
nullable: false,
defaultValue: '0.00',
description: 'Grand total charged in base currency',
},
{
name: 'currency',
type: 'varchar(3)',
nullable: false,
defaultValue: "'USD'",
description: 'ISO 4217 three-letter currency code',
},
{
name: 'billing_address',
type: 'jsonb',
nullable: true,
defaultValue: "'{}'::jsonb",
description: 'Structured billing snapshot',
},
{
name: 'shipping_address',
type: 'jsonb',
nullable: true,
defaultValue: "'{}'::jsonb",
description: 'Structured destination address',
},
{
name: 'shipped_at',
type: 'timestamptz',
nullable: true,
defaultValue: null,
description: 'Carrier pickup confirmation date',
},
{
name: 'created_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Checkout completion timestamp',
},
{
name: 'updated_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Order state mutation timestamp',
},
],
foreignKeys: [
{
name: 'fk_orders_user_id',
column: 'user_id',
foreignTable: 'users',
foreignColumn: 'id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
],
indexes: [
{ name: 'orders_pkey', type: 'B-Tree', columns: ['id'], size: '4.8 MB', isPrimary: true, isUnique: true },
{ name: 'idx_orders_user_id', type: 'B-Tree', columns: ['user_id'], size: '3.4 MB' },
{ name: 'idx_orders_status_created', type: 'B-Tree', columns: ['status', 'created_at DESC'], size: '5.1 MB' },
{ name: 'idx_orders_number_unique', type: 'B-Tree', columns: ['order_number'], size: '3.2 MB', isUnique: true },
],
ddl: `CREATE TABLE public.orders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
order_number varchar(48) NOT NULL UNIQUE,
user_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE ON UPDATE CASCADE,
status varchar(32) NOT NULL DEFAULT 'pending',
total_amount numeric(10,2) NOT NULL DEFAULT 0.00,
currency varchar(3) NOT NULL DEFAULT 'USD',
billing_address jsonb DEFAULT '{}'::jsonb,
shipping_address jsonb DEFAULT '{}'::jsonb,
shipped_at timestamptz,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);`,
},
{
name: 'order_items',
schema: 'public',
rowCount: 384120,
rowCountFormatted: '384,120 rows',
size: '112.5 MB',
primaryKey: 'id (UUID)',
lastAnalyzed: '8 mins ago',
description: 'Granular line items and unit pricing linked to purchase orders.',
columns: [
{
name: 'id',
type: 'uuid',
nullable: false,
defaultValue: 'gen_random_uuid()',
isPrimaryKey: true,
description: 'Line item primary key',
},
{
name: 'order_id',
type: 'uuid',
nullable: false,
defaultValue: null,
isForeignKey: true,
foreignKeyRef: 'orders.id',
description: 'Parent order reference',
},
{
name: 'product_id',
type: 'uuid',
nullable: false,
defaultValue: null,
isForeignKey: true,
foreignKeyRef: 'products.id',
description: 'Catalog item reference',
},
{ name: 'quantity', type: 'integer', nullable: false, defaultValue: '1', description: 'Units purchased' },
{
name: 'unit_price',
type: 'numeric(10,2)',
nullable: false,
defaultValue: '0.00',
description: 'Unit price captured at purchase',
},
{
name: 'discount_amount',
type: 'numeric(10,2)',
nullable: false,
defaultValue: '0.00',
description: 'Item level discount applied',
},
{
name: 'created_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Record insertion timestamp',
},
],
foreignKeys: [
{
name: 'fk_order_items_order_id',
column: 'order_id',
foreignTable: 'orders',
foreignColumn: 'id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
{
name: 'fk_order_items_product_id',
column: 'product_id',
foreignTable: 'products',
foreignColumn: 'id',
onDelete: 'RESTRICT',
onUpdate: 'CASCADE',
},
],
indexes: [
{ name: 'order_items_pkey', type: 'B-Tree', columns: ['id'], size: '12.1 MB', isPrimary: true, isUnique: true },
{ name: 'idx_order_items_order_id', type: 'B-Tree', columns: ['order_id'], size: '8.9 MB' },
{ name: 'idx_order_items_product_id', type: 'B-Tree', columns: ['product_id'], size: '8.4 MB' },
],
ddl: `CREATE TABLE public.order_items (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
order_id uuid NOT NULL REFERENCES public.orders(id) ON DELETE CASCADE ON UPDATE CASCADE,
product_id uuid NOT NULL REFERENCES public.products(id) ON DELETE RESTRICT ON UPDATE CASCADE,
quantity integer NOT NULL DEFAULT 1,
unit_price numeric(10,2) NOT NULL DEFAULT 0.00,
discount_amount numeric(10,2) NOT NULL DEFAULT 0.00,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);`,
},
{
name: 'products',
schema: 'public',
rowCount: 12400,
rowCountFormatted: '12,400 rows',
size: '9.8 MB',
primaryKey: 'id (UUID)',
lastAnalyzed: '45 mins ago',
description: 'Catalog products, SKU identifiers, real-time inventory counts, and price tiers.',
columns: [
{
name: 'id',
type: 'uuid',
nullable: false,
defaultValue: 'gen_random_uuid()',
isPrimaryKey: true,
description: 'Product primary key',
},
{
name: 'sku',
type: 'varchar(64)',
nullable: false,
defaultValue: null,
description: 'Stock keeping unit barcode index',
},
{ name: 'name', type: 'varchar(255)', nullable: false, defaultValue: null, description: 'Public product title' },
{
name: 'description',
type: 'text',
nullable: true,
defaultValue: null,
description: 'Detailed marketing specifications',
},
{ name: 'price', type: 'numeric(10,2)', nullable: false, defaultValue: '0.00', description: 'Base retail price' },
{
name: 'stock_quantity',
type: 'integer',
nullable: false,
defaultValue: '0',
description: 'Available warehouse stock',
},
{
name: 'is_active',
type: 'boolean',
nullable: false,
defaultValue: 'true',
description: 'Visibility in storefront',
},
{
name: 'metadata',
type: 'jsonb',
nullable: true,
defaultValue: "'{}'::jsonb",
description: 'Custom attributes and tags',
},
{
name: 'created_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Product onboarding timestamp',
},
{
name: 'updated_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Last catalog revision',
},
],
foreignKeys: [],
indexes: [
{ name: 'products_pkey', type: 'B-Tree', columns: ['id'], size: '380 KB', isPrimary: true, isUnique: true },
{ name: 'idx_products_sku', type: 'B-Tree', columns: ['sku'], size: '290 KB', isUnique: true },
{ name: 'idx_products_is_active', type: 'B-Tree', columns: ['is_active'], size: '180 KB' },
],
ddl: `CREATE TABLE public.products (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
sku varchar(64) NOT NULL UNIQUE,
name varchar(255) NOT NULL,
description text,
price numeric(10,2) NOT NULL DEFAULT 0.00,
stock_quantity integer NOT NULL DEFAULT 0,
is_active boolean NOT NULL DEFAULT true,
metadata jsonb DEFAULT '{}'::jsonb,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);`,
},
{
name: 'subscriptions',
schema: 'public',
rowCount: 19850,
rowCountFormatted: '19,850 rows',
size: '14.1 MB',
primaryKey: 'id (UUID)',
lastAnalyzed: '18 mins ago',
description: 'Recurring billing contracts, subscription plans, and renewal period bounds.',
columns: [
{
name: 'id',
type: 'uuid',
nullable: false,
defaultValue: 'gen_random_uuid()',
isPrimaryKey: true,
description: 'Subscription contract ID',
},
{
name: 'user_id',
type: 'uuid',
nullable: false,
defaultValue: null,
isForeignKey: true,
foreignKeyRef: 'users.id',
description: 'Subscriber user ID',
},
{
name: 'plan_tier',
type: 'varchar(32)',
nullable: false,
defaultValue: "'starter'",
description: 'SaaS plan designation',
},
{
name: 'status',
type: 'varchar(32)',
nullable: false,
defaultValue: "'active'",
description: 'Lifecycle status',
},
{
name: 'current_period_start',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Current billing window start',
},
{
name: 'current_period_end',
type: 'timestamptz',
nullable: false,
defaultValue: null,
description: 'Next scheduled billing date',
},
{
name: 'cancel_at_period_end',
type: 'boolean',
nullable: false,
defaultValue: 'false',
description: 'Cancellation intent at renewal',
},
{
name: 'created_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Initial signup timestamp',
},
],
foreignKeys: [
{
name: 'fk_subscriptions_user_id',
column: 'user_id',
foreignTable: 'users',
foreignColumn: 'id',
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
],
indexes: [
{ name: 'subscriptions_pkey', type: 'B-Tree', columns: ['id'], size: '620 KB', isPrimary: true, isUnique: true },
{ name: 'idx_subscriptions_user_id', type: 'B-Tree', columns: ['user_id'], size: '540 KB' },
{ name: 'idx_subscriptions_status', type: 'B-Tree', columns: ['status'], size: '410 KB' },
],
ddl: `CREATE TABLE public.subscriptions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE ON UPDATE CASCADE,
plan_tier varchar(32) NOT NULL DEFAULT 'starter',
status varchar(32) NOT NULL DEFAULT 'active',
current_period_start timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
current_period_end timestamptz NOT NULL,
cancel_at_period_end boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);`,
},
{
name: 'audit_logs',
schema: 'public',
rowCount: 920400,
rowCountFormatted: '920,400 rows',
size: '201.0 MB',
primaryKey: 'id (UUID)',
lastAnalyzed: '1 min ago',
description: 'Immutable security event stream, compliance audit records, and state change captures.',
columns: [
{
name: 'id',
type: 'uuid',
nullable: false,
defaultValue: 'gen_random_uuid()',
isPrimaryKey: true,
description: 'Event ledger entry identifier',
},
{
name: 'actor_id',
type: 'uuid',
nullable: true,
defaultValue: null,
isForeignKey: true,
foreignKeyRef: 'users.id',
description: 'Initiating user account reference',
},
{
name: 'action',
type: 'varchar(64)',
nullable: false,
defaultValue: null,
description: 'Standardized action identifier',
},
{
name: 'entity_type',
type: 'varchar(64)',
nullable: false,
defaultValue: null,
description: 'Target entity namespace',
},
{
name: 'entity_id',
type: 'varchar(128)',
nullable: false,
defaultValue: null,
description: 'Target entity unique identifier',
},
{ name: 'ip_address', type: 'inet', nullable: true, defaultValue: null, description: 'Client network IP' },
{
name: 'payload',
type: 'jsonb',
nullable: true,
defaultValue: "'{}'::jsonb",
description: 'Full before/after state diff',
},
{
name: 'created_at',
type: 'timestamptz',
nullable: false,
defaultValue: 'CURRENT_TIMESTAMP',
description: 'Exact audit record timestamp',
},
],
foreignKeys: [
{
name: 'fk_audit_logs_actor_id',
column: 'actor_id',
foreignTable: 'users',
foreignColumn: 'id',
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
},
],
indexes: [
{ name: 'audit_logs_pkey', type: 'B-Tree', columns: ['id'], size: '28.4 MB', isPrimary: true, isUnique: true },
{ name: 'idx_audit_logs_actor_id', type: 'B-Tree', columns: ['actor_id'], size: '19.2 MB' },
{
name: 'idx_audit_logs_action_created',
type: 'B-Tree',
columns: ['action', 'created_at DESC'],
size: '32.6 MB',
},
{ name: 'idx_audit_logs_entity', type: 'B-Tree', columns: ['entity_type', 'entity_id'], size: '24.1 MB' },
],
ddl: `CREATE TABLE public.audit_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id uuid REFERENCES public.users(id) ON DELETE SET NULL ON UPDATE CASCADE,
action varchar(64) NOT NULL,
entity_type varchar(64) NOT NULL,
entity_id varchar(128) NOT NULL,
ip_address inet,
payload jsonb DEFAULT '{}'::jsonb,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);`,
},
]
function formatRowCount(count: number): string {
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`
if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k`
return String(count)
}
export function DatabaseSchemaViewer({ className }: DatabaseSchemaViewerProps) {
const [searchQuery, setSearchQuery] = React.useState('')
const [selectedTableName, setSelectedTableName] = React.useState('orders')
const [activeTab, setActiveTab] = React.useState('columns')
const [copiedDDL, setCopiedDDL] = React.useState(false)
const [exportedAll, setExportedAll] = React.useState(false)
const filteredTables = React.useMemo(() => {
const query = searchQuery.trim().toLowerCase()
if (!query) return tables
return tables.filter(
(table) =>
table.name.toLowerCase().includes(query) ||
table.description.toLowerCase().includes(query) ||
table.columns.some((col) => col.name.toLowerCase().includes(query)),
)
}, [searchQuery])
const activeTable = React.useMemo(() => {
return tables.find((t) => t.name === selectedTableName) ?? tables[0]
}, [selectedTableName])
const copyToClipboard = React.useCallback((text: string) => {
if (typeof navigator !== 'undefined' && navigator.clipboard) {
navigator.clipboard.writeText(text)
setCopiedDDL(true)
setTimeout(() => {
setCopiedDDL(false)
}, 2000)
}
}, [])
const exportAllDDL = React.useCallback(() => {
const allSql = tables.map((t) => `-- Table: ${t.name}\n${t.ddl}`).join('\n\n')
if (typeof navigator !== 'undefined' && navigator.clipboard) {
navigator.clipboard.writeText(allSql)
setExportedAll(true)
setTimeout(() => {
setExportedAll(false)
}, 2000)
}
}, [])
return (
<div
data-slot="database-schema-viewer"
className={cn(
'bg-background text-foreground border-border w-full overflow-hidden rounded-xl border shadow-xs',
className,
)}
>
{/* Top Database Header */}
<header className="border-border bg-card/60 border-b px-4 py-3.5 sm:px-6">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="flex flex-wrap items-center gap-3">
<div className="bg-primary/10 text-primary flex size-9 items-center justify-center rounded-lg">
<Database className="size-4.5" />
</div>
<div className="flex flex-col">
<div className="flex flex-wrap items-center gap-2">
<span className="font-mono text-sm font-semibold tracking-tight">production-cluster-pg16</span>
<span className="bg-success flex size-2 rounded-full" title="Connected & Synced" />
<Badge variant="secondary" className="font-mono text-xs font-normal">
PostgreSQL 16.2
</Badge>
</div>
<span className="text-muted-foreground text-xs">18 tables · 420 MB</span>
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<div className="relative w-full sm:w-60">
<Search className="text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2" />
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search tables or columns..."
className="h-8 pl-8 text-xs"
/>
</div>
<Button
aria-label="Download attachment"
variant="outline"
size="sm"
className="h-8 shrink-0 gap-1.5 text-xs"
onClick={exportAllDDL}
>
{exportedAll ? <Check className="text-success size-3.5" /> : <Download className="size-3.5" />}
{exportedAll ? 'DDL Copied!' : 'Export DDL'}
</Button>
</div>
</div>
</header>
{/* Two-Column Inspector Body */}
<div className="grid grid-cols-1 md:grid-cols-[240px_1fr] lg:grid-cols-[260px_1fr]">
{/* Left Sidebar: Tables List */}
<aside className="border-border bg-muted/20 border-r p-3 sm:p-4">
<div className="mb-2.5 flex items-center justify-between gap-x-2 px-1">
<span className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
Database Tables
</span>
<Badge variant="secondary" className="h-5 px-1.5 font-mono text-xs">
{filteredTables.length}
</Badge>
</div>
<div className="space-y-1">
{filteredTables.map((table) => (
<button
key={table.name}
type="button"
className={cn(
'flex w-full items-center justify-between rounded-lg px-2.5 py-2 text-left text-xs transition-colors',
selectedTableName === table.name
? 'bg-primary/10 text-primary border-primary/20 border font-medium shadow-xs'
: 'text-muted-foreground hover:bg-muted/70 hover:text-foreground border border-transparent',
)}
onClick={() => setSelectedTableName(table.name)}
>
<div className="flex min-w-0 items-center gap-2">
<Table2 className="size-3.5 shrink-0 opacity-70" />
<span className="truncate font-mono">{table.name}</span>
</div>
<span className="bg-muted text-muted-foreground ml-2 shrink-0 rounded-full px-1.5 py-0.5 font-mono text-xs">
{formatRowCount(table.rowCount)}
</span>
</button>
))}
{filteredTables.length === 0 && (
<div className="text-muted-foreground py-6 text-center text-xs">No matching tables found</div>
)}
</div>
<Separator className="my-4" />
<div className="border-border/70 bg-card space-y-1.5 rounded-lg border p-3 text-xs">
<div className="text-muted-foreground flex items-center justify-between gap-x-2">
<span>Encoding:</span>
<span className="text-foreground font-mono">UTF8</span>
</div>
<div className="text-muted-foreground flex items-center justify-between gap-x-2">
<span>Collation:</span>
<span className="text-foreground font-mono">en_US.utf8</span>
</div>
<div className="text-muted-foreground flex items-center justify-between gap-x-2">
<span>Default Schema:</span>
<span className="text-foreground font-mono">public</span>
</div>
</div>
</aside>
{/* Right Main Panel: Table Detail Inspector */}
<main className="bg-card/30 min-w-0 space-y-5 p-4 sm:p-6">
{/* Active Table Header & Stats */}
<div className="border-border space-y-3 border-b pb-2">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-1">
<div className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground font-mono text-sm">public.</span>
<h2 className="text-foreground font-mono text-xl font-bold tracking-tight break-all">
{activeTable.name}
</h2>
<Badge variant="outline" className="font-mono text-xs font-normal">
BASE TABLE
</Badge>
</div>
<p className="text-muted-foreground text-xs">{activeTable.description}</p>
</div>
<Button
variant="outline"
size="sm"
className="h-8 shrink-0 gap-1.5 self-start text-xs sm:self-auto"
onClick={() => copyToClipboard(activeTable.ddl)}
>
{copiedDDL ? <Check className="text-success size-3.5" /> : <Copy className="size-3.5" />}
{copiedDDL ? 'Copied DDL' : 'Copy Table DDL'}
</Button>
</div>
{/* Metadata Stat Badges */}
<div className="flex flex-wrap items-center gap-2 pt-1">
<div className="border-border bg-card text-muted-foreground inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs">
<Table2 className="text-foreground/70 size-3.5" />
<span>Rows:</span>
<span className="text-foreground font-mono font-medium">{activeTable.rowCountFormatted}</span>
</div>
<div className="border-border bg-card text-muted-foreground inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs">
<Key className="text-warning size-3.5" />
<span>Primary Key:</span>
<span className="text-foreground font-mono font-medium">{activeTable.primaryKey}</span>
</div>
<div className="border-border bg-card text-muted-foreground inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs">
<HardDrive className="text-foreground/70 size-3.5" />
<span>Table Size:</span>
<span className="text-foreground font-mono font-medium">{activeTable.size}</span>
</div>
<div className="border-border bg-card text-muted-foreground inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs">
<Clock className="text-foreground/70 size-3.5" />
<span>Last Analyzed:</span>
<span className="text-foreground font-medium">{activeTable.lastAnalyzed}</span>
</div>
</div>
</div>
{/* View Tabs Navigation */}
<Tabs value={activeTab} onValueChange={setActiveTab} defaultValue="columns" className="w-full">
<TabsList className="grid h-9 w-full grid-cols-2 p-1 md:grid-cols-4">
<TabsTrigger value="columns" className="gap-1.5 text-xs">
<Table2 className="size-3.5" />
<span>Columns</span>
<span className="bg-muted py-0.2 rounded-full px-1.5 font-mono text-xs">
{activeTable.columns.length}
</span>
</TabsTrigger>
<TabsTrigger value="foreign-keys" className="gap-1.5 text-xs">
<Link2 className="size-3.5" />
<span>Foreign Keys</span>
<span className="bg-muted py-0.2 rounded-full px-1.5 font-mono text-xs">
{activeTable.foreignKeys.length}
</span>
</TabsTrigger>
<TabsTrigger value="indexes" className="gap-1.5 text-xs">
<Layers className="size-3.5" />
<span>Indexes</span>
<span className="bg-muted py-0.2 rounded-full px-1.5 font-mono text-xs">
{activeTable.indexes.length}
</span>
</TabsTrigger>
<TabsTrigger value="ddl" className="gap-1.5 text-xs">
<Code2 className="size-3.5" />
<span>SQL DDL Preview</span>
</TabsTrigger>
</TabsList>
{/* Tab 1: Columns List */}
<TabsContent value="columns" className="mt-4">
<Card className="border-border overflow-hidden border shadow-none">
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow className="bg-muted/40 hover:bg-muted/40">
<TableHead className="text-xs font-semibold">Column Name</TableHead>
<TableHead className="text-xs font-semibold">Data Type</TableHead>
<TableHead className="text-xs font-semibold">Nullable</TableHead>
<TableHead className="text-xs font-semibold">Default Value</TableHead>
<TableHead className="text-xs font-semibold">Constraints</TableHead>
<TableHead className="text-xs font-semibold">Description</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{activeTable.columns.map((col) => (
<TableRow key={col.name} className="text-xs">
<TableCell className="font-mono font-medium">
<div className="flex items-center gap-1.5">
{col.isPrimaryKey ? (
<Key className="text-warning size-3.5 shrink-0" />
) : col.isForeignKey ? (
<Link2 className="text-info size-3.5 shrink-0" />
) : null}
<span className={cn(col.isPrimaryKey && 'text-warning font-semibold')}>{col.name}</span>
</div>
</TableCell>
<TableCell>
<Badge variant="secondary" className="font-mono text-xs font-normal">
{col.type}
</Badge>
</TableCell>
<TableCell>
{!col.nullable ? (
<Badge variant="outline" className="text-muted-foreground text-xs">
NOT NULL
</Badge>
) : (
<span className="text-muted-foreground">NULL</span>
)}
</TableCell>
<TableCell className="text-muted-foreground font-mono">{col.defaultValue ?? '—'}</TableCell>
<TableCell>
<div className="flex flex-wrap items-center gap-1">
{col.isPrimaryKey && (
<Badge className="border-warning/30 bg-warning/15 text-warning text-xs">PK</Badge>
)}
{col.isForeignKey && (
<Badge variant="secondary" className="gap-1 font-mono text-xs">
FK → {col.foreignKeyRef}
</Badge>
)}
{!col.isPrimaryKey && !col.isForeignKey && (
<span className="text-muted-foreground">—</span>
)}
</div>
</TableCell>
<TableCell className="text-muted-foreground max-w-[200px] truncate">
{col.description ?? '—'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</Card>
</TabsContent>
{/* Tab 2: Foreign Keys & Relations */}
<TabsContent value="foreign-keys" className="mt-4 space-y-3">
{activeTable.foreignKeys.length > 0 ? (
<div className="space-y-3">
{activeTable.foreignKeys.map((fk) => (
<Card key={fk.name} className="border-border space-y-3 border p-4 shadow-none">
<div className="border-border flex flex-wrap items-center justify-between gap-2 border-b pb-2">
<div className="flex flex-wrap items-center gap-2">
<Link2 className="text-info size-4" />
<span className="text-foreground font-mono text-xs font-semibold">{fk.name}</span>
</div>
<Badge variant="secondary" className="font-mono text-xs">
Many-to-One (N:1)
</Badge>
</div>
<div className="bg-muted/40 border-border/60 flex flex-col justify-between gap-3 rounded-lg border p-3 sm:flex-row sm:items-center">
<div className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground text-xs">Source:</span>
<Badge variant="outline" className="font-mono text-xs">
{activeTable.name}.{fk.column}
</Badge>
</div>
<ArrowRight className="text-muted-foreground hidden size-4 sm:block" />
<div className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground text-xs">References:</span>
<Badge variant="default" className="font-mono text-xs">
{fk.foreignTable}.{fk.foreignColumn}
</Badge>
</div>
</div>
<div className="grid grid-cols-1 gap-2 pt-1 text-xs sm:grid-cols-2">
<div className="border-border/50 bg-card flex items-center justify-between gap-x-2 rounded-md border px-2.5 py-1.5">
<span className="text-muted-foreground">On Delete Action:</span>
<Badge variant="secondary" className="font-mono text-xs">
{fk.onDelete}
</Badge>
</div>
<div className="border-border/50 bg-card flex items-center justify-between gap-x-2 rounded-md border px-2.5 py-1.5">
<span className="text-muted-foreground">On Update Action:</span>
<Badge variant="secondary" className="font-mono text-xs">
{fk.onUpdate}
</Badge>
</div>
</div>
</Card>
))}
</div>
) : (
<Card className="border-border border border-dashed p-8 text-center">
<div className="flex flex-col items-center justify-center space-y-2">
<div className="bg-muted text-muted-foreground flex size-10 items-center justify-center rounded-full">
<Link2 className="size-5" />
</div>
<h3 className="text-sm font-semibold">No Outbound Foreign Keys</h3>
<p className="text-muted-foreground max-w-sm text-xs">
Table <span className="font-mono font-medium">{activeTable.name}</span> has no outbound relations.
It acts as an independent entity or is referenced by downstream child tables.
</p>
</div>
</Card>
)}
</TabsContent>
{/* Tab 3: Indexes & Constraints */}
<TabsContent value="indexes" className="mt-4">
<Card className="border-border overflow-hidden border shadow-none">
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow className="bg-muted/40 hover:bg-muted/40">
<TableHead className="text-xs font-semibold">Index Name</TableHead>
<TableHead className="text-xs font-semibold">Method</TableHead>
<TableHead className="text-xs font-semibold">Indexed Columns</TableHead>
<TableHead className="text-xs font-semibold">Index Size</TableHead>
<TableHead className="text-xs font-semibold">Constraint Type</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{activeTable.indexes.map((idx) => (
<TableRow key={idx.name} className="text-xs">
<TableCell className="font-mono font-medium">
<div className="flex items-center gap-1.5">
<Layers className="text-muted-foreground size-3.5 shrink-0" />
<span>{idx.name}</span>
</div>
</TableCell>
<TableCell>
<Badge variant="secondary" className="font-mono text-xs">
{idx.type}
</Badge>
</TableCell>
<TableCell className="text-foreground font-mono">{idx.columns.join(', ')}</TableCell>
<TableCell className="text-muted-foreground font-mono">{idx.size}</TableCell>
<TableCell>
{idx.isPrimary ? (
<Badge className="border-warning/30 bg-warning/15 text-warning text-xs">
PRIMARY KEY
</Badge>
) : idx.isUnique ? (
<Badge variant="secondary" className="text-xs">
UNIQUE
</Badge>
) : (
<Badge variant="outline" className="text-muted-foreground text-xs">
INDEX
</Badge>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</Card>
</TabsContent>
{/* Tab 4: SQL DDL Preview */}
<TabsContent value="ddl" className="mt-4">
<div className="border-border bg-muted/40 overflow-hidden rounded-lg border">
<div className="border-border bg-card flex items-center justify-between gap-x-2 border-b px-4 py-2.5">
<div className="flex flex-wrap items-center gap-2">
<Code2 className="text-primary size-4" />
<span className="text-foreground font-mono text-xs font-medium">{activeTable.name}.sql</span>
<Badge variant="secondary" className="font-mono text-xs">
PostgreSQL DDL
</Badge>
</div>
<Button
variant="ghost"
size="sm"
className="h-7 gap-1 text-xs"
onClick={() => copyToClipboard(activeTable.ddl)}
>
{copiedDDL ? <Check className="text-success size-3" /> : <Copy className="size-3" />}
{copiedDDL ? 'Copied' : 'Copy'}
</Button>
</div>
<div className="overflow-x-auto bg-neutral-950 p-4 font-mono text-xs leading-relaxed text-neutral-100 dark:bg-neutral-950">
<pre className="whitespace-pre">
<code>{activeTable.ddl}</code>
</pre>
</div>
</div>
</TabsContent>
</Tabs>
</main>
</div>
</div>
)
}
Raw manifest:https://uipkge.dev/r/react/database-schema-viewer.json