Complete recurrence editing and calendar preferences
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Save } from "lucide-react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
DismissibleAlert,
|
||||
FormField,
|
||||
ToggleSwitch,
|
||||
type ApiSettings,
|
||||
type AuthInfo
|
||||
} from "@govoplan/core-webui";
|
||||
import {
|
||||
getCalendarViewPreferences,
|
||||
updateCalendarViewPreferences,
|
||||
type CalendarViewPreferences
|
||||
} from "../../api/calendar";
|
||||
|
||||
type CalendarPreferenceDraft = Pick<
|
||||
CalendarViewPreferences,
|
||||
| "dim_weekends"
|
||||
| "dim_off_hours"
|
||||
| "workday_start_hour"
|
||||
| "workday_end_hour"
|
||||
| "continuous_virtualization"
|
||||
| "continuous_overscan_weeks"
|
||||
| "alternate_continuous_months"
|
||||
>;
|
||||
|
||||
const FALLBACK_DRAFT: CalendarPreferenceDraft = {
|
||||
dim_weekends: true,
|
||||
dim_off_hours: true,
|
||||
workday_start_hour: 6,
|
||||
workday_end_hour: 20,
|
||||
continuous_virtualization: true,
|
||||
continuous_overscan_weeks: 6,
|
||||
alternate_continuous_months: true
|
||||
};
|
||||
|
||||
export default function CalendarSettingsPanel({
|
||||
settings,
|
||||
auth
|
||||
}: {
|
||||
settings: ApiSettings;
|
||||
auth: AuthInfo;
|
||||
}) {
|
||||
const [loaded, setLoaded] = useState<CalendarPreferenceDraft | null>(null);
|
||||
const [draft, setDraft] = useState<CalendarPreferenceDraft>(FALLBACK_DRAFT);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState("");
|
||||
const [tone, setTone] = useState<"success" | "warning">("success");
|
||||
const dirty = useMemo(
|
||||
() => loaded !== null && JSON.stringify(loaded) !== JSON.stringify(draft),
|
||||
[draft, loaded]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void loadPreferences();
|
||||
}, [auth.user.id, settings.accessToken, settings.apiBaseUrl, settings.apiKey]);
|
||||
|
||||
async function loadPreferences() {
|
||||
setLoading(true);
|
||||
setMessage("");
|
||||
try {
|
||||
const response = await getCalendarViewPreferences(settings);
|
||||
const next = preferenceDraft(response);
|
||||
setLoaded(next);
|
||||
setDraft(next);
|
||||
} catch (error) {
|
||||
setTone("warning");
|
||||
setMessage(errorText(error));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function savePreferences() {
|
||||
if (draft.workday_end_hour <= draft.workday_start_hour) {
|
||||
setTone("warning");
|
||||
setMessage("Workday end must be after workday start.");
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
setMessage("");
|
||||
try {
|
||||
const response = await updateCalendarViewPreferences(settings, draft);
|
||||
const next = preferenceDraft(response);
|
||||
setLoaded(next);
|
||||
setDraft(next);
|
||||
setTone("success");
|
||||
setMessage("Calendar preferences saved.");
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("govoplan:calendar-preferences-changed")
|
||||
);
|
||||
} catch (error) {
|
||||
setTone("warning");
|
||||
setMessage(errorText(error));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
const disabled = loading || saving;
|
||||
return (
|
||||
<div className="dashboard-grid settings-dashboard-grid calendar-settings-panel">
|
||||
<Card title="Calendar display">
|
||||
<div className="form-grid">
|
||||
<ToggleSwitch
|
||||
label="Dim weekends"
|
||||
help="Use a quieter background for Saturday and Sunday in week views."
|
||||
checked={draft.dim_weekends}
|
||||
disabled={disabled}
|
||||
onChange={(dim_weekends) =>
|
||||
setDraft((current) => ({ ...current, dim_weekends }))
|
||||
}
|
||||
/>
|
||||
<ToggleSwitch
|
||||
label="Dim hours outside the workday"
|
||||
checked={draft.dim_off_hours}
|
||||
disabled={disabled}
|
||||
onChange={(dim_off_hours) =>
|
||||
setDraft((current) => ({ ...current, dim_off_hours }))
|
||||
}
|
||||
/>
|
||||
<div className="calendar-dialog-grid-two">
|
||||
<FormField label="Workday starts">
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
max={23}
|
||||
value={draft.workday_start_hour}
|
||||
disabled={disabled}
|
||||
onChange={(event) =>
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
workday_start_hour: Number(event.target.value)
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Workday ends">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={24}
|
||||
value={draft.workday_end_hour}
|
||||
disabled={disabled}
|
||||
onChange={(event) =>
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
workday_end_hour: Number(event.target.value)
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card title="Continuous view">
|
||||
<div className="form-grid">
|
||||
<ToggleSwitch
|
||||
label="Virtualize distant weeks"
|
||||
help="Keep the continuous calendar responsive by rendering only nearby weeks."
|
||||
checked={draft.continuous_virtualization}
|
||||
disabled={disabled}
|
||||
onChange={(continuous_virtualization) =>
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
continuous_virtualization
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<FormField
|
||||
label="Overscan weeks"
|
||||
help="Additional weeks rendered above and below the viewport."
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min={2}
|
||||
max={20}
|
||||
value={draft.continuous_overscan_weeks}
|
||||
disabled={disabled || !draft.continuous_virtualization}
|
||||
onChange={(event) =>
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
continuous_overscan_weeks: Number(event.target.value)
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</FormField>
|
||||
<ToggleSwitch
|
||||
label="Alternate month backgrounds"
|
||||
checked={draft.alternate_continuous_months}
|
||||
disabled={disabled}
|
||||
onChange={(alternate_continuous_months) =>
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
alternate_continuous_months
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<div className="button-row compact-actions">
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
disabled={disabled || !dirty}
|
||||
onClick={() => void savePreferences()}
|
||||
>
|
||||
<Save size={16} /> {saving ? "Saving" : "Save preferences"}
|
||||
</Button>
|
||||
</div>
|
||||
{message && (
|
||||
<DismissibleAlert tone={tone} resetKey={message} floating>
|
||||
{message}
|
||||
</DismissibleAlert>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function preferenceDraft(
|
||||
preferences: CalendarViewPreferences
|
||||
): CalendarPreferenceDraft {
|
||||
return {
|
||||
dim_weekends: preferences.dim_weekends,
|
||||
dim_off_hours: preferences.dim_off_hours,
|
||||
workday_start_hour: preferences.workday_start_hour,
|
||||
workday_end_hour: preferences.workday_end_hour,
|
||||
continuous_virtualization: preferences.continuous_virtualization,
|
||||
continuous_overscan_weeks: preferences.continuous_overscan_weeks,
|
||||
alternate_continuous_months: preferences.alternate_continuous_months
|
||||
};
|
||||
}
|
||||
|
||||
function errorText(error: unknown): string {
|
||||
return error instanceof Error
|
||||
? error.message
|
||||
: "Calendar preferences could not be loaded.";
|
||||
}
|
||||
Reference in New Issue
Block a user