Revert ReminderForm to single datetime-local input

Matches EventForm pattern used across UMBRA. Remind At and Recurrence
now sit side-by-side in a 2-column grid. Empty optional fields send
null instead of empty string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-02-24 01:11:31 +08:00
parent 89b4bd4870
commit 42e0fff40c

View File

@ -22,53 +22,28 @@ interface ReminderFormProps {
onClose: () => void;
}
function parseRemindAt(remindAt?: string) {
if (!remindAt) return { date: '', hour: '12', minute: '00', period: 'PM' as const };
const d = new Date(remindAt);
const date = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
let hours = d.getHours();
const period = hours >= 12 ? 'PM' as const : 'AM' as const;
hours = hours % 12 || 12;
return {
date,
hour: String(hours),
minute: String(d.getMinutes()).padStart(2, '0'),
period,
};
}
function buildRemindAt(date: string, hour: string, minute: string, period: 'AM' | 'PM'): string {
if (!date) return '';
let h = parseInt(hour, 10);
if (period === 'AM' && h === 12) h = 0;
else if (period === 'PM' && h !== 12) h += 12;
return `${date}T${String(h).padStart(2, '0')}:${minute}`;
}
export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
const queryClient = useQueryClient();
const parsed = parseRemindAt(reminder?.remind_at);
const [title, setTitle] = useState(reminder?.title || '');
const [description, setDescription] = useState(reminder?.description || '');
const [date, setDate] = useState(parsed.date);
const [hour, setHour] = useState(parsed.hour);
const [minute, setMinute] = useState(parsed.minute);
const [period, setPeriod] = useState<'AM' | 'PM'>(parsed.period);
const [recurrenceRule, setRecurrenceRule] = useState(reminder?.recurrence_rule || '');
const [formData, setFormData] = useState({
title: reminder?.title || '',
description: reminder?.description || '',
remind_at: reminder?.remind_at ? reminder.remind_at.slice(0, 16) : '',
recurrence_rule: reminder?.recurrence_rule || '',
});
const mutation = useMutation({
mutationFn: async () => {
const data = {
title,
description: description || null,
remind_at: buildRemindAt(date, hour, minute, period) || null,
recurrence_rule: recurrenceRule || null,
mutationFn: async (data: typeof formData) => {
const payload = {
...data,
description: data.description || null,
remind_at: data.remind_at || null,
recurrence_rule: data.recurrence_rule || null,
};
if (reminder) {
const response = await api.put(`/reminders/${reminder.id}`, data);
const response = await api.put(`/reminders/${reminder.id}`, payload);
return response.data;
} else {
const response = await api.post('/reminders', data);
const response = await api.post('/reminders', payload);
return response.data;
}
},
@ -86,7 +61,7 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
mutation.mutate();
mutation.mutate(formData);
};
return (
@ -102,8 +77,8 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
<Label htmlFor="title" required>Title</Label>
<Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
required
/>
</div>
@ -112,60 +87,29 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
className="min-h-[80px] flex-1"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="remind_date">Remind At</Label>
<div className="flex items-center gap-2">
<Label htmlFor="remind_at">Remind At</Label>
<Input
id="remind_date"
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
className="flex-1"
id="remind_at"
type="datetime-local"
value={formData.remind_at}
onChange={(e) => setFormData({ ...formData, remind_at: e.target.value })}
/>
<Select
value={hour}
onChange={(e) => setHour(e.target.value)}
className="w-[4.5rem]"
>
{Array.from({ length: 12 }, (_, i) => i + 1).map((h) => (
<option key={h} value={String(h)}>{h}</option>
))}
</Select>
<span className="text-muted-foreground">:</span>
<Select
value={minute}
onChange={(e) => setMinute(e.target.value)}
className="w-[4.5rem]"
>
{Array.from({ length: 12 }, (_, i) => i * 5).map((m) => (
<option key={m} value={String(m).padStart(2, '0')}>
{String(m).padStart(2, '0')}
</option>
))}
</Select>
<Select
value={period}
onChange={(e) => setPeriod(e.target.value as 'AM' | 'PM')}
className="w-[4.5rem]"
>
<option value="AM">AM</option>
<option value="PM">PM</option>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="recurrence">Recurrence</Label>
<Select
id="recurrence"
value={recurrenceRule}
onChange={(e) => setRecurrenceRule(e.target.value)}
value={formData.recurrence_rule}
onChange={(e) => setFormData({ ...formData, recurrence_rule: e.target.value })}
>
<option value="">None</option>
<option value="daily">Daily</option>
@ -174,6 +118,7 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
</Select>
</div>
</div>
</div>
<SheetFooter>
<Button type="button" variant="outline" onClick={onClose}>