Migrate Calendar interface patterns
This commit is contained in:
@@ -7,9 +7,11 @@ import {
|
||||
} from "react";
|
||||
import { ArrowRightLeft, ListChecks, RefreshCw, Trash2 } from "lucide-react";
|
||||
import {
|
||||
ActionBlockerHint,
|
||||
Button,
|
||||
ColorPickerField,
|
||||
Dialog,
|
||||
DocumentationHelpLink,
|
||||
PasswordField,
|
||||
SegmentedControl,
|
||||
ToggleSwitch,
|
||||
@@ -41,6 +43,10 @@ import {
|
||||
errorText,
|
||||
normalizeHexColor,
|
||||
} from "./calendarViewModel";
|
||||
import {
|
||||
CALENDAR_I18N,
|
||||
CALENDAR_SOURCE_DOCUMENTATION,
|
||||
} from "./interfacePatterns";
|
||||
|
||||
export type CalendarSourceMode = "local" | CalendarSyncSourceKind | "open_xchange";
|
||||
type CalendarSourceSwitchMode =
|
||||
@@ -161,12 +167,18 @@ export function CalendarCollectionDialog({
|
||||
!isExistingSyncSource && !canEditSource ||
|
||||
canEditSource && (!effectiveCollectionUrl || effectiveAuthType === "basic" && !username.trim() || needsSourceSecret));
|
||||
|
||||
const saveDisabled =
|
||||
saving ||
|
||||
migrationLocked ||
|
||||
!canWrite ||
|
||||
!name.trim() ||
|
||||
sourceDetailsInvalid;
|
||||
const saveDisabledReason = saving
|
||||
? CALENDAR_I18N.saving
|
||||
: migrationLocked
|
||||
? CALENDAR_I18N.migrationLocked
|
||||
: !canWrite
|
||||
? CALENDAR_I18N.calendarWriteRequired
|
||||
: !name.trim()
|
||||
? CALENDAR_I18N.calendarNameRequired
|
||||
: sourceDetailsInvalid
|
||||
? CALENDAR_I18N.sourceDetailsRequired
|
||||
: undefined;
|
||||
const saveDisabled = Boolean(saveDisabledReason);
|
||||
|
||||
const syncing = source ? syncingSourceId === source.id : false;
|
||||
|
||||
@@ -348,25 +360,41 @@ export function CalendarCollectionDialog({
|
||||
<>
|
||||
<div>
|
||||
{calendar && canDelete &&
|
||||
<Button type="button" variant="danger" onClick={() => onRequestDelete(calendar, state.kind === "edit" ? state.eventCount : null, state.kind === "edit" ? state.loadingEventCount : true)} disabled={saving || migrationLocked}>
|
||||
<Button type="button" variant="danger" onClick={() => onRequestDelete(calendar, state.kind === "edit" ? state.eventCount : null, state.kind === "edit" ? state.loadingEventCount : true)} disabled={saving || migrationLocked} disabledReason={saving ? CALENDAR_I18N.saving : migrationLocked ? CALENDAR_I18N.migrationLocked : undefined}>
|
||||
<Trash2 size={16} /> {calendarDeleteActionLabel(calendar)}
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
<div className="calendar-dialog-actions">
|
||||
<Button type="button" onClick={onCancel} disabled={saving}>i18n:govoplan-calendar.cancel.77dfd213</Button>
|
||||
<Button type="submit" form={formId} variant="primary" disabled={saveDisabled}>{saving ? "i18n:govoplan-calendar.saving.ae7e8875" : isEdit ? "i18n:govoplan-calendar.save.efc007a3" : "i18n:govoplan-calendar.add.61cc55aa"}</Button>
|
||||
<Button type="submit" form={formId} variant="primary" disabled={saveDisabled} disabledReason={saveDisabledReason}>{saving ? "i18n:govoplan-calendar.saving.ae7e8875" : isEdit ? "i18n:govoplan-calendar.save.efc007a3" : "i18n:govoplan-calendar.add.61cc55aa"}</Button>
|
||||
</div>
|
||||
</>
|
||||
}>
|
||||
|
||||
<form id={formId} className="calendar-dialog-form" onSubmit={submit}>
|
||||
{migrationLocked &&
|
||||
<p className="calendar-form-note">
|
||||
Calendar and event changes are locked while the destructive remote move is being reconciled.
|
||||
</p>
|
||||
}
|
||||
{sourceMode !== "local" && !canManageSources && <p className="calendar-form-note">i18n:govoplan-calendar.managing_sync_sources_requires_calendar_administ.835e29fa</p>}
|
||||
<div className="calendar-dialog-documentation">
|
||||
<DocumentationHelpLink reference={CALENDAR_SOURCE_DOCUMENTATION} />
|
||||
</div>
|
||||
{migrationLocked && (
|
||||
<ActionBlockerHint
|
||||
reason={{
|
||||
summary: CALENDAR_I18N.migrationLocked,
|
||||
requiredAction: "Open Remote move and reconcile or finish the batch before editing this calendar."
|
||||
}}
|
||||
documentation={CALENDAR_SOURCE_DOCUMENTATION}
|
||||
/>
|
||||
)}
|
||||
{sourceMode !== "local" && !canManageSources && (
|
||||
<ActionBlockerHint
|
||||
tone="info"
|
||||
reason={{
|
||||
summary: CALENDAR_I18N.calendarAdminRequired,
|
||||
details: "Source URLs, credentials, and synchronization policy are administrator-owned settings."
|
||||
}}
|
||||
documentation={CALENDAR_SOURCE_DOCUMENTATION}
|
||||
/>
|
||||
)}
|
||||
{!isEdit &&
|
||||
<SegmentedControl
|
||||
className="calendar-source-switch"
|
||||
@@ -498,7 +526,7 @@ export function CalendarCollectionDialog({
|
||||
}
|
||||
<div className={calendarSourceUsesCalDav(sourceMode) ? "calendar-discovery-actions" : "calendar-discovery-actions is-readonly"}>
|
||||
{calendarSourceUsesCalDav(sourceMode) &&
|
||||
<Button type="button" onClick={() => void handleDiscover()} disabled={saving || discovering || !canEditSource || !davUrl.trim() || effectiveAuthType === "basic" && !username.trim()}>
|
||||
<Button type="button" onClick={() => void handleDiscover()} disabled={saving || discovering || !canEditSource || !davUrl.trim() || effectiveAuthType === "basic" && !username.trim()} disabledReason={saving ? CALENDAR_I18N.saving : !canEditSource ? CALENDAR_I18N.calendarAdminRequired : !davUrl.trim() || effectiveAuthType === "basic" && !username.trim() ? CALENDAR_I18N.sourceDetailsRequired : undefined}>
|
||||
<RefreshCw size={16} /> {discovering ? "i18n:govoplan-calendar.discovering.1884f689" : "i18n:govoplan-calendar.discover.4827ea22"}
|
||||
</Button>
|
||||
}
|
||||
@@ -562,11 +590,12 @@ export function CalendarCollectionDialog({
|
||||
type="button"
|
||||
className={syncing ? "calendar-sync-button is-syncing" : "calendar-sync-button"}
|
||||
onClick={() => void onSync(source, syncTransientPayload(effectiveAuthType, password, bearerToken))}
|
||||
disabled={saving || syncing || migrationLocked || !canSyncSources}>
|
||||
disabled={saving || syncing || migrationLocked || !canSyncSources}
|
||||
disabledReason={saving ? CALENDAR_I18N.saving : syncing ? CALENDAR_I18N.syncing : migrationLocked ? CALENDAR_I18N.migrationLocked : !canSyncSources ? CALENDAR_I18N.syncPermissionRequired : undefined}>
|
||||
|
||||
<RefreshCw size={16} className={syncing ? "calendar-sync-spin" : undefined} /> {syncing ? "i18n:govoplan-calendar.syncing.e5c7727a" : "i18n:govoplan-calendar.sync_now.2b7d938e"}
|
||||
</Button>
|
||||
<Button type="button" onClick={() => void onSync(source, { ...syncTransientPayload(effectiveAuthType, password, bearerToken), force_full: true })} disabled={saving || syncing || migrationLocked || !canSyncSources}>
|
||||
<Button type="button" onClick={() => void onSync(source, { ...syncTransientPayload(effectiveAuthType, password, bearerToken), force_full: true })} disabled={saving || syncing || migrationLocked || !canSyncSources} disabledReason={saving ? CALENDAR_I18N.saving : syncing ? CALENDAR_I18N.syncing : migrationLocked ? CALENDAR_I18N.migrationLocked : !canSyncSources ? CALENDAR_I18N.syncPermissionRequired : undefined}>
|
||||
i18n:govoplan-calendar.full_sync.21b89c76
|
||||
</Button>
|
||||
{source.source_kind === "caldav" && canManageSources && (
|
||||
@@ -660,6 +689,9 @@ export function CalendarCollectionDeleteDialog({
|
||||
}>
|
||||
|
||||
<div className="calendar-delete-dialog-body">
|
||||
<div className="calendar-dialog-documentation">
|
||||
<DocumentationHelpLink reference={CALENDAR_SOURCE_DOCUMENTATION} />
|
||||
</div>
|
||||
<p className="calendar-delete-warning">
|
||||
{loadingEventCount ?
|
||||
"i18n:govoplan-calendar.loading_event_count.716ad3c2" :
|
||||
|
||||
Reference in New Issue
Block a user