feat: require updateScope selection and improve form UX

Location Edit Form:
- Add validation requiring user to select update scope when editing locations
- Add "no-scope-selected" as placeholder option that must be replaced with valid choice
- Display validation error if user attempts to submit without selecting scope
- Clarify update scope options with improved wording (e.g., "ALL months (past and future)")

Bill Form UX:
- Add emoji icons (👤 tenant, 🔑 landlord) to "who bears cost" options for visual clarity

Translation updates:
- Add "update-scope-required" validation message (EN/HR)
- Improve clarity of update scope option labels
- Standardize Croatian terminology ("zadani" instead of "trenutni" for current month)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2025-12-31 09:47:26 +01:00
parent 7e7eb5a2d8
commit 554dd8617f
4 changed files with 36 additions and 12 deletions

View File

@@ -27,6 +27,7 @@ export type State = {
rentDueNotificationEnabled?: string[];
rentDueDay?: string[];
rentAmount?: string[];
updateScope?: string[];
};
message?:string | null;
};
@@ -51,7 +52,7 @@ const FormSchema = (t:IntlTemplateFn) => z.object({
rentDueDay: z.coerce.number().min(1).max(31).optional().nullable(),
rentAmount: z.coerce.number().int(t("rent-amount-integer")).positive(t("rent-amount-positive")).optional().nullable(),
addToSubsequentMonths: z.boolean().optional().nullable(),
updateScope: z.enum(["current", "subsequent", "all"]).optional().nullable(),
updateScope: z.enum(["no-scope-selected", "current", "subsequent", "all"]),
})
// dont include the _id field in the response
.omit({ _id: true })
@@ -100,6 +101,16 @@ const FormSchema = (t:IntlTemplateFn) => z.object({
}, {
message: t("rent-amount-required"),
path: ["rentAmount"],
})
.refine((data) => {
// When updateScope field is present (editing mode), user must select a valid option
if (data.updateScope === "no-scope-selected") {
return false;
}
return true;
}, {
message: t("update-scope-required"),
path: ["updateScope"],
});
/**
@@ -130,7 +141,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
rentDueDay: formData.get('rentDueDay') || null,
rentAmount: formData.get('rentAmount') || null,
addToSubsequentMonths: formData.get('addToSubsequentMonths') === 'on',
updateScope: formData.get('updateScope') as "current" | "subsequent" | "all" | undefined,
updateScope: formData.get('updateScope') as "current" | "subsequent" | "all" | "",
});
// If form validation fails, return errors early. Otherwise, continue...

View File

@@ -445,11 +445,20 @@ export const LocationEditForm: FC<LocationEditFormProps> = ({ location, yearMont
<InfoBox>{t("update-scope-info")}</InfoBox>
<fieldset className="fieldset mt-2 p-2">
<legend className="fieldset-legend">{t("update-scope-legend")}</legend>
<select defaultValue="current" className="select input-bordered w-full" name="updateScope">
<select className="select input-bordered w-full" name="updateScope">
<option value="no-scope-selected">{t("update-option-placeholder")}</option>
<option value="current">{t("update-current-month")}</option>
<option value="subsequent">{t("update-subsequent-months")}</option>
<option value="all">{t("update-all-months")}</option>
</select>
<div id="updateScope-error" aria-live="polite" aria-atomic="true">
{state.errors?.updateScope &&
state.errors.updateScope.map((error: string) => (
<p className="mt-2 text-sm text-red-500" key={error}>
{error}
</p>
))}
</div>
</fieldset>
</>
)}

View File

@@ -138,8 +138,8 @@
"attachment": "Attachment",
"back-button": "Back",
"billed-to-legend": "Who bears the cost?",
"billed-to-tenant-option": "the tenant bears this cost",
"billed-to-landlord-option": "the landlord bears this cost",
"billed-to-tenant-option": "👤 the tenant bears this cost",
"billed-to-landlord-option": "🔑 the landlord bears this cost",
"billed-to-info": "This option is intended for cases where part of the utility costs are not charged to the tenant. If 'the landlord bears this cost' is selected, this bill will not be included in the monthly statement shown to the tenant.",
"upload-proof-of-payment-legend": "Proof of payment",
"upload-proof-of-payment-label": "Here you can upload proof of payment:"
@@ -209,13 +209,14 @@
"save-button": "Save",
"cancel-button": "Cancel",
"delete-tooltip": "Delete realestate",
"scope-legend": "Scope of changes",
"scope-legend": "Update scope",
"add-to-subsequent-months": "add to all subsequent months",
"update-scope-info": "Location records for each month are stored separately. Please choose which records you want to update.",
"update-scope-legend": "I want to update the following records:",
"update-option-placeholder": "👉 choose one of available options",
"update-current-month": "current month only",
"update-subsequent-months": "current and all future months",
"update-all-months": "all months",
"update-all-months": "ALL months (past and future)",
"validation": {
"location-name-required": "Relaestate name is required",
"tenant-name-required": "tenant name is missing",
@@ -226,6 +227,7 @@
"rent-amount-required": "rent amount is required when rent notification is enabled",
"rent-amount-integer": "rent amount must be a whole number (no decimal places)",
"rent-amount-positive": "rent amount must be a positive number",
"update-scope-required": "please select which records to update",
"validation-failed": "Validation failed. Please check the form and try again."
}
},

View File

@@ -137,8 +137,8 @@
"attachment": "Privitak",
"back-button": "Nazad",
"billed-to-legend": "Tko snosi trošak?",
"billed-to-tenant-option": "ovaj trošak snosi podstanar",
"billed-to-landlord-option": "ovaj trošak snosi vlasnik",
"billed-to-tenant-option": "👤 ovaj trošak snosi podstanar ",
"billed-to-landlord-option": "🔑 ovaj trošak snosi vlasnik",
"billed-to-info": "Ova opcija je predviđena za slučaj kada se dio režija ne naplaćuje od podstanara. Ako je odabrano 'trošak snosi vlasnik', ovaj račun neće biti uključen u mjesečni obračun koji se prikazuje podstanaru.",
"upload-proof-of-payment-legend": "Potvrda o uplati",
"upload-proof-of-payment-label": "Ovdje možete priložiti potvrdu o uplati:"
@@ -212,9 +212,10 @@
"add-to-subsequent-months": "dodaj u sve mjesece koji slijede",
"update-scope-info": "Zapisi o lokaciji su za svaki mjesec pohranjeni zasebno. Molimo odaberite koje zapise želite ažurirati.",
"update-scope-legend": "Želim ažurirati sljedeće zapise:",
"update-current-month": "samo trenutni mjesec",
"update-subsequent-months": "trenutni i sve buduće mjesece",
"update-all-months": "sve mjesece",
"update-option-placeholder": "👉 odaberi jednu od ponuđenih opcija",
"update-current-month": "samo zadani mjesec",
"update-subsequent-months": "zadani i sve buduće mjesece",
"update-all-months": "SVE mjesece (prošle i buduće)",
"validation": {
"location-name-required": "Ime nekretnine je obavezno",
"tenant-name-required": "nedostaje ime i prezime podstanara",
@@ -225,6 +226,7 @@
"rent-amount-required": "iznos najamnine je obavezan kada je uključena obavijest o najamnini",
"rent-amount-integer": "iznos najamnine mora biti cijeli broj (bez decimalnih mjesta)",
"rent-amount-positive": "iznos najamnine mora biti pozitivan broj",
"update-scope-required": "molimo odaberite koje zapise želite ažurirati",
"validation-failed": "Validacija nije uspjela. Molimo provjerite formu i pokušajte ponovno."
}
},