David Headrick f5b1fe6397 Update mailing and target management features
- Added new methods for creating mailings and testing targets.
- Updated configuration files for JWT settings and connection strings.
- Introduced new DTOs for target column updates and test targets.
- Enhanced MailingStatistic with a new SentDate property.
- Created new components for handling cancelled mailings and target samples.
- Refactored authentication in Login.tsx to use fetch API.
- Updated various services and repositories to support new functionalities.
2025-04-07 12:13:44 -05:00

258 lines
11 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
import VisibilityIcon from '@mui/icons-material/Visibility';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import CancelIcon from '@mui/icons-material/Cancel';
import RefreshIcon from '@mui/icons-material/Refresh';
import { Box, useTheme, useMediaQuery, CircularProgress, IconButton, List, Card, CardContent, Typography } from '@mui/material';
import { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
import Mailing from '@/types/mailing';
import MailingEdit from "@/components/modals/MailingEdit";
import MailingView from "@/components/modals/MailingView"; // Assume this is a new read-only view component
import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
function ScheduleMailings() {
const theme = useTheme();
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 [viewOpen, setViewOpen] = useState<boolean>(false);
const [editOpen, setEditOpen] = useState<boolean>(false);
const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
const [mailingToCancel, setMailingToCancel] = useState<Mailing | null>(null);
const [forceRender, setForceRender] = useState(false);
const formatRecurringString = (typeCode: string, startDate: string): string => {
const date = new Date(startDate);
switch (typeCode.toUpperCase()) {
case 'D':
return `Daily at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
case 'W':
return `Weekly on ${date.toLocaleDateString('en-US', { weekday: 'long' })} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
case 'M':
return `Monthly on day ${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
default:
return 'Custom recurring schedule';
}
};
const columns: GridColDef<Mailing>[] = [
{
field: "actions",
headerName: "",
sortable: false,
width: 140,
renderCell: (params: GridRenderCellParams<Mailing>) => (
<>
<IconButton color="primary" onClick={(e) => { e.stopPropagation(); handleView(params.row); }}>
<VisibilityIcon />
</IconButton>
<IconButton color="primary" onClick={(e) => { e.stopPropagation(); handleCopy(params.row); }}>
<ContentCopyIcon />
</IconButton>
<IconButton color="secondary" onClick={(e) => { e.stopPropagation(); handleCancelClick(params.row); }}>
<CancelIcon />
</IconButton>
</>
),
},
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 },
{
field: "scheduleDate",
headerName: "Schedule Date",
flex: 1,
minWidth: 200,
valueGetter: (_: any, row: Mailing) => row.scheduleDate ? new Date(row.scheduleDate).toLocaleString() : 'N/A'
},
{
field: "recurring",
headerName: "Recurring",
flex: 1,
minWidth: 200,
valueGetter: (_: any, mailing: Mailing) => mailing.recurringTypeCode && mailing.recurringStartDate
? formatRecurringString(mailing.recurringTypeCode, mailing.recurringStartDate)
: ''
},
];
const reloadMailings = async () => {
setMailingsLoading(true);
const mailingsResponse = await fetch("/api/mailings/status/sc"); // Adjust endpoint as needed
const mailingsData = await mailingsResponse.json();
if (mailingsData) {
setMailings(mailingsData);
setMailingsLoading(false);
setForceRender(true);
} else {
console.error("Failed to fetch scheduled mailings");
setMailingsLoading(false);
}
};
const handleView = (row: Mailing) => {
setSelectedRow(row);
setViewOpen(true);
};
const handleCopy = (row: Mailing) => {
const newMailing = { ...row, id: 0 }; // Copy all fields except ID
setSelectedRow(newMailing);
setEditOpen(true);
};
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);
};
const handleUpdateRow = (updatedRow: Mailing) => {
setMailings((prev) => [...prev, updatedRow]);
};
useEffect(() => {
reloadMailings();
}, []);
useEffect(() => {
if (forceRender) {
setForceRender(false);
}
}, [forceRender]);
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 ? (
mailingsLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
<CircularProgress />
</Box>
) : (
<List>
{mailings.map((mailing) => (
<Card key={mailing.id} sx={{ marginBottom: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<CardContent>
<Typography variant="h6">{mailing.name}</Typography>
<Typography variant="body2">
Schedule Date: {mailing.scheduleDate ? new Date(mailing.scheduleDate).toLocaleString() : 'N/A'}
</Typography>
<Typography variant="body2">
Recurring: {mailing.recurringTypeCode && mailing.recurringStartDate
? formatRecurringString(mailing.recurringTypeCode, mailing.recurringStartDate)
: 'None'}
</Typography>
</CardContent>
<Box>
<IconButton onClick={() => handleView(mailing)}>
<VisibilityIcon />
</IconButton>
<IconButton onClick={() => handleCopy(mailing)}>
<ContentCopyIcon />
</IconButton>
<IconButton color="secondary" onClick={() => handleCancelClick(mailing)}>
<CancelIcon />
</IconButton>
</Box>
</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={() => 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: 'scheduleDate', sort: 'asc' }], // Default sort by scheduleDate, descending
},
}}
pageSizeOptions={[10, 20, 50, 100]}
/>
)}
</Box>
{viewOpen && (
<MailingView
open={viewOpen}
mailing={selectedRow}
onClose={() => setViewOpen(false)}
/>
)}
{editOpen && (
<MailingEdit
open={editOpen}
mailing={selectedRow}
onClose={(reason) => { if (reason !== 'backdropClick') setEditOpen(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 ScheduleMailings;