fix(salary): one save per surface on the employee form (#1623)

* fix(salary): one save per surface on the employee form

The employee edit page stacked two competing saves: the opening-balances
Card ended in "Spara ingaende saldon" and the page ended in "Spara
andringar" 80px below, with no visible boundary between their scopes.
Worse, both self-saving panels lived INSIDE the page <form> and shadcn
Button sets no default type, so every panel button (save opening
balances, add/remove benefit) implicitly submitted the outer form too,
firing the full employee PATCH alongside the panel's own request.

Restructure so each surface owns exactly one save:

- The employee <form> now closes right after the Bank card, with
  Avbryt + "Spara andringar" directly under the fields it actually saves.
- Formaner and Ingaende saldon move below the form into a "Sparas
  separat" section (uppercase kicker + one-line scope hint) so the page
  save structurally cannot include them and their buttons can no longer
  leak submits into the employee form.
- OpeningBalancesPanel becomes its own <form>: Enter saves the panel,
  and the save button enables only when its fields are actually dirty
  (fingerprint of loaded values, reset on successful save).
- EmployeeBenefitsPanel buttons get explicit type="button".

New strings in both messages/sv.json and messages/en.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(salary): release the loading skeleton when the balances fetch fails

CodeRabbit on #1623: a rejected fetch or JSON parse skipped the
setLoading(false) line, holding the skeleton forever. The load now
wraps in try/finally; a failed load falls back to the empty form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-16 17:04:57 +02:00
committed by GitHub
co-authored by Claude Fable 5 Jakob Wennberg
parent dd4ced1f93
commit 51539b93ed
6 changed files with 129 additions and 30 deletions
+19 -6
View File
@@ -487,12 +487,6 @@ export default function EmployeeDetailPage({ params }: { params: Promise<{ id: s
</CardContent>
</Card>
{/* Benefits */}
<EmployeeBenefitsPanel employeeId={id} canWrite={canWrite} />
{/* Ingående saldon (payroll cutover) */}
<OpeningBalancesPanel employeeId={id} canWrite={canWrite} />
{canWrite && (
<div className="flex justify-end gap-3">
<Button variant="outline" asChild>
@@ -506,6 +500,25 @@ export default function EmployeeDetailPage({ params }: { params: Promise<{ id: s
)}
</form>
{/* Self-saving sections. These live OUTSIDE the employee form on purpose:
benefits and opening balances write to their own endpoints, so the
page-level "Spara ändringar" never touches them, and their buttons can
no longer implicitly submit the employee form (default type="submit"). */}
<section className="space-y-4">
<div>
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
{t('detail_self_saving_heading')}
</h2>
<p className="text-sm text-muted-foreground mt-1">{t('detail_self_saving_hint')}</p>
</div>
{/* Benefits */}
<EmployeeBenefitsPanel employeeId={id} canWrite={canWrite} />
{/* Ingående saldon (payroll cutover) */}
<OpeningBalancesPanel employeeId={id} canWrite={canWrite} />
</section>
<DestructiveConfirmDialog {...dialogProps} />
</div>
)