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 (
+
+ {value === index && {children}}
+
+ );
+}
+
+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
+
+ }>
+ Logout
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+ }
+ onClick={() => setOpenCreateTableDialog(true)}
+ sx={{ mr: 2 }}
+ >
+ Create Table
+
+ }
+ onClick={() => setOpenDropTableDialog(true)}
+ >
+ Drop Table
+
+
+
+
+
+
+ Existing Tables
+
+
+ {tables.map(table => (
+
+
+
+
+
+
+ ))}
+ {tables.length === 0 && (
+
+
+
+ )}
+
+
+
+
+
+
+
+ Column Manager
+
+
+
+
+ Select a table to manage its columns:
+
+
+
+
+ {selectedTableForColumn && (
+ <>
+
+ }
+ onClick={() => setOpenAddColumnDialog(true)}
+ sx={{ mr: 2 }}
+ >
+ Add Column
+
+ }
+ onClick={() => setOpenModifyColumnDialog(true)}
+ sx={{ mr: 2 }}
+ >
+ Modify Column
+
+ }
+ onClick={() => setOpenDropColumnDialog(true)}
+ >
+ Drop Column
+
+
+
+ {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 */}
+
+
+ {/* Drop Table Dialog */}
+
+
+ {/* Add Column Dialog */}
+
+
+ {/* Modify Column Dialog */}
+
+
+ {/* Drop Column Dialog */}
+
+
+
+ );
+}
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
-
-
-
- }
- onClick={() => setOpenCreateTableDialog(true)}
- sx={{ mr: 2 }}
- >
- Create Table
-
- }
- onClick={() => setOpenDropTableDialog(true)}
- >
- Drop Table
-
-
-
-
-
-
- Existing Tables
-
-
- {tables.map(table => (
-
-
-
-
-
-
- ))}
- {tables.length === 0 && (
-
-
-
- )}
-
-
-
-
-
-
-
- Column Manager
-
-
-
-
- Select a table to manage its columns:
-
-
-
-
- {selectedTableForColumn && (
- <>
-
- }
- onClick={() => setOpenAddColumnDialog(true)}
- sx={{ mr: 2 }}
- >
- Add Column
-
- }
- onClick={() => setOpenModifyColumnDialog(true)}
- sx={{ mr: 2 }}
- >
- Modify Column
-
- }
- onClick={() => setOpenDropColumnDialog(true)}
- >
- Drop Column
-
-
-
- {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 */}
-
-
- {/* Drop Table Dialog */}
-
-
- {/* Add Column Dialog */}
-
-
- {/* Modify Column Dialog */}
-
-
- {/* Drop Column Dialog */}
-
);
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",