Files
ersatztv/web/src/schedules/ScheduleForm.tsx
T

124 lines
4.4 KiB
TypeScript

import { useState } from 'react';
import { Button, Checkbox, Dialog, Input, Select } from '../components';
import {
createSchedule,
type CreateScheduleRequest,
messageFromScheduleError,
type ProgramSchedule,
updateSchedule
} from '../api';
export interface ScheduleFormProps {
mode: 'create' | 'edit';
schedule: ProgramSchedule | null;
onSaved: (schedule: ProgramSchedule) => void;
onClose: () => void;
}
// Create / edit the six schedule properties. treatCollectionsAsShows is disabled (and the server
// coerces it false) unless keepMultiPartEpisodesTogether is on. Unique-name 422 is surfaced inline.
export function ScheduleForm({ mode, schedule, onSaved, onClose }: ScheduleFormProps) {
const [name, setName] = useState(schedule?.name ?? '');
const [keepMultiPart, setKeepMultiPart] = useState(schedule?.keepMultiPartEpisodesTogether ?? false);
const [treatAsShows, setTreatAsShows] = useState(schedule?.treatCollectionsAsShows ?? false);
const [shuffle, setShuffle] = useState(schedule?.shuffleScheduleItems ?? false);
const [randomStart, setRandomStart] = useState(schedule?.randomStartPoint ?? false);
const [fixedBehavior, setFixedBehavior] = useState<'Strict' | 'Flexible'>(schedule?.fixedStartTimeBehavior ?? 'Strict');
const [padToNearest, setPadToNearest] = useState<string>(
schedule?.padToNearestMinute != null ? String(schedule.padToNearestMinute) : ''
);
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const submit = () => {
const trimmed = name.trim();
if (!trimmed) {
setError('Name is required');
return;
}
if (trimmed.length > 50) {
setError('Name must be 50 characters or fewer');
return;
}
const body: CreateScheduleRequest = {
name: trimmed,
keepMultiPartEpisodesTogether: keepMultiPart,
treatCollectionsAsShows: keepMultiPart ? treatAsShows : false,
shuffleScheduleItems: shuffle,
randomStartPoint: randomStart,
fixedStartTimeBehavior: fixedBehavior,
padToNearestMinute: padToNearest === '' ? null : Number(padToNearest)
};
setSaving(true);
setError(null);
const request = mode === 'create' ? createSchedule(body) : updateSchedule(schedule!.id, body);
request
.then((saved) => {
setSaving(false);
onSaved(saved);
})
.catch((err: unknown) => {
setSaving(false);
setError(messageFromScheduleError(err, 'Unable to save schedule'));
});
};
return (
<Dialog
open
onClose={onClose}
title={mode === 'create' ? 'Add schedule' : 'Edit schedule'}
footer={
<>
<Button variant="ghost" onClick={onClose} disabled={saving}>Cancel</Button>
<Button onClick={submit} disabled={saving}>{saving ? 'Saving…' : 'Save'}</Button>
</>
}
>
<div className="ctv-schedule-form-stack">
<Input label="Name" value={name} error={error} onChange={(e) => setName(e.target.value)} />
<Checkbox
label="Keep multi-part episodes together"
checked={keepMultiPart}
onChange={(v) => {
setKeepMultiPart(v);
if (!v) {
setTreatAsShows(false);
}
}}
/>
<Checkbox
label="Treat collections as shows"
checked={treatAsShows}
disabled={!keepMultiPart}
onChange={setTreatAsShows}
/>
<Checkbox label="Shuffle schedule items" checked={shuffle} onChange={setShuffle} />
<p className="ctv-field-help">Shuffling disables fixed start times and flood mode.</p>
<Checkbox label="Random start point" checked={randomStart} onChange={setRandomStart} />
<Select
label="Fixed start time behavior"
value={fixedBehavior}
onChange={(e) => setFixedBehavior(e.target.value as 'Strict' | 'Flexible')}
options={['Strict', 'Flexible']}
/>
<Select
label="Pad to clock boundary"
value={padToNearest}
onChange={(e) => setPadToNearest(e.target.value)}
options={[
{ label: '(none)', value: '' },
{ label: '5', value: '5' },
{ label: '10', value: '10' },
{ label: '15', value: '15' },
{ label: '30', value: '30' },
{ label: '60', value: '60' }
]}
/>
</div>
</Dialog>
);
}