Migrate Calendar interface patterns

This commit is contained in:
2026-08-03 15:08:18 +02:00
parent a125b666da
commit d7fd9447a2
13 changed files with 486 additions and 42 deletions
@@ -1,10 +1,12 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { RefreshCw, RotateCcw, ScanSearch, Trash2 } from "lucide-react";
import {
ActionBlockerHint,
Button,
ConfirmDialog,
Dialog,
DismissibleAlert,
DocumentationHelpLink,
SegmentedControl,
StatusBadge,
type ApiSettings,
@@ -17,6 +19,10 @@ import {
type CalendarSyncSource,
} from "../../api/calendar";
import { dateTimeLabel, errorText } from "./calendarViewModel";
import {
CALENDAR_I18N,
CALENDAR_RECOVERY_DOCUMENTATION,
} from "./interfacePatterns";
type OutboxFilter = "unresolved" | "all";
type RecoveryAction = "retry" | "reconcile" | "discard";
@@ -94,7 +100,7 @@ export function CalendarOutboxDialog({
onClose={onClose}
footer={
<>
<Button type="button" onClick={() => void load()} disabled={loading || Boolean(busyOperationId)}>
<Button type="button" onClick={() => void load()} disabled={loading || Boolean(busyOperationId)} disabledReason={loading ? CALENDAR_I18N.loading : busyOperationId ? CALENDAR_I18N.saving : undefined}>
<RefreshCw size={16} className={loading ? "calendar-sync-spin" : undefined} /> i18n:govoplan-calendar.refresh.56e3badc
</Button>
<Button type="button" onClick={onClose} disabled={Boolean(busyOperationId)}>
@@ -105,6 +111,7 @@ export function CalendarOutboxDialog({
>
<div className="calendar-outbox-body">
<p className="calendar-outbox-calendar-name">{calendar.name}</p>
<DocumentationHelpLink reference={CALENDAR_RECOVERY_DOCUMENTATION} />
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
<div className="calendar-outbox-toolbar">
<SegmentedControl<OutboxFilter>
@@ -191,23 +198,29 @@ function RecoveryActions({
{available.length > 0 && (
<div className="calendar-outbox-actions">
{available.includes("retry") && (
<Button type="button" onClick={() => onRecover("retry")} disabled={busy}>
<Button type="button" onClick={() => onRecover("retry")} disabled={busy} disabledReason={busy ? CALENDAR_I18N.saving : undefined}>
<RotateCcw size={15} /> i18n:govoplan-calendar.retry.3fda8f1c
</Button>
)}
{available.includes("reconcile") && (
<Button type="button" onClick={() => onRecover("reconcile")} disabled={busy}>
<Button type="button" onClick={() => onRecover("reconcile")} disabled={busy} disabledReason={busy ? CALENDAR_I18N.saving : undefined}>
<ScanSearch size={15} /> i18n:govoplan-calendar.reconcile.6c595f64
</Button>
)}
{available.includes("discard") && (
<Button type="button" variant="danger" onClick={() => onRecover("discard")} disabled={busy}>
<Button type="button" variant="danger" onClick={() => onRecover("discard")} disabled={busy} disabledReason={busy ? CALENDAR_I18N.saving : undefined}>
<Trash2 size={15} /> i18n:govoplan-calendar.discard.23a76911
</Button>
)}
</div>
)}
{!available.length && unavailableReason && <p className="calendar-form-note">{unavailableReason}</p>}
{!available.length && unavailableReason && (
<ActionBlockerHint
tone="info"
reason={{ summary: unavailableReason }}
documentation={CALENDAR_RECOVERY_DOCUMENTATION}
/>
)}
</div>
);
}