feat: implement MySQL database driver and API service for schema management and data operations

This commit is contained in:
Ümit Tunç
2026-04-24 22:43:28 +03:00
parent 22ff72d39c
commit e75657d22a
6 changed files with 137 additions and 19 deletions
+61 -2
View File
@@ -20,7 +20,13 @@ import {
Save,
Undo
} from '@mui/icons-material';
import { DataGrid, useGridApiRef, type GridPaginationModel, type GridRowModel } from '@mui/x-data-grid';
import {
DataGrid,
useGridApiRef,
type GridPaginationModel,
type GridRowModel,
type GridFilterModel
} from '@mui/x-data-grid';
import { useAppStore } from '../store/useAppStore';
import { SchemaService } from '../services/api';
import TransferContent from './TransferContent';
@@ -39,6 +45,7 @@ const MainContent: React.FC = () => {
page: 0,
pageSize: 100,
});
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
const [notification, setNotification] = useState<MainNotification>({
open: false,
@@ -58,7 +65,7 @@ const MainContent: React.FC = () => {
const [selectionEnd, setSelectionEnd] = useState<{ id: string | number, field: string } | null>(null);
const { columns, loading: loadingSchema, primaryKey } = useTableSchema(activeTable, activeDatabase);
const { rows, rowCount, loading: loadingData, refetch } = useTableData(activeTable, activeDatabase, paginationModel);
const { rows, rowCount, loading: loadingData, refetch } = useTableData(activeTable, activeDatabase, paginationModel, filterModel);
const handleRowUpdate = (newRow: GridRowModel, oldRow: GridRowModel) => {
const hasChanged = Object.keys(newRow).some(key => newRow[key] !== oldRow[key]);
@@ -101,6 +108,51 @@ const MainContent: React.FC = () => {
}
};
const handleExport = async (format: 'sql' | 'csv') => {
if (!activeTable || !activeDatabase) return;
try {
setNotification({ open: true, title: 'Exporting', message: 'Generating export file...', severity: 'success' });
// For now, we'll use the existing SQL export for 'sql'
// and implement a client-side CSV export for 'csv' since we have the rows
if (format === 'sql') {
const response = await SchemaService.exportDatabase(activeDatabase, activeTable);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${activeTable}_${new Date().toISOString().split('T')[0]}.sql`);
document.body.appendChild(link);
link.click();
link.remove();
} else {
// Simple CSV Export
const headers = columns.map(c => c.headerName || c.field).join(',');
const csvRows = rows.map(row =>
columns.map(c => {
const val = row[c.field];
return typeof val === 'string' ? `"${val.replace(/"/g, '""')}"` : val;
}).join(',')
);
const csvContent = [headers, ...csvRows].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${activeTable}_export.csv`);
document.body.appendChild(link);
link.click();
link.remove();
}
} catch (error: any) {
setNotification({
open: true,
title: 'Export Failed',
message: error.message,
severity: 'error'
});
}
};
const handlePaste = useCallback((event: React.ClipboardEvent) => {
if (!cellFocus || !activeTable) return;
@@ -385,6 +437,10 @@ const MainContent: React.FC = () => {
<Box sx={{ px: 1.5, py: 0.5, borderRadius: 10, bgcolor: 'rgba(0, 97, 255, 0.1)', color: 'primary.main' }}>
<Typography variant="caption" sx={{ fontWeight: 700 }}>{rowCount} rows found</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button size="small" variant="outlined" startIcon={<CloudDownload />} onClick={() => handleExport('sql')} sx={{ borderRadius: 2, fontSize: '0.7rem' }}>SQL Export</Button>
<Button size="small" variant="outlined" startIcon={<TableChart />} onClick={() => handleExport('csv')} sx={{ borderRadius: 2, fontSize: '0.7rem' }}>Excel/CSV</Button>
</Box>
</Box>
{Object.keys(pendingChanges).length > 0 && (
@@ -444,6 +500,9 @@ const MainContent: React.FC = () => {
paginationMode="server"
paginationModel={paginationModel}
onPaginationModelChange={setPaginationModel}
filterMode="server"
filterModel={filterModel}
onFilterModelChange={setFilterModel}
pageSizeOptions={[25, 50, 100]}
processRowUpdate={handleRowUpdate}
onProcessRowUpdateError={(error) => {
+9 -4
View File
@@ -1,11 +1,12 @@
import { useState, useEffect, useCallback } from 'react';
import type { GridPaginationModel } from '@mui/x-data-grid';
import type { GridPaginationModel, GridFilterModel } from '@mui/x-data-grid';
import { SchemaService } from '../services/api';
export const useTableData = (
activeTable: string | null,
activeDatabase: string | null,
paginationModel: GridPaginationModel
paginationModel: GridPaginationModel,
filterModel?: GridFilterModel
) => {
const [rows, setRows] = useState<any[]>([]);
const [rowCount, setRowCount] = useState(0);
@@ -16,13 +17,17 @@ export const useTableData = (
setLoading(true);
try {
const params = {
const params: any = {
skip: paginationModel.page * paginationModel.pageSize,
take: paginationModel.pageSize,
requireTotalCount: true,
database: activeDatabase,
};
if (filterModel && filterModel.items.length > 0) {
params.filters = JSON.stringify(filterModel.items);
}
const response = await SchemaService.getTableData(activeTable, params);
const dataWithIds = response.data.data.map((row: any, index: number) => ({
@@ -37,7 +42,7 @@ export const useTableData = (
} finally {
setLoading(false);
}
}, [activeTable, activeDatabase, paginationModel]);
}, [activeTable, activeDatabase, paginationModel, filterModel]);
useEffect(() => {
fetchData();