fix: Database schema fixes. CI/CD improvements.

This commit is contained in:
Eric Gullickson
2025-12-27 16:23:22 -06:00
parent 344df5184c
commit dc2c731119
26 changed files with 242360 additions and 481192 deletions

View File

@@ -28,6 +28,7 @@ import {
Tooltip,
Typography,
Alert,
Collapse,
} from '@mui/material';
import {
Search,
@@ -35,6 +36,8 @@ import {
FileDownload,
FileUpload,
Clear,
ExpandMore,
ExpandLess,
} from '@mui/icons-material';
import toast from 'react-hot-toast';
import { useAdminAccess } from '../../core/auth/useAdminAccess';
@@ -52,6 +55,7 @@ import {
import {
CatalogSearchResult,
ImportPreviewResult,
ImportApplyResult,
} from '../../features/admin/types/admin.types';
const PAGE_SIZE_OPTIONS = [25, 50, 100];
@@ -76,6 +80,8 @@ export const AdminCatalogPage: React.FC = () => {
// Import state
const [importDialogOpen, setImportDialogOpen] = useState(false);
const [importPreview, setImportPreview] = useState<ImportPreviewResult | null>(null);
const [importResult, setImportResult] = useState<ImportApplyResult | null>(null);
const [errorsExpanded, setErrorsExpanded] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
// Hooks
@@ -217,15 +223,38 @@ export const AdminCatalogPage: React.FC = () => {
if (!importPreview?.previewId) return;
try {
await importApplyMutation.mutateAsync(importPreview.previewId);
setImportDialogOpen(false);
setImportPreview(null);
const result = await importApplyMutation.mutateAsync(importPreview.previewId);
setImportResult(result);
if (result.errors.length > 0) {
toast.error(
`Import completed with ${result.errors.length} error(s): ${result.created} created, ${result.updated} updated`
);
// Keep dialog open for error review
} else {
toast.success(
`Import completed successfully: ${result.created} created, ${result.updated} updated`
);
// Auto-close on complete success
setImportDialogOpen(false);
setImportPreview(null);
setImportResult(null);
}
refetch();
} catch (error) {
// Error is handled by mutation
// Error is handled by mutation's onError
}
}, [importPreview, importApplyMutation, refetch]);
const handleImportDialogClose = useCallback(() => {
if (importApplyMutation.isPending) return;
setImportDialogOpen(false);
setImportPreview(null);
setImportResult(null);
setErrorsExpanded(false);
}, [importApplyMutation.isPending]);
// Export handler
const handleExport = useCallback(() => {
exportMutation.mutate();
@@ -506,18 +535,20 @@ export const AdminCatalogPage: React.FC = () => {
</DialogActions>
</Dialog>
{/* Import Preview Dialog */}
{/* Import Preview/Results Dialog */}
<Dialog
open={importDialogOpen}
onClose={() => !importApplyMutation.isPending && setImportDialogOpen(false)}
onClose={handleImportDialogClose}
maxWidth="md"
fullWidth
>
<DialogTitle>Import Preview</DialogTitle>
<DialogTitle>
{importResult ? 'Import Results' : 'Import Preview'}
</DialogTitle>
<DialogContent>
{importPreview && (
{/* Preview Mode */}
{importPreview && !importResult && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
{/* Summary */}
<Box sx={{ display: 'flex', gap: 3 }}>
<Typography>
<strong>To Create:</strong> {importPreview.toCreate.length}
@@ -527,7 +558,6 @@ export const AdminCatalogPage: React.FC = () => {
</Typography>
</Box>
{/* Errors */}
{importPreview.errors.length > 0 && (
<Alert severity="error">
<Typography variant="subtitle2" gutterBottom>
@@ -546,7 +576,6 @@ export const AdminCatalogPage: React.FC = () => {
</Alert>
)}
{/* Valid status */}
{importPreview.valid ? (
<Alert severity="success">
The import file is valid and ready to be applied.
@@ -558,23 +587,86 @@ export const AdminCatalogPage: React.FC = () => {
)}
</Box>
)}
{/* Results Mode */}
{importResult && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
<Box sx={{ display: 'flex', gap: 3 }}>
<Typography>
<strong>Created:</strong> {importResult.created}
</Typography>
<Typography>
<strong>Updated:</strong> {importResult.updated}
</Typography>
</Box>
{importResult.errors.length > 0 && (
<Box sx={{ border: 1, borderColor: 'error.main', borderRadius: 1 }}>
<Box
onClick={() => setErrorsExpanded(!errorsExpanded)}
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
p: 2,
bgcolor: 'error.light',
cursor: 'pointer',
'&:hover': { bgcolor: 'error.main', color: 'white' },
}}
>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{importResult.errors.length} Error(s) Occurred
</Typography>
<IconButton size="small" sx={{ color: 'inherit' }}>
{errorsExpanded ? <ExpandLess /> : <ExpandMore />}
</IconButton>
</Box>
<Collapse in={errorsExpanded}>
<Box sx={{ maxHeight: 400, overflow: 'auto', p: 2, bgcolor: 'background.paper' }}>
<Box component="ul" sx={{ m: 0, pl: 2 }}>
{importResult.errors.map((err, idx) => (
<Typography
component="li"
key={idx}
variant="body2"
sx={{ mb: 1, fontFamily: 'monospace', fontSize: '0.875rem' }}
>
<strong>Row {err.row}:</strong> {err.error}
</Typography>
))}
</Box>
</Box>
</Collapse>
</Box>
)}
{importResult.errors.length === 0 && (
<Alert severity="success">
Import completed successfully with no errors.
</Alert>
)}
</Box>
)}
</DialogContent>
<DialogActions>
<Button
onClick={() => setImportDialogOpen(false)}
onClick={handleImportDialogClose}
disabled={importApplyMutation.isPending}
sx={{ textTransform: 'none' }}
>
Cancel
</Button>
<Button
onClick={handleImportConfirm}
disabled={!importPreview?.valid || importApplyMutation.isPending}
variant="contained"
sx={{ textTransform: 'none' }}
>
{importApplyMutation.isPending ? <CircularProgress size={20} /> : 'Apply Import'}
{importResult ? 'Close' : 'Cancel'}
</Button>
{!importResult && (
<Button
onClick={handleImportConfirm}
disabled={!importPreview?.valid || importApplyMutation.isPending}
variant="contained"
sx={{ textTransform: 'none' }}
>
{importApplyMutation.isPending ? <CircularProgress size={20} /> : 'Apply Import'}
</Button>
)}
</DialogActions>
</Dialog>
</Box>