482 lines
14 KiB
Text
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>
|
|
}
|