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 List<Dictionary<string, string>> Rows { get; set; } = new List<Dictionary<string, string>>();
public int TotalRows { get; set; } = 0;
}
public class TargetSampleColumn
{

View File

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

View File

@ -13,12 +13,15 @@ import {
Box,
MenuItem,
Select,
InputLabel
InputLabel,
Typography,
CircularProgress
} from "@mui/material";
import VisibilityIcon from '@mui/icons-material/Visibility';
import Template from "@/types/template";
import Mailing from "@/types/mailing";
import TargetSample from "@/types/targetSample";
import Target from "@/types/target";
//import MailingTemplate from "@/types/mailingTemplate";
//import MailingTarget from "@/types/mailingTarget";
@ -234,6 +237,8 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
const [currentTemplate, setCurrentTemplate] = useState<Template | null>(null);
const [TargetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
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>({
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) => {
if (list) {
setTestEmailListId(list.id);
@ -521,7 +549,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
render={({ field }) => (
<TextField
{...field}
label="To Name"
label="From Name"
fullWidth
margin="dense"
error={!!errors.template?.fromName}
@ -562,6 +590,12 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
)}
/>
{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
onClick={handleTargetSampleModalOpen}
variant="outlined"
@ -570,6 +604,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
>
View
</Button>
</Box>
)}
</Box>
<Autocomplete
@ -692,10 +727,11 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
onClose={() => { setTemplateViewerOpen(false) }}
/>
)}
{TargetSampleModalOpen && (
{TargetSampleModalOpen && currentTarget && (
<TargetSampleModal
open={TargetSampleModalOpen}
target={currentTarget!}
target={currentTarget}
targetSample={targetSample}
onClose={() => { setTargetSampleModalOpen(false) }}
/>
)}

View File

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