budgit/internal/ui/forms/recurring_event.templ
2026-05-22 13:02:57 +00:00

482 lines
14 KiB
Text

package forms
import "git.juancwu.dev/juancwu/budgit/internal/misc/timezone"
import "git.juancwu.dev/juancwu/budgit/internal/model"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/card"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/checkbox"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/form"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/selectbox"
import "git.juancwu.dev/juancwu/budgit/internal/ui/components/textarea"
type RecurringEventFormProps struct {
SpaceID string
Action string
CancelHref string
SubmitLabel string
Accounts []*model.Account
Timezones []timezone.TimezoneOption
Title string
Kind string
SourceAccountID string
Amount string
Description string
Frequency string
IntervalCount string
DayOfWeek string
DayOfMonth string
MonthOfYear string
FireTime string
Timezone string
StartDate string
BusinessDaysOnly bool
TitleErr string
KindErr string
SourceErr string
AmountErr string
FrequencyErr string
IntervalErr string
DayOfWeekErr string
DayOfMonthErr string
MonthOfYearErr string
FireTimeErr string
TimezoneErr string
StartDateErr string
GeneralErr string
}
func (p RecurringEventFormProps) HasError() bool {
return p.TitleErr != "" || p.KindErr != "" || p.SourceErr != "" ||
p.AmountErr != "" || p.FrequencyErr != "" || p.IntervalErr != "" ||
p.DayOfWeekErr != "" || p.DayOfMonthErr != "" || p.MonthOfYearErr != "" ||
p.FireTimeErr != "" || p.TimezoneErr != "" || p.StartDateErr != ""
}
var weekdayNames = []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
var monthNames = []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
templ RecurringEventForm(props RecurringEventFormProps) {
<form id="recurring-event-form" hx-post={ props.Action } hx-target="#recurring-event-form" hx-swap="outerHTML">
@card.Card(card.Props{Class: "rounded-sm"}) {
@card.Content(card.ContentProps{Class: "p-4 space-y-4"}) {
if props.GeneralErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.GeneralErr }
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "title"}) {
Title
}
@input.Input(input.Props{
ID: "title",
Name: "title",
Type: input.TypeText,
Placeholder: "e.g. Rent",
Class: "rounded-sm",
Value: props.Title,
HasError: props.TitleErr != "",
Required: true,
})
if props.TitleErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.TitleErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "kind"}) {
Kind
}
@selectbox.SelectBox() {
@selectbox.Trigger(selectbox.TriggerProps{
ID: "kind",
Name: "kind",
HasError: props.KindErr != "",
}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select a kind…"}) {
{ kindLabel(props.Kind) }
}
}
@selectbox.Content(selectbox.ContentProps{NoSearch: true}) {
@selectbox.Item(selectbox.ItemProps{
Value: string(model.RecurringEventKindBill),
Selected: props.Kind == string(model.RecurringEventKindBill),
}) {
Bill (withdrawal)
}
@selectbox.Item(selectbox.ItemProps{
Value: string(model.RecurringEventKindFund),
Selected: props.Kind == string(model.RecurringEventKindFund),
}) {
Fund (deposit)
}
}
}
if props.KindErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.KindErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "source_account"}) {
Account
}
@selectbox.SelectBox() {
@selectbox.Trigger(selectbox.TriggerProps{
ID: "source_account",
Name: "source_account",
HasError: props.SourceErr != "",
}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select an account…"}) {
{ accountLabel(props.Accounts, props.SourceAccountID) }
}
}
@selectbox.Content(selectbox.ContentProps{SearchPlaceholder: "Search accounts…"}) {
for _, a := range props.Accounts {
@selectbox.Item(selectbox.ItemProps{
Value: a.ID,
Selected: props.SourceAccountID == a.ID,
}) {
{ a.Name }
}
}
}
}
if props.SourceErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.SourceErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "amount"}) {
Amount
}
@input.Input(input.Props{
ID: "amount",
Name: "amount",
Type: input.TypeNumber,
Placeholder: "0.00",
Class: "rounded-sm",
Value: props.Amount,
HasError: props.AmountErr != "",
Required: true,
Attributes: templ.Attributes{
"step": "0.01",
"min": "0",
"inputmode": "decimal",
},
})
if props.AmountErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.AmountErr }
}
}
}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@form.Item() {
@form.Label(form.LabelProps{For: "frequency"}) {
Frequency
}
@selectbox.SelectBox() {
@selectbox.Trigger(selectbox.TriggerProps{
ID: "frequency",
Name: "frequency",
HasError: props.FrequencyErr != "",
}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select a frequency…"}) {
{ frequencyLabel(props.Frequency) }
}
}
@selectbox.Content(selectbox.ContentProps{NoSearch: true}) {
@selectbox.Item(selectbox.ItemProps{
Value: string(model.RecurringFrequencyDaily),
Selected: props.Frequency == string(model.RecurringFrequencyDaily),
}) {
Daily
}
@selectbox.Item(selectbox.ItemProps{
Value: string(model.RecurringFrequencyWeekly),
Selected: props.Frequency == string(model.RecurringFrequencyWeekly),
}) {
Weekly
}
@selectbox.Item(selectbox.ItemProps{
Value: string(model.RecurringFrequencyMonthly),
Selected: props.Frequency == string(model.RecurringFrequencyMonthly),
}) {
Monthly
}
@selectbox.Item(selectbox.ItemProps{
Value: string(model.RecurringFrequencyYearly),
Selected: props.Frequency == string(model.RecurringFrequencyYearly),
}) {
Yearly
}
}
}
if props.FrequencyErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.FrequencyErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "interval_count"}) {
Repeat every
}
@input.Input(input.Props{
ID: "interval_count",
Name: "interval_count",
Type: input.TypeNumber,
Class: "rounded-sm",
Value: props.IntervalCount,
HasError: props.IntervalErr != "",
Required: true,
Attributes: templ.Attributes{
"min": "1",
"step": "1",
},
})
@form.Description() {
e.g. "every 2" + Weekly = every other week.
}
if props.IntervalErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.IntervalErr }
}
}
}
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
@form.Item() {
@form.Label(form.LabelProps{For: "day_of_week"}) {
Day of week
}
@selectbox.SelectBox() {
@selectbox.Trigger(selectbox.TriggerProps{
ID: "day_of_week",
Name: "day_of_week",
HasError: props.DayOfWeekErr != "",
}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "—"}) {
{ weekdayLabel(props.DayOfWeek) }
}
}
@selectbox.Content(selectbox.ContentProps{NoSearch: true}) {
@selectbox.Item(selectbox.ItemProps{
Value: "",
Selected: props.DayOfWeek == "",
}) {
}
for i, name := range weekdayNames {
@selectbox.Item(selectbox.ItemProps{
Value: intToStr(i),
Selected: props.DayOfWeek == intToStr(i),
}) {
{ name }
}
}
}
}
@form.Description() {
Used for weekly events.
}
if props.DayOfWeekErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.DayOfWeekErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "day_of_month"}) {
Day of month
}
@input.Input(input.Props{
ID: "day_of_month",
Name: "day_of_month",
Type: input.TypeNumber,
Class: "rounded-sm",
Value: props.DayOfMonth,
HasError: props.DayOfMonthErr != "",
Attributes: templ.Attributes{
"min": "1",
"max": "31",
},
})
@form.Description() {
Used for monthly/yearly. Clamps to last day of short months.
}
if props.DayOfMonthErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.DayOfMonthErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "month_of_year"}) {
Month
}
@selectbox.SelectBox() {
@selectbox.Trigger(selectbox.TriggerProps{
ID: "month_of_year",
Name: "month_of_year",
HasError: props.MonthOfYearErr != "",
}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "—"}) {
{ monthLabel(props.MonthOfYear) }
}
}
@selectbox.Content(selectbox.ContentProps{NoSearch: true}) {
@selectbox.Item(selectbox.ItemProps{
Value: "",
Selected: props.MonthOfYear == "",
}) {
}
for i, name := range monthNames {
@selectbox.Item(selectbox.ItemProps{
Value: intToStr(i + 1),
Selected: props.MonthOfYear == intToStr(i + 1),
}) {
{ name }
}
}
}
}
@form.Description() {
Used for yearly events.
}
if props.MonthOfYearErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.MonthOfYearErr }
}
}
}
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
@form.Item() {
@form.Label(form.LabelProps{For: "fire_time"}) {
Time of day
}
@input.Input(input.Props{
ID: "fire_time",
Name: "fire_time",
Type: input.TypeTime,
Class: "rounded-sm",
Value: props.FireTime,
HasError: props.FireTimeErr != "",
Required: true,
})
if props.FireTimeErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.FireTimeErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "timezone"}) {
Timezone
}
@selectbox.SelectBox() {
@selectbox.Trigger(selectbox.TriggerProps{
ID: "timezone",
Name: "timezone",
HasError: props.TimezoneErr != "",
}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select a timezone…"}) {
{ timezoneLabel(props.Timezones, props.Timezone) }
}
}
@selectbox.Content(selectbox.ContentProps{SearchPlaceholder: "Search timezones…"}) {
for _, tz := range props.Timezones {
@selectbox.Item(selectbox.ItemProps{
Value: tz.Value,
Selected: props.Timezone == tz.Value,
}) {
{ tz.Label }
}
}
}
}
if props.TimezoneErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.TimezoneErr }
}
}
}
@form.Item() {
@form.Label(form.LabelProps{For: "start_date"}) {
Start date
}
@input.Input(input.Props{
ID: "start_date",
Name: "start_date",
Type: input.TypeDate,
Class: "rounded-sm",
Value: props.StartDate,
HasError: props.StartDateErr != "",
Required: true,
})
@form.Description() {
First firing on or after this local date.
}
if props.StartDateErr != "" {
@form.Message(form.MessageProps{Variant: form.MessageVariantError}) {
{ props.StartDateErr }
}
}
}
</div>
@form.Item() {
<div class="flex items-start gap-2">
@checkbox.Checkbox(checkbox.Props{
ID: "business_days_only",
Name: "business_days_only",
Value: "1",
Checked: props.BusinessDaysOnly,
})
<div class="space-y-1">
<label for="business_days_only" class="text-sm font-medium leading-none cursor-pointer">
Skip non-business days
</label>
@form.Description() {
If a firing lands on Saturday or Sunday, push it to the following Monday.
}
</div>
</div>
}
@form.Item() {
@form.Label(form.LabelProps{For: "description"}) {
Description
}
@textarea.Textarea(textarea.Props{
ID: "description",
Name: "description",
Placeholder: "Optional",
Rows: 3,
Value: props.Description,
})
}
}
@card.Footer(card.FooterProps{Class: "flex justify-end gap-2"}) {
@button.Button(button.Props{
Variant: button.VariantGhost,
Href: props.CancelHref,
}) {
Cancel
}
@button.Button(button.Props{Type: button.TypeSubmit}) {
{ props.SubmitLabel }
}
}
}
</form>
}