Add TotalRows to TargetSample and update related components

- Updated `TargetSample` class to include `TotalRows` property.
- Set `TotalRows` in `TargetRepository` based on database query.
- Added state management for `targetSample` and loading in `MailingEdit.tsx`.
- Modified rendering logic in `MailingEdit.tsx` to display total rows.
- Updated `TargetSampleModal` to accept `targetSample` as a prop.
- Created new `targetSample.ts` file defining the `TargetSample` interface.
This commit is contained in:
David Headrick 2025-04-17 08:15:19 -05:00
parent 51c267e48f
commit 712dbb5046
5 changed files with 89 additions and 40 deletions

View File

@ -10,6 +10,7 @@ namespace Surge365.MassEmailReact.Application.DTOs
{ {
public Dictionary<string, TargetSampleColumn> Columns { get; set; } = new Dictionary<string, TargetSampleColumn>(); public Dictionary<string, TargetSampleColumn> Columns { get; set; } = new Dictionary<string, TargetSampleColumn>();
public List<Dictionary<string, string>> Rows { get; set; } = new List<Dictionary<string, string>>(); public List<Dictionary<string, string>> Rows { get; set; } = new List<Dictionary<string, string>>();
public int TotalRows { get; set; } = 0;
} }
public class TargetSampleColumn public class TargetSampleColumn
{ {

View File

@ -218,6 +218,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
// Build TargetSample // Build TargetSample
var targetSample = new TargetSample(); var targetSample = new TargetSample();
targetSample.TotalRows = rowCount;
bool emailFound = false; bool emailFound = false;
bool uniqueIDFound = false; bool uniqueIDFound = false;
bool bounceFound = false; bool bounceFound = false;

View File

@ -13,12 +13,15 @@ import {
Box, Box,
MenuItem, MenuItem,
Select, Select,
InputLabel InputLabel,
Typography,
CircularProgress
} from "@mui/material"; } from "@mui/material";
import VisibilityIcon from '@mui/icons-material/Visibility'; import VisibilityIcon from '@mui/icons-material/Visibility';
import Template from "@/types/template"; import Template from "@/types/template";
import Mailing from "@/types/mailing"; import Mailing from "@/types/mailing";
import TargetSample from "@/types/targetSample";
import Target from "@/types/target"; import Target from "@/types/target";
//import MailingTemplate from "@/types/mailingTemplate"; //import MailingTemplate from "@/types/mailingTemplate";
//import MailingTarget from "@/types/mailingTarget"; //import MailingTarget from "@/types/mailingTarget";
@ -234,6 +237,8 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
const [currentTemplate, setCurrentTemplate] = useState<Template | null>(null); const [currentTemplate, setCurrentTemplate] = useState<Template | null>(null);
const [TargetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false); const [TargetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
const [currentTarget, setCurrentTarget] = useState<Target | null>(null); const [currentTarget, setCurrentTarget] = useState<Target | null>(null);
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
const [targetSampleLoading, setTargetSampleLoading] = useState(false);
const { register, trigger, control, handleSubmit, reset, setValue, formState: { errors } } = useForm<Mailing>({ const { register, trigger, control, handleSubmit, reset, setValue, formState: { errors } } = useForm<Mailing>({
mode: "onBlur", mode: "onBlur",
@ -374,6 +379,29 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
} }
}; };
useEffect(() => {
if (open) {
const fetchSampleData = async () => {
if (!currentTarget?.id)
return;
setTargetSampleLoading(true);
try {
const response = await fetch(`/api/targets/${currentTarget.id}/sample`);
if (!response.ok) throw new Error("Failed to fetch sample data");
const data: TargetSample = await response.json();
setTargetSample(data);
} catch (error) {
console.error("Error fetching target sample:", error);
setTargetSample(null);
} finally {
setTargetSampleLoading(false);
}
};
fetchSampleData();
}
}, [open, currentTarget?.id]);
const handleTestEmailListChange = (list: TestEmailList | null) => { const handleTestEmailListChange = (list: TestEmailList | null) => {
if (list) { if (list) {
setTestEmailListId(list.id); setTestEmailListId(list.id);
@ -521,7 +549,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
render={({ field }) => ( render={({ field }) => (
<TextField <TextField
{...field} {...field}
label="To Name" label="From Name"
fullWidth fullWidth
margin="dense" margin="dense"
error={!!errors.template?.fromName} error={!!errors.template?.fromName}
@ -562,6 +590,12 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
)} )}
/> />
{currentTarget && ( {currentTarget && (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0 }}>
{targetSampleLoading ? (
<CircularProgress size={16} sx={{ mb: 1 }} color="inherit" />
) : (
<Typography sx={{}}>Rows: {targetSample?.totalRows}</Typography>
)}
<Button <Button
onClick={handleTargetSampleModalOpen} onClick={handleTargetSampleModalOpen}
variant="outlined" variant="outlined"
@ -570,6 +604,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
> >
View View
</Button> </Button>
</Box>
)} )}
</Box> </Box>
<Autocomplete <Autocomplete
@ -692,10 +727,11 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
onClose={() => { setTemplateViewerOpen(false) }} onClose={() => { setTemplateViewerOpen(false) }}
/> />
)} )}
{TargetSampleModalOpen && ( {TargetSampleModalOpen && currentTarget && (
<TargetSampleModal <TargetSampleModal
open={TargetSampleModalOpen} open={TargetSampleModalOpen}
target={currentTarget!} target={currentTarget}
targetSample={targetSample}
onClose={() => { setTargetSampleModalOpen(false) }} onClose={() => { setTargetSampleModalOpen(false) }}
/> />
)} )}

