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:
parent
89b4bd4870
commit
42e0fff40c
@ -22,53 +22,28 @@ interface ReminderFormProps {
|
|||||||
onClose: () => void;
|
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) {
|
export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const parsed = parseRemindAt(reminder?.remind_at);
|
const [formData, setFormData] = useState({
|
||||||
const [title, setTitle] = useState(reminder?.title || '');
|
title: reminder?.title || '',
|
||||||
const [description, setDescription] = useState(reminder?.description || '');
|
description: reminder?.description || '',
|
||||||
const [date, setDate] = useState(parsed.date);
|
remind_at: reminder?.remind_at ? reminder.remind_at.slice(0, 16) : '',
|
||||||
const [hour, setHour] = useState(parsed.hour);
|
recurrence_rule: reminder?.recurrence_rule || '',
|
||||||
const [minute, setMinute] = useState(parsed.minute);
|
});
|
||||||
const [period, setPeriod] = useState<'AM' | 'PM'>(parsed.period);
|
|
||||||
const [recurrenceRule, setRecurrenceRule] = useState(reminder?.recurrence_rule || '');
|
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async (data: typeof formData) => {
|
||||||
const data = {
|
const payload = {
|
||||||
title,
|
...data,
|
||||||
description: description || null,
|
description: data.description || null,
|
||||||
remind_at: buildRemindAt(date, hour, minute, period) || null,
|
remind_at: data.remind_at || null,
|
||||||
recurrence_rule: recurrenceRule || null,
|
recurrence_rule: data.recurrence_rule || null,
|
||||||
};
|
};
|
||||||
if (reminder) {
|
if (reminder) {
|
||||||
const response = await api.put(`/reminders/${reminder.id}`, data);
|
const response = await api.put(`/reminders/${reminder.id}`, payload);
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
const response = await api.post('/reminders', data);
|
const response = await api.post('/reminders', payload);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -86,7 +61,7 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
|
|||||||
|
|
||||||
const handleSubmit = (e: FormEvent) => {
|
const handleSubmit = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
mutation.mutate();
|
mutation.mutate(formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -102,8 +77,8 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
|
|||||||
<Label htmlFor="title" required>Title</Label>
|
<Label htmlFor="title" required>Title</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
value={title}
|
value={formData.title}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -112,67 +87,37 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
|
|||||||
<Label htmlFor="description">Description</Label>
|
<Label htmlFor="description">Description</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
value={description}
|
value={formData.description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
className="min-h-[80px] flex-1"
|
className="min-h-[80px] flex-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<Label htmlFor="remind_date">Remind At</Label>
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<Label htmlFor="remind_at">Remind At</Label>
|
||||||
<Input
|
<Input
|
||||||
id="remind_date"
|
id="remind_at"
|
||||||
type="date"
|
type="datetime-local"
|
||||||
value={date}
|
value={formData.remind_at}
|
||||||
onChange={(e) => setDate(e.target.value)}
|
onChange={(e) => setFormData({ ...formData, remind_at: e.target.value })}
|
||||||
className="flex-1"
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="recurrence">Recurrence</Label>
|
||||||
<Select
|
<Select
|
||||||
value={hour}
|
id="recurrence"
|
||||||
onChange={(e) => setHour(e.target.value)}
|
value={formData.recurrence_rule}
|
||||||
className="w-[4.5rem]"
|
onChange={(e) => setFormData({ ...formData, recurrence_rule: e.target.value })}
|
||||||
>
|
>
|
||||||
{Array.from({ length: 12 }, (_, i) => i + 1).map((h) => (
|
<option value="">None</option>
|
||||||
<option key={h} value={String(h)}>{h}</option>
|
<option value="daily">Daily</option>
|
||||||
))}
|
<option value="weekly">Weekly</option>
|
||||||
</Select>
|
<option value="monthly">Monthly</option>
|
||||||
<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>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="recurrence">Recurrence</Label>
|
|
||||||
<Select
|
|
||||||
id="recurrence"
|
|
||||||
value={recurrenceRule}
|
|
||||||
onChange={(e) => setRecurrenceRule(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">None</option>
|
|
||||||
<option value="daily">Daily</option>
|
|
||||||
<option value="weekly">Weekly</option>
|
|
||||||
<option value="monthly">Monthly</option>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SheetFooter>
|
<SheetFooter>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user