Adopt shared WebUI layout primitives

This commit is contained in:
2026-08-18 11:30:39 +02:00
parent cf8d7fab11
commit 9a2f13bc9a
16 changed files with 74 additions and 71 deletions
@@ -1,3 +1,4 @@
import { MetricGrid } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Pencil } from "lucide-react"; import { Pencil } from "lucide-react";
import { useGuardedNavigate, usePlatformModuleInstalled, usePlatformUiCapability, type FilesFileExplorerUiCapability, type FilesFileSpace } from "@govoplan/core-webui"; import { useGuardedNavigate, usePlatformModuleInstalled, usePlatformUiCapability, type FilesFileExplorerUiCapability, type FilesFileSpace } from "@govoplan/core-webui";
@@ -268,12 +269,12 @@ export default function AttachmentsDataPage({ settings, campaignId }: {settings:
<LoadingFrame loading={loading || !draft} label="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50"> <LoadingFrame loading={loading || !draft} label="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50">
<> <>
<div className="metric-grid attachment-statistics-grid"> <MetricGrid>
<MetricCard label="i18n:govoplan-campaign.base_paths.edf35a97" value={basePaths.length} tone="neutral" /> <MetricCard label="i18n:govoplan-campaign.base_paths.edf35a97" value={basePaths.length} tone="neutral" />
<MetricCard label="i18n:govoplan-campaign.global_attachments.438263a1" value={`${globalSummary.direct} / ${globalSummary.rules}`} tone="info" detail="direct / rules" /> <MetricCard label="i18n:govoplan-campaign.global_attachments.438263a1" value={`${globalSummary.direct} / ${globalSummary.rules}`} tone="info" detail="direct / rules" />
<MetricCard label="i18n:govoplan-campaign.per_recipient_patterns.53da30da" value={individualRulesCount} tone="neutral" /> <MetricCard label="i18n:govoplan-campaign.per_recipient_patterns.53da30da" value={individualRulesCount} tone="neutral" />
<MetricCard label="i18n:govoplan-campaign.upload_support.1c54931c" value={managedFilesAvailable ? "Files" : "Manual"} tone={managedFilesAvailable ? "good" : filesModuleInstalled ? "warning" : "neutral"} /> <MetricCard label="i18n:govoplan-campaign.upload_support.1c54931c" value={managedFilesAvailable ? "Files" : "Manual"} tone={managedFilesAvailable ? "good" : filesModuleInstalled ? "warning" : "neutral"} />
</div> </MetricGrid>
<Card title="i18n:govoplan-campaign.attachment_sources.8ef0a6ce"> <Card title="i18n:govoplan-campaign.attachment_sources.8ef0a6ce">
<div className="admin-table-surface attachment-sources-table-surface"> <div className="admin-table-surface attachment-sources-table-surface">
@@ -1,3 +1,4 @@
import { MetricGrid } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Archive, CalendarClock, Copy, ExternalLink, LockKeyhole, LockOpen, Pause, Play, Trash2 } from "lucide-react"; import { Archive, CalendarClock, Copy, ExternalLink, LockKeyhole, LockOpen, Pause, Play, Trash2 } from "lucide-react";
import { Link } from "react-router"; import { Link } from "react-router";
@@ -378,12 +379,12 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
> >
<LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_campaign_overview.ffa1adf0"> <LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_campaign_overview.ffa1adf0">
<div className="metric-grid campaign-overview-metrics"> <MetricGrid>
<MetricCard label="i18n:govoplan-campaign.queueable.ea776f8d" value={data.summary?.cards?.queueable ?? "—"} tone="good" detail="i18n:govoplan-campaign.ready_or_warning.4dcce676" /> <MetricCard label="i18n:govoplan-campaign.queueable.ea776f8d" value={data.summary?.cards?.queueable ?? "—"} tone="good" detail="i18n:govoplan-campaign.ready_or_warning.4dcce676" />
<MetricCard label="i18n:govoplan-campaign.needs_attention.a126722e" value={data.summary?.cards?.needs_attention ?? "—"} tone="warning" detail="i18n:govoplan-campaign.review_first.741ac781" /> <MetricCard label="i18n:govoplan-campaign.needs_attention.a126722e" value={data.summary?.cards?.needs_attention ?? "—"} tone="warning" detail="i18n:govoplan-campaign.review_first.741ac781" />
<MetricCard label="i18n:govoplan-campaign.sent.35f49dcf" value={data.summary?.cards?.sent ?? "—"} tone="info" detail="i18n:govoplan-campaign.smtp_success.3591a856" /> <MetricCard label="i18n:govoplan-campaign.sent.35f49dcf" value={data.summary?.cards?.sent ?? "—"} tone="info" detail="i18n:govoplan-campaign.smtp_success.3591a856" />
<MetricCard label="i18n:govoplan-campaign.failed.09fef5d8" value={data.summary?.cards?.failed ?? "—"} tone="danger" detail="i18n:govoplan-campaign.smtp_failures.00b33b85" /> <MetricCard label="i18n:govoplan-campaign.failed.09fef5d8" value={data.summary?.cards?.failed ?? "—"} tone="danger" detail="i18n:govoplan-campaign.smtp_failures.00b33b85" />
</div> </MetricGrid>
<Card <Card
title="i18n:govoplan-campaign.campaign_identity.a00ca574" title="i18n:govoplan-campaign.campaign_identity.a00ca574"
@@ -451,18 +452,18 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
i18n:govoplan-campaign.open.cf9b7706 i18n:govoplan-campaign.open.cf9b7706
</Link> </Link>
</div>}> </div>}>
<div className="metric-grid inside campaign-versions-metrics"> <MetricGrid spacing="inset">
<MetricCard label="i18n:govoplan-campaign.version.2da600bf" value={data.currentVersion?.version_number ? `#${data.currentVersion.version_number}` : "—"} tone="neutral" /> <MetricCard label="i18n:govoplan-campaign.version.2da600bf" value={data.currentVersion?.version_number ? `#${data.currentVersion.version_number}` : "—"} tone="neutral" />
<MetricCard label="i18n:govoplan-campaign.fields.e8b68527" value={versionMetrics.fieldCount} tone="info" /> <MetricCard label="i18n:govoplan-campaign.fields.e8b68527" value={versionMetrics.fieldCount} tone="info" />
<MetricCard label="i18n:govoplan-campaign.recipients.78cbf8eb" value={versionMetrics.recipientCount} tone="neutral" detail="i18n:govoplan-campaign.active_inline_recipients.8ba58f6e" /> <MetricCard label="i18n:govoplan-campaign.recipients.78cbf8eb" value={versionMetrics.recipientCount} tone="neutral" detail="i18n:govoplan-campaign.active_inline_recipients.8ba58f6e" />
<MetricCard label="i18n:govoplan-campaign.template_health.22e14b59" value={versionMetrics.templateHealthValue} tone={versionMetrics.templateHealthTone} detail={versionMetrics.templateHealthDetail} /> <MetricCard label="i18n:govoplan-campaign.template_health.22e14b59" value={versionMetrics.templateHealthValue} tone={versionMetrics.templateHealthTone} detail={versionMetrics.templateHealthDetail} />
</div> </MetricGrid>
<div className="metric-grid inside"> <MetricGrid spacing="inset">
<MetricCard label="i18n:govoplan-campaign.validation_errors.e54ca4fe" value={summaryValue(data.currentVersion?.validation_summary, ["error_count", "errors", "blocked"])} /> <MetricCard label="i18n:govoplan-campaign.validation_errors.e54ca4fe" value={summaryValue(data.currentVersion?.validation_summary, ["error_count", "errors", "blocked"])} />
<MetricCard label="i18n:govoplan-campaign.warnings.1430f976" value={summaryValue(data.currentVersion?.validation_summary, ["warning_count", "warnings"])} /> <MetricCard label="i18n:govoplan-campaign.warnings.1430f976" value={summaryValue(data.currentVersion?.validation_summary, ["warning_count", "warnings"])} />
<MetricCard label="i18n:govoplan-campaign.built_messages.1fb804f2" value={summaryValue(data.currentVersion?.build_summary, ["built_count", "built", "messages_built"])} /> <MetricCard label="i18n:govoplan-campaign.built_messages.1fb804f2" value={summaryValue(data.currentVersion?.build_summary, ["built_count", "built", "messages_built"])} />
<MetricCard label="i18n:govoplan-campaign.jobs_total.98da65bc" value={data.summary?.cards?.jobs_total ?? "—"} /> <MetricCard label="i18n:govoplan-campaign.jobs_total.98da65bc" value={data.summary?.cards?.jobs_total ?? "—"} />
</div> </MetricGrid>
<div className="admin-table-surface version-history-table-surface"> <div className="admin-table-surface version-history-table-surface">
<DataGrid <DataGrid
id={`campaign-${campaignId}-versions`} id={`campaign-${campaignId}-versions`}
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Check, RotateCcw, Search, X } from "lucide-react"; import { Check, RotateCcw, Search, X } from "lucide-react";
import type { ApiSettings } from "../../types"; import type { ApiSettings } from "../../types";
@@ -463,7 +464,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_report_data.0908ade5"> <LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_report_data.0908ade5">
<ContentGrid columns={2} collapseAt="workspace" className=""> <ContentGrid columns={2} collapseAt="workspace" className="">
<Card title="i18n:govoplan-campaign.delivery_outcome.f9d7c085"> <Card title="i18n:govoplan-campaign.delivery_outcome.f9d7c085">
<dl className="detail-list"> <DescriptionList variant="inline">
<div><dt>i18n:govoplan-campaign.generated.8eefdd52</dt><dd>{formatDateTime(data.summary?.generated_at)}</dd></div> <div><dt>i18n:govoplan-campaign.generated.8eefdd52</dt><dd>{formatDateTime(data.summary?.generated_at)}</dd></div>
{deliveryOutcomeShortcuts.map(({ label, value, shortcutId }) => { {deliveryOutcomeShortcuts.map(({ label, value, shortcutId }) => {
const active = activeJobGridShortcut === shortcutId; const active = activeJobGridShortcut === shortcutId;
@@ -478,10 +479,10 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
</div> </div>
); );
})} })}
</dl> </DescriptionList>
</Card> </Card>
<Card title="i18n:govoplan-campaign.imap_and_execution_plan.4c80c058"> <Card title="i18n:govoplan-campaign.imap_and_execution_plan.4c80c058">
<dl className="detail-list"> <DescriptionList variant="inline">
{imapOutcomeShortcuts.map(({ label, value, shortcutId }) => { {imapOutcomeShortcuts.map(({ label, value, shortcutId }) => {
const active = activeJobGridShortcut === shortcutId; const active = activeJobGridShortcut === shortcutId;
return ( return (
@@ -499,10 +500,10 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<div><dt>i18n:govoplan-campaign.rate_limit.d08e55f5</dt><dd>{rateLimit.messages_per_minute ? i18nMessage("i18n:govoplan-campaign.value_minute.aeb1a9ea", { value0: String(rateLimit.messages_per_minute) }) : "—"}</dd></div> <div><dt>i18n:govoplan-campaign.rate_limit.d08e55f5</dt><dd>{rateLimit.messages_per_minute ? i18nMessage("i18n:govoplan-campaign.value_minute.aeb1a9ea", { value0: String(rateLimit.messages_per_minute) }) : "—"}</dd></div>
<div><dt>i18n:govoplan-campaign.minimum_remaining_duration.639b792c</dt><dd>{String(delivery.estimated_remaining_send_human ?? "—")}</dd></div> <div><dt>i18n:govoplan-campaign.minimum_remaining_duration.639b792c</dt><dd>{String(delivery.estimated_remaining_send_human ?? "—")}</dd></div>
<div><dt>i18n:govoplan-campaign.execution_snapshot.5a67f098</dt><dd title={String(delivery.execution_snapshot_hash ?? "")}>{delivery.execution_snapshot_hash ? i18nMessage("i18n:govoplan-campaign.value.382bcd25", { value0: String(delivery.execution_snapshot_hash).slice(0, 12) }) : "i18n:govoplan-campaign.missing.92185dc5"}</dd></div> <div><dt>i18n:govoplan-campaign.execution_snapshot.5a67f098</dt><dd title={String(delivery.execution_snapshot_hash ?? "")}>{delivery.execution_snapshot_hash ? i18nMessage("i18n:govoplan-campaign.value.382bcd25", { value0: String(delivery.execution_snapshot_hash).slice(0, 12) }) : "i18n:govoplan-campaign.missing.92185dc5"}</dd></div>
</dl> </DescriptionList>
</Card> </Card>
<Card title="Postbox receipt evidence"> <Card title="Postbox receipt evidence">
<dl className="detail-list"> <DescriptionList variant="inline">
<div> <div>
<dt>Evidence state</dt> <dt>Evidence state</dt>
<dd> <dd>
@@ -523,7 +524,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<div><dt>Read</dt><dd>{String(postboxReceipts.read_delivery_count ?? "—")}</dd></div> <div><dt>Read</dt><dd>{String(postboxReceipts.read_delivery_count ?? "—")}</dd></div>
<div><dt>Acknowledged</dt><dd>{String(postboxReceipts.acknowledged_delivery_count ?? "—")}</dd></div> <div><dt>Acknowledged</dt><dd>{String(postboxReceipts.acknowledged_delivery_count ?? "—")}</dd></div>
<div><dt>Withdrawn / expired copies</dt><dd>{String(Number(postboxReceipts.withdrawn_message_count ?? 0) + Number(postboxReceipts.expired_message_count ?? 0))}</dd></div> <div><dt>Withdrawn / expired copies</dt><dd>{String(Number(postboxReceipts.withdrawn_message_count ?? 0) + Number(postboxReceipts.expired_message_count ?? 0))}</dd></div>
</dl> </DescriptionList>
</Card> </Card>
<RetentionPrivacyCard retention={retention} /> <RetentionPrivacyCard retention={retention} />
<Card title="i18n:govoplan-campaign.explicit_delivery_actions.b35e72a4"> <Card title="i18n:govoplan-campaign.explicit_delivery_actions.b35e72a4">
@@ -607,7 +608,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
{detail && {detail &&
<div className="stacked-sections"> <div className="stacked-sections">
<dl className="detail-list"> <DescriptionList variant="inline">
<div><dt>i18n:govoplan-campaign.recipient.90343260</dt><dd>{String(detail.job.recipient_email ?? "—")}</dd></div> <div><dt>i18n:govoplan-campaign.recipient.90343260</dt><dd>{String(detail.job.recipient_email ?? "—")}</dd></div>
<div><dt>i18n:govoplan-campaign.subject.8d183dbd</dt><dd>{String(detail.job.subject ?? "—")}</dd></div> <div><dt>i18n:govoplan-campaign.subject.8d183dbd</dt><dd>{String(detail.job.subject ?? "—")}</dd></div>
<div><dt>i18n:govoplan-campaign.message_id.465056ba</dt><dd>{String(detail.job.message_id_header ?? "—")}</dd></div> <div><dt>i18n:govoplan-campaign.message_id.465056ba</dt><dd>{String(detail.job.message_id_header ?? "—")}</dd></div>
@@ -619,7 +620,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<div><dt>i18n:govoplan-campaign.imap_state.03b83be0</dt><dd><StatusBadge status={String(detail.job.imap_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.imap_status ?? "unknown"))} /></dd></div> <div><dt>i18n:govoplan-campaign.imap_state.03b83be0</dt><dd><StatusBadge status={String(detail.job.imap_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.imap_status ?? "unknown"))} /></dd></div>
<div><dt>i18n:govoplan-campaign.attachments.6771ade6</dt><dd>{String(detail.job.matched_file_count ?? detail.job.attachment_count ?? 0)}</dd></div> <div><dt>i18n:govoplan-campaign.attachments.6771ade6</dt><dd>{String(detail.job.matched_file_count ?? detail.job.attachment_count ?? 0)}</dd></div>
<div><dt>Message SHA-256</dt><dd><code>{String(detail.job.eml_sha256 ?? "—")}</code></dd></div> <div><dt>Message SHA-256</dt><dd><code>{String(detail.job.eml_sha256 ?? "—")}</code></dd></div>
</dl> </DescriptionList>
<PostboxTargetEvidenceSection <PostboxTargetEvidenceSection
targets={ targets={
Array.isArray(detail.job.resolved_postbox_targets) Array.isArray(detail.job.resolved_postbox_targets)
@@ -671,7 +672,7 @@ function RetentionPrivacyCard({ retention }: { retention?: CampaignRetentionRepo
<Card title="i18n:govoplan-campaign.retention_and_privacy.24418676"> <Card title="i18n:govoplan-campaign.retention_and_privacy.24418676">
<p>{retention.privacy_impact.summary}</p> <p>{retention.privacy_impact.summary}</p>
{retention.policy_reason && <p className="muted">{retention.policy_reason}</p>} {retention.policy_reason && <p className="muted">{retention.policy_reason}</p>}
<dl className="detail-list"> <DescriptionList variant="inline">
<div> <div>
<dt>i18n:govoplan-campaign.policy_state.7e955a0d</dt> <dt>i18n:govoplan-campaign.policy_state.7e955a0d</dt>
<dd> <dd>
@@ -713,7 +714,7 @@ function RetentionPrivacyCard({ retention }: { retention?: CampaignRetentionRepo
</dd> </dd>
</div> </div>
)} )}
</dl> </DescriptionList>
</Card> </Card>
); );
} }
@@ -1,3 +1,4 @@
import { MetricGrid } from "@govoplan/core-webui";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FormGrid, import { FormGrid,
Button, Button,
@@ -412,12 +413,12 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
</FormField> </FormField>
</FormGrid> </FormGrid>
{selectedProfileUnavailable && <DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-campaign.the_referenced_mail_profile_is_inactive_unavaila.abeebe26</DismissibleAlert>} {selectedProfileUnavailable && <DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-campaign.the_referenced_mail_profile_is_inactive_unavaila.abeebe26</DismissibleAlert>}
{selectedProfile && <div className="metric-grid inside"> {selectedProfile && <MetricGrid spacing="inset">
<MetricCard label="i18n:govoplan-campaign.profile.ff4fc027" value={selectedProfile.name} /> <MetricCard label="i18n:govoplan-campaign.profile.ff4fc027" value={selectedProfile.name} />
<MetricCard label="i18n:govoplan-campaign.scope.4651a34e" value={profileScopeLabel(selectedProfile)} /> <MetricCard label="i18n:govoplan-campaign.scope.4651a34e" value={profileScopeLabel(selectedProfile)} />
<MetricCard label="i18n:govoplan-campaign.smtp.efff9cca" value={selectedSmtpServer?.name || "i18n:govoplan-campaign.not_configured.811931bb"} tone={selectedSmtpServer ? "good" : "neutral"} /> <MetricCard label="i18n:govoplan-campaign.smtp.efff9cca" value={selectedSmtpServer?.name || "i18n:govoplan-campaign.not_configured.811931bb"} tone={selectedSmtpServer ? "good" : "neutral"} />
<MetricCard label="i18n:govoplan-campaign.imap.271f9ef2" value={selectedImapServer?.name || "i18n:govoplan-campaign.not_configured.811931bb"} tone={selectedImapServer ? "good" : "neutral"} /> <MetricCard label="i18n:govoplan-campaign.imap.271f9ef2" value={selectedImapServer?.name || "i18n:govoplan-campaign.not_configured.811931bb"} tone={selectedImapServer ? "good" : "neutral"} />
</div>} </MetricGrid>}
<div className="button-row compact-actions"> <div className="button-row compact-actions">
<Button onClick={() => void runProfileTest("smtp")} disabled={!selectedSmtpServer || locked || Boolean(mailActionState)}>{mailActionState === "smtp" ? "i18n:govoplan-campaign.testing_smtp.8e9f8247" : "i18n:govoplan-campaign.test_profile_smtp.884a0e66"}</Button> <Button onClick={() => void runProfileTest("smtp")} disabled={!selectedSmtpServer || locked || Boolean(mailActionState)}>{mailActionState === "smtp" ? "i18n:govoplan-campaign.testing_smtp.8e9f8247" : "i18n:govoplan-campaign.test_profile_smtp.884a0e66"}</Button>
<Button onClick={() => void runProfileTest("imap")} disabled={!selectedProfileHasImap || locked || Boolean(mailActionState)}>{mailActionState === "imap" ? "i18n:govoplan-campaign.testing_imap.13d255cf" : "i18n:govoplan-campaign.test_profile_imap.e1cec0e0"}</Button> <Button onClick={() => void runProfileTest("imap")} disabled={!selectedProfileHasImap || locked || Boolean(mailActionState)}>{mailActionState === "imap" ? "i18n:govoplan-campaign.testing_imap.13d255cf" : "i18n:govoplan-campaign.test_profile_imap.e1cec0e0"}</Button>
@@ -432,7 +433,6 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
title="Add campaign credential" title="Add campaign credential"
onClose={() => setCredentialDialogOpen(false)} onClose={() => setCredentialDialogOpen(false)}
closeDisabled={credentialSaving} closeDisabled={credentialSaving}
className="adaptive-config-dialog"
footerClassName="button-row compact-actions" footerClassName="button-row compact-actions"
footer={ footer={
<> <>
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { import {
BarChart3, BarChart3,
@@ -1423,7 +1424,7 @@ export default function ReviewSendPage({
{visibleValidationIssues.length > 0 && {visibleValidationIssues.length > 0 &&
<div className="review-flow-data-section"> <div className="review-flow-data-section">
<h3>i18n:govoplan-campaign.validation_details.aa503267</h3> <h3>i18n:govoplan-campaign.validation_details.aa503267</h3>
<dl className="detail-list"> <DescriptionList variant="inline">
{visibleValidationIssues.map((issue, index) => {visibleValidationIssues.map((issue, index) =>
<div key={`${String(issue.code ?? "issue")}:${index}`}> <div key={`${String(issue.code ?? "issue")}:${index}`}>
<dt>{humanize(String(issue.severity ?? "issue"))}</dt> <dt>{humanize(String(issue.severity ?? "issue"))}</dt>
@@ -1434,7 +1435,7 @@ export default function ReviewSendPage({
</dd> </dd>
</div> </div>
)} )}
</dl> </DescriptionList>
{validationIssues.length > visibleValidationIssues.length && <p className="muted small-note">i18n:govoplan-campaign.showing.163d8174 {visibleValidationIssues.length} of {validationIssues.length} i18n:govoplan-campaign.validation_issue_s.c6a8b911</p>} {validationIssues.length > visibleValidationIssues.length && <p className="muted small-note">i18n:govoplan-campaign.showing.163d8174 {visibleValidationIssues.length} of {validationIssues.length} i18n:govoplan-campaign.validation_issue_s.c6a8b911</p>}
</div> </div>
} }
@@ -1488,11 +1489,11 @@ export default function ReviewSendPage({
</Button> </Button>
)} )}
</div> </div>
<dl className="detail-list"> <DescriptionList variant="inline">
<div><dt>Template hash</dt><dd><code>{getText(printOutput, "template_hash") || "—"}</code></dd></div> <div><dt>Template hash</dt><dd><code>{getText(printOutput, "template_hash") || "—"}</code></dd></div>
<div><dt>Input hash</dt><dd><code>{getText(printOutput, "input_hash") || "—"}</code></dd></div> <div><dt>Input hash</dt><dd><code>{getText(printOutput, "input_hash") || "—"}</code></dd></div>
<div><dt>Output hash</dt><dd><code>{getText(printOutput, "output_sha256") || "—"}</code></dd></div> <div><dt>Output hash</dt><dd><code>{getText(printOutput, "output_sha256") || "—"}</code></dd></div>
</dl> </DescriptionList>
</div> </div>
)} )}
<div className="button-row compact-actions review-flow-stage-actions"> <div className="button-row compact-actions review-flow-stage-actions">
@@ -1815,7 +1816,7 @@ export default function ReviewSendPage({
{recentFailures.length > 0 && {recentFailures.length > 0 &&
<div className="review-flow-data-section"> <div className="review-flow-data-section">
<h3>i18n:govoplan-campaign.recent_delivery_failures.282285fb</h3> <h3>i18n:govoplan-campaign.recent_delivery_failures.282285fb</h3>
<dl className="detail-list"> <DescriptionList variant="inline">
{recentFailures.map((failure, index) => {recentFailures.map((failure, index) =>
<div key={String(failure.job_id ?? index)}> <div key={String(failure.job_id ?? index)}>
<dt>{String(failure.recipient_email ?? failure.entry_id ?? i18nMessage("i18n:govoplan-campaign.value.44b8c76f", { value0: String(failure.entry_index ?? index + 1) }))}</dt> <dt>{String(failure.recipient_email ?? failure.entry_id ?? i18nMessage("i18n:govoplan-campaign.value.44b8c76f", { value0: String(failure.entry_index ?? index + 1) }))}</dt>
@@ -1826,7 +1827,7 @@ export default function ReviewSendPage({
</dd> </dd>
</div> </div>
)} )}
</dl> </DescriptionList>
</div> </div>
} }
<div className="button-row compact-actions"> <div className="button-row compact-actions">
@@ -694,7 +694,7 @@ export default function TemplateDataPage({ settings, campaignId }: {settings: Ap
<Dialog <Dialog
open={contentSaveOpen} open={contentSaveOpen}
title="Save reusable content" title="Save reusable content"
className="campaign-content-save-dialog" size="large"
helpContextId="campaign.template.content-library" helpContextId="campaign.template.content-library"
closeDisabled={contentSaveBusy} closeDisabled={contentSaveBusy}
onClose={() => setContentSaveOpen(false)} onClose={() => setContentSaveOpen(false)}
@@ -91,7 +91,7 @@ export default function PostboxTargetsDialog({
<Dialog <Dialog
open={open} open={open}
title={title} title={title}
className="campaign-postbox-target-modal" size="wide"
bodyClassName="campaign-postbox-target-body" bodyClassName="campaign-postbox-target-body"
onClose={onClose} onClose={onClose}
footer={ footer={
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { import {
Button, Button,
@@ -264,7 +265,7 @@ export default function AddressSourceImportDialog({
{snapshot && ( {snapshot && (
<> <>
<dl className="detail-list recipient-import-summary"> <DescriptionList columns={5} collapseAt="never" className="recipient-import-summary">
<div> <div>
<dt>Source</dt> <dt>Source</dt>
<dd>{snapshot.source_label}</dd> <dd>{snapshot.source_label}</dd>
@@ -295,7 +296,7 @@ export default function AddressSourceImportDialog({
<dt>Revision</dt> <dt>Revision</dt>
<dd className="mono-small">{snapshot.source_revision}</dd> <dd className="mono-small">{snapshot.source_revision}</dd>
</div> </div>
</dl> </DescriptionList>
{snapshot.excluded_count > 0 && ( {snapshot.excluded_count > 0 && (
<DismissibleAlert tone="warning" compact dismissible={false}> <DismissibleAlert tone="warning" compact dismissible={false}>
{snapshot.excluded_count} contact point{snapshot.excluded_count === 1 ? " was" : "s were"} excluded by effective communication-governance facts. {snapshot.excluded_count} contact point{snapshot.excluded_count === 1 ? " was" : "s were"} excluded by effective communication-governance facts.
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { import {
Button, Button,
@@ -294,7 +295,7 @@ export default function DistributionListImportDialog({
)} )}
{preview && ( {preview && (
<> <>
<dl className="detail-list recipient-import-summary"> <DescriptionList columns={5} collapseAt="never" className="recipient-import-summary">
<div><dt>List</dt><dd>{preview.source.name}</dd></div> <div><dt>List</dt><dd>{preview.source.name}</dd></div>
<div><dt>Revision</dt><dd>{preview.source.revision}</dd></div> <div><dt>Revision</dt><dd>{preview.source.revision}</dd></div>
<div><dt>Included</dt><dd>{preview.recipients.length}</dd></div> <div><dt>Included</dt><dd>{preview.recipients.length}</dd></div>
@@ -302,7 +303,7 @@ export default function DistributionListImportDialog({
<div><dt>Providers</dt><dd>{preview.provider_evidence.length}</dd></div> <div><dt>Providers</dt><dd>{preview.provider_evidence.length}</dd></div>
<div><dt>Route decisions</dt><dd>{unresolvedRoutes.length ? `${unresolvedRoutes.length} required` : "Complete"}</dd></div> <div><dt>Route decisions</dt><dd>{unresolvedRoutes.length ? `${unresolvedRoutes.length} required` : "Complete"}</dd></div>
<div><dt>State</dt><dd>{preview.stale ? "Stale" : preview.truncated ? "Truncated" : "Current"}</dd></div> <div><dt>State</dt><dd>{preview.stale ? "Stale" : preview.truncated ? "Truncated" : "Current"}</dd></div>
</dl> </DescriptionList>
{preview.stale && ( {preview.stale && (
<DismissibleAlert tone="warning" compact dismissible={false}> <DismissibleAlert tone="warning" compact dismissible={false}>
At least one provider result is stale. Review its diagnostics before freezing this expansion. At least one provider result is stale. Review its diagnostics before freezing this expansion.
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import type { ApiSettings } from "../../../types"; import type { ApiSettings } from "../../../types";
import { import {
@@ -506,12 +507,12 @@ export function RecipientImportDialog({ settings, campaignId, existingEntries, e
</div> </div>
{table && {table &&
<> <>
<dl className="detail-list recipient-import-summary"> <DescriptionList columns={5} collapseAt="never" className="recipient-import-summary">
<div><dt>{sourceType === "xlsx" ? "i18n:govoplan-campaign.sheet.53bc47a7" : "i18n:govoplan-campaign.separator.b4b289a7"}</dt><dd>{sourceType === "xlsx" ? selectedSheet?.name ?? "i18n:govoplan-campaign.workbook.b4418e62" : formatDelimiter(table.delimiter)}</dd></div> <div><dt>{sourceType === "xlsx" ? "i18n:govoplan-campaign.sheet.53bc47a7" : "i18n:govoplan-campaign.separator.b4b289a7"}</dt><dd>{sourceType === "xlsx" ? selectedSheet?.name ?? "i18n:govoplan-campaign.workbook.b4418e62" : formatDelimiter(table.delimiter)}</dd></div>
<div><dt>i18n:govoplan-campaign.header_rows.9814b9e3</dt><dd>{table.headerRows.length}</dd></div> <div><dt>i18n:govoplan-campaign.header_rows.9814b9e3</dt><dd>{table.headerRows.length}</dd></div>
<div><dt>i18n:govoplan-campaign.data_rows.3734724c</dt><dd>{table.dataRows.length}</dd></div> <div><dt>i18n:govoplan-campaign.data_rows.3734724c</dt><dd>{table.dataRows.length}</dd></div>
<div><dt>i18n:govoplan-campaign.columns.cf723c59</dt><dd>{table.headers.length}</dd></div> <div><dt>i18n:govoplan-campaign.columns.cf723c59</dt><dd>{table.headers.length}</dd></div>
</dl> </DescriptionList>
<RecipientImportRawTable tableRows={table.rows} headerRowCount={table.headerRows.length} columnCount={table.headers.length} /> <RecipientImportRawTable tableRows={table.rows} headerRowCount={table.headerRows.length} columnCount={table.headers.length} />
</> </>
} }
@@ -543,12 +544,12 @@ export function RecipientImportDialog({ settings, campaignId, existingEntries, e
<> <>
{preview && {preview &&
<> <>
<dl className="detail-list recipient-import-summary"> <DescriptionList columns={5} collapseAt="never" className="recipient-import-summary">
<div><dt>i18n:govoplan-campaign.rows.52d0b352</dt><dd>{preview.validCount} i18n:govoplan-campaign.valid.210cece7 {preview.invalidCount} invalid</dd></div> <div><dt>i18n:govoplan-campaign.rows.52d0b352</dt><dd>{preview.validCount} i18n:govoplan-campaign.valid.210cece7 {preview.invalidCount} invalid</dd></div>
<div><dt>i18n:govoplan-campaign.fields.e8b68527</dt><dd>{preview.fieldNamesToCreate.length} new</dd></div> <div><dt>i18n:govoplan-campaign.fields.e8b68527</dt><dd>{preview.fieldNamesToCreate.length} new</dd></div>
<div><dt>i18n:govoplan-campaign.patterns.4d34f7a2</dt><dd>{preview.patternCount} from {patternRows} i18n:govoplan-campaign.row_s.61464061</dd></div> <div><dt>i18n:govoplan-campaign.patterns.4d34f7a2</dt><dd>{preview.patternCount} from {patternRows} i18n:govoplan-campaign.row_s.61464061</dd></div>
<div><dt>i18n:govoplan-campaign.source.6da13add</dt><dd>{preview.patternCount ? defaultAttachmentBasePath?.name ?? "i18n:govoplan-campaign.campaign_files.96e7004b" : "i18n:govoplan-campaign.no_patterns.5e1e46fa"}</dd></div> <div><dt>i18n:govoplan-campaign.source.6da13add</dt><dd>{preview.patternCount ? defaultAttachmentBasePath?.name ?? "i18n:govoplan-campaign.campaign_files.96e7004b" : "i18n:govoplan-campaign.no_patterns.5e1e46fa"}</dd></div>
</dl> </DescriptionList>
{preview.fieldNamesToCreate.length > 0 && {preview.fieldNamesToCreate.length > 0 &&
<p className="muted small-note">i18n:govoplan-campaign.new_fields.c61e20c0 {preview.fieldNamesToCreate.join(", ")}</p> <p className="muted small-note">i18n:govoplan-campaign.new_fields.c61e20c0 {preview.fieldNamesToCreate.join(", ")}</p>
} }
@@ -676,12 +677,12 @@ function RecipientImportFileLinkStep({ preview, basePath, resolution, resolving,
{resolution && {resolution &&
<> <>
<dl className="detail-list recipient-import-summary"> <DescriptionList columns={5} collapseAt="never" className="recipient-import-summary">
<div><dt>i18n:govoplan-campaign.patterns.4d34f7a2</dt><dd>{resolution.patterns.length}</dd></div> <div><dt>i18n:govoplan-campaign.patterns.4d34f7a2</dt><dd>{resolution.patterns.length}</dd></div>
<div><dt>i18n:govoplan-campaign.matched_files.f79c63bb</dt><dd>{resolution.files.length}</dd></div> <div><dt>i18n:govoplan-campaign.matched_files.f79c63bb</dt><dd>{resolution.files.length}</dd></div>
<div><dt>i18n:govoplan-campaign.already_linked.7d6c3dd5</dt><dd>{resolution.linkedFiles.length}</dd></div> <div><dt>i18n:govoplan-campaign.already_linked.7d6c3dd5</dt><dd>{resolution.linkedFiles.length}</dd></div>
<div><dt>i18n:govoplan-campaign.need_linking.a7617722</dt><dd>{resolution.linkableFiles.length}</dd></div> <div><dt>i18n:govoplan-campaign.need_linking.a7617722</dt><dd>{resolution.linkableFiles.length}</dd></div>
</dl> </DescriptionList>
{resolution.unmatchedPatterns.length > 0 && {resolution.unmatchedPatterns.length > 0 &&
<div className="recipient-import-unmatched-patterns"> <div className="recipient-import-unmatched-patterns">
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { Button, Dialog, i18nMessage } from "@govoplan/core-webui"; import { Button, Dialog, i18nMessage } from "@govoplan/core-webui";
import { asArray, asRecord, formatDateTime, humanize } from "../utils/campaignView"; import { asArray, asRecord, formatDateTime, humanize } from "../utils/campaignView";
@@ -28,7 +29,7 @@ export default function DeliveryJobDetailOverlay({
</Button> </Button>
} }
> >
<dl className="detail-list"> <DescriptionList variant="inline">
<div> <div>
<dt>i18n:govoplan-campaign.recipient.90343260</dt> <dt>i18n:govoplan-campaign.recipient.90343260</dt>
<dd> <dd>
@@ -54,7 +55,7 @@ export default function DeliveryJobDetailOverlay({
<dd>{String(job.last_error)}</dd> <dd>{String(job.last_error)}</dd>
</div> </div>
) : null} ) : null}
</dl> </DescriptionList>
{issues.length > 0 && ( {issues.length > 0 && (
<AttemptList <AttemptList
title="i18n:govoplan-campaign.message_issues.9092a7db" title="i18n:govoplan-campaign.message_issues.9092a7db"
@@ -90,7 +91,7 @@ function AttemptList({
{rows.length === 0 ? ( {rows.length === 0 ? (
<p className="muted small-note">{emptyText}</p> <p className="muted small-note">{emptyText}</p>
) : ( ) : (
<dl className="detail-list"> <DescriptionList variant="inline">
{rows.map((row, index) => ( {rows.map((row, index) => (
<div key={`${String(row.id ?? row.code ?? index)}:${index}`}> <div key={`${String(row.id ?? row.code ?? index)}:${index}`}>
<dt> <dt>
@@ -129,7 +130,7 @@ function AttemptList({
</dd> </dd>
</div> </div>
))} ))}
</dl> </DescriptionList>
)} )}
</section> </section>
); );
@@ -1,3 +1,4 @@
import { MetricGrid } from "@govoplan/core-webui";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FormGrid, Button } from "@govoplan/core-webui"; import { FormGrid, Button } from "@govoplan/core-webui";
import { FieldLabel } from "@govoplan/core-webui"; import { FieldLabel } from "@govoplan/core-webui";
@@ -189,11 +190,11 @@ export function ReviewStep({ version, onValidate }: {version: unknown;onValidate
<h2>i18n:govoplan-campaign.review_setup.d0059acb</h2> <h2>i18n:govoplan-campaign.review_setup.d0059acb</h2>
<p>i18n:govoplan-campaign.validate_the_campaign_definition_before_building.d36ee1dc</p> <p>i18n:govoplan-campaign.validate_the_campaign_definition_before_building.d36ee1dc</p>
</div> </div>
<div className="metric-grid inside"> <MetricGrid spacing="inset">
<MetricCard label="i18n:govoplan-campaign.errors.805e86a8" value={summaryValue(asRecord(record.validation_summary), ["error_count", "errors", "blocked"])} tone="danger" /> <MetricCard label="i18n:govoplan-campaign.errors.805e86a8" value={summaryValue(asRecord(record.validation_summary), ["error_count", "errors", "blocked"])} tone="danger" />
<MetricCard label="i18n:govoplan-campaign.warnings.1430f976" value={summaryValue(asRecord(record.validation_summary), ["warning_count", "warnings"])} tone="warning" /> <MetricCard label="i18n:govoplan-campaign.warnings.1430f976" value={summaryValue(asRecord(record.validation_summary), ["warning_count", "warnings"])} tone="warning" />
<MetricCard label="i18n:govoplan-campaign.built.a6ad3f82" value={summaryValue(asRecord(record.build_summary), ["built_count", "built", "messages_built"])} tone="info" /> <MetricCard label="i18n:govoplan-campaign.built.a6ad3f82" value={summaryValue(asRecord(record.build_summary), ["built_count", "built", "messages_built"])} tone="info" />
</div> </MetricGrid>
<Button variant="primary" onClick={onValidate}>i18n:govoplan-campaign.validate_campaign.6934c1c2</Button> <Button variant="primary" onClick={onValidate}>i18n:govoplan-campaign.validate_campaign.6934c1c2</Button>
</div>); </div>);
@@ -1,3 +1,4 @@
import { MetricGrid } from "@govoplan/core-webui";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Ban, ExternalLink, Eye, Pause, Play, RotateCcw, Send, ShieldCheck } from "lucide-react"; import { Ban, ExternalLink, Eye, Pause, Play, RotateCcw, Send, ShieldCheck } from "lucide-react";
import { useSearchParams } from "react-router"; import { useSearchParams } from "react-router";
@@ -774,7 +775,7 @@ export default function OperatorQueuePage({ settings, auth }: {settings: ApiSett
<section className="queue-pressure-section" aria-labelledby="operator-queue-pressure-title"> <section className="queue-pressure-section" aria-labelledby="operator-queue-pressure-title">
<h2 id="operator-queue-pressure-title" className="queue-pressure-heading">i18n:govoplan-campaign.queue_pressure.28eee15e</h2> <h2 id="operator-queue-pressure-title" className="queue-pressure-heading">i18n:govoplan-campaign.queue_pressure.28eee15e</h2>
<div className="metric-grid queue-pressure-grid"> <MetricGrid columns="auto" spacing="none" className="queue-pressure-metrics">
<MetricCard label="i18n:govoplan-campaign.queued.6a599877" value={totals.queued} tone="neutral" /> <MetricCard label="i18n:govoplan-campaign.queued.6a599877" value={totals.queued} tone="neutral" />
<MetricCard label="i18n:govoplan-campaign.claimed.83c87884" value={totals.claimed} tone="info" /> <MetricCard label="i18n:govoplan-campaign.claimed.83c87884" value={totals.claimed} tone="info" />
<MetricCard label="i18n:govoplan-campaign.sending.ceafde86" value={totals.sending} tone="info" /> <MetricCard label="i18n:govoplan-campaign.sending.ceafde86" value={totals.sending} tone="info" />
@@ -784,7 +785,7 @@ export default function OperatorQueuePage({ settings, auth }: {settings: ApiSett
<MetricCard label="i18n:govoplan-campaign.unattempted.e7411dd6" value={totals.notAttempted} tone="neutral" /> <MetricCard label="i18n:govoplan-campaign.unattempted.e7411dd6" value={totals.notAttempted} tone="neutral" />
<MetricCard label="i18n:govoplan-campaign.paused.c7dfb6f1" value={totals.paused} tone="neutral" /> <MetricCard label="i18n:govoplan-campaign.paused.c7dfb6f1" value={totals.paused} tone="neutral" />
<MetricCard label="i18n:govoplan-campaign.imap_failed.50dbca55" value={totals.imapFailed} tone="warning" /> <MetricCard label="i18n:govoplan-campaign.imap_failed.50dbca55" value={totals.imapFailed} tone="warning" />
</div> </MetricGrid>
</section> </section>
<LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_operator_queue.ae6576e0"> <LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_operator_queue.ae6576e0">
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Eye, RefreshCw } from "lucide-react"; import { Eye, RefreshCw } from "lucide-react";
import { useSearchParams } from "react-router"; import { useSearchParams } from "react-router";
@@ -198,12 +199,12 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
{report && outcomes && ( {report && outcomes && (
<> <>
<Card title={report.campaign.name}> <Card title={report.campaign.name}>
<dl className="detail-list"> <DescriptionList variant="inline">
<div><dt>i18n:govoplan-campaign.status.bae7d5be</dt><dd><StatusBadge status={report.campaign.status} /></dd></div> <div><dt>i18n:govoplan-campaign.status.bae7d5be</dt><dd><StatusBadge status={report.campaign.status} /></dd></div>
<div><dt>i18n:govoplan-campaign.completion.2ff25568</dt><dd><StatusBadge status={report.completion_state} label={aggregateCompletionLabel(report.completion_state)} /></dd></div> <div><dt>i18n:govoplan-campaign.completion.2ff25568</dt><dd><StatusBadge status={report.completion_state} label={aggregateCompletionLabel(report.completion_state)} /></dd></div>
<div><dt>i18n:govoplan-campaign.version.2da600bf</dt><dd>{report.version_number ?? "—"}</dd></div> <div><dt>i18n:govoplan-campaign.version.2da600bf</dt><dd>{report.version_number ?? "—"}</dd></div>
<div><dt>i18n:govoplan-campaign.generated.8eefdd52</dt><dd>{formatDateTime(report.generated_at)}</dd></div> <div><dt>i18n:govoplan-campaign.generated.8eefdd52</dt><dd>{formatDateTime(report.generated_at)}</dd></div>
</dl> </DescriptionList>
</Card> </Card>
{/* Aggregate outcome cards are deliberately not filter shortcuts: each subgroup {/* Aggregate outcome cards are deliberately not filter shortcuts: each subgroup
@@ -223,19 +224,19 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
</ContentGrid> </ContentGrid>
<Card title="i18n:govoplan-campaign.population_and_exclusions.cdff62ad"> <Card title="i18n:govoplan-campaign.population_and_exclusions.cdff62ad">
<dl className="detail-list"> <DescriptionList variant="inline">
<div><dt>i18n:govoplan-campaign.report_denominator.d089074a</dt><dd>{countValue(report.population.denominator)}</dd></div> <div><dt>i18n:govoplan-campaign.report_denominator.d089074a</dt><dd>{countValue(report.population.denominator)}</dd></div>
<div><dt>i18n:govoplan-campaign.inactive_source_entries.88fa0cf8</dt><dd>{countValue(report.population.inactive_source_entries)}</dd></div> <div><dt>i18n:govoplan-campaign.inactive_source_entries.88fa0cf8</dt><dd>{countValue(report.population.inactive_source_entries)}</dd></div>
<div><dt>i18n:govoplan-campaign.excluded_or_blocked_jobs.e2190d47</dt><dd>{countValue(report.population.excluded_or_blocked_jobs)}</dd></div> <div><dt>i18n:govoplan-campaign.excluded_or_blocked_jobs.e2190d47</dt><dd>{countValue(report.population.excluded_or_blocked_jobs)}</dd></div>
</dl> </DescriptionList>
<p className="muted">i18n:govoplan-campaign.all_persisted_recipient_delivery_jobs_for_the_se.208e90cc</p> <p className="muted">i18n:govoplan-campaign.all_persisted_recipient_delivery_jobs_for_the_se.208e90cc</p>
</Card> </Card>
<Card title="i18n:govoplan-campaign.delivery_activity_range.0d1b62ec"> <Card title="i18n:govoplan-campaign.delivery_activity_range.0d1b62ec">
<dl className="detail-list"> <DescriptionList variant="inline">
<div><dt>i18n:govoplan-campaign.first_activity.3fe36ef4</dt><dd>{report.time_range.suppressed ? "i18n:govoplan-campaign.suppressed.fce88db5" : formatDateTime(report.time_range.first_activity_at ?? undefined)}</dd></div> <div><dt>i18n:govoplan-campaign.first_activity.3fe36ef4</dt><dd>{report.time_range.suppressed ? "i18n:govoplan-campaign.suppressed.fce88db5" : formatDateTime(report.time_range.first_activity_at ?? undefined)}</dd></div>
<div><dt>i18n:govoplan-campaign.last_activity.1c73b806</dt><dd>{report.time_range.suppressed ? "i18n:govoplan-campaign.suppressed.fce88db5" : formatDateTime(report.time_range.last_activity_at ?? undefined)}</dd></div> <div><dt>i18n:govoplan-campaign.last_activity.1c73b806</dt><dd>{report.time_range.suppressed ? "i18n:govoplan-campaign.suppressed.fce88db5" : formatDateTime(report.time_range.last_activity_at ?? undefined)}</dd></div>
</dl> </DescriptionList>
</Card> </Card>
<Card title="i18n:govoplan-campaign.privacy_boundary.d37e12bc"> <Card title="i18n:govoplan-campaign.privacy_boundary.d37e12bc">
@@ -1,3 +1,4 @@
import { DescriptionList } from "@govoplan/core-webui";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui"; import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui";
@@ -206,12 +207,12 @@ function TemplateOverview({ template }: {template: TemplateRecord;}) {
return ( return (
<ContentGrid columns={2} collapseAt="workspace" className=""> <ContentGrid columns={2} collapseAt="workspace" className="">
<Card title="i18n:govoplan-campaign.template_status.8bec97cf"> <Card title="i18n:govoplan-campaign.template_status.8bec97cf">
<dl className="detail-list compact-detail-list"> <DescriptionList variant="inline" density="compact">
<div><dt>i18n:govoplan-campaign.status.bae7d5be</dt><dd><StatusBadge status={template.status} /></dd></div> <div><dt>i18n:govoplan-campaign.status.bae7d5be</dt><dd><StatusBadge status={template.status} /></dd></div>
<div><dt>i18n:govoplan-campaign.type.3deb7456</dt><dd>{templateTypeLabel(template.type)}</dd></div> <div><dt>i18n:govoplan-campaign.type.3deb7456</dt><dd>{templateTypeLabel(template.type)}</dd></div>
<div><dt>i18n:govoplan-campaign.updated.f2f8570d</dt><dd>{template.updatedAt}</dd></div> <div><dt>i18n:govoplan-campaign.updated.f2f8570d</dt><dd>{template.updatedAt}</dd></div>
<div><dt>i18n:govoplan-campaign.versions.a239107e</dt><dd>{template.versions}</dd></div> <div><dt>i18n:govoplan-campaign.versions.a239107e</dt><dd>{template.versions}</dd></div>
</dl> </DescriptionList>
</Card> </Card>
<Card title="i18n:govoplan-campaign.compatibility_notes.7d648a6d"> <Card title="i18n:govoplan-campaign.compatibility_notes.7d648a6d">
<p className="muted">i18n:govoplan-campaign.campaigns_can_reuse_this_template_but_each_campa.bdb25009</p> <p className="muted">i18n:govoplan-campaign.campaigns_can_reuse_this_template_but_each_campa.bdb25009</p>
@@ -285,10 +286,10 @@ function TemplateUsage({ template }: {template: TemplateRecord;}) {
return ( return (
<Card title="i18n:govoplan-campaign.usage.0bb18642"> <Card title="i18n:govoplan-campaign.usage.0bb18642">
<p className="muted">i18n:govoplan-campaign.this_view_will_list_campaigns_using_the_template.116f7ee0</p> <p className="muted">i18n:govoplan-campaign.this_view_will_list_campaigns_using_the_template.116f7ee0</p>
<dl className="detail-list compact-detail-list"> <DescriptionList variant="inline" density="compact">
<div><dt>i18n:govoplan-campaign.currently_used_by.a065cfbe</dt><dd>{template.usedBy}</dd></div> <div><dt>i18n:govoplan-campaign.currently_used_by.a065cfbe</dt><dd>{template.usedBy}</dd></div>
<div><dt>i18n:govoplan-campaign.safe_to_edit.57b64df5</dt><dd>i18n:govoplan-campaign.requires_versioning_once_templates_are_shared.e020b221</dd></div> <div><dt>i18n:govoplan-campaign.safe_to_edit.57b64df5</dt><dd>i18n:govoplan-campaign.requires_versioning_once_templates_are_shared.e020b221</dd></div>
</dl> </DescriptionList>
</Card>); </Card>);
} }
+5 -14
View File
@@ -299,20 +299,16 @@
color: var(--text-strong); color: var(--text-strong);
font-size: 16px; font-size: 16px;
} }
.queue-pressure-grid { .queue-pressure-metrics {
grid-template-columns: repeat(5, minmax(112px, 1fr));
margin: 0;
}
.queue-pressure-grid .metric-card {
min-width: 0; min-width: 0;
} }
.queue-pressure-grid .metric-label { .queue-pressure-metrics .metric-card {
min-width: 0;
}
.queue-pressure-metrics .metric-label {
min-height: 2.4em; min-height: 2.4em;
} }
@media (max-width: 1150px) {
.queue-pressure-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
}
.module-table th, .module-table th,
.module-table td { .module-table td {
white-space: nowrap; white-space: nowrap;
@@ -966,10 +962,6 @@
margin-top: 16px; margin-top: 16px;
} }
.campaign-postbox-target-modal {
width: min(1040px, calc(100vw - 32px));
}
.campaign-postbox-target-body { .campaign-postbox-target-body {
max-height: min(76vh, 820px); max-height: min(76vh, 820px);
overflow: auto; overflow: auto;
@@ -2799,7 +2791,6 @@
} }
.campaign-content-library-dialog { width: min(880px, calc(100vw - 32px)); } .campaign-content-library-dialog { width: min(880px, calc(100vw - 32px)); }
.campaign-content-save-dialog { width: min(680px, calc(100vw - 32px)); }
.campaign-content-library, .campaign-content-save-form { display: grid; gap: 14px; } .campaign-content-library, .campaign-content-save-form { display: grid; gap: 14px; }
.campaign-content-library input, .campaign-content-save-form input, .campaign-content-save-form textarea { width: 100%; } .campaign-content-library input, .campaign-content-save-form input, .campaign-content-save-form textarea { width: 100%; }
.campaign-content-library-list { display: grid; max-height: min(56vh, 560px); overflow: auto; border: var(--border-line); } .campaign-content-library-list { display: grid; max-height: min(56vh, 560px); overflow: auto; border: var(--border-line); }