View File

@ -6,52 +6,54 @@ import {
Dialog, Dialog,
DialogTitle, DialogTitle,
DialogContent, DialogContent,
Typography
} from "@mui/material"; } from "@mui/material";
import Target from "@/types/target"; import Target from "@/types/target";
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import TargetSample from "@/types/targetSample";
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid"; import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
import { GridColDef } from '@mui/x-data-grid'; import { GridColDef } from '@mui/x-data-grid';
type TargetSampleModalProps = { type TargetSampleModalProps = {
open: boolean; open: boolean;
target: Target; target: Target;
targetSample?: TargetSample | null;
onClose: () => void; onClose: () => void;
}; };
type TargetSampleColumn = { const TargetSampleModal = ({ open, target, targetSample, onClose }: TargetSampleModalProps) => {
name: string; const [localTargetSample, setLocalTargetSample] = useState<TargetSample | null>(null);
type: string;
};
type TargetSample = {
columns: { [key: string]: TargetSampleColumn }[];
rows: { [key: string]: string }[];
};
const TargetSampleModal = ({ open, target, onClose }: TargetSampleModalProps) => {
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
const fetchSampleData = async () => { const fetchSampleData = async () => {
if (targetSample) {
setLocalTargetSample(targetSample);
}
else if (target.id) {
setLoading(true); setLoading(true);
try { try {
const response = await fetch(`/api/targets/${target.id}/sample`); const response = await fetch(`/api/targets/${target.id}/sample`);
if (!response.ok) throw new Error("Failed to fetch sample data"); if (!response.ok) throw new Error("Failed to fetch sample data");
const data: TargetSample = await response.json(); const data: TargetSample = await response.json();
setTargetSample(data); setLocalTargetSample(data);
} catch (error) { } catch (error) {
console.error("Error fetching target sample:", error); console.error("Error fetching target sample:", error);
setTargetSample(null); setLocalTargetSample(null);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}
else {
setLocalTargetSample(null);
}
}; };
fetchSampleData(); fetchSampleData();
} }
}, [open, target.id]); }, [open, targetSample, target.id]);
const columns: GridColDef[] = Object.keys(targetSample?.columns ?? {}).map((colName) => ({ const columns: GridColDef[] = Object.keys(localTargetSample?.columns ?? {}).map((colName) => ({
field: colName, field: colName,
headerName: colName, headerName: colName,
minWidth: 150, minWidth: 150,
@ -60,9 +62,9 @@ const TargetSampleModal = ({ open, target, onClose }: TargetSampleModalProps) =>
sortable: true, sortable: true,
})) || []; })) || [];
const rows = targetSample?.rows?.map((row, index) => { const rows = localTargetSample?.rows?.map((row, index) => {
const rowData: { [key: string]: string } = { id: index.toString() }; const rowData: { [key: string]: string } = { id: index.toString() };
Object.keys(targetSample?.columns || {}).forEach((key) => { Object.keys(localTargetSample?.columns || {}).forEach((key) => {
rowData[key] = row[key] ?? ""; rowData[key] = row[key] ?? "";
}); });
return rowData; return rowData;
@ -113,6 +115,7 @@ const TargetSampleModal = ({ open, target, onClose }: TargetSampleModalProps) =>
</Box> </Box>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<Typography sx={{ mb: 1, fontWeight: 'bold' }} >Total Rows: {localTargetSample?.totalRows}</Typography>
<TargetSampleDataGrid columns={columns} rows={rows} /> <TargetSampleDataGrid columns={columns} rows={rows} />
</DialogContent> </DialogContent>
</> </>

View File

@ -0,0 +1,8 @@
import { TargetSampleColumn } from './targetSampleColumn';
export interface TargetSample {
columns: { [key: string]: TargetSampleColumn }[];
rows: { [key: string]: string }[];
totalRows: number;
}
export default TargetSample;