- 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.
258 lines
11 KiB
TypeScript
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; |