Refactor campaign recipient and review presentation

This commit is contained in:
2026-07-31 02:48:56 +02:00
parent 5f7503598c
commit e689fdf495
30 changed files with 2175 additions and 1977 deletions
@@ -1,6 +1,11 @@
import { useState, type CSSProperties, type ReactNode } from "react";
import { ChevronDown, LockKeyhole, type LucideIcon } from "lucide-react";
import { InlineHelp, i18nMessage } from "@govoplan/core-webui";
import {
InlineHelp,
StageRail,
i18nMessage,
type StageRailTone
} from "@govoplan/core-webui";
import { humanize } from "../utils/campaignView";
@@ -39,6 +44,18 @@ const stateColors: Record<FlowState, string> = {
pending: "var(--muted)"
};
const stageRailTones: Record<FlowState, StageRailTone> = {
complete: "success",
warning: "warning",
danger: "danger",
active: "active",
locked: "neutral",
running: "active",
partial: "partial",
stale: "partial",
pending: "neutral"
};
export function WorkflowNavigation({
stages,
onSelect
@@ -47,50 +64,29 @@ export function WorkflowNavigation({
onSelect: (id: string) => void;
}) {
return (
<nav className="review-flow-navigation" aria-label="i18n:govoplan-campaign.review_and_send_workflow_steps.77fc8af2">
<div className="review-flow-navigation-track">
{stages.map((stage, index) => {
const Icon = stage.icon;
const nextStage = stages[index + 1];
const connectorState = stageConnectorState(stage);
const nextConnectorState = nextStage ? stageConnectorState(nextStage) : connectorState;
const title = i18nMessage(stage.title);
const shortTitle = i18nMessage(stage.shortTitle);
const stateText = i18nMessage(stage.stateLabel);
const style = {
"--review-nav-color": stateColors[stage.state],
"--review-nav-line-color": stateColors[connectorState],
"--review-nav-line-next-color": stateColors[nextConnectorState]
} as CSSProperties;
const showSecondaryState = !["active", "locked"].includes(stage.state);
return (
<div className="review-flow-navigation-group" key={stage.id} style={style}>
<button
type="button"
className="review-flow-navigation-item"
data-state={stage.state}
onClick={() => onSelect(stage.id)}
title={`${title}: ${stateText}`}
>
<span className="review-flow-navigation-icon">
<Icon size={17} strokeWidth={1.8} aria-hidden="true" />
</span>
<span className="review-flow-navigation-copy">
<strong>
<span>{shortTitle}</span>
{stage.state === "locked" && (
<LockKeyhole size={12} aria-label="i18n:govoplan-campaign.locked.a798882f" />
)}
</strong>
{showSecondaryState && <small>{stateText}</small>}
</span>
</button>
{nextStage && <span className="review-flow-navigation-line" aria-hidden="true" />}
</div>
);
})}
</div>
</nav>
<StageRail
className="review-flow-navigation"
ariaLabel="i18n:govoplan-campaign.review_and_send_workflow_steps.77fc8af2"
onSelect={onSelect}
items={stages.map((stage) => {
const Icon = stage.icon;
const title = i18nMessage(stage.title);
const stateText = i18nMessage(stage.stateLabel);
return {
id: stage.id,
label: i18nMessage(stage.shortTitle),
statusLabel: ["active", "locked"].includes(stage.state)
? undefined
: stateText,
title: `${title}: ${stateText}`,
icon: <Icon size={17} strokeWidth={1.8} aria-hidden="true" />,
tone: stageRailTones[stage.state],
connectorTone: stageRailTones[stageConnectorState(stage)],
locked: stage.state === "locked",
lockedLabel: "i18n:govoplan-campaign.locked.a798882f"
};
})}
/>
);
}