Integrate Distribution Lists with Campaign recipients
This commit is contained in:
@@ -3,7 +3,10 @@ import type { ApiSettings } from "../../types";
|
||||
import {
|
||||
getCampaignPostboxCatalog,
|
||||
listCampaignRecipientAddressSources,
|
||||
listCampaignRecipientDistributionLists,
|
||||
snapshotCampaignRecipientAddressSource,
|
||||
type CampaignDistributionListExpansion,
|
||||
type CampaignDistributionListSource,
|
||||
type CampaignPostboxCatalog,
|
||||
type CampaignRecipientAddressSource,
|
||||
type CampaignRecipientAddressSourceSnapshot } from
|
||||
@@ -38,6 +41,11 @@ import {
|
||||
import { addressesFromValue, type MailboxAddress } from "@govoplan/core-webui";
|
||||
import { insertAfter, moveArrayItem, useGuardedNavigate, usePlatformLanguage } from "@govoplan/core-webui";
|
||||
import AddressSourceImportDialog from "./recipients/AddressSourceImportDialog";
|
||||
import DistributionListImportDialog from "./recipients/DistributionListImportDialog";
|
||||
import {
|
||||
distributionListDrift,
|
||||
materializeDistributionListExpansion
|
||||
} from "./utils/distributionListImport";
|
||||
import {
|
||||
AddressHeaderControl,
|
||||
HeaderAddressEditorDialog,
|
||||
@@ -71,6 +79,11 @@ export default function RecipientDataPage({ settings, campaignId }: {settings: A
|
||||
const [addressSourcesAvailable, setAddressSourcesAvailable] = useState(false);
|
||||
const [addressSourcesLoading, setAddressSourcesLoading] = useState(false);
|
||||
const [addressSources, setAddressSources] = useState<CampaignRecipientAddressSource[]>([]);
|
||||
const [distributionListImportOpen, setDistributionListImportOpen] = useState(false);
|
||||
const [distributionListImportInitialId, setDistributionListImportInitialId] = useState("");
|
||||
const [distributionListsAvailable, setDistributionListsAvailable] = useState(false);
|
||||
const [distributionListsLoading, setDistributionListsLoading] = useState(false);
|
||||
const [distributionLists, setDistributionLists] = useState<CampaignDistributionListSource[]>([]);
|
||||
const [recipientProfilesPage, setRecipientProfilesPage] = useState(1);
|
||||
const [recipientProfilesPageSize, setRecipientProfilesPageSize] = useState(10);
|
||||
const [recipientProfilesQuery, setRecipientProfilesQuery] = useState<DataGridQueryState>({ sort: null, filters: {} });
|
||||
@@ -130,6 +143,10 @@ export default function RecipientDataPage({ settings, campaignId }: {settings: A
|
||||
}).
|
||||
filter((record) => record.sourceId && record.currentRevision && record.importedRevision && record.currentRevision !== record.importedRevision);
|
||||
}, [addressSourceRevisionById, entries.imports]);
|
||||
const staleDistributionListImports = useMemo(
|
||||
() => distributionListDrift(entries.imports, distributionLists),
|
||||
[distributionLists, entries.imports]
|
||||
);
|
||||
const defaultFrom = addressesFromValue(recipientsSection.from).slice(0, 1);
|
||||
const globalReplyTo = addressesFromValue(recipientsSection.reply_to);
|
||||
const globalRecipientValues: Record<string, MailboxAddress[]> = {
|
||||
@@ -159,6 +176,26 @@ export default function RecipientDataPage({ settings, campaignId }: {settings: A
|
||||
return () => {cancelled = true;};
|
||||
}, [campaignId, settings.accessToken, settings.apiBaseUrl, settings.apiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setDistributionListsLoading(true);
|
||||
void listCampaignRecipientDistributionLists(settings, campaignId)
|
||||
.then((response) => {
|
||||
if (cancelled) return;
|
||||
setDistributionListsAvailable(response.available && response.expand_available);
|
||||
setDistributionLists(response.available ? response.sources ?? [] : []);
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled) return;
|
||||
setDistributionListsAvailable(false);
|
||||
setDistributionLists([]);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setDistributionListsLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [campaignId, settings.accessToken, settings.apiBaseUrl, settings.apiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!postboxModuleInstalled) {
|
||||
setPostboxCatalog({
|
||||
@@ -322,6 +359,13 @@ export default function RecipientDataPage({ settings, campaignId }: {settings: A
|
||||
setAddressSourceImportOpen(false);
|
||||
}
|
||||
|
||||
function applyDistributionListImport(snapshot: CampaignDistributionListExpansion, mode: RecipientImportMode) {
|
||||
if (locked || !draft) return;
|
||||
setDraft(materializeDistributionListExpansion(draft, snapshot, mode));
|
||||
markDirty();
|
||||
setDistributionListImportOpen(false);
|
||||
}
|
||||
|
||||
function saveHeaderAddresses(values: HeaderAddressValues) {
|
||||
const nextRecipients = { ...recipientsSection };
|
||||
for (const [key, addresses] of Object.entries(values) as Array<[AddressFieldKey, MailboxAddress[]]>) {
|
||||
@@ -435,6 +479,11 @@ export default function RecipientDataPage({ settings, campaignId }: {settings: A
|
||||
<Button disabled={locked || addressSourcesLoading} onClick={() => {setAddressSourceImportInitialId("");setAddressSourceImportOpen(true);}}>
|
||||
Import address book/list
|
||||
</Button>
|
||||
}
|
||||
{(distributionListsAvailable || distributionListsLoading) &&
|
||||
<Button disabled={locked || distributionListsLoading || !distributionListsAvailable} onClick={() => {setDistributionListImportInitialId("");setDistributionListImportOpen(true);}}>
|
||||
Import Distribution List
|
||||
</Button>
|
||||
}
|
||||
<Button disabled={locked} onClick={() => setImportOpen(true)}>i18n:govoplan-campaign.import.d6fbc9d2</Button>
|
||||
</div>
|
||||
@@ -459,6 +508,27 @@ export default function RecipientDataPage({ settings, campaignId }: {settings: A
|
||||
</div>
|
||||
</DismissibleAlert>
|
||||
}
|
||||
{staleDistributionListImports.length > 0 &&
|
||||
<DismissibleAlert tone="warning" dismissible={false}>
|
||||
<div className="stale-address-import-warning">
|
||||
<span>Frozen Distribution List imports have changed or become unavailable. Campaign recipients remain unchanged until an explicit refresh.</span>
|
||||
<div className="button-row compact-actions">
|
||||
{staleDistributionListImports.map((item) =>
|
||||
<span key={`${item.sourceId}:${item.importedRevision ?? "unknown"}`} className="button-row compact-actions">
|
||||
<span>{item.sourceLabel}: {item.reason}</span>
|
||||
{item.currentRevision !== null &&
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {setDistributionListImportInitialId(item.sourceId);setDistributionListImportOpen(true);}}>
|
||||
Refresh {item.sourceLabel}
|
||||
</Button>
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DismissibleAlert>
|
||||
}
|
||||
{!source.type &&
|
||||
<div className="admin-table-surface recipient-profiles-table-surface">
|
||||
<DataGrid
|
||||
@@ -527,6 +597,16 @@ export default function RecipientDataPage({ settings, campaignId }: {settings: A
|
||||
onCancel={() => setAddressSourceImportOpen(false)}
|
||||
onImport={applyAddressSourceImport} />
|
||||
|
||||
}
|
||||
{distributionListImportOpen &&
|
||||
<DistributionListImportDialog
|
||||
settings={settings}
|
||||
campaignId={campaignId}
|
||||
sources={distributionLists}
|
||||
initialSourceId={distributionListImportInitialId}
|
||||
onCancel={() => setDistributionListImportOpen(false)}
|
||||
onImport={applyDistributionListImport} />
|
||||
|
||||
}
|
||||
{recipientAddressEditorIndex !== null && inlineEntries[recipientAddressEditorIndex] &&
|
||||
<RecipientAddressEditorDialog
|
||||
|
||||
Reference in New Issue
Block a user