xtablo-source/backend/templates/events.templ
Arthur Belleville a30a6f9088
feat(13-02): replace CSS files, migrate card.templ to typed API, update all template hardcodes
- button.css: replaced with go-backend multi-class selector version + ghost variant rules
- badge.css: replaced with go-backend pill-shape version + primary variant
- card.css: replaced with go-backend token-based header/body/footer version
- card.templ: migrated from children passthrough to typed CardProps{Header/Body/Footer}
- ui_test.go: rewrote TestCard_RendersChildren -> TestCard_RendersTypedRegions; added TestBadge_PrimaryVariant; added textComponent helper + io import
- auth_login.templ, auth_signup.templ: migrated Card usage to typed CardProps API
- tablos.templ: migrated TabloCard to typed CardProps API with extracted tabloCardBody
- planning.templ, tasks.templ, events.templ, etapes.templ: all compound button class strings updated to multi-class pattern
- go test ./... passes (all packages green)
- just generate succeeds
2026-05-16 13:55:30 +02:00

282 lines
12 KiB
Text

package templates
import (
"backend/internal/db/sqlc"
"backend/internal/web/ui"
"github.com/google/uuid"
)
templ EventsTabFragment(tablo sqlc.Tablo, calendar EventsCalendar, csrfToken string) {
<div id="events-tab" class="space-y-6">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 class="text-xl font-semibold leading-snug text-slate-800">Events</h2>
<p class="text-sm text-slate-600">{ calendar.MonthLabel }</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<a
href={ templ.SafeURL(EventMonthURL(tablo.ID, calendar.PrevMonth)) }
hx-get={ EventMonthURL(tablo.ID, calendar.PrevMonth) }
hx-target="#tab-content"
hx-swap="innerHTML"
hx-push-url={ EventMonthURL(tablo.ID, calendar.PrevMonth) }
class="ui-button ui-button-soft ui-button-neutral ui-button-md"
aria-label={ "Previous month: " + calendar.PrevMonthLabel }
>Previous month</a>
<a
href={ templ.SafeURL(EventMonthURL(tablo.ID, calendar.NextMonth)) }
hx-get={ EventMonthURL(tablo.ID, calendar.NextMonth) }
hx-target="#tab-content"
hx-swap="innerHTML"
hx-push-url={ EventMonthURL(tablo.ID, calendar.NextMonth) }
class="ui-button ui-button-soft ui-button-neutral ui-button-md"
aria-label={ "Next month: " + calendar.NextMonthLabel }
>Next month</a>
@ui.Button(ui.ButtonProps{
Label: "New event",
Variant: ui.ButtonVariantDefault,
Tone: ui.ButtonToneSolid,
Size: ui.SizeMD,
Type: "button",
Attrs: templ.Attributes{
"hx-get": EventNewURL(tablo.ID, calendar.Month),
"hx-target": "#event-form-slot",
"hx-swap": "innerHTML",
"aria-label": "New event",
},
})
</div>
</div>
<div id="event-form-slot"></div>
<div class="overflow-x-auto">
<div class="grid min-w-[680px] grid-cols-7 gap-px rounded border border-slate-200 bg-slate-200">
for _, label := range calendar.WeekdayLabs {
<div class="bg-slate-50 px-2 py-2 text-sm font-semibold text-slate-700">{ label }</div>
}
for _, day := range calendar.Days {
@EventDayCell(tablo.ID, calendar.Month, day)
}
</div>
</div>
</div>
}
templ EventDayCell(tabloID uuid.UUID, month string, day EventCalendarDay) {
<div
class="min-h-24 bg-white p-2"
if !day.InMonth {
class="min-h-24 bg-slate-50 p-2 text-slate-400"
}
>
<div class="flex items-start justify-between gap-2">
<span class="text-sm font-semibold">{ day.DayNumber }</span>
if day.InMonth {
<button
type="button"
class="text-xs text-slate-500 hover:text-slate-800"
hx-get={ EventNewForDayURL(tabloID, day.Date, month) }
hx-target="#event-form-slot"
hx-swap="innerHTML"
aria-label={ "Create event on " + day.Date }
>+</button>
}
</div>
if len(day.Events) > 0 {
<div class="mt-2 space-y-1">
for _, event := range EventDayEvents(day.Events) {
<button
type="button"
class="block w-full truncate rounded border border-slate-200 bg-slate-50 px-2 py-1 text-left text-sm text-slate-800 hover:border-slate-400"
hx-get={ EventEditURL(tabloID, event.ID, month) }
hx-target="#event-form-slot"
hx-swap="innerHTML"
aria-label={ "Edit event: " + event.Title }
>{ event.Title }</button>
}
if day.MoreCount > 0 {
<p class="text-xs text-slate-500">{ EventMoreLabel(day.MoreCount) }</p>
}
</div>
}
</div>
}
templ EventCreateFormFragment(tabloID uuid.UUID, form EventCreateForm, errs EventCreateErrors, csrfToken string, month string) {
<form
method="POST"
action={ templ.SafeURL(EventPostURL(tabloID, month)) }
hx-post={ EventPostURL(tabloID, month) }
hx-target="#events-tab"
hx-swap="outerHTML"
class="rounded border border-slate-200 bg-white p-3 shadow-sm space-y-3"
>
@ui.CSRFField(csrfToken)
@GeneralError(errs.General)
<div>
<label for="event-title" class="block text-sm font-medium text-slate-700">Title</label>
<input id="event-title" type="text" name="title" value={ form.Title } maxlength="255" required class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.Title)
</div>
<div class="grid gap-3 sm:grid-cols-3">
<div>
<label for="event-date" class="block text-sm font-medium text-slate-700">Date</label>
<input id="event-date" type="date" name="event_date" value={ form.EventDate } required class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.EventDate)
</div>
<div>
<label for="event-start-time" class="block text-sm font-medium text-slate-700">Start time</label>
<input id="event-start-time" type="time" name="start_time" value={ form.StartTime } required class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.StartTime)
</div>
<div>
<label for="event-end-time" class="block text-sm font-medium text-slate-700">End time <span class="text-slate-400">(optional)</span></label>
<input id="event-end-time" type="time" name="end_time" value={ form.EndTime } class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.EndTime)
</div>
</div>
<div>
<label for="event-location" class="block text-sm font-medium text-slate-700">Location <span class="text-slate-400">(optional)</span></label>
<input id="event-location" type="text" name="location" value={ form.Location } maxlength="255" class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none"/>
</div>
<div>
<label for="event-description" class="block text-sm font-medium text-slate-700">Description <span class="text-slate-400">(optional)</span></label>
<textarea id="event-description" name="description" rows="3" class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none">{ form.Description }</textarea>
</div>
<div class="flex items-center gap-2">
@ui.Button(ui.ButtonProps{
Label: "Create event",
Variant: ui.ButtonVariantDefault,
Tone: ui.ButtonToneSolid,
Size: ui.SizeMD,
Type: "submit",
})
@ui.Button(ui.ButtonProps{
Label: "Close form",
Variant: ui.ButtonVariantNeutral,
Tone: ui.ButtonToneSoft,
Size: ui.SizeMD,
Type: "button",
Attrs: templ.Attributes{
"hx-get": "/tablos/" + tabloID.String() + "/events/cancel-new",
"hx-target": "#event-form-slot",
"hx-swap": "innerHTML",
},
})
</div>
</form>
}
templ EventEditFormFragment(tabloID uuid.UUID, event sqlc.Event, form EventCreateForm, errs EventCreateErrors, csrfToken string, month string) {
<form
method="POST"
action={ templ.SafeURL(EventUpdateURL(tabloID, event.ID, month)) }
hx-post={ EventUpdateURL(tabloID, event.ID, month) }
hx-target="#events-tab"
hx-swap="outerHTML"
class="rounded border border-slate-200 bg-white p-3 shadow-sm space-y-3"
>
@ui.CSRFField(csrfToken)
@GeneralError(errs.General)
<div>
<label for="event-edit-title" class="block text-sm font-medium text-slate-700">Title</label>
<input id="event-edit-title" type="text" name="title" value={ form.Title } maxlength="255" required class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.Title)
</div>
<div class="grid gap-3 sm:grid-cols-3">
<div>
<label for="event-edit-date" class="block text-sm font-medium text-slate-700">Date</label>
<input id="event-edit-date" type="date" name="event_date" value={ form.EventDate } required class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.EventDate)
</div>
<div>
<label for="event-edit-start-time" class="block text-sm font-medium text-slate-700">Start time</label>
<input id="event-edit-start-time" type="time" name="start_time" value={ form.StartTime } required class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.StartTime)
</div>
<div>
<label for="event-edit-end-time" class="block text-sm font-medium text-slate-700">End time <span class="text-slate-400">(optional)</span></label>
<input id="event-edit-end-time" type="time" name="end_time" value={ form.EndTime } class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-slate-500 focus:outline-none"/>
@FieldError(errs.EndTime)
</div>
</div>
<div>
<label for="event-edit-location" class="block text-sm font-medium text-slate-700">Location <span class="text-slate-400">(optional)</span></label>
<input id="event-edit-location" type="text" name="location" value={ form.Location } maxlength="255" class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none"/>
</div>
<div>
<label for="event-edit-description" class="block text-sm font-medium text-slate-700">Description <span class="text-slate-400">(optional)</span></label>
<textarea id="event-edit-description" name="description" rows="3" class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none">{ form.Description }</textarea>
</div>
<div class="flex flex-wrap items-center gap-2">
@ui.Button(ui.ButtonProps{
Label: "Save event changes",
Variant: ui.ButtonVariantDefault,
Tone: ui.ButtonToneSolid,
Size: ui.SizeMD,
Type: "submit",
})
@ui.Button(ui.ButtonProps{
Label: "Delete event",
Variant: ui.ButtonVariantDanger,
Tone: ui.ButtonToneSoft,
Size: ui.SizeMD,
Type: "button",
Attrs: templ.Attributes{
"hx-get": EventDeleteConfirmURL(tabloID, event.ID, month),
"hx-target": "#event-form-slot",
"hx-swap": "innerHTML",
"aria-label": "Delete event: " + event.Title,
},
})
@ui.Button(ui.ButtonProps{
Label: "Close form",
Variant: ui.ButtonVariantNeutral,
Tone: ui.ButtonToneSoft,
Size: ui.SizeMD,
Type: "button",
Attrs: templ.Attributes{
"hx-get": "/tablos/" + tabloID.String() + "/events/cancel-new",
"hx-target": "#event-form-slot",
"hx-swap": "innerHTML",
},
})
</div>
</form>
}
templ EventDeleteConfirmFragment(tabloID uuid.UUID, event sqlc.Event, csrfToken string, month string) {
<div class="rounded border border-slate-200 bg-white p-3 shadow-sm space-y-3">
<p class="text-sm font-semibold text-slate-800">Delete event?</p>
<p class="text-sm text-slate-600">This removes the event from this tablo. This cannot be undone.</p>
<div class="flex flex-wrap items-center gap-2">
<form
method="POST"
action={ templ.SafeURL(EventDeleteURL(tabloID, event.ID, month)) }
hx-post={ EventDeleteURL(tabloID, event.ID, month) }
hx-target="#events-tab"
hx-swap="outerHTML"
>
@ui.CSRFField(csrfToken)
@ui.Button(ui.ButtonProps{
Label: "Delete event",
Variant: ui.ButtonVariantDanger,
Tone: ui.ButtonToneSolid,
Size: ui.SizeMD,
Type: "submit",
})
</form>
@ui.Button(ui.ButtonProps{
Label: "Keep event",
Variant: ui.ButtonVariantNeutral,
Tone: ui.ButtonToneSoft,
Size: ui.SizeMD,
Type: "button",
Attrs: templ.Attributes{
"hx-get": "/tablos/" + tabloID.String() + "/events/cancel-new",
"hx-target": "#event-form-slot",
"hx-swap": "innerHTML",
},
})
</div>
</div>
}