ワンクリックで
sif-soknad-vedlegg-step
Implementer et vedleggssteg i en v2-app — VedleggPanel, PersistedVedlegg, hydration og MSW-handlere.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Implementer et vedleggssteg i en v2-app — VedleggPanel, PersistedVedlegg, hydration og MSW-handlere.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Mønster for å kombinere flere API-hooks til én asynkron laster (useInitialData + InitialDataLoader) i sif-soknad-apper.
Legg til et nytt steg i en søknadsapp som bruker @sif/soknad og @sif/rhf — oppretter alle filer og oppdaterer config/routing/i18n.
Arkitekturreferanse og beslutningslogg for @sif/soknad-app — rammeverket for søknadsapper i sif-brukerdialog.
Sett opp OppsummeringSteg i en v2-app — DTO-basert oppsummering med FormSummary, bekreftelsescheckbox og i18n-nøkler.
Veiledning for oppsett av en ny søknadsapp med @sif/soknad-app — SøknadRouter, SøknadAppProvider, AppContext og routing shell.
Bruk denne skillen for å fange opp og rette feilaktig datoparsing fra backend i v2-apper — tidssoneproblemer, feil bruk av Date/dayjs og blandede ISODate/ISODateTime-verdier i frontend.
| name | sif-soknad-vedlegg-step |
| type | action |
| description | Implementer et vedleggssteg i en v2-app — VedleggPanel, PersistedVedlegg, hydration og MSW-handlere. |
vedlegg, last opp, filvedlegg, VedleggPanel, PersistedVedlegg.src/app/steps/<steg>/types.tssrc/app/steps/<steg>/<prefix>StegUtils.tssrc/app/steps/<steg>/<Prefix>Form.tsxsrc/app/types/Soknadsdata.tssrc/app/utils/formValuesToSoknadsdata.tssrc/app/utils/soknadsdataToSoknadDTO.tsmock/msw/handlers.tssrc/app/steps/oppsummering/OppsummeringSteg.tsxPersistedVedlegg er en delt type fra @sif/soknad-forms. Ikke definer den lokalt.
import { PersistedVedlegg } from '@sif/soknad-forms';
export interface <Prefix>Søknadsdata {
vedlegg: PersistedVedlegg[];
}
PersistedVedlegg inneholder id, url (frontend-URL for visning), backendUrl (full API-URL for innsending), name, size, type og lastModified.
UploadedFile[] i formenexport enum <Prefix>FormFields {
vedlegg = 'vedlegg',
}
export interface <Prefix>FormValues extends StepFormValues {
[<Prefix>FormFields.vedlegg]: UploadedFile[];
}
Bruk delte hjelpere fra @sif/soknad-forms. Ikke dupliser disse lokalt.
import { <Prefix>Søknadsdata } from '@app/types/Soknadsdata';
import { getVedleggApiUrl } from '@sif/api/k9-prosessering';
import { isUploadedVedlegg, toPersistedVedlegg, toUploadedFile } from '@sif/soknad-forms';
import { <Prefix>FormFields, <Prefix>FormValues } from './types';
export const to<Prefix>FormValues = (
søknadsdata: <Prefix>Søknadsdata | undefined,
): <Prefix>FormValues => ({
[<Prefix>FormFields.vedlegg]: søknadsdata?.vedlegg.map(toUploadedFile) ?? [],
});
export const toSøknadsdata = (values: <Prefix>FormValues): <Prefix>Søknadsdata => ({
vedlegg: (values[<Prefix>FormFields.vedlegg] ?? [])
.filter(isUploadedVedlegg)
.map((file) => toPersistedVedlegg(file, getVedleggApiUrl(file.info.id))),
});
Hjelperne gjør følgende:
toUploadedFile(vedlegg) — hydrerer et PersistedVedlegg til UploadedFile (for react-hook-form)isUploadedVedlegg(file) — type guard som filtrerer bort feil/pending filertoPersistedVedlegg(file, backendUrl) — mapper tilbake til PersistedVedlegg med file-metadata og backendUrlimport { useLenker } from '@app/lenker';
import { SøknadStepId } from '@app/types/SoknadStepId';
import { <Prefix>Søknadsdata } from '@app/types/Soknadsdata';
import { UploadedFile } from '@sif/rhf';
import { SøknadStep, SøknadStepForm, useMellomlagring, useSaveSøknadFormValues, useStepData } from '@sif/soknad-app';
import { VedleggPanel } from '@sif/soknad-forms';
import { useForm } from 'react-hook-form';
import { to<Prefix>FormValues, to<Prefix>Søknadsdata } from './<prefix>StegUtils';
import { <Prefix>FormFields, <Prefix>FormValues } from './types';
const stepId = SøknadStepId.<STEP_ID>;
export const <Prefix>Form = () => {
const { text } = useAppIntl();
const lenker = useLenker();
const { lagretData, commit, draftFormValues } = useStepData<<Prefix>Søknadsdata, <Prefix>FormValues>(stepId);
const initialVedlegg = (draftFormValues ?? to<Prefix>FormValues(lagretData))[<Prefix>FormFields.vedlegg];
const methods = useForm<<Prefix>FormValues>({ defaultValues: draftFormValues ?? to<Prefix>FormValues(lagretData) });
useSaveSøknadFormValues(stepId, methods.getValues);
const { lagre } = useMellomlagring();
const vedlegg: UploadedFile[] = methods.watch(<Prefix>FormFields.vedlegg) ?? [];
const hasPendingUploads = vedlegg.some((file) => file.pending);
const onSubmit = (data: <Prefix>FormValues) => commit(to<Prefix>Søknadsdata(data));
return (
<SøknadStep stepId={stepId}>
<SøknadStepForm
stepId={stepId}
methods={methods}
onSubmit={onSubmit}
isPending={false}
submitDisabled={hasPendingUploads}>
<VedleggPanel<<Prefix>FormValues>
name={<Prefix>FormFields.vedlegg}
initialFiles={initialVedlegg}
onVedleggEndret={() => lagre()}
label={text('<prefix>Steg.vedlegg.label')}
uploadLaterURL={lenker.omsorgspengerEttersending}
showPictureScanningGuide={true}
/>
</SøknadStepForm>
</SøknadStep>
);
};
lagre() fra useMellomlagring() i onVedleggEndret — lagrer alle live formverdier inkl. vedlegg.initialFiles skal være den initielle listen, ikke watch(...)-verdien.uploadLaterURL hentes fra delt lenkekilde via useLenker().case SøknadStepId.<PREFIX>:
return toSøknadsdata(formValues as <Prefix>FormValues);
Vedlegg sendes som backend-URLer (full API-URL), ikke bare IDer. backendUrl er allerede satt på PersistedVedlegg av toPersistedVedlegg i steg-utils.
const <prefix>Vedlegg = søknadsdata[SøknadStepId.<PREFIX>]?.vedlegg.map((v) => v.backendUrl) ?? [];
return {
...,
<prefix>: <prefix>Vedlegg,
};
Bruk vedlegg fra state.søknadsdata, ikke DTO.
import { VedleggSummaryList } from '@sif/soknad-ui/components';
<VedleggSummaryList vedlegg={state.søknadsdata[SøknadStepId.LEGEERKLÆRING]?.vedlegg ?? []} />;
DTO har normalt bare ID-er. Oppsummering trenger name, url og size.
let vedleggCounter = 0;
const vedleggStore = new Map<string, { name: string; type: string; content: ArrayBuffer }>();
const getVedleggUrl = (vedleggId: string) => `${K9_BRUKERDIALOG_PROSESSERING_FRONTEND_PATH}/vedlegg/${vedleggId}`;
http.post(`**/vedlegg`, async ({ request }) => {
const formData = await request.formData();
const vedlegg = formData.get('vedlegg');
if (!(vedlegg instanceof File)) {
return HttpResponse.json({ message: 'Mangler vedlegg i request' }, { status: 400 });
}
vedleggCounter += 1;
const vedleggId = `mock-vedlegg-${vedleggCounter}`;
vedleggStore.set(vedleggId, {
name: vedlegg.name,
type: vedlegg.type,
content: await vedlegg.arrayBuffer(),
});
return new HttpResponse(null, {
headers: {
Location: getVedleggUrl(vedleggId),
'access-control-expose-headers': 'Location',
},
});
});
http.get(`**/vedlegg/:vedleggId`, ({ params }) => {
const file = vedleggStore.get(String(params.vedleggId));
if (!file) return HttpResponse.json({ message: 'Vedlegg ikke funnet' }, { status: 404 });
return new HttpResponse(file.content, {
headers: {
'Content-Type': file.type || 'application/octet-stream',
'Content-Disposition': `inline; filename="${encodeURIComponent(file.name)}"`,
},
});
});
http.delete(`**/vedlegg/:vedleggId`, ({ params }) => {
vedleggStore.delete(String(params.vedleggId));
return HttpResponse.json({});
});
Legg vedlegg-handlerne før catch-all-handlere.
zLagreVedleggData bruker z.string() i stedet for z.instanceof(Blob).Content-type: application/json.Location-header og GET /vedlegg/:id.it('mapper bare opplastede filer til søknadsdata', () => { ... });
it('ekskluderer filer med feil fra søknadsdata', () => { ... });
it('hydrerer lagrede vedlegg til UploadedFile-format', () => { ... });