Decision 6 / REQ-010 promised the pickers survive a fail(400) "including pre-selected persons/documents", but EventForm only seeded them from event/initialPersons — never from the fail payload — and the payload carried only bare ids, which can't rebuild a chip (chips need displayName/title). On the use:enhance path the in-memory selection survived; on a no-JS full reload the chips were silently dropped. Now the save action re-fetches the selected persons/documents by id (lookupSelections, non-ok swallowed like the prefill path) and returns full chip data; EventForm seeds the pickers from form.persons/documents ahead of the seeded event. Extract preservedFormFields() to DRY the four fail payloads; validateEventForm now returns the error pair and the route owns the fail(). Component test pins the rehydration; the server spec now asserts the fail payload carries labelled chips, not just ids. Addresses PR #832 review (Developer + Requirements Engineer concern, REQ-010). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
108 lines
2.8 KiB
TypeScript
108 lines
2.8 KiB
TypeScript
import { error, fail, redirect } from '@sveltejs/kit';
|
|
import { createApiClient, extractErrorCode } from '$lib/shared/api.server';
|
|
import { getErrorMessage } from '$lib/shared/errors';
|
|
import { requireWriteAll } from '$lib/shared/server/permissions';
|
|
import {
|
|
parseEventForm,
|
|
validateEventForm,
|
|
preservedFormFields,
|
|
lookupSelections,
|
|
toEventRequest,
|
|
resolveNavTarget
|
|
} from '$lib/timeline/eventFormServer';
|
|
|
|
export async function load({
|
|
locals,
|
|
params,
|
|
url,
|
|
fetch
|
|
}: {
|
|
locals: App.Locals;
|
|
params: { id: string };
|
|
url: URL;
|
|
fetch: typeof globalThis.fetch;
|
|
}) {
|
|
requireWriteAll(locals);
|
|
|
|
const api = createApiClient(fetch);
|
|
const result = await api.GET('/api/timeline/events/{id}', {
|
|
params: { path: { id: params.id } }
|
|
});
|
|
|
|
// Fail closed: derived person-events (Geburt/Tod/Heirat) are not persisted and
|
|
// have no UUID, so the API 404s for them. Any non-ok response → 404; never
|
|
// render a blank editable form that silently POSTs a new event.
|
|
if (!result.response.ok) throw error(404, 'Not found');
|
|
|
|
return { event: result.data!, originPersonId: url.searchParams.get('personId') ?? '' };
|
|
}
|
|
|
|
export const actions = {
|
|
save: async ({
|
|
request,
|
|
params,
|
|
fetch
|
|
}: {
|
|
request: Request;
|
|
params: { id: string };
|
|
fetch: typeof globalThis.fetch;
|
|
}) => {
|
|
const formData = await request.formData();
|
|
const parsed = parseEventForm(formData);
|
|
const api = createApiClient(fetch);
|
|
|
|
const errors = validateEventForm(parsed);
|
|
if (errors) {
|
|
const { persons, documents } = await lookupSelections(
|
|
api,
|
|
parsed.personIds,
|
|
parsed.documentIds
|
|
);
|
|
return fail(400, { ...errors, ...preservedFormFields(parsed), persons, documents });
|
|
}
|
|
|
|
const versionRaw = formData.get('version')?.toString();
|
|
const version = versionRaw ? Number(versionRaw) : undefined;
|
|
|
|
const result = await api.PUT('/api/timeline/events/{id}', {
|
|
params: { path: { id: params.id } },
|
|
body: toEventRequest(parsed, version)
|
|
});
|
|
|
|
if (!result.response.ok) {
|
|
return fail(result.response.status, {
|
|
error: getErrorMessage(extractErrorCode(result.error)),
|
|
...preservedFormFields(parsed)
|
|
});
|
|
}
|
|
|
|
throw redirect(303, resolveNavTarget(parsed.originPersonId));
|
|
},
|
|
|
|
delete: async ({
|
|
request,
|
|
params,
|
|
fetch
|
|
}: {
|
|
request: Request;
|
|
params: { id: string };
|
|
fetch: typeof globalThis.fetch;
|
|
}) => {
|
|
const formData = await request.formData();
|
|
const originPersonId = formData.get('originPersonId')?.toString() ?? '';
|
|
|
|
const api = createApiClient(fetch);
|
|
const result = await api.DELETE('/api/timeline/events/{id}', {
|
|
params: { path: { id: params.id } }
|
|
});
|
|
|
|
if (!result.response.ok) {
|
|
return fail(result.response.status, {
|
|
error: getErrorMessage(extractErrorCode(result.error))
|
|
});
|
|
}
|
|
|
|
throw redirect(303, resolveNavTarget(originPersonId));
|
|
}
|
|
};
|