CoolFace
Apppublic

Leon4gr45/builder

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes
schema-viewer.tsx188 linesDownload Raw Back to database-manager
1'use client';2 3import React, { useState, useEffect } from 'react';4import { TableInfo } from '@/lib/vfs/types';5import { ChevronRight, ChevronDown, Table2, KeyRound, Loader2, AlertCircle, Eye, EyeOff } from 'lucide-react';6import { cn } from '@/lib/utils';7import { Button } from '@/components/ui/button';8 9interface SchemaViewerProps {10  deploymentId?: string;11  schemaEndpoint?: string;12  showSystemTablesToggle?: boolean;13  workspaceId?: string;14}15 16export function SchemaViewer({ deploymentId, schemaEndpoint, showSystemTablesToggle = true, workspaceId }: SchemaViewerProps) {17  const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api';18  const [tables, setTables] = useState<TableInfo[]>([]);19  const [loading, setLoading] = useState(true);20  const [error, setError] = useState<string | null>(null);21  const [expandedTables, setExpandedTables] = useState<Set<string>>(new Set());22  const [showSystemTables, setShowSystemTables] = useState(false);23 24  const endpoint = schemaEndpoint || `${apiBase}/admin/deployments/${deploymentId}/database/schema`;25 26  useEffect(() => {27    loadSchema();28  }, [endpoint]);29 30  const loadSchema = async () => {31    try {32      setLoading(true);33      setError(null);34      const res = await fetch(endpoint);35      if (!res.ok) {36        const data = await res.json();37        throw new Error(data.error || 'Failed to load schema');38      }39      const data = await res.json();40      setTables(data.tables);41    } catch (err) {42      setError(err instanceof Error ? err.message : 'Failed to load schema');43    } finally {44      setLoading(false);45    }46  };47 48  const toggleTable = (tableName: string) => {49    setExpandedTables(prev => {50      const next = new Set(prev);51      if (next.has(tableName)) {52        next.delete(tableName);53      } else {54        next.add(tableName);55      }56      return next;57    });58  };59 60  const filteredTables = showSystemTables61    ? tables62    : tables.filter(t => !t.isSystemTable);63 64  if (loading) {65    return (66      <div className="flex items-center justify-center h-full">67        <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />68      </div>69    );70  }71 72  if (error) {73    return (74      <div className="flex flex-col items-center justify-center h-full gap-4">75        <AlertCircle className="h-8 w-8 text-destructive" />76        <p className="text-sm text-muted-foreground">{error}</p>77        <Button variant="outline" onClick={loadSchema}>78          Retry79        </Button>80      </div>81    );82  }83 84  return (85    <div className="h-full flex flex-col">86      <div className="flex items-center justify-between mb-4">87        <h3 className="text-sm font-medium">Database Tables</h3>88        {showSystemTablesToggle && (89          <Button90            variant="ghost"91            size="sm"92            onClick={() => setShowSystemTables(!showSystemTables)}93            className="text-xs"94          >95            {showSystemTables ? (96              <>97                <EyeOff className="h-3.5 w-3.5 mr-1" />98                Hide System Tables99              </>100            ) : (101              <>102                <Eye className="h-3.5 w-3.5 mr-1" />103                Show System Tables104              </>105            )}106          </Button>107        )}108      </div>109 110      <div className="flex-1 overflow-auto border rounded-lg">111        {filteredTables.length === 0 ? (112          <div className="flex flex-col items-center justify-center h-full p-8 text-center">113            <Table2 className="h-8 w-8 text-muted-foreground mb-2" />114            <p className="text-sm text-muted-foreground">No user tables found</p>115            <p className="text-xs text-muted-foreground mt-1">116              Create tables using the SQL editor117            </p>118          </div>119        ) : (120          <div className="divide-y">121            {filteredTables.map(table => (122              <div key={table.name} className={cn(123                "transition-colors",124                table.isSystemTable && "bg-muted/30"125              )}>126                <button127                  onClick={() => toggleTable(table.name)}128                  className="w-full flex items-center gap-2 p-3 text-left hover:bg-muted/50 transition-colors"129                >130                  {expandedTables.has(table.name) ? (131                    <ChevronDown className="h-4 w-4 text-muted-foreground" />132                  ) : (133                    <ChevronRight className="h-4 w-4 text-muted-foreground" />134                  )}135                  <Table2 className="h-4 w-4 text-blue-500" />136                  <span className="flex-1 font-mono text-sm">{table.name}</span>137                  <span className="text-xs text-muted-foreground">138                    {table.rowCount} row{table.rowCount !== 1 ? 's' : ''}139                  </span>140                  {table.isSystemTable && (141                    <span className="text-xs bg-muted px-1.5 py-0.5 rounded">system</span>142                  )}143                </button>144 145                {expandedTables.has(table.name) && (146                  <div className="bg-muted/20 border-t">147                    <table className="w-full text-sm">148                      <thead>149                        <tr className="border-b bg-muted/30">150                          <th className="text-left p-2 font-medium">Column</th>151                          <th className="text-left p-2 font-medium">Type</th>152                          <th className="text-left p-2 font-medium">Nullable</th>153                          <th className="text-left p-2 font-medium">Default</th>154                        </tr>155                      </thead>156                      <tbody>157                        {table.columns.map(col => (158                          <tr key={col.name} className="border-b last:border-0">159                            <td className="p-2 font-mono flex items-center gap-1.5">160                              {col.primaryKey && (161                                <KeyRound className="h-3 w-3 text-yellow-500" />162                              )}163                              {col.name}164                            </td>165                            <td className="p-2 font-mono text-muted-foreground">166                              {col.type || 'TEXT'}167                            </td>168                            <td className="p-2 text-muted-foreground">169                              {col.nullable ? 'Yes' : 'No'}170                            </td>171                            <td className="p-2 font-mono text-muted-foreground text-xs">172                              {col.defaultValue || '-'}173                            </td>174                          </tr>175                        ))}176                      </tbody>177                    </table>178                  </div>179                )}180              </div>181            ))}182          </div>183        )}184      </div>185    </div>186  );187}188