DataGrid list type; Review/Send refinment; User lock; Table actions
This commit is contained in:
@@ -10,7 +10,7 @@ import VersionLine from "./components/VersionLine";
|
||||
import ToggleSwitch from "../../components/ToggleSwitch";
|
||||
import EmailAddressInput from "../../components/email/EmailAddressInput";
|
||||
import DismissibleAlert from "../../components/DismissibleAlert";
|
||||
import DataGrid, { type DataGridColumn } from "../../components/table/DataGrid";
|
||||
import DataGrid, { DataGridRowActions, type DataGridColumn } from "../../components/table/DataGrid";
|
||||
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
|
||||
import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor";
|
||||
import { asArray, asRecord, isAuditLockedVersion } from "./utils/campaignView";
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
collectCampaignAddressSuggestions,
|
||||
type MailboxAddress
|
||||
} from "../../utils/emailAddresses";
|
||||
import { insertAfter, moveArrayItem } from "../../utils/arrayOrder";
|
||||
|
||||
const recipientHeaderRows = [
|
||||
{ key: "to", label: "To", toggleKey: "allow_individual_to", toggleLabel: "Allow individual To", addLabel: "Add recipient", emptyText: "No global recipients configured." },
|
||||
@@ -79,7 +80,7 @@ export default function RecipientDataPage({ settings, campaignId }: { settings:
|
||||
patch(["entries", "inline"], nextEntries);
|
||||
}
|
||||
|
||||
function addRecipient() {
|
||||
function addRecipient(afterIndex = inlineEntries.length - 1) {
|
||||
const nextIndex = inlineEntries.length + 1;
|
||||
const newEntry = {
|
||||
id: uniqueEntryId(inlineEntries, `recipient-${nextIndex}`),
|
||||
@@ -92,7 +93,7 @@ export default function RecipientDataPage({ settings, campaignId }: { settings:
|
||||
from: {},
|
||||
reply_to: []
|
||||
};
|
||||
replaceInlineEntries([...inlineEntries, newEntry]);
|
||||
replaceInlineEntries(insertAfter(inlineEntries, afterIndex, newEntry));
|
||||
}
|
||||
|
||||
function updateEntry(index: number, updater: (entry: Record<string, unknown>) => Record<string, unknown>) {
|
||||
@@ -122,6 +123,11 @@ export default function RecipientDataPage({ settings, campaignId }: { settings:
|
||||
replaceInlineEntries(inlineEntries.filter((_, currentIndex) => currentIndex !== index));
|
||||
}
|
||||
|
||||
function moveEntry(index: number, targetIndex: number) {
|
||||
if (locked || index === targetIndex) return;
|
||||
replaceInlineEntries(moveArrayItem(inlineEntries, index, targetIndex));
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="content-pad workspace-data-page">
|
||||
@@ -222,19 +228,18 @@ export default function RecipientDataPage({ settings, campaignId }: { settings:
|
||||
|
||||
<Card
|
||||
title="Recipient profiles"
|
||||
actions={[<Button key="import" disabled>Import</Button>, <Button key="add" variant="primary" onClick={addRecipient} disabled={locked}>Add recipient</Button>]}
|
||||
actions={<Button disabled>Import</Button>}
|
||||
>
|
||||
{inlineEntries.length === 0 && !source.type && <p className="muted">No recipient profiles are stored in the current version yet.</p>}
|
||||
{inlineEntries.length === 0 && Boolean(source.type) && (
|
||||
<DismissibleAlert tone="info">This campaign references an external recipient source. A parsed preview table will be added when file/source preview support is implemented.</DismissibleAlert>
|
||||
)}
|
||||
{inlineEntries.length > 0 && (
|
||||
{!source.type && (
|
||||
<DataGrid
|
||||
id={`campaign-${campaignId}-recipient-profiles`}
|
||||
rows={inlineEntries.slice(0, 100)}
|
||||
columns={recipientProfileColumns({ locked, entryAddressColumns, addressSuggestions, updateEntryAddressList, updateEntry, removeEntry })}
|
||||
columns={recipientProfileColumns({ locked, entries: inlineEntries, entryAddressColumns, addressSuggestions, updateEntryAddressList, updateEntry, addRecipient, moveEntry, removeEntry })}
|
||||
getRowKey={(entry, index) => String(entry.id || index)}
|
||||
emptyText="No recipient profiles are stored in the current version yet."
|
||||
emptyText={<div className="data-grid-empty-action"><span>No recipient profiles are stored in the current version yet.</span><Button variant="primary" onClick={() => addRecipient(-1)} disabled={locked}>Add recipient</Button></div>}
|
||||
className="recipient-table-wrap recipient-address-table"
|
||||
/>
|
||||
)}
|
||||
@@ -251,14 +256,17 @@ export default function RecipientDataPage({ settings, campaignId }: { settings:
|
||||
|
||||
type RecipientProfileColumnContext = {
|
||||
locked: boolean;
|
||||
entries: Record<string, unknown>[];
|
||||
entryAddressColumns: EntryAddressColumn[];
|
||||
addressSuggestions: MailboxAddress[];
|
||||
updateEntryAddressList: (index: number, key: EntryAddressColumn["key"], addresses: MailboxAddress[]) => void;
|
||||
updateEntry: (index: number, updater: (entry: Record<string, unknown>) => Record<string, unknown>) => void;
|
||||
addRecipient: (afterIndex?: number) => void;
|
||||
moveEntry: (index: number, targetIndex: number) => void;
|
||||
removeEntry: (index: number) => void;
|
||||
};
|
||||
|
||||
function recipientProfileColumns({ locked, entryAddressColumns, addressSuggestions, updateEntryAddressList, updateEntry, removeEntry }: RecipientProfileColumnContext): DataGridColumn<Record<string, unknown>>[] {
|
||||
function recipientProfileColumns({ locked, entries, entryAddressColumns, addressSuggestions, updateEntryAddressList, updateEntry, addRecipient, moveEntry, removeEntry }: RecipientProfileColumnContext): DataGridColumn<Record<string, unknown>>[] {
|
||||
return [
|
||||
{ id: "number", header: "#", width: 70, sortable: true, sticky: "start", render: (_entry, index) => <span className="mono-small">{index + 1}</span>, value: (_entry, index) => index + 1 },
|
||||
...entryAddressColumns.map((column): DataGridColumn<Record<string, unknown>> => ({
|
||||
@@ -282,7 +290,25 @@ function recipientProfileColumns({ locked, entryAddressColumns, addressSuggestio
|
||||
value: (entry) => getEntryAddresses(entry, column.key).map((address) => `${address.name ?? ""} ${address.email ?? ""}`).join(", ")
|
||||
})),
|
||||
{ id: "active", header: "Active", width: 130, sortable: true, filterable: true, render: (entry, index) => <ToggleSwitch label="Active" checked={entry.active !== false} disabled={locked} onChange={(checked) => updateEntry(index, (current) => ({ ...current, active: checked }))} />, value: (entry) => entry.active !== false ? "active" : "inactive" },
|
||||
{ id: "actions", header: "Actions", width: 120, sticky: "end", render: (_entry, index) => <Button variant="danger" onClick={() => removeEntry(index)} disabled={locked}>Remove</Button> }
|
||||
{
|
||||
id: "actions",
|
||||
header: "Actions",
|
||||
width: 150,
|
||||
sticky: "end",
|
||||
render: (_entry, index) => (
|
||||
<DataGridRowActions
|
||||
disabled={locked}
|
||||
onAddBelow={() => addRecipient(index)}
|
||||
onRemove={() => removeEntry(index)}
|
||||
onMoveUp={index > 0 ? () => moveEntry(index, index - 1) : undefined}
|
||||
onMoveDown={index < entries.length - 1 ? () => moveEntry(index, index + 1) : undefined}
|
||||
addLabel="Add recipient below"
|
||||
removeLabel="Remove recipient"
|
||||
moveUpLabel="Move recipient up"
|
||||
moveDownLabel="Move recipient down"
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user