From 5c8b3f895dde571baee293df783eaff475098586 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Sun, 22 Feb 2026 19:11:48 +0800 Subject: [PATCH] Fix template event creation, add location to templates, fix timestamps - EventForm: check event?.id instead of event to decide PUT vs POST, fixes "unable to parse string as integer" when creating from template - TemplateForm: add LocationPicker for setting location on templates - docker-compose: set TZ=Australia/Perth on backend and db containers so datetime.now() and PostgreSQL NOW() return local time Co-Authored-By: Claude Opus 4.6 --- docker-compose.yaml | 4 ++ .../src/components/calendar/EventForm.tsx | 2 +- .../src/components/calendar/TemplateForm.tsx | 49 ++++++++++++++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 23491da..e1aab25 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,6 +3,8 @@ services: image: postgres:16-alpine restart: unless-stopped env_file: .env + environment: + - TZ=Australia/Perth volumes: - postgres_data:/var/lib/postgresql/data healthcheck: @@ -17,6 +19,8 @@ services: ports: - "8000:8000" env_file: .env + environment: + - TZ=Australia/Perth depends_on: db: condition: service_healthy diff --git a/frontend/src/components/calendar/EventForm.tsx b/frontend/src/components/calendar/EventForm.tsx index c12d9d4..152cc1f 100644 --- a/frontend/src/components/calendar/EventForm.tsx +++ b/frontend/src/components/calendar/EventForm.tsx @@ -159,7 +159,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD recurrence_rule: rule, }; - if (event) { + if (event?.id) { if (editScope) { payload.edit_scope = editScope; } diff --git a/frontend/src/components/calendar/TemplateForm.tsx b/frontend/src/components/calendar/TemplateForm.tsx index b016efd..39a359f 100644 --- a/frontend/src/components/calendar/TemplateForm.tsx +++ b/frontend/src/components/calendar/TemplateForm.tsx @@ -1,8 +1,8 @@ import { useState, FormEvent } from 'react'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import api, { getErrorMessage } from '@/lib/api'; -import type { EventTemplate } from '@/types'; +import type { EventTemplate, Location } from '@/types'; import { useCalendars } from '@/hooks/useCalendars'; import { Dialog, @@ -18,6 +18,7 @@ import { Select } from '@/components/ui/select'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; +import LocationPicker from '@/components/ui/location-picker'; interface TemplateFormProps { template: EventTemplate | null; @@ -29,12 +30,24 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) { const { data: calendars = [] } = useCalendars(); const selectableCalendars = calendars.filter((c) => !c.is_system); + const { data: locations = [] } = useQuery({ + queryKey: ['locations'], + queryFn: async () => { + const { data } = await api.get('/locations'); + return data; + }, + }); + + const existingLocation = locations.find((l) => l.id === template?.location_id); + const [locationSearch, setLocationSearch] = useState(existingLocation?.name || ''); + const [formData, setFormData] = useState({ name: template?.name || '', title: template?.title || '', description: template?.description || '', duration_minutes: template?.duration_minutes?.toString() || '60', calendar_id: template?.calendar_id?.toString() || '', + location_id: template?.location_id?.toString() || '', all_day: template?.all_day || false, is_starred: template?.is_starred || false, }); @@ -47,6 +60,7 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) { description: data.description || null, duration_minutes: parseInt(data.duration_minutes) || 60, calendar_id: data.calendar_id ? parseInt(data.calendar_id) : null, + location_id: data.location_id ? parseInt(data.location_id) : null, all_day: data.all_day, is_starred: data.is_starred, }; @@ -142,6 +156,37 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) { +
+ + { + setLocationSearch(val); + if (!val) setFormData({ ...formData, location_id: '' }); + }} + onSelect={async (result) => { + if (result.source === 'local' && result.location_id) { + setFormData({ ...formData, location_id: result.location_id.toString() }); + } else if (result.source === 'nominatim') { + try { + const { data: newLoc } = await api.post('/locations', { + name: result.name, + address: result.address, + category: 'other', + }); + queryClient.invalidateQueries({ queryKey: ['locations'] }); + setFormData({ ...formData, location_id: newLoc.id.toString() }); + toast.success(`Location "${result.name}" created`); + } catch { + toast.error('Failed to create location'); + } + } + }} + placeholder="Search for a location..." + /> +
+