David Headrick 9703517974 Update configuration and mailing service functionality
- Added HTTP client configuration for SendGrid in Program.cs.
- Cleaned up appsettings files for development and production environments.
- Modified MailingService to use IHttpClientFactory for SendGrid.
- Enhanced NewMailings component with cancel functionality and confirmation dialog.
- Updated project dependencies in .csproj and created package-lock.json.
2025-04-09 11:57:32 -05:00

227 lines
9.5 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";
function NewMailings() {
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 fetch("/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 fetch(`/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;