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 <noreply@anthropic.com>
This commit is contained in:
parent
a144945077
commit
5c8b3f895d
@ -3,6 +3,8 @@ services:
|
|||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
- TZ=Australia/Perth
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@ -17,6 +19,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
- TZ=Australia/Perth
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@ -159,7 +159,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD
|
|||||||
recurrence_rule: rule,
|
recurrence_rule: rule,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (event) {
|
if (event?.id) {
|
||||||
if (editScope) {
|
if (editScope) {
|
||||||
payload.edit_scope = editScope;
|
payload.edit_scope = editScope;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useState, FormEvent } from 'react';
|
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 { toast } from 'sonner';
|
||||||
import api, { getErrorMessage } from '@/lib/api';
|
import api, { getErrorMessage } from '@/lib/api';
|
||||||
import type { EventTemplate } from '@/types';
|
import type { EventTemplate, Location } from '@/types';
|
||||||
import { useCalendars } from '@/hooks/useCalendars';
|
import { useCalendars } from '@/hooks/useCalendars';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -18,6 +18,7 @@ import { Select } from '@/components/ui/select';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import LocationPicker from '@/components/ui/location-picker';
|
||||||
|
|
||||||
interface TemplateFormProps {
|
interface TemplateFormProps {
|
||||||
template: EventTemplate | null;
|
template: EventTemplate | null;
|
||||||
@ -29,12 +30,24 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
|||||||
const { data: calendars = [] } = useCalendars();
|
const { data: calendars = [] } = useCalendars();
|
||||||
const selectableCalendars = calendars.filter((c) => !c.is_system);
|
const selectableCalendars = calendars.filter((c) => !c.is_system);
|
||||||
|
|
||||||
|
const { data: locations = [] } = useQuery({
|
||||||
|
queryKey: ['locations'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await api.get<Location[]>('/locations');
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingLocation = locations.find((l) => l.id === template?.location_id);
|
||||||
|
const [locationSearch, setLocationSearch] = useState(existingLocation?.name || '');
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: template?.name || '',
|
name: template?.name || '',
|
||||||
title: template?.title || '',
|
title: template?.title || '',
|
||||||
description: template?.description || '',
|
description: template?.description || '',
|
||||||
duration_minutes: template?.duration_minutes?.toString() || '60',
|
duration_minutes: template?.duration_minutes?.toString() || '60',
|
||||||
calendar_id: template?.calendar_id?.toString() || '',
|
calendar_id: template?.calendar_id?.toString() || '',
|
||||||
|
location_id: template?.location_id?.toString() || '',
|
||||||
all_day: template?.all_day || false,
|
all_day: template?.all_day || false,
|
||||||
is_starred: template?.is_starred || false,
|
is_starred: template?.is_starred || false,
|
||||||
});
|
});
|
||||||
@ -47,6 +60,7 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
|||||||
description: data.description || null,
|
description: data.description || null,
|
||||||
duration_minutes: parseInt(data.duration_minutes) || 60,
|
duration_minutes: parseInt(data.duration_minutes) || 60,
|
||||||
calendar_id: data.calendar_id ? parseInt(data.calendar_id) : null,
|
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,
|
all_day: data.all_day,
|
||||||
is_starred: data.is_starred,
|
is_starred: data.is_starred,
|
||||||
};
|
};
|
||||||
@ -142,6 +156,37 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="tmpl-location">Location</Label>
|
||||||
|
<LocationPicker
|
||||||
|
id="tmpl-location"
|
||||||
|
value={locationSearch}
|
||||||
|
onChange={(val) => {
|
||||||
|
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..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user