Adopt shared WebUI layout primitives

This commit is contained in:
2026-08-18 10:42:51 +02:00
parent 68459fde15
commit cf8d7fab11
11 changed files with 65 additions and 65 deletions
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
import { Archive, CalendarClock, Copy, ExternalLink, LockKeyhole, LockOpen, Pause, Play, Trash2 } from "lucide-react";
import { Link } from "react-router";
import type { ApiSettings, AuthInfo } from "../../types";
import { Button } from "@govoplan/core-webui";
import { FormGrid, Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui";
@@ -389,7 +389,7 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
title="i18n:govoplan-campaign.campaign_identity.a00ca574"
collapsible
actions={<Link className="btn btn-secondary" to="wizard">i18n:govoplan-campaign.edit_with_wizard.672a7d1a</Link>}>
<div className="form-grid campaign-identity-grid">
<FormGrid columns={1} collapseAt="standard" className="campaign-identity-grid">
<FormField label="i18n:govoplan-campaign.campaign_id.4c4ed79e">
<input value={identity.external_id} onChange={(event) => patchIdentity("external_id", event.target.value)} />
</FormField>
@@ -404,7 +404,7 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
<FormField label="i18n:govoplan-campaign.description.55f8ebc8">
<textarea rows={4} value={identity.description} onChange={(event) => patchIdentity("description", event.target.value)} />
</FormField>
</div>
</FormGrid>
</Card>
{(canSchedule || schedules.length > 0) && <Card
@@ -14,7 +14,7 @@ import {
type CampaignJobsResponse,
type CampaignRetentionReport } from
"../../api/campaigns";
import { Card } from "@govoplan/core-webui";
import { ContentGrid, Card } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { DataGrid, type DataGridColumn, type DataGridListOption, type DataGridQueryState } from "@govoplan/core-webui";
@@ -461,7 +461,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
}
>
<LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_report_data.0908ade5">
<div className="dashboard-grid">
<ContentGrid columns={2} collapseAt="workspace" className="">
<Card title="i18n:govoplan-campaign.delivery_outcome.f9d7c085">
<dl className="detail-list">
<div><dt>i18n:govoplan-campaign.generated.8eefdd52</dt><dd>{formatDateTime(data.summary?.generated_at)}</dd></div>
@@ -536,7 +536,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<Button onClick={() => void runExplicitAction("unattempted")} disabled={!version || Boolean(busyAction)}>i18n:govoplan-campaign.send_unattempted_jobs.db7acc9f</Button>
</div>
</Card>
</div>
</ContentGrid>
<Card title="i18n:govoplan-campaign.recipient_delivery_jobs.52492608">
<p className="muted small-note">
@@ -6,7 +6,7 @@ import {
type CampaignCalendarCatalog,
type CampaignPostboxCatalog
} from "../../api/campaigns";
import { Button } from "@govoplan/core-webui";
import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui";
@@ -211,7 +211,7 @@ export default function GlobalSettingsPage({ settings, auth, campaignId, view =
}
<div className="dashboard-grid below-grid">
<ContentGrid columns={2} collapseAt="workspace" className="below-grid">
<Card title="i18n:govoplan-campaign.validation_policy.57dcc756" collapsible>
<PolicyTable className="campaign-policy-table" rowClassName="campaign-policy-row" headerClassName="campaign-policy-row-header" fieldLabel="i18n:govoplan-campaign.policy.bb9cf141" settingLabel="i18n:govoplan-campaign.setting.fb449f71" effectiveLabel="i18n:govoplan-campaign.effective_behavior.3daa11c2" showEffectiveColumn>
<PolicyRow
@@ -321,20 +321,20 @@ export default function GlobalSettingsPage({ settings, auth, campaignId, view =
</PolicyTable>
</Card>
</div>
</ContentGrid>
</> :
<>
{data.campaign && <CampaignAccessCard settings={settings} auth={auth} campaign={data.campaign} onChanged={reload} onError={setError} />}
<div className="dashboard-grid below-grid">
<ContentGrid columns={2} collapseAt="workspace" className="below-grid">
<Card title="i18n:govoplan-campaign.delivery_defaults.33cc3b97" collapsible>
<div className="form-grid compact responsive-form-grid">
<FormGrid columns={2} collapseAt="wide" className="">
<FormField label="i18n:govoplan-campaign.messages_per_minute.bea49348"><input type="number" min={1} value={getNumber(rateLimit, "messages_per_minute", 5)} disabled={locked} onChange={(event) => patch(["delivery", "rate_limit", "messages_per_minute"], Number(event.target.value || 1))} /></FormField>
<FormField label="i18n:govoplan-campaign.concurrency.2ec390bf"><input type="number" min={1} value={getNumber(rateLimit, "concurrency", 1)} disabled={locked} onChange={(event) => patch(["delivery", "rate_limit", "concurrency"], Number(event.target.value || 1))} /></FormField>
<FormField label="i18n:govoplan-campaign.max_attempts.f684fef4"><input type="number" min={1} value={getNumber(retry, "max_attempts", 3)} disabled={locked} onChange={(event) => patch(["delivery", "retry", "max_attempts"], Number(event.target.value || 1))} /></FormField>
<ToggleSwitch label="i18n:govoplan-campaign.status_tracking.15f61f88" checked={getBool(statusTracking, "enabled", true)} disabled={locked} onChange={(checked) => patch(["status_tracking", "enabled"], checked)} />
</div>
</FormGrid>
{postboxModuleInstalled &&
<div className="campaign-postbox-delivery-settings">
<FormField label="Delivery channels">
@@ -375,7 +375,7 @@ export default function GlobalSettingsPage({ settings, auth, campaignId, view =
</Card>
<Card title="Calendar invitations" collapsible>
<div className="form-grid compact responsive-form-grid">
<FormGrid columns={2} collapseAt="wide" className="">
<ToggleSwitch
label="Send individualized invitations"
checked={getBool(calendarInvitation, "enabled")}
@@ -454,7 +454,7 @@ export default function GlobalSettingsPage({ settings, auth, campaignId, view =
onChange={(event) => patch(["delivery", "calendar_invitation", "description_template"], event.target.value)}
/>
</FormField>
</div>
</FormGrid>
{!mailModuleInstalled &&
<DismissibleAlert tone="warning" dismissible={false}>
Mail must be active to deliver iCalendar invitations.
@@ -483,7 +483,7 @@ export default function GlobalSettingsPage({ settings, auth, campaignId, view =
</div>
<p className="muted small-note">i18n:govoplan-campaign.these_opt_ins_are_stored_in_the_draft_editor_met.dc6ffbfd</p>
</Card>
</div>
</ContentGrid>
</>
}
</LoadingFrame>
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import {
import { FormGrid,
Button,
Card,
Dialog,
@@ -375,7 +375,7 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
<Button variant="primary" onClick={openCredentialDialog} disabled={locked || profilesLoading || !selectedProfile || !selectedSmtpServer && !selectedImapServer}>Add campaign credential</Button>
</div>}>
<p className="muted small-note">The campaign stores stable references to the envelope, selected servers, and credentials.</p>
<div className="form-grid compact responsive-form-grid">
<FormGrid columns={2} collapseAt="wide" className="">
<FormField label="i18n:govoplan-campaign.profile.ff4fc027">
<select value={selectedProfileId} disabled={locked || profilesLoading} onChange={(event) => selectMailProfile(event.target.value)}>
<option value="">i18n:govoplan-campaign.select_a_mail_profile.76480af0</option>
@@ -410,7 +410,7 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
)}
</select>
</FormField>
</div>
</FormGrid>
{selectedProfileUnavailable && <DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-campaign.the_referenced_mail_profile_is_inactive_unavaila.abeebe26</DismissibleAlert>}
{selectedProfile && <div className="metric-grid inside">
<MetricCard label="i18n:govoplan-campaign.profile.ff4fc027" value={selectedProfile.name} />
@@ -427,12 +427,12 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
{profileError && <DismissibleAlert tone="warning" resetKey={profileError} floating>{profileError}</DismissibleAlert>}
</Card>}
<Dialog
<Dialog variant="administration" size="wide"
open={credentialDialogOpen}
title="Add campaign credential"
onClose={() => setCredentialDialogOpen(false)}
closeDisabled={credentialSaving}
className="admin-dialog admin-dialog-wide adaptive-config-dialog"
className="adaptive-config-dialog"
footerClassName="button-row compact-actions"
footer={
<>
@@ -459,7 +459,7 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
<h3>Campaign login</h3>
<p>This credential belongs to this campaign and can use only the selected mail servers.</p>
</header>
<div className="form-grid two">
<FormGrid columns={2} collapseAt="standard" className="">
<FormField label="Name">
<input value={credentialDraft.name} disabled={credentialSaving} onChange={(event) => setCredentialDraft({ ...credentialDraft, name: event.target.value })} autoFocus />
</FormField>
@@ -469,17 +469,17 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
<FormField label="Password">
<PasswordField value={credentialDraft.password} onValueChange={(password) => setCredentialDraft({ ...credentialDraft, password })} disabled={credentialSaving} generator autoComplete="new-password" />
</FormField>
</div>
<div className="form-grid two">
</FormGrid>
<FormGrid columns={2} collapseAt="standard" className="">
{selectedSmtpServer && <ToggleSwitch checked={credentialDraft.useSmtp} disabled={credentialSaving} onChange={(useSmtp) => setCredentialDraft({ ...credentialDraft, useSmtp })} label={`Use for SMTP: ${selectedSmtpServer.name}`} />}
{selectedImapServer && <ToggleSwitch checked={credentialDraft.useImap} disabled={credentialSaving} onChange={(useImap) => setCredentialDraft({ ...credentialDraft, useImap })} label={`Use for IMAP: ${selectedImapServer.name}`} />}
</div>
</FormGrid>
</section>
</div>
</Dialog>
{!isPolicyView && <Card title="i18n:govoplan-campaign.imap_append.8c0d9e96" collapsible>
<div className="form-grid compact responsive-form-grid mail-server-form-grid">
<FormGrid columns={2} collapseAt="wide" className="mail-server-form-grid">
<div className="mail-server-field-span mail-server-toggle-row mail-server-plain-toggle-row">
<ToggleSwitch
label="i18n:govoplan-campaign.append_successful_messages_to_sent_via_imap.dbd1b1d8"
@@ -499,7 +499,7 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
</div>
</FormField>
<MailServerFolderLookupResultView result={folderResult} disabled={locked || !imapAppendEnabled} onUseDetected={useDetectedSentFolder} floatingFailures />
</div>
</FormGrid>
</Card>}
</>
</LoadingFrame>
@@ -37,7 +37,7 @@ import {
type CampaignSummary } from
"../../api/campaigns";
import { getMockMailboxMessage, type MockMailboxMessage } from "../../api/mail";
import { Button, Dialog, FormField, SegmentedControl, hasScope, useDeltaWatermarks, useGuardedNavigate, usePlatformUiCapability, type AuthInfo, type MailDevMailboxUiCapability } from "@govoplan/core-webui";
import { FormGrid, Button, Dialog, FormField, SegmentedControl, hasScope, useDeltaWatermarks, useGuardedNavigate, usePlatformUiCapability, type AuthInfo, type MailDevMailboxUiCapability } from "@govoplan/core-webui";
import { DataGrid, type DataGridQueryState } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
@@ -1899,7 +1899,7 @@ export default function ReviewSendPage({
{busy === "approval" ? "Requesting..." : "Request approval"}
</Button>
</>}>
<div className="form-grid">
<FormGrid columns={1} collapseAt="standard" className="">
<p className="muted small-note form-grid-wide">The request covers the exact built message, attachment, recipient, policy, and transport snapshot. Rebuilding invalidates and removes this gate.</p>
<FormField label="Approver type">
<select value={approvalSelectorKind} disabled={busy === "approval"} onChange={(event) => setApprovalSelectorKind(event.target.value as typeof approvalSelectorKind)}>
@@ -1933,7 +1933,7 @@ export default function ReviewSendPage({
})} />)}
</div>
</div>
</div>
</FormGrid>
</Dialog>
<ConfirmDialog
@@ -2029,7 +2029,7 @@ export default function ReviewSendPage({
}
>
{singleSendConfirmRow && (
<div className="form-grid">
<FormGrid columns={1} collapseAt="standard" className="">
<SegmentedControl
value={singleMessageActionKind}
width="fill"
@@ -2089,7 +2089,7 @@ export default function ReviewSendPage({
/>
</label>
)}
</div>
</FormGrid>
)}
</Dialog>
@@ -11,7 +11,7 @@ import {
type CampaignContentLibraryTarget,
type CampaignPrintTemplate
} from "../../api/campaigns";
import { Button } from "@govoplan/core-webui";
import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
@@ -433,9 +433,9 @@ export default function TemplateDataPage({ settings, campaignId }: {settings: Ap
<LoadingFrame loading={loading || !draft} label="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50">
<>
<div className="dashboard-grid template-editor-grid">
<ContentGrid columns={2} collapseAt="workspace" className="template-editor-grid">
<Card title="i18n:govoplan-campaign.editable_template.5e747a1e" actions={<Button onClick={() => setPreviewOpen(true)}>i18n:govoplan-campaign.preview.f1fbb2b4</Button>}>
<div className="form-grid">
<FormGrid columns={1} collapseAt="standard" className="">
<FormField label="i18n:govoplan-campaign.subject.8d183dbd">
<input
ref={subjectRef}
@@ -506,13 +506,13 @@ export default function TemplateDataPage({ settings, campaignId }: {settings: Ap
<Button onClick={() => setContentLibraryOpen(true)} disabled={locked}>i18n:govoplan-campaign.load_from_library.327ada7c</Button>
<Button onClick={openContentSaveDialog} disabled={locked}>i18n:govoplan-campaign.save_to_library.396649bf</Button>
</div>
</div>
</FormGrid>
</Card>
<div className="template-side-stack">
<Card title="Printable output">
<LoadingFrame loading={printTemplatesLoading} label="Loading printable templates">
<div className="form-grid">
<FormGrid columns={1} collapseAt="standard" className="">
{printTemplatesError && <DismissibleAlert tone="danger" compact resetKey={printTemplatesError}>{printTemplatesError}</DismissibleAlert>}
{!printTemplatesError && !printTemplatesAvailable && (
<DismissibleAlert tone="info" compact dismissible={false}>
@@ -565,7 +565,7 @@ export default function TemplateDataPage({ settings, campaignId }: {settings: Ap
/>
</>
)}
</div>
</FormGrid>
</LoadingFrame>
</Card>
<Card title="i18n:govoplan-campaign.fields.e8b68527">
@@ -598,7 +598,7 @@ export default function TemplateDataPage({ settings, campaignId }: {settings: Ap
<UndefinedPlaceholderList items={undefinedPlaceholders} onSelect={setUndefinedDialog} />
</Card>
</div>
</div>
</ContentGrid>
</>
</LoadingFrame>
@@ -1,6 +1,6 @@
import { useEffect, useRef, type ReactNode } from "react";
import { ArrowBigLeft, ArrowBigLeftDash, ArrowBigRight, ArrowBigRightDash } from "lucide-react";
import { Button, Dialog, MessageDisplayPanel, type MessageDisplayAttachment } from "@govoplan/core-webui";
import { ActionToolbar, Button, Dialog, MessageDisplayPanel, type MessageDisplayAttachment } from "@govoplan/core-webui";
// Campaign review/template/mock previews need recipient navigation, review notes,
// raw MIME inspection and attachment grouping. Generic mailbox reading uses
@@ -106,7 +106,7 @@ export default function CampaignMessagePreviewOverlay({
>
<div ref={contentRef} className="message-preview-content">
{(recipientLabel || recipientNote || navigation) &&
<div className="template-preview-toolbar">
<ActionToolbar className="template-preview-toolbar">
<div>
{recipientLabel && <strong>{recipientLabel}</strong>}
{recipientNote && <p className="muted small-note">{recipientNote}</p>}
@@ -120,7 +120,7 @@ export default function CampaignMessagePreviewOverlay({
<button type="button" className="version-arrow" onClick={navigation.onLast} disabled={navigation.index >= navigation.total - 1} title="i18n:govoplan-campaign.last_message.83741110" aria-label="i18n:govoplan-campaign.last_message.83741110"><ArrowBigRightDash aria-hidden="true" /></button>
</div>
}
</div>
</ActionToolbar>
}
<MessageDisplayPanel
@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { Button, Dialog, DismissibleAlert, FormField, TableActionGroup, ToggleSwitch } from "@govoplan/core-webui";
import { FormGrid, ActionToolbar, Button, Dialog, DismissibleAlert, FormField, TableActionGroup, ToggleSwitch } from "@govoplan/core-webui";
import { ArrowDown, ArrowUp, Plus, Trash2 } from "lucide-react";
import type {
CampaignPostboxCatalog,
@@ -101,7 +101,7 @@ export default function PostboxTargetsDialog({
</>
}
>
<div className="campaign-postbox-target-toolbar">
<ActionToolbar className="campaign-postbox-target-toolbar">
{showMerge &&
<ToggleSwitch
label="Postbox target defaults"
@@ -121,7 +121,7 @@ export default function PostboxTargetsDialog({
<Plus aria-hidden="true" />
Add target
</Button>
</div>
</ActionToolbar>
{error && <DismissibleAlert tone="danger" dismissible={false}>{error}</DismissibleAlert>}
{draftTargets.length === 0 &&
@@ -181,7 +181,7 @@ function PostboxTargetRow({
{ id: "remove", label: "Remove target", icon: <Trash2 aria-hidden="true" />, disabled: locked, variant: "danger", onClick: onRemove }
]} />
</div>
<div className="form-grid compact responsive-form-grid campaign-postbox-target-grid">
<FormGrid columns={2} collapseAt="wide" className="campaign-postbox-target-grid">
<FormField label="Target type">
<select
value={target.mode}
@@ -219,7 +219,7 @@ function PostboxTargetRow({
onChange={onChange}
/>
}
</div>
</FormGrid>
</section>
);
}
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { Button } from "@govoplan/core-webui";
import { FormGrid, Button } from "@govoplan/core-webui";
import { FieldLabel } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { ToggleSwitch } from "@govoplan/core-webui";
@@ -22,7 +22,7 @@ export type WizardStepProps = {
export function BasicsStep({ draft, patch }: WizardStepProps) {
const campaign = asRecord(draft.campaign);
return (
<div className="form-grid">
<FormGrid columns={1} collapseAt="standard" className="">
<FormField label="i18n:govoplan-campaign.campaign_name.aa5d0e72" help="i18n:govoplan-campaign.a_human_readable_name_shown_in_lists_and_reports.afc23e7e">
<input value={getText(campaign, "name")} onChange={(event) => patch(["campaign", "name"], event.target.value)} />
</FormField>
@@ -39,7 +39,7 @@ export function BasicsStep({ draft, patch }: WizardStepProps) {
<FormField label="i18n:govoplan-campaign.description.55f8ebc8">
<textarea rows={5} value={getText(campaign, "description")} onChange={(event) => patch(["campaign", "description"], event.target.value)} />
</FormField>
</div>);
</FormGrid>);
}
@@ -54,7 +54,7 @@ export function SenderStep({ draft, patch }: WizardStepProps) {
const delivery = asRecord(draft.delivery);
const imapAppend = asRecord(delivery.imap_append_sent);
return (
<div className="form-grid">
<FormGrid columns={1} collapseAt="standard" className="">
<FormField label="i18n:govoplan-campaign.default_from_address.b9ee6d77">
<EmailAddressInput
value={from}
@@ -108,7 +108,7 @@ export function SenderStep({ draft, patch }: WizardStepProps) {
</FormField>
<p className="muted small-note">i18n:govoplan-campaign.select_the_delivery_profile_on_the_mail_settings.a89cbb5f</p>
<ToggleSwitch label="i18n:govoplan-campaign.append_successful_messages_to_sent_via_imap.dbd1b1d8" checked={getBool(imapAppend, "enabled")} onChange={(checked) => patch(["delivery", "imap_append_sent", "enabled"], checked)} />
</div>);
</FormGrid>);
}
@@ -144,7 +144,7 @@ export function TemplateStep({ draft, patch }: WizardStepProps) {
<h2>i18n:govoplan-campaign.template.3ec1ae06</h2>
<p>i18n:govoplan-campaign.compose_the_subject_and_body_merge_fields_can_la.4fb6e97b</p>
</div>
<div className="form-grid">
<FormGrid columns={1} collapseAt="standard" className="">
<FormField label="i18n:govoplan-campaign.subject.8d183dbd"><input value={getText(template, "subject")} onChange={(event) => patch(["template", "subject"], event.target.value)} /></FormField>
<FormField label="i18n:govoplan-campaign.plain_text_body.030a0da0"><textarea rows={12} value={getText(template, "text")} onChange={(event) => patch(["template", "text"], event.target.value)} /></FormField>
<div className="form-field">
@@ -157,7 +157,7 @@ export function TemplateStep({ draft, patch }: WizardStepProps) {
onChange={(value) => patch(["template", "html"], value)}
/>
</div>
</div>
</FormGrid>
</div>);
}
@@ -170,12 +170,12 @@ export function AttachmentsStep({ draft, patch }: WizardStepProps) {
<h2>i18n:govoplan-campaign.attachments.6771ade6</h2>
<p>i18n:govoplan-campaign.configure_campaign_wide_attachment_behavior_and_.441a6cd2</p>
</div>
<div className="form-grid compact responsive-form-grid">
<FormGrid columns={2} collapseAt="wide" className="">
<FormField label="i18n:govoplan-campaign.campaign_attachment_base_path.84827619"><input value={getText(attachments, "base_path", ".")} onChange={(event) => patch(["attachments", "base_path"], event.target.value)} /></FormField>
<FormField label="i18n:govoplan-campaign.missing_behavior.de3d1ac7"><select value={getText(attachments, "missing_behavior", "warn")} onChange={(event) => patch(["attachments", "missing_behavior"], event.target.value)}>{behaviorOptions.map((option) => <option key={option}>{option}</option>)}</select></FormField>
<FormField label="i18n:govoplan-campaign.ambiguous_behavior.e86e724b"><select value={getText(attachments, "ambiguous_behavior", "ask")} onChange={(event) => patch(["attachments", "ambiguous_behavior"], event.target.value)}>{behaviorOptions.map((option) => <option key={option}>{option}</option>)}</select></FormField>
<ToggleSwitch label="i18n:govoplan-campaign.allow_individual_attachments.a6ff0e87" checked={getBool(attachments, "allow_individual")} onChange={(checked) => patch(["attachments", "allow_individual"], checked)} />
</div>
</FormGrid>
<JsonEditor value={attachments.global ?? []} onValid={(value) => patch(["attachments", "global"], value)} />
</div>);
@@ -208,10 +208,10 @@ export function SendStep({ draft, patch }: WizardStepProps) {
<h2>i18n:govoplan-campaign.send_preparation.6e078f43</h2>
<p>i18n:govoplan-campaign.configure_rate_limits_and_prepare_the_final_send.af3d4e48</p>
</div>
<div className="form-grid compact responsive-form-grid">
<FormGrid columns={2} collapseAt="wide" className="">
<FormField label="i18n:govoplan-campaign.messages_per_minute.bea49348"><input type="number" min={1} value={getNumber(rateLimit, "messages_per_minute", 5)} onChange={(event) => patch(["delivery", "rate_limit", "messages_per_minute"], Number(event.target.value || 1))} /></FormField>
<FormField label="i18n:govoplan-campaign.concurrency.2ec390bf"><input type="number" min={1} value={getNumber(rateLimit, "concurrency", 1)} onChange={(event) => patch(["delivery", "rate_limit", "concurrency"], Number(event.target.value || 1))} /></FormField>
</div>
</FormGrid>
<p className="muted">i18n:govoplan-campaign.test_send_and_queue_actions_remain_in_the_send_w.60dee716</p>
</div>);
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Eye, RefreshCw } from "lucide-react";
import { useSearchParams } from "react-router";
import {
import { ContentGrid,
Button,
Card,
DataGrid,
@@ -208,7 +208,7 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
{/* Aggregate outcome cards are deliberately not filter shortcuts: each subgroup
would require a separately suppressed aggregate response from the server. */}
<div className="dashboard-grid">
<ContentGrid columns={2} collapseAt="workspace" className="">
<MetricCard label="i18n:govoplan-campaign.smtp_accepted.e3aa7603" value={countValue(outcomes.smtp_accepted)} tone="good" />
<MetricCard label="Postbox accepted" value={countValue(outcomes.postbox_accepted)} tone="good" />
<MetricCard label="Print accepted" value={countValue(outcomes.print_accepted)} tone="good" />
@@ -220,7 +220,7 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
<MetricCard label="i18n:govoplan-campaign.not_attempted.e1be3c69" value={countValue(outcomes.not_attempted)} />
<MetricCard label="i18n:govoplan-campaign.cancelled.a1bf92ef" value={countValue(outcomes.cancelled)} />
<MetricCard label="i18n:govoplan-campaign.excluded.9804952b" value={countValue(outcomes.excluded)} />
</div>
</ContentGrid>
<Card title="i18n:govoplan-campaign.population_and_exclusions.cdff62ad">
<dl className="detail-list">
@@ -1,6 +1,6 @@
import { useMemo, useState } from "react";
import { ExternalLink } from "lucide-react";
import { Button } from "@govoplan/core-webui";
import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { PageLayout, WorkspaceLayout } from "@govoplan/core-webui";
import { FieldLabel } from "@govoplan/core-webui";
@@ -204,7 +204,7 @@ export default function TemplatesPage() {
function TemplateOverview({ template }: {template: TemplateRecord;}) {
return (
<div className="dashboard-grid settings-dashboard-grid">
<ContentGrid columns={2} collapseAt="workspace" className="">
<Card title="i18n:govoplan-campaign.template_status.8bec97cf">
<dl className="detail-list compact-detail-list">
<div><dt>i18n:govoplan-campaign.status.bae7d5be</dt><dd><StatusBadge status={template.status} /></dd></div>
@@ -221,7 +221,7 @@ function TemplateOverview({ template }: {template: TemplateRecord;}) {
<span>i18n:govoplan-campaign.a_mapping_wizard_can_be_added_later.6b2fe0f3</span>
</div>
</Card>
</div>);
</ContentGrid>);
}
@@ -231,10 +231,10 @@ function TemplateContent({ template }: {template: TemplateRecord;}) {
const body = translateText(template.body);
return (
<Card title="i18n:govoplan-campaign.template_content.48630575" actions={<Button disabled>i18n:govoplan-campaign.save_changes.179359b3</Button>}>
<div className="form-grid compact responsive-form-grid">
<FormGrid columns={2} collapseAt="wide" className="">
<label className="form-field"><FieldLabel className="form-label" help="i18n:govoplan-campaign.read_only_subject_stored_in_this_reusable_templa.549ae246">i18n:govoplan-campaign.subject.8d183dbd</FieldLabel><input value={subject} readOnly /></label>
<label className="form-field full-span"><FieldLabel className="form-label" help="i18n:govoplan-campaign.read_only_body_stored_in_this_reusable_template_.98fbf178">i18n:govoplan-campaign.body.718a7e8a</FieldLabel><textarea rows={10} value={body} readOnly /></label>
</div>
</FormGrid>
</Card>);
}