Convert TemplateForm from Dialog to Sheet side panel
Matches the EventForm UI pattern for consistency — same slide-in panel, same layout structure with scrollable content area and pinned footer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4a8c44ab80
commit
b21343601b
@ -5,13 +5,13 @@ import api, { getErrorMessage } from '@/lib/api';
|
||||
import type { EventTemplate, Location } from '@/types';
|
||||
import { useCalendars } from '@/hooks/useCalendars';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
} from '@/components/ui/dialog';
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetFooter,
|
||||
SheetClose,
|
||||
} from '@/components/ui/sheet';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select } from '@/components/ui/select';
|
||||
@ -87,119 +87,123 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogClose onClick={onClose} />
|
||||
<DialogHeader>
|
||||
<DialogTitle>{template ? 'Edit Template' : 'New Template'}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-name" required>Template Name</Label>
|
||||
<Input
|
||||
id="tmpl-name"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="e.g., Weekly standup"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-title" required>Event Title</Label>
|
||||
<Input
|
||||
id="tmpl-title"
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||
placeholder="Title for created events"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-desc">Description</Label>
|
||||
<Textarea
|
||||
id="tmpl-desc"
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
className="min-h-[60px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-calendar">Calendar</Label>
|
||||
<Select
|
||||
id="tmpl-calendar"
|
||||
value={formData.calendar_id}
|
||||
onChange={(e) => setFormData({ ...formData, calendar_id: e.target.value })}
|
||||
>
|
||||
<option value="">Default</option>
|
||||
{selectableCalendars.map((cal) => (
|
||||
<option key={cal.id} value={cal.id}>{cal.name}</option>
|
||||
))}
|
||||
</Select>
|
||||
</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-2">
|
||||
<Checkbox
|
||||
id="tmpl-allday"
|
||||
checked={formData.all_day}
|
||||
onChange={(e) => setFormData({ ...formData, all_day: (e.target as HTMLInputElement).checked })}
|
||||
<Sheet open={true} onOpenChange={onClose}>
|
||||
<SheetContent>
|
||||
<SheetClose onClick={onClose} />
|
||||
<SheetHeader>
|
||||
<SheetTitle>{template ? 'Edit Template' : 'New Template'}</SheetTitle>
|
||||
</SheetHeader>
|
||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
||||
<div className="px-6 py-5 space-y-4 flex-1">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-name" required>Template Name</Label>
|
||||
<Input
|
||||
id="tmpl-name"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="e.g., Weekly standup"
|
||||
required
|
||||
/>
|
||||
<Label htmlFor="tmpl-allday">All day</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="tmpl-starred"
|
||||
checked={formData.is_starred}
|
||||
onChange={(e) => setFormData({ ...formData, is_starred: (e.target as HTMLInputElement).checked })}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-title" required>Event Title</Label>
|
||||
<Input
|
||||
id="tmpl-title"
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||
placeholder="Title for created events"
|
||||
required
|
||||
/>
|
||||
<Label htmlFor="tmpl-starred">Starred</Label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-desc">Description</Label>
|
||||
<Textarea
|
||||
id="tmpl-desc"
|
||||
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="tmpl-calendar">Calendar</Label>
|
||||
<Select
|
||||
id="tmpl-calendar"
|
||||
value={formData.calendar_id}
|
||||
onChange={(e) => setFormData({ ...formData, calendar_id: e.target.value })}
|
||||
>
|
||||
<option value="">Default</option>
|
||||
{selectableCalendars.map((cal) => (
|
||||
<option key={cal.id} value={cal.id}>{cal.name}</option>
|
||||
))}
|
||||
</Select>
|
||||
</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>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="tmpl-allday"
|
||||
checked={formData.all_day}
|
||||
onChange={(e) => setFormData({ ...formData, all_day: (e.target as HTMLInputElement).checked })}
|
||||
/>
|
||||
<Label htmlFor="tmpl-allday">All day</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="tmpl-starred"
|
||||
checked={formData.is_starred}
|
||||
onChange={(e) => setFormData({ ...formData, is_starred: (e.target as HTMLInputElement).checked })}
|
||||
/>
|
||||
<Label htmlFor="tmpl-starred">Starred</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<SheetFooter>
|
||||
<Button type="button" variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? 'Saving...' : template ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</SheetFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user