From ce17c4940da572d3d229d4b641d2908b699d22ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:08:55 +0000 Subject: [PATCH 1/3] Initial plan From 7a868a2cb7e01e67aa5ea88ccc72cd581e79f22c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:18:04 +0000 Subject: [PATCH 2/3] Refactor admin dashboard to be config-driven with features.json Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- package-lock.json | 2 +- package.json | 2 +- src/app/admin/dashboard/page-old.tsx | 1161 ++++++++++++++++++++++++++ src/app/admin/dashboard/page.tsx | 858 ++++--------------- src/components/admin/SQLQueryTab.tsx | 72 ++ src/components/admin/TablesTab.tsx | 72 ++ src/config/features.json | 6 + 7 files changed, 1470 insertions(+), 703 deletions(-) create mode 100644 src/app/admin/dashboard/page-old.tsx create mode 100644 src/components/admin/SQLQueryTab.tsx create mode 100644 src/components/admin/TablesTab.tsx diff --git a/package-lock.json b/package-lock.json index aff7dd5..6a7b13f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,7 @@ "storybook": "^10.1.11", "tailwindcss": "^4.1.17", "tsx": "^4.21.0", - "typescript": "^5.9.3", + "typescript": "5.9.3", "vite-tsconfig-paths": "^5.1.4", "vitest": "^4.0.15", "vitest-browser-react": "^2.0.2" diff --git a/package.json b/package.json index 1ce9e2b..d67df6e 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "storybook": "^10.1.11", "tailwindcss": "^4.1.17", "tsx": "^4.21.0", - "typescript": "^5.9.3", + "typescript": "5.9.3", "vite-tsconfig-paths": "^5.1.4", "vitest": "^4.0.15", "vitest-browser-react": "^2.0.2" diff --git a/src/app/admin/dashboard/page-old.tsx b/src/app/admin/dashboard/page-old.tsx new file mode 100644 index 0000000..f246dd6 --- /dev/null +++ b/src/app/admin/dashboard/page-old.tsx @@ -0,0 +1,1161 @@ +'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 RuleIcon from '@mui/icons-material/Rule'; +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 ConstraintManagerTab from '@/components/admin/ConstraintManagerTab'; +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 ( + + ); +} + +export default function AdminDashboard() { + const router = useRouter(); + const [tabValue, setTabValue] = useState(0); + const [tables, setTables] = useState([]); + const [selectedTable, setSelectedTable] = useState(''); + const [queryText, setQueryText] = useState(''); + const [queryResult, setQueryResult] = useState(null); + const [tableSchema, setTableSchema] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [successMessage, setSuccessMessage] = useState(''); + + // Table Manager states + const [openCreateTableDialog, setOpenCreateTableDialog] = useState(false); + const [openDropTableDialog, setOpenDropTableDialog] = useState(false); + const [newTableName, setNewTableName] = useState(''); + const [tableColumns, setTableColumns] = useState([{ 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); + } + }; + + // Constraint Management Handlers + const handleAddConstraint = async (tableName: string, data: any) => { + setLoading(true); + setError(''); + setSuccessMessage(''); + + try { + const response = await fetch('/api/admin/constraints', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + tableName, + ...data, + }), + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || 'Failed to add constraint'); + } + + setSuccessMessage(result.message || 'Constraint added successfully'); + } catch (err: any) { + setError(err.message); + throw err; + } finally { + setLoading(false); + } + }; + + const handleDropConstraint = async (tableName: string, constraintName: string) => { + setLoading(true); + setError(''); + setSuccessMessage(''); + + try { + const response = await fetch('/api/admin/constraints', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + tableName, + constraintName, + }), + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || 'Failed to drop constraint'); + } + + setSuccessMessage(result.message || 'Constraint dropped successfully'); + } catch (err: any) { + setError(err.message); + throw err; + } finally { + setLoading(false); + } + }; + + return ( + + + theme.zIndex.drawer + 1 }} + > + + + + Postgres Admin Panel + + + + + + + + + + + setTabValue(0)}> + + + + + + + + setTabValue(1)}> + + + + + + + + setTabValue(2)}> + + + + + + + + setTabValue(3)}> + + + + + + + + setTabValue(4)}> + + + + + + + + + + + + + + + + Database Tables + + + + + {tables.map(table => ( + + handleTableClick(table.table_name)}> + + + + + + + ))} + + + + {selectedTable && ( + + Table: + {' '} + {selectedTable} + + )} + + + + + SQL Query Interface + + + + setQueryText(e.target.value)} + placeholder="SELECT * FROM your_table LIMIT 10;" + sx={{ mb: 2 }} + /> + + + + + + + Table Manager + + + + + + + + + + + Existing Tables + + + {tables.map(table => ( + + + + + + + ))} + {tables.length === 0 && ( + + + + )} + + + + + + + + Column Manager + + + + + Select a table to manage its columns: + + + + + {selectedTableForColumn && ( + <> + + + + + + + {tableSchema && ( + + + + Current Columns for {selectedTableForColumn} + + + + + + Column Name + Data Type + Nullable + Default + + + + {tableSchema.columns?.map((col: any) => ( + + {col.column_name} + {col.data_type} + {col.is_nullable} + {col.column_default || 'NULL'} + + ))} + +
+
+
+
+ )} + + )} +
+ + + + + + {successMessage && ( + setSuccessMessage('')}> + {successMessage} + + )} + + {error && ( + + {error} + + )} + + {loading && ( + + + + )} + + {queryResult && !loading && ( + + + + Rows returned: + {' '} + {queryResult.rowCount} + + + + + + + {queryResult.fields?.map((field: any) => ( + + {field.name} + + ))} + + + + {queryResult.rows?.map((row: any, idx: number) => ( + + {queryResult.fields?.map((field: any) => ( + + {row[field.name] !== null + ? String(row[field.name]) + : 'NULL'} + + ))} + + ))} + +
+
+
+ )} +
+ + {/* Create Table Dialog */} + setOpenCreateTableDialog(false)} maxWidth="md" fullWidth> + Create New Table + + setNewTableName(e.target.value)} + sx={{ mt: 2, mb: 2 }} + /> + + Columns: + + {tableColumns.map((col, index) => ( + + updateColumnField(index, 'name', e.target.value)} + sx={{ mr: 1, mb: 1 }} + /> + + {(col.type === 'VARCHAR') && ( + updateColumnField(index, 'length', e.target.value)} + sx={{ mr: 1, mb: 1, width: 100 }} + /> + )} + updateColumnField(index, 'nullable', e.target.checked)} + /> + } + label="Nullable" + sx={{ mr: 1 }} + /> + updateColumnField(index, 'primaryKey', e.target.checked)} + /> + } + label="Primary Key" + sx={{ mr: 1 }} + /> + {tableColumns.length > 1 && ( + removeColumn(index)} color="error" size="small"> + + + )} + + ))} + + + + + + + + + {/* Drop Table Dialog */} + setOpenDropTableDialog(false)}> + Drop Table + + + Warning: This will permanently delete the table and all its data! + + + + + + + + + + {/* Add Column Dialog */} + setOpenAddColumnDialog(false)}> + Add Column to {selectedTableForColumn} + + setNewColumnName(e.target.value)} + sx={{ mt: 2, mb: 2 }} + /> + + setNewColumnNullable(e.target.checked)} + /> + } + label="Nullable" + sx={{ mb: 2 }} + /> + setNewColumnDefault(e.target.value)} + /> + + + + + + + + {/* Modify Column Dialog */} + setOpenModifyColumnDialog(false)}> + Modify Column in {selectedTableForColumn} + + + {columnToModify && ( + <> + + setNewColumnNullable(e.target.checked)} + /> + } + label="Nullable" + /> + + )} + + + + + + + + {/* Drop Column Dialog */} + setOpenDropColumnDialog(false)}> + Drop Column from {selectedTableForColumn} + + + Warning: This will permanently delete the column and all its data! + + + + + + + + +
+
+ ); +} diff --git a/src/app/admin/dashboard/page.tsx b/src/app/admin/dashboard/page.tsx index f246dd6..3b733d4 100644 --- a/src/app/admin/dashboard/page.tsx +++ b/src/app/admin/dashboard/page.tsx @@ -1,11 +1,10 @@ '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 AccountTreeIcon from '@mui/icons-material/AccountTree'; +import CodeIcon from '@mui/icons-material/Code'; import RuleIcon from '@mui/icons-material/Rule'; +import SpeedIcon from '@mui/icons-material/Speed'; import StorageIcon from '@mui/icons-material/Storage'; import TableChartIcon from '@mui/icons-material/TableChart'; import ViewColumnIcon from '@mui/icons-material/ViewColumn'; @@ -13,42 +12,50 @@ 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 TablesTab from '@/components/admin/TablesTab'; +import SQLQueryTab from '@/components/admin/SQLQueryTab'; +import TableManagerTab from '@/components/admin/TableManagerTab'; +import ColumnManagerTab from '@/components/admin/ColumnManagerTab'; import ConstraintManagerTab from '@/components/admin/ConstraintManagerTab'; +import QueryBuilderTab from '@/components/admin/QueryBuilderTab'; +import IndexManagerTab from '@/components/admin/IndexManagerTab'; +import { getNavItems, getFeatureById } from '@/utils/featureConfig'; import { theme } from '@/utils/theme'; +import Button from '@mui/material/Button'; const DRAWER_WIDTH = 240; +// Icon map for dynamic icon rendering +const iconMap: Record> = { + Storage: StorageIcon, + Code: CodeIcon, + AccountTree: AccountTreeIcon, + TableChart: TableChartIcon, + ViewColumn: ViewColumnIcon, + Rule: RuleIcon, + Speed: SpeedIcon, +}; + type TabPanelProps = { children?: React.ReactNode; index: number; @@ -76,31 +83,13 @@ export default function AdminDashboard() { const [tabValue, setTabValue] = useState(0); const [tables, setTables] = useState([]); const [selectedTable, setSelectedTable] = useState(''); - const [queryText, setQueryText] = useState(''); const [queryResult, setQueryResult] = useState(null); - const [tableSchema, setTableSchema] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [successMessage, setSuccessMessage] = useState(''); - // Table Manager states - const [openCreateTableDialog, setOpenCreateTableDialog] = useState(false); - const [openDropTableDialog, setOpenDropTableDialog] = useState(false); - const [newTableName, setNewTableName] = useState(''); - const [tableColumns, setTableColumns] = useState([{ 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(''); + // Get navigation items from features.json + const navItems = getNavItems(); const fetchTables = useCallback(async () => { try { @@ -123,31 +112,6 @@ export default function AdminDashboard() { 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); @@ -156,7 +120,6 @@ export default function AdminDashboard() { setQueryResult(null); try { - // Fetch table data const dataResponse = await fetch('/api/admin/table-data', { method: 'POST', headers: { @@ -172,20 +135,6 @@ export default function AdminDashboard() { 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 { @@ -193,12 +142,7 @@ export default function AdminDashboard() { } }; - const handleQuerySubmit = async () => { - if (!queryText.trim()) { - setError('Please enter a query'); - return; - } - + const handleExecuteQuery = async (query: string) => { setLoading(true); setError(''); setQueryResult(null); @@ -209,7 +153,7 @@ export default function AdminDashboard() { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ query: queryText }), + body: JSON.stringify({ query }), }); const data = await response.json(); @@ -219,6 +163,7 @@ export default function AdminDashboard() { } setQueryResult(data); + setSuccessMessage('Query executed successfully'); } catch (err: any) { setError(err.message); } finally { @@ -239,17 +184,7 @@ export default function AdminDashboard() { }; // 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; - } - + const handleCreateTable = async (tableName: string, columns: any[]) => { setLoading(true); setError(''); setSuccessMessage(''); @@ -261,8 +196,8 @@ export default function AdminDashboard() { 'Content-Type': 'application/json', }, body: JSON.stringify({ - tableName: newTableName, - columns: tableColumns.filter(col => col.name.trim()), + tableName, + columns, }), }); @@ -273,23 +208,16 @@ export default function AdminDashboard() { } setSuccessMessage(data.message); - setOpenCreateTableDialog(false); - setNewTableName(''); - setTableColumns([{ name: '', type: 'VARCHAR', length: 255, nullable: true, primaryKey: false }]); await fetchTables(); } catch (err: any) { setError(err.message); + throw err; } finally { setLoading(false); } }; - const handleDropTable = async () => { - if (!tableToDelete) { - setError('Please select a table to drop'); - return; - } - + const handleDropTable = async (tableName: string) => { setLoading(true); setError(''); setSuccessMessage(''); @@ -300,7 +228,7 @@ export default function AdminDashboard() { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ tableName: tableToDelete }), + body: JSON.stringify({ tableName }), }); const data = await response.json(); @@ -310,97 +238,53 @@ export default function AdminDashboard() { } setSuccessMessage(data.message); - setOpenDropTableDialog(false); - setTableToDelete(''); - if (selectedTable === tableToDelete) { + if (selectedTable === tableName) { setSelectedTable(''); setQueryResult(null); } await fetchTables(); } catch (err: any) { setError(err.message); + throw err; } 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; - } - + const handleAddColumn = async (tableName: string, data: any) => { 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), + body: JSON.stringify({ + tableName, + ...data, + }), }); - const data = await response.json(); + const result = await response.json(); if (!response.ok) { - throw new Error(data.error || 'Failed to add column'); + throw new Error(result.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); - } + setSuccessMessage(result.message); } catch (err: any) { setError(err.message); + throw err; } finally { setLoading(false); } }; - const handleModifyColumn = async () => { - if (!selectedTableForColumn || !columnToModify) { - setError('Table name and column name are required'); - return; - } - + const handleModifyColumn = async (tableName: string, data: any) => { setLoading(true); setError(''); setSuccessMessage(''); @@ -412,42 +296,27 @@ export default function AdminDashboard() { 'Content-Type': 'application/json', }, body: JSON.stringify({ - tableName: selectedTableForColumn, - columnName: columnToModify, - newType: newColumnType, - nullable: newColumnNullable, + tableName, + ...data, }), }); - const data = await response.json(); + const result = await response.json(); if (!response.ok) { - throw new Error(data.error || 'Failed to modify column'); + throw new Error(result.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); - } + setSuccessMessage(result.message); } catch (err: any) { setError(err.message); + throw err; } finally { setLoading(false); } }; - const handleDropColumn = async () => { - if (!selectedTableForColumn || !columnToDelete) { - setError('Table name and column name are required'); - return; - } - + const handleDropColumn = async (tableName: string, data: any) => { setLoading(true); setError(''); setSuccessMessage(''); @@ -459,27 +328,21 @@ export default function AdminDashboard() { 'Content-Type': 'application/json', }, body: JSON.stringify({ - tableName: selectedTableForColumn, - columnName: columnToDelete, + tableName, + ...data, }), }); - const data = await response.json(); + const result = await response.json(); if (!response.ok) { - throw new Error(data.error || 'Failed to drop column'); + throw new Error(result.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); - } + setSuccessMessage(result.message); } catch (err: any) { setError(err.message); + throw err; } finally { setLoading(false); } @@ -550,6 +413,41 @@ export default function AdminDashboard() { } }; + // Query Builder Handler + const handleExecuteBuiltQuery = async (params: any) => { + setLoading(true); + setError(''); + + try { + const response = await fetch('/api/admin/query-builder', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Query failed'); + } + + return data; + } catch (err: any) { + setError(err.message); + throw err; + } finally { + setLoading(false); + } + }; + + // Get icon component for navigation item + const getIconComponent = (iconName: string) => { + const IconComponent = iconMap[iconName]; + return IconComponent ? : ; + }; + return ( @@ -582,46 +480,19 @@ export default function AdminDashboard() { - - setTabValue(0)}> - - - - - - - - setTabValue(1)}> - - - - - - - - setTabValue(2)}> - - - - - - - - setTabValue(3)}> - - - - - - - - setTabValue(4)}> - - - - - - + {navItems.map((item, index) => ( + + setTabValue(index)} + > + + {getIconComponent(item.icon)} + + + + + ))} @@ -636,207 +507,55 @@ export default function AdminDashboard() { > - - - Database Tables - - - - - {tables.map(table => ( - - handleTableClick(table.table_name)}> - - - - - - - ))} - - - - {selectedTable && ( - - Table: - {' '} - {selectedTable} - - )} - - - - - SQL Query Interface - - - - setQueryText(e.target.value)} - placeholder="SELECT * FROM your_table LIMIT 10;" - sx={{ mb: 2 }} - /> - - - - - - - Table Manager - - - - - - - - - - - Existing Tables - - - {tables.map(table => ( - - - - - - - ))} - {tables.length === 0 && ( - - - - )} - - - - - - - - Column Manager - - - - - Select a table to manage its columns: - - - - - {selectedTableForColumn && ( - <> - - - - - - - {tableSchema && ( - - - - Current Columns for {selectedTableForColumn} - - - - - - Column Name - Data Type - Nullable - Default - - - - {tableSchema.columns?.map((col: any) => ( - - {col.column_name} - {col.data_type} - {col.is_nullable} - {col.column_default || 'NULL'} - - ))} - -
-
-
-
- )} - - )} -
- - - - + {/* Render tabs dynamically based on navItems */} + {navItems.map((item, index) => ( + + {item.id === 'tables' && ( + + )} + {item.id === 'query' && ( + + )} + {item.id === 'query-builder' && ( + + )} + {item.id === 'table-manager' && ( + + )} + {item.id === 'column-manager' && ( + + )} + {item.id === 'constraints' && ( + + )} + {item.id === 'indexes' && ( + + )} + + ))} {successMessage && ( setSuccessMessage('')}> @@ -845,7 +564,7 @@ export default function AdminDashboard() { )} {error && ( - + setError('')}> {error} )} @@ -860,9 +579,7 @@ export default function AdminDashboard() { - Rows returned: - {' '} - {queryResult.rowCount} + Rows returned: {queryResult.rowCount} @@ -894,267 +611,6 @@ export default function AdminDashboard() { )}
- - {/* Create Table Dialog */} - setOpenCreateTableDialog(false)} maxWidth="md" fullWidth> - Create New Table - - setNewTableName(e.target.value)} - sx={{ mt: 2, mb: 2 }} - /> - - Columns: - - {tableColumns.map((col, index) => ( - - updateColumnField(index, 'name', e.target.value)} - sx={{ mr: 1, mb: 1 }} - /> - - {(col.type === 'VARCHAR') && ( - updateColumnField(index, 'length', e.target.value)} - sx={{ mr: 1, mb: 1, width: 100 }} - /> - )} - updateColumnField(index, 'nullable', e.target.checked)} - /> - } - label="Nullable" - sx={{ mr: 1 }} - /> - updateColumnField(index, 'primaryKey', e.target.checked)} - /> - } - label="Primary Key" - sx={{ mr: 1 }} - /> - {tableColumns.length > 1 && ( - removeColumn(index)} color="error" size="small"> - - - )} - - ))} - - - - - - - - - {/* Drop Table Dialog */} - setOpenDropTableDialog(false)}> - Drop Table - - - Warning: This will permanently delete the table and all its data! - - - - - - - - - - {/* Add Column Dialog */} - setOpenAddColumnDialog(false)}> - Add Column to {selectedTableForColumn} - - setNewColumnName(e.target.value)} - sx={{ mt: 2, mb: 2 }} - /> - - setNewColumnNullable(e.target.checked)} - /> - } - label="Nullable" - sx={{ mb: 2 }} - /> - setNewColumnDefault(e.target.value)} - /> - - - - - - - - {/* Modify Column Dialog */} - setOpenModifyColumnDialog(false)}> - Modify Column in {selectedTableForColumn} - - - {columnToModify && ( - <> - - setNewColumnNullable(e.target.checked)} - /> - } - label="Nullable" - /> - - )} - - - - - - - - {/* Drop Column Dialog */} - setOpenDropColumnDialog(false)}> - Drop Column from {selectedTableForColumn} - - - Warning: This will permanently delete the column and all its data! - - - - - - - -
); diff --git a/src/components/admin/SQLQueryTab.tsx b/src/components/admin/SQLQueryTab.tsx new file mode 100644 index 0000000..26c0ad9 --- /dev/null +++ b/src/components/admin/SQLQueryTab.tsx @@ -0,0 +1,72 @@ +'use client'; + +import { + Box, + Button, + CircularProgress, + Paper, + TextField, + Typography, +} from '@mui/material'; +import { useState } from 'react'; +import { getFeatureById } from '@/utils/featureConfig'; + +type SQLQueryTabProps = { + onExecuteQuery: (query: string) => Promise; +}; + +export default function SQLQueryTab({ onExecuteQuery }: SQLQueryTabProps) { + const [queryText, setQueryText] = useState(''); + const [loading, setLoading] = useState(false); + + // Get feature configuration from JSON + const feature = getFeatureById('sql-query'); + + const handleExecute = async () => { + if (!queryText.trim()) { + return; + } + + setLoading(true); + try { + await onExecuteQuery(queryText); + } finally { + setLoading(false); + } + }; + + return ( + + + {feature?.name || 'SQL Query Interface'} + + + {feature?.description && ( + + {feature.description} + + )} + + + setQueryText(e.target.value)} + placeholder="SELECT * FROM your_table LIMIT 10;" + sx={{ mb: 2 }} + /> + + + + ); +} diff --git a/src/components/admin/TablesTab.tsx b/src/components/admin/TablesTab.tsx new file mode 100644 index 0000000..9396b54 --- /dev/null +++ b/src/components/admin/TablesTab.tsx @@ -0,0 +1,72 @@ +'use client'; + +import StorageIcon from '@mui/icons-material/Storage'; +import { + Box, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Paper, + Typography, +} from '@mui/material'; +import { getFeatureById } from '@/utils/featureConfig'; + +type TablesTabProps = { + tables: Array<{ table_name: string }>; + selectedTable: string; + onTableClick: (tableName: string) => void; +}; + +export default function TablesTab({ + tables, + selectedTable, + onTableClick, +}: TablesTabProps) { + // Get feature configuration from JSON + const feature = getFeatureById('database-crud'); + + return ( + + + {feature?.name || 'Database Tables'} + + + {feature?.description && ( + + {feature.description} + + )} + + + + {tables.map(table => ( + + onTableClick(table.table_name)} + > + + + + + + + ))} + {tables.length === 0 && ( + + + + )} + + + + {selectedTable && ( + + Table: {selectedTable} + + )} + + ); +} diff --git a/src/config/features.json b/src/config/features.json index 4d1d1d4..10f8928 100644 --- a/src/config/features.json +++ b/src/config/features.json @@ -2863,6 +2863,12 @@ "icon": "TableChart", "featureId": "table-management" }, + { + "id": "column-manager", + "label": "Column Manager", + "icon": "ViewColumn", + "featureId": "column-management" + }, { "id": "constraints", "label": "Constraints", From c9e4d76aa3126d4b6b05110c301e549345a15b49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:20:17 +0000 Subject: [PATCH 3/3] Fix import ordering and remove backup files Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/app/admin/dashboard/page-old.tsx | 1161 ----------------------- src/app/admin/dashboard/page.tsx | 10 +- src/app/admin/dashboard/page.tsx.backup | 369 ------- 3 files changed, 5 insertions(+), 1535 deletions(-) delete mode 100644 src/app/admin/dashboard/page-old.tsx delete mode 100644 src/app/admin/dashboard/page.tsx.backup diff --git a/src/app/admin/dashboard/page-old.tsx b/src/app/admin/dashboard/page-old.tsx deleted file mode 100644 index f246dd6..0000000 --- a/src/app/admin/dashboard/page-old.tsx +++ /dev/null @@ -1,1161 +0,0 @@ -'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 RuleIcon from '@mui/icons-material/Rule'; -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 ConstraintManagerTab from '@/components/admin/ConstraintManagerTab'; -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 ( - - ); -} - -export default function AdminDashboard() { - const router = useRouter(); - const [tabValue, setTabValue] = useState(0); - const [tables, setTables] = useState([]); - const [selectedTable, setSelectedTable] = useState(''); - const [queryText, setQueryText] = useState(''); - const [queryResult, setQueryResult] = useState(null); - const [tableSchema, setTableSchema] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [successMessage, setSuccessMessage] = useState(''); - - // Table Manager states - const [openCreateTableDialog, setOpenCreateTableDialog] = useState(false); - const [openDropTableDialog, setOpenDropTableDialog] = useState(false); - const [newTableName, setNewTableName] = useState(''); - const [tableColumns, setTableColumns] = useState([{ 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); - } - }; - - // Constraint Management Handlers - const handleAddConstraint = async (tableName: string, data: any) => { - setLoading(true); - setError(''); - setSuccessMessage(''); - - try { - const response = await fetch('/api/admin/constraints', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - tableName, - ...data, - }), - }); - - const result = await response.json(); - - if (!response.ok) { - throw new Error(result.error || 'Failed to add constraint'); - } - - setSuccessMessage(result.message || 'Constraint added successfully'); - } catch (err: any) { - setError(err.message); - throw err; - } finally { - setLoading(false); - } - }; - - const handleDropConstraint = async (tableName: string, constraintName: string) => { - setLoading(true); - setError(''); - setSuccessMessage(''); - - try { - const response = await fetch('/api/admin/constraints', { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - tableName, - constraintName, - }), - }); - - const result = await response.json(); - - if (!response.ok) { - throw new Error(result.error || 'Failed to drop constraint'); - } - - setSuccessMessage(result.message || 'Constraint dropped successfully'); - } catch (err: any) { - setError(err.message); - throw err; - } finally { - setLoading(false); - } - }; - - return ( - - - theme.zIndex.drawer + 1 }} - > - - - - Postgres Admin Panel - - - - - - - - - - - setTabValue(0)}> - - - - - - - - setTabValue(1)}> - - - - - - - - setTabValue(2)}> - - - - - - - - setTabValue(3)}> - - - - - - - - setTabValue(4)}> - - - - - - - - - - - - - - - - Database Tables - - - - - {tables.map(table => ( - - handleTableClick(table.table_name)}> - - - - - - - ))} - - - - {selectedTable && ( - - Table: - {' '} - {selectedTable} - - )} - - - - - SQL Query Interface - - - - setQueryText(e.target.value)} - placeholder="SELECT * FROM your_table LIMIT 10;" - sx={{ mb: 2 }} - /> - - - - - - - Table Manager - - - - - - - - - - - Existing Tables - - - {tables.map(table => ( - - - - - - - ))} - {tables.length === 0 && ( - - - - )} - - - - - - - - Column Manager - - - - - Select a table to manage its columns: - - - - - {selectedTableForColumn && ( - <> - - - - - - - {tableSchema && ( - - - - Current Columns for {selectedTableForColumn} - - - - - - Column Name - Data Type - Nullable - Default - - - - {tableSchema.columns?.map((col: any) => ( - - {col.column_name} - {col.data_type} - {col.is_nullable} - {col.column_default || 'NULL'} - - ))} - -
-
-
-
- )} - - )} -
- - - - - - {successMessage && ( - setSuccessMessage('')}> - {successMessage} - - )} - - {error && ( - - {error} - - )} - - {loading && ( - - - - )} - - {queryResult && !loading && ( - - - - Rows returned: - {' '} - {queryResult.rowCount} - - - - - - - {queryResult.fields?.map((field: any) => ( - - {field.name} - - ))} - - - - {queryResult.rows?.map((row: any, idx: number) => ( - - {queryResult.fields?.map((field: any) => ( - - {row[field.name] !== null - ? String(row[field.name]) - : 'NULL'} - - ))} - - ))} - -
-
-
- )} -
- - {/* Create Table Dialog */} - setOpenCreateTableDialog(false)} maxWidth="md" fullWidth> - Create New Table - - setNewTableName(e.target.value)} - sx={{ mt: 2, mb: 2 }} - /> - - Columns: - - {tableColumns.map((col, index) => ( - - updateColumnField(index, 'name', e.target.value)} - sx={{ mr: 1, mb: 1 }} - /> - - {(col.type === 'VARCHAR') && ( - updateColumnField(index, 'length', e.target.value)} - sx={{ mr: 1, mb: 1, width: 100 }} - /> - )} - updateColumnField(index, 'nullable', e.target.checked)} - /> - } - label="Nullable" - sx={{ mr: 1 }} - /> - updateColumnField(index, 'primaryKey', e.target.checked)} - /> - } - label="Primary Key" - sx={{ mr: 1 }} - /> - {tableColumns.length > 1 && ( - removeColumn(index)} color="error" size="small"> - - - )} - - ))} - - - - - - - - - {/* Drop Table Dialog */} - setOpenDropTableDialog(false)}> - Drop Table - - - Warning: This will permanently delete the table and all its data! - - - - - - - - - - {/* Add Column Dialog */} - setOpenAddColumnDialog(false)}> - Add Column to {selectedTableForColumn} - - setNewColumnName(e.target.value)} - sx={{ mt: 2, mb: 2 }} - /> - - setNewColumnNullable(e.target.checked)} - /> - } - label="Nullable" - sx={{ mb: 2 }} - /> - setNewColumnDefault(e.target.value)} - /> - - - - - - - - {/* Modify Column Dialog */} - setOpenModifyColumnDialog(false)}> - Modify Column in {selectedTableForColumn} - - - {columnToModify && ( - <> - - setNewColumnNullable(e.target.checked)} - /> - } - label="Nullable" - /> - - )} - - - - - - - - {/* Drop Column Dialog */} - setOpenDropColumnDialog(false)}> - Drop Column from {selectedTableForColumn} - - - Warning: This will permanently delete the column and all its data! - - - - - - - - -
-
- ); -} diff --git a/src/app/admin/dashboard/page.tsx b/src/app/admin/dashboard/page.tsx index 3b733d4..1f019fc 100644 --- a/src/app/admin/dashboard/page.tsx +++ b/src/app/admin/dashboard/page.tsx @@ -32,14 +32,14 @@ import { import { ThemeProvider } from '@mui/material/styles'; import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useState } from 'react'; -import TablesTab from '@/components/admin/TablesTab'; -import SQLQueryTab from '@/components/admin/SQLQueryTab'; -import TableManagerTab from '@/components/admin/TableManagerTab'; import ColumnManagerTab from '@/components/admin/ColumnManagerTab'; import ConstraintManagerTab from '@/components/admin/ConstraintManagerTab'; -import QueryBuilderTab from '@/components/admin/QueryBuilderTab'; import IndexManagerTab from '@/components/admin/IndexManagerTab'; -import { getNavItems, getFeatureById } from '@/utils/featureConfig'; +import QueryBuilderTab from '@/components/admin/QueryBuilderTab'; +import SQLQueryTab from '@/components/admin/SQLQueryTab'; +import TableManagerTab from '@/components/admin/TableManagerTab'; +import TablesTab from '@/components/admin/TablesTab'; +import { getFeatureById, getNavItems } from '@/utils/featureConfig'; import { theme } from '@/utils/theme'; import Button from '@mui/material/Button'; diff --git a/src/app/admin/dashboard/page.tsx.backup b/src/app/admin/dashboard/page.tsx.backup deleted file mode 100644 index 275d9ea..0000000 --- a/src/app/admin/dashboard/page.tsx.backup +++ /dev/null @@ -1,369 +0,0 @@ -'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 { - Alert, - AppBar, - Box, - Button, - CircularProgress, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - Drawer, - IconButton, - List, - ListItem, - ListItemButton, - ListItemIcon, - ListItemText, - Paper, - 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 ( - - ); -} - -export default function AdminDashboard() { - const router = useRouter(); - const [tabValue, setTabValue] = useState(0); - const [tables, setTables] = useState([]); - const [selectedTable, setSelectedTable] = useState(''); - const [queryText, setQueryText] = useState(''); - const [queryResult, setQueryResult] = useState(null); - const [tableSchema, setTableSchema] = useState(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(null); - const [deletingRecord, setDeletingRecord] = useState(null); - const [formData, setFormData] = 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]); - - 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 (!response.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); - } - }; - - return ( - - - theme.zIndex.drawer + 1 }} - > - - - - Postgres Admin Panel - - - - - - - - - - - setTabValue(0)}> - - - - - - - - setTabValue(1)}> - - - - - - - - - - - - - - - - Database Tables - - - - - {tables.map(table => ( - - handleTableClick(table.table_name)}> - - - - - - - ))} - - - - {selectedTable && ( - - Table: - {' '} - {selectedTable} - - )} - - - - - SQL Query Interface - - - - setQueryText(e.target.value)} - placeholder="SELECT * FROM your_table LIMIT 10;" - sx={{ mb: 2 }} - /> - - - - - {error && ( - - {error} - - )} - - {loading && ( - - - - )} - - {queryResult && !loading && ( - - - - Rows returned: - {' '} - {queryResult.rowCount} - - - - - - - {queryResult.fields?.map((field: any) => ( - - {field.name} - - ))} - - - - {queryResult.rows?.map((row: any, idx: number) => ( - - {queryResult.fields?.map((field: any) => ( - - {row[field.name] !== null - ? String(row[field.name]) - : 'NULL'} - - ))} - - ))} - -
-
-
- )} -
-
-
- ); -}