133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
import React, { useState } from 'react'
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
Grid,
|
|
List,
|
|
ListItem,
|
|
ListItemButton,
|
|
ListItemIcon,
|
|
ListItemText,
|
|
Divider,
|
|
CircularProgress,
|
|
Chip,
|
|
} from '@mui/material'
|
|
import {
|
|
TableChart as TableIcon,
|
|
Storage as SchemaIcon,
|
|
ChevronRight as ChevronRightIcon,
|
|
} from '@mui/icons-material'
|
|
import { DataGrid, GridColDef } from '@mui/x-data-grid'
|
|
import { useDatabase } from '../hooks/useDatabase'
|
|
import { DbTable } from '../services/api'
|
|
|
|
export default function Database() {
|
|
const { tables, isLoadingTables, useTableData } = useDatabase()
|
|
const [selectedTable, setSelectedTable] = useState<DbTable | null>(null)
|
|
|
|
const { data: rows, isLoading: isLoadingData } = useTableData(
|
|
selectedTable?.schema || null,
|
|
selectedTable?.name || null
|
|
)
|
|
|
|
const columns: GridColDef[] = rows && rows.length > 0
|
|
? Object.keys(rows[0]).map(key => ({
|
|
field: key,
|
|
headerName: key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, ' '),
|
|
flex: 1,
|
|
minWidth: 150,
|
|
}))
|
|
: []
|
|
|
|
return (
|
|
<Box>
|
|
<Box sx={{ mb: 3 }}>
|
|
<Typography variant="h4" gutterBottom>
|
|
Database Browser
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Explore and manage your project's database tables and data
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Grid container spacing={3}>
|
|
{/* Table List */}
|
|
<Grid item xs={12} md={3}>
|
|
<Paper sx={{ height: 600, overflow: 'auto' }}>
|
|
<Box sx={{ p: 2, bgcolor: 'background.paper', position: 'sticky', top: 0, zIndex: 1 }}>
|
|
<Typography variant="h6">Tables</Typography>
|
|
</Box>
|
|
<Divider />
|
|
{isLoadingTables ? (
|
|
<Box sx={{ p: 3, textAlign: 'center' }}>
|
|
<CircularProgress size={24} />
|
|
</Box>
|
|
) : (
|
|
<List disablePadding>
|
|
{tables.map((table) => (
|
|
<ListItem key={`${table.schema}.${table.name}`} disablePadding>
|
|
<ListItemButton
|
|
selected={selectedTable?.name === table.name && selectedTable?.schema === table.schema}
|
|
onClick={() => setSelectedTable(table)}
|
|
>
|
|
<ListItemIcon>
|
|
<TableIcon color="primary" />
|
|
</ListItemIcon>
|
|
<ListItemText
|
|
primary={table.name}
|
|
secondary={table.schema}
|
|
/>
|
|
</ListItemButton>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
)}
|
|
</Paper>
|
|
</Grid>
|
|
|
|
{/* Data View */}
|
|
<Grid item xs={12} md={9}>
|
|
<Paper sx={{ height: 600, display: 'flex', flexDirection: 'column' }}>
|
|
{!selectedTable ? (
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100%', gap: 2 }}>
|
|
<TableIcon sx={{ fontSize: 64, color: 'text.disabled' }} />
|
|
<Typography color="text.secondary">Select a table to view data</Typography>
|
|
</Box>
|
|
) : (
|
|
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
<Box sx={{ p: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<SchemaIcon color="action" fontSize="small" />
|
|
<Typography variant="h6">{selectedTable.schema}.</Typography>
|
|
<Typography variant="h6" color="primary">{selectedTable.name}</Typography>
|
|
</Box>
|
|
<Divider />
|
|
<Box sx={{ flexGrow: 1 }}>
|
|
{isLoadingData ? (
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
|
<CircularProgress />
|
|
</Box>
|
|
) : (
|
|
<DataGrid
|
|
rows={rows || []}
|
|
columns={columns}
|
|
getRowId={(row) => row.id || row.uuid || JSON.stringify(row)}
|
|
pageSizeOptions={[10, 25, 50]}
|
|
initialState={{
|
|
pagination: {
|
|
paginationModel: { pageSize: 10 },
|
|
},
|
|
}}
|
|
disableRowSelectionOnClick
|
|
/>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
</Paper>
|
|
</Grid>
|
|
</Grid>
|
|
</Box>
|
|
)
|
|
}
|