onRowSelectionChange computed the next selection from the render-time
`rowSelection` closure, which could be stale by the time a second row was
toggled. The updater then produced a selection containing only the latest
row, and the persistedMap sync deleted the earlier one — so checking a
second part unchecked the first, making multi-select behave as single-select.
Track the latest selection in a ref and derive the updater from it, so each
toggle builds on the current selection. Fixes adding multiple parts/services/
expense items in invoices, estimates, and job cards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The vendor detail page and layout called api.vendors.getById without a
try/catch. The API client throws ApiError on any non-2xx, so a 404 (missing
vendor), 401, or 5xx during SSR threw out of the server component and
white-screened the whole route with a generic "server-side exception" —
the existing `if (!vendor)` fallback was never reached.
Guard both fetches: log the error and degrade gracefully (page shows
"Vendor not found", layout renders chrome with a fallback title).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The vehicle form uploads as multipart/form-data for the image, and the
FormData builder stringified every value, turning has_insurance=false into
the literal "false". Laravel's boolean rule rejects "true"/"false" strings,
producing "The has insurance field must be true or false." Serialize
booleans as "1"/"0" so they validate; null values remain skipped (nullable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After login/activation, show a full-screen loader while company settings
resolve, then force the user onto /settings/company and block navigation to
any other route until the company info is filled in. The gate releases once the
company record has a name; fails open on API error so a transient failure can
never lock the user out.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>