282 lines
12 KiB
Text
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-neutral-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-neutral-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>
|
|
}
|