Remove duration_minutes from event templates, auto-prefill event times
- Drop duration_minutes column from event_templates (model, schema, migration) - Remove duration field from TemplateForm UI and TypeScript types - EventForm now defaults start to current date/time and end to +1 hour when no initial values are provided (new events and template-based events) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
87708ae195
commit
b1075d6ad4
27
backend/alembic/versions/013_drop_template_duration.py
Normal file
27
backend/alembic/versions/013_drop_template_duration.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""drop duration_minutes from event_templates
|
||||
|
||||
Revision ID: 013
|
||||
Revises: 012
|
||||
Create Date: 2026-02-23
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "013"
|
||||
down_revision = "012"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.drop_column("event_templates", "duration_minutes")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.add_column(
|
||||
"event_templates",
|
||||
sa.Column("duration_minutes", sa.Integer(), server_default="60", nullable=False),
|
||||
)
|
||||
@ -1,4 +1,4 @@
|
||||
from sqlalchemy import String, Text, Integer, Boolean, ForeignKey
|
||||
from sqlalchemy import String, Text, Boolean, ForeignKey, Integer
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
@ -12,7 +12,6 @@ class EventTemplate(Base):
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
duration_minutes: Mapped[int] = mapped_column(Integer, default=60)
|
||||
calendar_id: Mapped[Optional[int]] = mapped_column(
|
||||
Integer, ForeignKey("calendars.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
@ -7,7 +7,6 @@ class EventTemplateCreate(BaseModel):
|
||||
name: str
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
duration_minutes: int = 60
|
||||
calendar_id: Optional[int] = None
|
||||
recurrence_rule: Optional[str] = None
|
||||
all_day: bool = False
|
||||
@ -19,7 +18,6 @@ class EventTemplateUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
duration_minutes: Optional[int] = None
|
||||
calendar_id: Optional[int] = None
|
||||
recurrence_rule: Optional[str] = None
|
||||
all_day: Optional[bool] = None
|
||||
@ -32,7 +30,6 @@ class EventTemplateResponse(BaseModel):
|
||||
name: str
|
||||
title: str
|
||||
description: Optional[str]
|
||||
duration_minutes: int
|
||||
calendar_id: Optional[int]
|
||||
recurrence_rule: Optional[str]
|
||||
all_day: bool
|
||||
|
||||
@ -75,12 +75,29 @@ function parseRecurrenceRule(raw?: string): RecurrenceRule | null {
|
||||
}
|
||||
}
|
||||
|
||||
function nowLocal(): string {
|
||||
const now = new Date();
|
||||
const pad = (n: number) => n.toString().padStart(2, '0');
|
||||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
||||
}
|
||||
|
||||
function plusOneHour(dt: string): string {
|
||||
const d = new Date(dt);
|
||||
d.setHours(d.getHours() + 1);
|
||||
const pad = (n: number) => n.toString().padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
export default function EventForm({ event, initialStart, initialEnd, initialAllDay, editScope, onClose }: EventFormProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: calendars = [] } = useCalendars();
|
||||
const isAllDay = event?.all_day ?? initialAllDay ?? false;
|
||||
const rawStart = event?.start_datetime || initialStart || '';
|
||||
const rawEnd = event?.end_datetime || initialEnd || '';
|
||||
|
||||
// Default to current time / +1 hour when creating a new event with no selection
|
||||
const defaultStart = nowLocal();
|
||||
const defaultEnd = plusOneHour(defaultStart);
|
||||
const rawStart = event?.start_datetime || initialStart || defaultStart;
|
||||
const rawEnd = event?.end_datetime || initialEnd || defaultEnd;
|
||||
|
||||
const defaultCalendar = calendars.find((c) => c.is_default);
|
||||
const initialCalendarId = event?.calendar_id?.toString() || defaultCalendar?.id?.toString() || '';
|
||||
|
||||
@ -45,7 +45,6 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
||||
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,
|
||||
@ -58,7 +57,6 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
||||
name: data.name,
|
||||
title: data.title,
|
||||
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,
|
||||
@ -128,19 +126,6 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-duration">Duration (min)</Label>
|
||||
<Input
|
||||
id="tmpl-duration"
|
||||
type="number"
|
||||
min={5}
|
||||
max={1440}
|
||||
value={formData.duration_minutes}
|
||||
onChange={(e) => setFormData({ ...formData, duration_minutes: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-calendar">Calendar</Label>
|
||||
<Select
|
||||
@ -154,7 +139,6 @@ export default function TemplateForm({ template, onClose }: TemplateFormProps) {
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tmpl-location">Location</Label>
|
||||
|
||||
@ -232,7 +232,6 @@ export interface EventTemplate {
|
||||
name: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
duration_minutes: number;
|
||||
calendar_id?: number;
|
||||
recurrence_rule?: string;
|
||||
all_day: boolean;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user