Files
postgres/src/app/admin/dashboard/page.tsx
2026-01-08 02:53:50 +00:00

1087 lines
35 KiB
TypeScript

'use client';
import AddIcon from '@mui/icons-material/Add';
import CodeIcon from '@mui/icons-material/Code';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import LogoutIcon from '@mui/icons-material/Logout';
import StorageIcon from '@mui/icons-material/Storage';
import TableChartIcon from '@mui/icons-material/TableChart';
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
import {
Alert,
AppBar,
Box,
Button,
Checkbox,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Drawer,
FormControlLabel,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
MenuItem,
Paper,
Select,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Toolbar,
Typography,
} from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';
import { theme } from '@/utils/theme';
const DRAWER_WIDTH = 240;
type TabPanelProps = {
children?: React.ReactNode;
index: number;
value: number;
};
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`tabpanel-${index}`}
aria-labelledby={`tab-${index}`}
{...other}
>
{value === index && <Box sx={{ p: 3 }}>{children}</Box>}
</div>
);
}
export default function AdminDashboard() {
const router = useRouter();
const [tabValue, setTabValue] = useState(0);
const [tables, setTables] = useState<any[]>([]);
const [selectedTable, setSelectedTable] = useState<string>('');
const [queryText, setQueryText] = useState('');
const [queryResult, setQueryResult] = useState<any>(null);
const [tableSchema, setTableSchema] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
// Dialog states
const [openCreateDialog, setOpenCreateDialog] = useState(false);
const [openEditDialog, setOpenEditDialog] = useState(false);
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
const [editingRecord, setEditingRecord] = useState<any>(null);
const [deletingRecord, setDeletingRecord] = useState<any>(null);
const [formData, setFormData] = useState<any>({});
// Table Manager states
const [openCreateTableDialog, setOpenCreateTableDialog] = useState(false);
const [openDropTableDialog, setOpenDropTableDialog] = useState(false);
const [newTableName, setNewTableName] = useState('');
const [tableColumns, setTableColumns] = useState<any[]>([{ name: '', type: 'VARCHAR', length: 255, nullable: true, primaryKey: false }]);
const [tableToDelete, setTableToDelete] = useState('');
// Column Manager states
const [openAddColumnDialog, setOpenAddColumnDialog] = useState(false);
const [openModifyColumnDialog, setOpenModifyColumnDialog] = useState(false);
const [openDropColumnDialog, setOpenDropColumnDialog] = useState(false);
const [selectedTableForColumn, setSelectedTableForColumn] = useState('');
const [newColumnName, setNewColumnName] = useState('');
const [newColumnType, setNewColumnType] = useState('VARCHAR');
const [newColumnNullable, setNewColumnNullable] = useState(true);
const [newColumnDefault, setNewColumnDefault] = useState('');
const [columnToModify, setColumnToModify] = useState('');
const [columnToDelete, setColumnToDelete] = useState('');
const fetchTables = useCallback(async () => {
try {
const response = await fetch('/api/admin/tables');
if (!response.ok) {
if (response.status === 401) {
router.push('/admin/login');
return;
}
throw new Error('Failed to fetch tables');
}
const data = await response.json();
setTables(data.tables);
} catch (err: any) {
setError(err.message);
}
}, [router]);
useEffect(() => {
fetchTables();
}, [fetchTables]);
useEffect(() => {
if (selectedTableForColumn && tabValue === 3) {
// Fetch schema when a table is selected in Column Manager
const fetchSchema = async () => {
try {
const schemaResponse = await fetch('/api/admin/table-schema', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tableName: selectedTableForColumn }),
});
if (schemaResponse.ok) {
const schemaData = await schemaResponse.json();
setTableSchema(schemaData);
}
} catch (err) {
console.error('Failed to fetch schema:', err);
}
};
fetchSchema();
}
}, [selectedTableForColumn, tabValue]);
const handleTableClick = async (tableName: string) => {
setSelectedTable(tableName);
setLoading(true);
setError('');
setSuccessMessage('');
setQueryResult(null);
try {
// Fetch table data
const dataResponse = await fetch('/api/admin/table-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tableName }),
});
if (!dataResponse.ok) {
const data = await dataResponse.json();
throw new Error(data.error || 'Query failed');
}
const data = await dataResponse.json();
setQueryResult(data);
// Fetch table schema
const schemaResponse = await fetch('/api/admin/table-schema', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tableName }),
});
if (schemaResponse.ok) {
const schemaData = await schemaResponse.json();
setTableSchema(schemaData);
}
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleQuerySubmit = async () => {
if (!queryText.trim()) {
setError('Please enter a query');
return;
}
setLoading(true);
setError('');
setQueryResult(null);
try {
const response = await fetch('/api/admin/query', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: queryText }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Query failed');
}
setQueryResult(data);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleLogout = async () => {
try {
await fetch('/api/admin/logout', {
method: 'POST',
});
router.push('/admin/login');
router.refresh();
} catch (err) {
console.error('Logout error:', err);
}
};
// Table Management Handlers
const handleCreateTable = async () => {
if (!newTableName.trim()) {
setError('Table name is required');
return;
}
if (tableColumns.length === 0 || !tableColumns[0].name) {
setError('At least one column is required');
return;
}
setLoading(true);
setError('');
setSuccessMessage('');
try {
const response = await fetch('/api/admin/table-manage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tableName: newTableName,
columns: tableColumns.filter(col => col.name.trim()),
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to create table');
}
setSuccessMessage(data.message);
setOpenCreateTableDialog(false);
setNewTableName('');
setTableColumns([{ name: '', type: 'VARCHAR', length: 255, nullable: true, primaryKey: false }]);
await fetchTables();
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleDropTable = async () => {
if (!tableToDelete) {
setError('Please select a table to drop');
return;
}
setLoading(true);
setError('');
setSuccessMessage('');
try {
const response = await fetch('/api/admin/table-manage', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tableName: tableToDelete }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to drop table');
}
setSuccessMessage(data.message);
setOpenDropTableDialog(false);
setTableToDelete('');
if (selectedTable === tableToDelete) {
setSelectedTable('');
setQueryResult(null);
}
await fetchTables();
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const addColumnToTable = () => {
setTableColumns([...tableColumns, { name: '', type: 'VARCHAR', length: 255, nullable: true, primaryKey: false }]);
};
const updateColumnField = (index: number, field: string, value: any) => {
const updated = [...tableColumns];
updated[index] = { ...updated[index], [field]: value };
setTableColumns(updated);
};
const removeColumn = (index: number) => {
if (tableColumns.length > 1) {
setTableColumns(tableColumns.filter((_, i) => i !== index));
}
};
// Column Management Handlers
const handleAddColumn = async () => {
if (!selectedTableForColumn || !newColumnName.trim() || !newColumnType) {
setError('Table name, column name, and data type are required');
return;
}
setLoading(true);
setError('');
setSuccessMessage('');
try {
const payload: any = {
tableName: selectedTableForColumn,
columnName: newColumnName,
dataType: newColumnType,
nullable: newColumnNullable,
};
if (newColumnDefault) {
payload.defaultValue = newColumnDefault;
}
const response = await fetch('/api/admin/column-manage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to add column');
}
setSuccessMessage(data.message);
setOpenAddColumnDialog(false);
setNewColumnName('');
setNewColumnType('VARCHAR');
setNewColumnNullable(true);
setNewColumnDefault('');
// Refresh table schema if viewing the modified table
if (selectedTable === selectedTableForColumn) {
await handleTableClick(selectedTableForColumn);
}
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleModifyColumn = async () => {
if (!selectedTableForColumn || !columnToModify) {
setError('Table name and column name are required');
return;
}
setLoading(true);
setError('');
setSuccessMessage('');
try {
const response = await fetch('/api/admin/column-manage', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tableName: selectedTableForColumn,
columnName: columnToModify,
newType: newColumnType,
nullable: newColumnNullable,
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to modify column');
}
setSuccessMessage(data.message);
setOpenModifyColumnDialog(false);
setColumnToModify('');
setNewColumnType('VARCHAR');
setNewColumnNullable(true);
// Refresh table schema if viewing the modified table
if (selectedTable === selectedTableForColumn) {
await handleTableClick(selectedTableForColumn);
}
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handleDropColumn = async () => {
if (!selectedTableForColumn || !columnToDelete) {
setError('Table name and column name are required');
return;
}
setLoading(true);
setError('');
setSuccessMessage('');
try {
const response = await fetch('/api/admin/column-manage', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tableName: selectedTableForColumn,
columnName: columnToDelete,
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to drop column');
}
setSuccessMessage(data.message);
setOpenDropColumnDialog(false);
setColumnToDelete('');
// Refresh table schema if viewing the modified table
if (selectedTable === selectedTableForColumn) {
await handleTableClick(selectedTableForColumn);
}
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<ThemeProvider theme={theme}>
<Box sx={{ display: 'flex' }}>
<AppBar
position="fixed"
sx={{ zIndex: theme => theme.zIndex.drawer + 1 }}
>
<Toolbar>
<StorageIcon sx={{ mr: 2 }} />
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
Postgres Admin Panel
</Typography>
<Button color="inherit" onClick={handleLogout} startIcon={<LogoutIcon />}>
Logout
</Button>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
sx={{
'width': DRAWER_WIDTH,
'flexShrink': 0,
'& .MuiDrawer-paper': {
width: DRAWER_WIDTH,
boxSizing: 'border-box',
},
}}
>
<Toolbar />
<Box sx={{ overflow: 'auto' }}>
<List>
<ListItem disablePadding>
<ListItemButton onClick={() => setTabValue(0)}>
<ListItemIcon>
<StorageIcon />
</ListItemIcon>
<ListItemText primary="Tables" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton onClick={() => setTabValue(1)}>
<ListItemIcon>
<CodeIcon />
</ListItemIcon>
<ListItemText primary="SQL Query" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton onClick={() => setTabValue(2)}>
<ListItemIcon>
<TableChartIcon />
</ListItemIcon>
<ListItemText primary="Table Manager" />
</ListItemButton>
</ListItem>
<ListItem disablePadding>
<ListItemButton onClick={() => setTabValue(3)}>
<ListItemIcon>
<ViewColumnIcon />
</ListItemIcon>
<ListItemText primary="Column Manager" />
</ListItemButton>
</ListItem>
</List>
</Box>
</Drawer>
<Box
component="main"
sx={{
flexGrow: 1,
bgcolor: 'background.default',
p: 3,
}}
>
<Toolbar />
<TabPanel value={tabValue} index={0}>
<Typography variant="h5" gutterBottom>
Database Tables
</Typography>
<Paper sx={{ mt: 2, mb: 2 }}>
<List>
{tables.map(table => (
<ListItem key={table.table_name} disablePadding>
<ListItemButton onClick={() => handleTableClick(table.table_name)}>
<ListItemIcon>
<StorageIcon />
</ListItemIcon>
<ListItemText primary={table.table_name} />
</ListItemButton>
</ListItem>
))}
</List>
</Paper>
{selectedTable && (
<Typography variant="h6" gutterBottom>
Table:
{' '}
{selectedTable}
</Typography>
)}
</TabPanel>
<TabPanel value={tabValue} index={1}>
<Typography variant="h5" gutterBottom>
SQL Query Interface
</Typography>
<Paper sx={{ p: 2, mt: 2 }}>
<TextField
fullWidth
multiline
rows={6}
label="SQL Query (SELECT only)"
variant="outlined"
value={queryText}
onChange={e => setQueryText(e.target.value)}
placeholder="SELECT * FROM your_table LIMIT 10;"
sx={{ mb: 2 }}
/>
<Button
variant="contained"
onClick={handleQuerySubmit}
disabled={loading}
>
{loading ? <CircularProgress size={24} /> : 'Execute Query'}
</Button>
</Paper>
</TabPanel>
<TabPanel value={tabValue} index={2}>
<Typography variant="h5" gutterBottom>
Table Manager
</Typography>
<Box sx={{ mt: 2, mb: 2 }}>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setOpenCreateTableDialog(true)}
sx={{ mr: 2 }}
>
Create Table
</Button>
<Button
variant="outlined"
color="error"
startIcon={<DeleteIcon />}
onClick={() => setOpenDropTableDialog(true)}
>
Drop Table
</Button>
</Box>
<Paper sx={{ mt: 2 }}>
<Box sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Existing Tables
</Typography>
<List>
{tables.map(table => (
<ListItem key={table.table_name}>
<ListItemIcon>
<TableChartIcon />
</ListItemIcon>
<ListItemText primary={table.table_name} />
</ListItem>
))}
{tables.length === 0 && (
<ListItem>
<ListItemText primary="No tables found" />
</ListItem>
)}
</List>
</Box>
</Paper>
</TabPanel>
<TabPanel value={tabValue} index={3}>
<Typography variant="h5" gutterBottom>
Column Manager
</Typography>
<Paper sx={{ p: 2, mt: 2, mb: 2 }}>
<Typography variant="subtitle1" gutterBottom>
Select a table to manage its columns:
</Typography>
<Select
fullWidth
value={selectedTableForColumn}
onChange={e => setSelectedTableForColumn(e.target.value)}
displayEmpty
>
<MenuItem value="">
<em>Select a table</em>
</MenuItem>
{tables.map(table => (
<MenuItem key={table.table_name} value={table.table_name}>
{table.table_name}
</MenuItem>
))}
</Select>
</Paper>
{selectedTableForColumn && (
<>
<Box sx={{ mb: 2 }}>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => setOpenAddColumnDialog(true)}
sx={{ mr: 2 }}
>
Add Column
</Button>
<Button
variant="outlined"
startIcon={<EditIcon />}
onClick={() => setOpenModifyColumnDialog(true)}
sx={{ mr: 2 }}
>
Modify Column
</Button>
<Button
variant="outlined"
color="error"
startIcon={<DeleteIcon />}
onClick={() => setOpenDropColumnDialog(true)}
>
Drop Column
</Button>
</Box>
{tableSchema && (
<Paper sx={{ mt: 2 }}>
<Box sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Current Columns for {selectedTableForColumn}
</Typography>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell><strong>Column Name</strong></TableCell>
<TableCell><strong>Data Type</strong></TableCell>
<TableCell><strong>Nullable</strong></TableCell>
<TableCell><strong>Default</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{tableSchema.columns?.map((col: any) => (
<TableRow key={col.column_name}>
<TableCell>{col.column_name}</TableCell>
<TableCell>{col.data_type}</TableCell>
<TableCell>{col.is_nullable}</TableCell>
<TableCell>{col.column_default || 'NULL'}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
</Paper>
)}
</>
)}
</TabPanel>
{successMessage && (
<Alert severity="success" sx={{ mt: 2 }} onClose={() => setSuccessMessage('')}>
{successMessage}
</Alert>
)}
{error && (
<Alert severity="error" sx={{ mt: 2 }}>
{error}
</Alert>
)}
{loading && (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
<CircularProgress />
</Box>
)}
{queryResult && !loading && (
<Paper sx={{ mt: 2, overflow: 'auto' }}>
<Box sx={{ p: 2 }}>
<Typography variant="subtitle2" gutterBottom>
Rows returned:
{' '}
{queryResult.rowCount}
</Typography>
</Box>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
{queryResult.fields?.map((field: any) => (
<TableCell key={field.name}>
<strong>{field.name}</strong>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{queryResult.rows?.map((row: any, idx: number) => (
<TableRow key={idx}>
{queryResult.fields?.map((field: any) => (
<TableCell key={field.name}>
{row[field.name] !== null
? String(row[field.name])
: 'NULL'}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
)}
</Box>
{/* Create Table Dialog */}
<Dialog open={openCreateTableDialog} onClose={() => setOpenCreateTableDialog(false)} maxWidth="md" fullWidth>
<DialogTitle>Create New Table</DialogTitle>
<DialogContent>
<TextField
fullWidth
label="Table Name"
value={newTableName}
onChange={e => setNewTableName(e.target.value)}
sx={{ mt: 2, mb: 2 }}
/>
<Typography variant="subtitle1" gutterBottom>
Columns:
</Typography>
{tableColumns.map((col, index) => (
<Box key={index} sx={{ mb: 2, p: 2, border: '1px solid #ddd', borderRadius: 1 }}>
<TextField
label="Column Name"
value={col.name}
onChange={e => updateColumnField(index, 'name', e.target.value)}
sx={{ mr: 1, mb: 1 }}
/>
<Select
value={col.type}
onChange={e => updateColumnField(index, 'type', e.target.value)}
sx={{ mr: 1, mb: 1, minWidth: 120 }}
>
<MenuItem value="INTEGER">INTEGER</MenuItem>
<MenuItem value="BIGINT">BIGINT</MenuItem>
<MenuItem value="SERIAL">SERIAL</MenuItem>
<MenuItem value="VARCHAR">VARCHAR</MenuItem>
<MenuItem value="TEXT">TEXT</MenuItem>
<MenuItem value="BOOLEAN">BOOLEAN</MenuItem>
<MenuItem value="TIMESTAMP">TIMESTAMP</MenuItem>
<MenuItem value="DATE">DATE</MenuItem>
<MenuItem value="JSON">JSON</MenuItem>
<MenuItem value="JSONB">JSONB</MenuItem>
</Select>
{(col.type === 'VARCHAR') && (
<TextField
label="Length"
type="number"
value={col.length || 255}
onChange={e => updateColumnField(index, 'length', e.target.value)}
sx={{ mr: 1, mb: 1, width: 100 }}
/>
)}
<FormControlLabel
control={
<Checkbox
checked={col.nullable}
onChange={e => updateColumnField(index, 'nullable', e.target.checked)}
/>
}
label="Nullable"
sx={{ mr: 1 }}
/>
<FormControlLabel
control={
<Checkbox
checked={col.primaryKey}
onChange={e => updateColumnField(index, 'primaryKey', e.target.checked)}
/>
}
label="Primary Key"
sx={{ mr: 1 }}
/>
{tableColumns.length > 1 && (
<IconButton onClick={() => removeColumn(index)} color="error" size="small">
<DeleteIcon />
</IconButton>
)}
</Box>
))}
<Button startIcon={<AddIcon />} onClick={addColumnToTable} variant="outlined">
Add Column
</Button>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenCreateTableDialog(false)}>Cancel</Button>
<Button onClick={handleCreateTable} variant="contained" disabled={loading}>
Create Table
</Button>
</DialogActions>
</Dialog>
{/* Drop Table Dialog */}
<Dialog open={openDropTableDialog} onClose={() => setOpenDropTableDialog(false)}>
<DialogTitle>Drop Table</DialogTitle>
<DialogContent>
<Typography variant="body2" color="error" gutterBottom>
Warning: This will permanently delete the table and all its data!
</Typography>
<Select
fullWidth
value={tableToDelete}
onChange={e => setTableToDelete(e.target.value)}
displayEmpty
sx={{ mt: 2 }}
>
<MenuItem value="">
<em>Select a table to drop</em>
</MenuItem>
{tables.map(table => (
<MenuItem key={table.table_name} value={table.table_name}>
{table.table_name}
</MenuItem>
))}
</Select>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDropTableDialog(false)}>Cancel</Button>
<Button onClick={handleDropTable} color="error" variant="contained" disabled={loading}>
Drop Table
</Button>
</DialogActions>
</Dialog>
{/* Add Column Dialog */}
<Dialog open={openAddColumnDialog} onClose={() => setOpenAddColumnDialog(false)}>
<DialogTitle>Add Column to {selectedTableForColumn}</DialogTitle>
<DialogContent>
<TextField
fullWidth
label="Column Name"
value={newColumnName}
onChange={e => setNewColumnName(e.target.value)}
sx={{ mt: 2, mb: 2 }}
/>
<Select
fullWidth
value={newColumnType}
onChange={e => setNewColumnType(e.target.value)}
sx={{ mb: 2 }}
>
<MenuItem value="INTEGER">INTEGER</MenuItem>
<MenuItem value="BIGINT">BIGINT</MenuItem>
<MenuItem value="SERIAL">SERIAL</MenuItem>
<MenuItem value="VARCHAR">VARCHAR(255)</MenuItem>
<MenuItem value="TEXT">TEXT</MenuItem>
<MenuItem value="BOOLEAN">BOOLEAN</MenuItem>
<MenuItem value="TIMESTAMP">TIMESTAMP</MenuItem>
<MenuItem value="DATE">DATE</MenuItem>
<MenuItem value="JSON">JSON</MenuItem>
<MenuItem value="JSONB">JSONB</MenuItem>
</Select>
<FormControlLabel
control={
<Checkbox
checked={newColumnNullable}
onChange={e => setNewColumnNullable(e.target.checked)}
/>
}
label="Nullable"
sx={{ mb: 2 }}
/>
<TextField
fullWidth
label="Default Value (optional)"
value={newColumnDefault}
onChange={e => setNewColumnDefault(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenAddColumnDialog(false)}>Cancel</Button>
<Button onClick={handleAddColumn} variant="contained" disabled={loading}>
Add Column
</Button>
</DialogActions>
</Dialog>
{/* Modify Column Dialog */}
<Dialog open={openModifyColumnDialog} onClose={() => setOpenModifyColumnDialog(false)}>
<DialogTitle>Modify Column in {selectedTableForColumn}</DialogTitle>
<DialogContent>
<Select
fullWidth
value={columnToModify}
onChange={e => setColumnToModify(e.target.value)}
displayEmpty
sx={{ mt: 2, mb: 2 }}
>
<MenuItem value="">
<em>Select a column to modify</em>
</MenuItem>
{tableSchema?.columns?.map((col: any) => (
<MenuItem key={col.column_name} value={col.column_name}>
{col.column_name}
</MenuItem>
))}
</Select>
{columnToModify && (
<>
<Select
fullWidth
value={newColumnType}
onChange={e => setNewColumnType(e.target.value)}
sx={{ mb: 2 }}
>
<MenuItem value="INTEGER">INTEGER</MenuItem>
<MenuItem value="BIGINT">BIGINT</MenuItem>
<MenuItem value="VARCHAR">VARCHAR(255)</MenuItem>
<MenuItem value="TEXT">TEXT</MenuItem>
<MenuItem value="BOOLEAN">BOOLEAN</MenuItem>
<MenuItem value="TIMESTAMP">TIMESTAMP</MenuItem>
<MenuItem value="DATE">DATE</MenuItem>
<MenuItem value="JSON">JSON</MenuItem>
<MenuItem value="JSONB">JSONB</MenuItem>
</Select>
<FormControlLabel
control={
<Checkbox
checked={newColumnNullable}
onChange={e => setNewColumnNullable(e.target.checked)}
/>
}
label="Nullable"
/>
</>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenModifyColumnDialog(false)}>Cancel</Button>
<Button onClick={handleModifyColumn} variant="contained" disabled={loading || !columnToModify}>
Modify Column
</Button>
</DialogActions>
</Dialog>
{/* Drop Column Dialog */}
<Dialog open={openDropColumnDialog} onClose={() => setOpenDropColumnDialog(false)}>
<DialogTitle>Drop Column from {selectedTableForColumn}</DialogTitle>
<DialogContent>
<Typography variant="body2" color="error" gutterBottom>
Warning: This will permanently delete the column and all its data!
</Typography>
<Select
fullWidth
value={columnToDelete}
onChange={e => setColumnToDelete(e.target.value)}
displayEmpty
sx={{ mt: 2 }}
>
<MenuItem value="">
<em>Select a column to drop</em>
</MenuItem>
{tableSchema?.columns?.map((col: any) => (
<MenuItem key={col.column_name} value={col.column_name}>
{col.column_name}
</MenuItem>
))}
</Select>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDropColumnDialog(false)}>Cancel</Button>
<Button onClick={handleDropColumn} color="error" variant="contained" disabled={loading || !columnToDelete}>
Drop Column
</Button>
</DialogActions>
</Dialog>
</Box>
</ThemeProvider>
);
}