David Headrick 0e099bfd07 Enhance authentication and logging mechanisms
Updated authentication handling in controllers, added JWT support, and improved error logging. Introduced centralized API calls with customFetch for better token management. Added Grafana's Faro SDK for monitoring and tracing. Refactored project files for improved structure and maintainability.
2025-05-19 17:26:37 -05:00

229 lines
9.6 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
import { useSetupData, SetupData } from "@/context/SetupDataContext";
import EditIcon from '@mui/icons-material/Edit';
import AddIcon from '@mui/icons-material/Add';
import RefreshIcon from '@mui/icons-material/Refresh';
import CancelIcon from '@mui/icons-material/Cancel';
import { List, Card, CardContent, Typography, Box, useTheme, useMediaQuery, CircularProgress, IconButton } from '@mui/material';
import { DataGrid, GridColDef, GridRenderCellParams, GridRowModel, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
import Mailing from '@/types/mailing';
//import Template from '@/types/template';
import MailingEdit from "@/components/modals/MailingEdit";
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
import { useCustomFetch } from "@/utils/customFetch";
function NewMailings() {
const customFetch = useCustomFetch();
const theme = useTheme();
const setupData: SetupData = useSetupData();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const gridContainerRef = useRef<HTMLDivElement | null>(null);
const [mailingsLoading, setMailingsLoading] = useState<boolean>(false);
const [mailings, setMailings] = useState<Mailing[]>([]);
const [selectedRow, setSelectedRow] = useState<Mailing | null>(null);
const [open, setOpen] = useState<boolean>(false);
const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
const [mailingToCancel, setMailingToCancel] = useState<Mailing | null>(null);
const columns: GridColDef<Mailing>[] = [
{
field: "actions",
headerName: "",
sortable: false,
width: 100,
renderCell: (params: GridRenderCellParams<Mailing>) => (
<>
<IconButton color="primary" onClick={(e) => { e.stopPropagation(); handleEdit(params.row); }}>
<EditIcon />
</IconButton>
<IconButton color="secondary" onClick={(e) => { e.stopPropagation(); handleCancelClick(params.row); }}>
<CancelIcon />
</IconButton>
</>
),
},
{ field: "id", headerName: "ID", width: 80 },
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 },
{ field: "description", headerName: "Description", flex: 1, minWidth: 200 },
{
field: "templateId",
headerName: "Subject",
flex: 1,
minWidth: 160,
valueGetter: (_: number, row: Mailing) => setupData.templates.find(t => t.id === row.templateId)?.subject || 'Unknown',
},
];
const reloadMailings = async () => {
setMailingsLoading(true);
const mailingsResponse = await customFetch("/api/mailings/status/ed");
const mailingsData = await mailingsResponse.json();
if (mailingsData) {
setMailings(mailingsData);
setMailingsLoading(false);
}
else {
console.error("Failed to fetch mailings");
setMailingsLoading(false);
}
}
const handleNew = () => {
setSelectedRow(null);
setOpen(true);
};
const handleEdit = (row: GridRowModel<Mailing>) => {
setSelectedRow(row);
setOpen(true);
};
const handleUpdateRow = (updatedRow: Mailing) => {
if (updatedRow.statusCode.toUpperCase() !== "ED")
removeMailing(updatedRow);
else
updateMailings(updatedRow);
};
const handleCancelClick = (row: Mailing) => {
setMailingToCancel(row);
setConfirmDialogOpen(true);
};
const handleCancelConfirm = async () => {
if (!mailingToCancel) return;
try {
const response = await customFetch(`/api/mailings/${mailingToCancel.id}/cancel`, { method: 'POST' });
if (response.ok) {
setMailings((prev) => prev.filter(m => m.id !== mailingToCancel.id));
} else {
console.error("Failed to cancel mailing");
}
} catch (error) {
console.error("Error cancelling mailing:", error);
} finally {
setConfirmDialogOpen(false);
setMailingToCancel(null);
}
};
const handleCancelDialogClose = () => {
setConfirmDialogOpen(false);
setMailingToCancel(null);
};
useEffect(() => {
reloadMailings();
}, []);
const removeMailing = (mailing: Mailing) => {
setMailings((prevMailings) => {
return prevMailings.filter(el => el.id !== mailing.id);
});
}
const updateMailings = (updatedMailing: Mailing) => {
setMailings((prev) => {
const exists = prev.some((e) => e.id === updatedMailing.id);
return exists
? prev.map((server) => (server.id === updatedMailing.id ? updatedMailing : server))
: [...prev, updatedMailing];
});
};
return (
<Box ref={gridContainerRef} sx={{
position: 'relative', left: 0, right: 0, height: "calc(100vh - 124px)", overflow: "hidden",
transition: theme.transitions.create(['width', 'height'], {
easing: theme.transitions.easing.easeInOut,
duration: theme.transitions.duration.standard,
})
}}>
<Box sx={{ position: 'absolute', inset: 0 }}>
{isMobile ? (
<List>
{mailings.map((row) => (
<Card key={row.id} sx={{ marginBottom: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<CardContent>
<Typography variant="h6">{row.name}</Typography>
<Typography variant="body2">ID: {row.id}</Typography>
<Typography variant="body2">Description: {row.description}</Typography>
<Typography variant="body2">Subject: {setupData.templates.find(t => t.id === row.templateId)?.subject || 'Unknown'}</Typography>
</CardContent>
<IconButton onClick={(e) => { e.stopPropagation(); handleEdit(row); }}>
<EditIcon />
</IconButton>
<IconButton color="secondary" onClick={() => handleCancelClick(row)}>
<CancelIcon />
</IconButton>
</Box>
</Card>
))}
</List>
) : (
<DataGrid
rows={mailings}
columns={columns}
autoPageSize
sx={{ minWidth: "600px" }}
slots={{
toolbar: () => (
<GridToolbarContainer sx={{ display: "flex", alignItems: "center" }}>
<IconButton size="small" color="primary" onClick={handleNew} sx={{ marginLeft: 1 }}>
<AddIcon />
</IconButton>
<IconButton size="small" color="primary" onClick={() => reloadMailings()} sx={{ marginLeft: 1 }}>
{mailingsLoading ? <CircularProgress size={24} color="inherit" /> : <RefreshIcon />}
</IconButton>
<GridToolbarColumnsButton />
<GridToolbarDensitySelector />
<GridToolbarExport />
<GridToolbarQuickFilter sx={{ ml: "auto" }} />
</GridToolbarContainer>
),
}}
slotProps={{
toolbar: {
showQuickFilter: true,
},
}}
initialState={{
pagination: {
paginationModel: {
pageSize: 20,
},
},
sorting: {
sortModel: [{ field: 'id', sort: 'asc' }],
},
}}
pageSizeOptions={[10, 20, 50, 100]}
/>
)}
</Box>
{open && (
<MailingEdit
open={open}
mailing={selectedRow}
onClose={(reason) => { if (reason !== 'backdropClick') setOpen(false) }}
onSave={handleUpdateRow}
/>
)}
{confirmDialogOpen && (
<ConfirmationDialog
open={confirmDialogOpen}
title="Cancel Mailing"
message={`Are you sure you want to cancel the mailing "${mailingToCancel?.name}"? This action cannot be undone.`}
onConfirm={handleCancelConfirm}
onCancel={handleCancelDialogClose}
/>
)}
</Box>
);
}
export default NewMailings;