176 lines
6.2 KiB
TypeScript
176 lines
6.2 KiB
TypeScript
import { useCallback, useState } from "react";
|
|
import { Button, Field, Input, MessageBar, MessageBarBody, Select, Spinner, Text } from "@fluentui/react-components";
|
|
import { PlayRegular } from "@fluentui/react-icons";
|
|
import { useCommonSectionStyles } from "../../theme/commonStyles";
|
|
import { useSchedule } from "../../hooks/useBlocklist";
|
|
import { useBlocklistStyles } from "./blocklistStyles";
|
|
import type { ScheduleConfig, ScheduleFrequency } from "../../types/blocklist";
|
|
|
|
const FREQUENCY_LABELS: Record<ScheduleFrequency, string> = {
|
|
hourly: "Every N hours",
|
|
daily: "Daily",
|
|
weekly: "Weekly",
|
|
};
|
|
|
|
const DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
|
|
|
interface ScheduleSectionProps {
|
|
onRunImport: () => void;
|
|
runImportRunning: boolean;
|
|
}
|
|
|
|
export function BlocklistScheduleSection({ onRunImport, runImportRunning }: ScheduleSectionProps): React.JSX.Element {
|
|
const styles = useBlocklistStyles();
|
|
const sectionStyles = useCommonSectionStyles();
|
|
const { info, loading, error, saveSchedule } = useSchedule();
|
|
const [saving, setSaving] = useState(false);
|
|
const [saveMsg, setSaveMsg] = useState<string | null>(null);
|
|
|
|
const config = info?.config ?? {
|
|
frequency: "daily" as ScheduleFrequency,
|
|
interval_hours: 24,
|
|
hour: 3,
|
|
minute: 0,
|
|
day_of_week: 0,
|
|
};
|
|
|
|
const [draft, setDraft] = useState<ScheduleConfig>(config);
|
|
|
|
const handleSave = useCallback((): void => {
|
|
setSaving(true);
|
|
saveSchedule(draft)
|
|
.then(() => {
|
|
setSaveMsg("Schedule saved.");
|
|
setSaving(false);
|
|
setTimeout(() => { setSaveMsg(null); }, 3000);
|
|
})
|
|
.catch((err: unknown) => {
|
|
setSaveMsg(err instanceof Error ? err.message : "Failed to save schedule");
|
|
setSaving(false);
|
|
});
|
|
}, [draft, saveSchedule]);
|
|
|
|
return (
|
|
<div className={sectionStyles.section}>
|
|
<div className={sectionStyles.sectionHeader}>
|
|
<Text size={500} weight="semibold">
|
|
Import Schedule
|
|
</Text>
|
|
<Button icon={<PlayRegular />} appearance="secondary" onClick={onRunImport} disabled={runImportRunning}>
|
|
{runImportRunning ? <Spinner size="tiny" /> : "Run Now"}
|
|
</Button>
|
|
</div>
|
|
|
|
{error && (
|
|
<MessageBar intent="error">
|
|
<MessageBarBody>{error}</MessageBarBody>
|
|
</MessageBar>
|
|
)}
|
|
{saveMsg && (
|
|
<MessageBar intent={saveMsg === "Schedule saved." ? "success" : "error"}>
|
|
<MessageBarBody>{saveMsg}</MessageBarBody>
|
|
</MessageBar>
|
|
)}
|
|
|
|
{loading ? (
|
|
<div className={styles.centred}>
|
|
<Spinner label="Loading schedule…" />
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className={styles.scheduleForm}>
|
|
<Field label="Frequency" className={styles.scheduleField}>
|
|
<Select
|
|
value={draft.frequency}
|
|
onChange={(_ev, d) => { setDraft((p) => ({ ...p, frequency: d.value as ScheduleFrequency })); }}
|
|
>
|
|
{(["hourly", "daily", "weekly"] as ScheduleFrequency[]).map((f) => (
|
|
<option key={f} value={f}>
|
|
{FREQUENCY_LABELS[f]}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</Field>
|
|
|
|
{draft.frequency === "hourly" && (
|
|
<Field label="Every (hours)" className={styles.scheduleField}>
|
|
<Input
|
|
type="number"
|
|
value={String(draft.interval_hours)}
|
|
onChange={(_ev, d) => { setDraft((p) => ({ ...p, interval_hours: Math.max(1, parseInt(d.value, 10) || 1) })); }}
|
|
min={1}
|
|
max={168}
|
|
/>
|
|
</Field>
|
|
)}
|
|
|
|
{draft.frequency !== "hourly" && (
|
|
<>
|
|
{draft.frequency === "weekly" && (
|
|
<Field label="Day of week" className={styles.scheduleField}>
|
|
<Select
|
|
value={String(draft.day_of_week)}
|
|
onChange={(_ev, d) => { setDraft((p) => ({ ...p, day_of_week: parseInt(d.value, 10) })); }}
|
|
>
|
|
{DAYS.map((day, i) => (
|
|
<option key={day} value={i}>
|
|
{day}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</Field>
|
|
)}
|
|
|
|
<Field label="Hour (UTC)" className={styles.scheduleField}>
|
|
<Select
|
|
value={String(draft.hour)}
|
|
onChange={(_ev, d) => { setDraft((p) => ({ ...p, hour: parseInt(d.value, 10) })); }}
|
|
>
|
|
{Array.from({ length: 24 }, (_, i) => (
|
|
<option key={i} value={i}>
|
|
{String(i).padStart(2, "0")}:00
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</Field>
|
|
|
|
<Field label="Minute" className={styles.scheduleField}>
|
|
<Select
|
|
value={String(draft.minute)}
|
|
onChange={(_ev, d) => { setDraft((p) => ({ ...p, minute: parseInt(d.value, 10) })); }}
|
|
>
|
|
{[0, 15, 30, 45].map((m) => (
|
|
<option key={m} value={m}>
|
|
{String(m).padStart(2, "0")}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</Field>
|
|
</>
|
|
)}
|
|
|
|
<Button appearance="primary" onClick={handleSave} disabled={saving} style={{ alignSelf: "flex-end" }}>
|
|
{saving ? <Spinner size="tiny" /> : "Save Schedule"}
|
|
</Button>
|
|
</div>
|
|
|
|
<div className={styles.metaRow}>
|
|
<div className={styles.metaItem}>
|
|
<Text size={200} weight="semibold">
|
|
Last run
|
|
</Text>
|
|
<Text size={200}>{info?.last_run_at ?? "Never"}</Text>
|
|
</div>
|
|
<div className={styles.metaItem}>
|
|
<Text size={200} weight="semibold">
|
|
Next run
|
|
</Text>
|
|
<Text size={200}>{info?.next_run_at ?? "Not scheduled"}</Text>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|