From 8014dcd602e69bf0f30345d7510032a732ccaef5 Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 15:30:03 +0000 Subject: [PATCH] feat: add calendar plan create and edit ui --- src/pages/CalendarPage.tsx | 275 ++++++++++++++++++++++++++++++++++++- 1 file changed, 272 insertions(+), 3 deletions(-) diff --git a/src/pages/CalendarPage.tsx b/src/pages/CalendarPage.tsx index 6606b74..733bd19 100644 --- a/src/pages/CalendarPage.tsx +++ b/src/pages/CalendarPage.tsx @@ -51,6 +51,9 @@ interface PlanListResponse { plans: SchedulePlanResponse[] } +type Weekday = 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' +type MonthName = 'jan' | 'feb' | 'mar' | 'apr' | 'may' | 'jun' | 'jul' | 'aug' | 'sep' | 'oct' | 'nov' | 'dec' + interface WorkloadWarning { period: string slot_type: string @@ -77,6 +80,31 @@ const EVENT_TYPES: { value: EventType; label: string }[] = [ { value: 'system_event', label: 'System Event' }, ] +const WEEKDAYS: { value: Weekday; label: string }[] = [ + { value: 'sun', label: 'Sunday' }, + { value: 'mon', label: 'Monday' }, + { value: 'tue', label: 'Tuesday' }, + { value: 'wed', label: 'Wednesday' }, + { value: 'thu', label: 'Thursday' }, + { value: 'fri', label: 'Friday' }, + { value: 'sat', label: 'Saturday' }, +] + +const MONTHS: { value: MonthName; label: string }[] = [ + { value: 'jan', label: 'January' }, + { value: 'feb', label: 'February' }, + { value: 'mar', label: 'March' }, + { value: 'apr', label: 'April' }, + { value: 'may', label: 'May' }, + { value: 'jun', label: 'June' }, + { value: 'jul', label: 'July' }, + { value: 'aug', label: 'August' }, + { value: 'sep', label: 'September' }, + { value: 'oct', label: 'October' }, + { value: 'nov', label: 'November' }, + { value: 'dec', label: 'December' }, +] + const SLOT_TYPE_ICONS: Record = { work: '💼', on_call: '📞', @@ -116,6 +144,20 @@ export default function CalendarPage() { }) const [slotSaving, setSlotSaving] = useState(false) const [warnings, setWarnings] = useState([]) + const [showPlanModal, setShowPlanModal] = useState(false) + const [editingPlan, setEditingPlan] = useState(null) + const [planSaving, setPlanSaving] = useState(false) + const [planForm, setPlanForm] = useState({ + slot_type: 'work' as SlotType, + estimated_duration: 25, + at_time: '09:00', + on_day: '' as Weekday | '', + on_week: '' as string, + on_month: '' as MonthName | '', + event_type: '' as string, + event_data_code: '', + event_data_event: '', + }) const fetchSlots = async (date: string) => { setLoading(true) @@ -211,6 +253,84 @@ export default function CalendarPage() { return null } + const buildPlanEventData = () => { + if (!planForm.event_type) return null + if (planForm.event_type === 'job') { + return planForm.event_data_code ? { type: 'Task', code: planForm.event_data_code } : null + } + if (planForm.event_type === 'system_event') { + return planForm.event_data_event ? { event: planForm.event_data_event } : null + } + return null + } + + const openCreatePlanModal = () => { + setEditingPlan(null) + setPlanForm({ + slot_type: 'work', + estimated_duration: 25, + at_time: '09:00', + on_day: '', + on_week: '', + on_month: '', + event_type: '', + event_data_code: '', + event_data_event: '', + }) + setError('') + setShowPlanModal(true) + } + + const openEditPlanModal = (plan: SchedulePlanResponse) => { + setEditingPlan(plan) + setPlanForm({ + slot_type: plan.slot_type, + estimated_duration: plan.estimated_duration, + at_time: plan.at_time.slice(0, 5), + on_day: (plan.on_day?.toLowerCase() as Weekday | undefined) || '', + on_week: plan.on_week ? String(plan.on_week) : '', + on_month: (plan.on_month?.toLowerCase() as MonthName | undefined) || '', + event_type: plan.event_type || '', + event_data_code: plan.event_data?.code || '', + event_data_event: plan.event_data?.event || '', + }) + setError('') + setShowPlanModal(true) + } + + const handleSavePlan = async () => { + setPlanSaving(true) + setError('') + try { + const payload: any = { + slot_type: planForm.slot_type, + estimated_duration: planForm.estimated_duration, + at_time: planForm.at_time, + on_day: planForm.on_day || null, + on_week: planForm.on_week ? Number(planForm.on_week) : null, + on_month: planForm.on_month || null, + event_type: planForm.event_type || null, + event_data: buildPlanEventData(), + } + + if (editingPlan) { + await api.patch(`/calendar/plans/${editingPlan.id}`, payload) + } else { + await api.post('/calendar/plans', payload) + } + + setShowPlanModal(false) + fetchPlans() + if (activeTab === 'daily') { + fetchSlots(selectedDate) + } + } catch (err: any) { + setError(err.response?.data?.detail || 'Save plan failed') + } finally { + setPlanSaving(false) + } + } + const handleSaveSlot = async () => { setSlotSaving(true) setError('') @@ -429,7 +549,12 @@ export default function CalendarPage() { )} {activeTab === 'plans' && ( -
+ <> +
+ +
+ +
{plans.length === 0 ? (
No schedule plans configured. @@ -449,7 +574,14 @@ export default function CalendarPage() { {plan.event_type &&
📌 {plan.event_type.replace('_', ' ')}
}
{plan.is_active && ( -
+
+
+
+ )} {/* Create/Edit Slot Modal */} @@ -577,6 +710,142 @@ export default function CalendarPage() {
)} + + {showPlanModal && ( +
setShowPlanModal(false)}> +
e.stopPropagation()}> +

{editingPlan ? 'Edit Plan' : 'New Plan'}

+ + + + + + + + + + + + + + + + {planForm.event_type === 'job' && ( + + )} + + {planForm.event_type === 'system_event' && ( + + )} + +
+ + +
+
+
+ )} ) }