diff --git a/frontend/src/lib/person/PersonReviewRow.svelte b/frontend/src/lib/person/PersonReviewRow.svelte new file mode 100644 index 00000000..09eddaf1 --- /dev/null +++ b/frontend/src/lib/person/PersonReviewRow.svelte @@ -0,0 +1,158 @@ + + +
  • +
    + +
    + +
    + +
    +

    {person.displayName}

    +

    + {documentCount === 1 + ? m.person_card_doc_count_one() + : m.person_card_doc_count_many({ count: documentCount })} +

    +
    + +
    + + +
    + + +
    +
    { + const ok = await confirm({ + title: m.persons_review_delete_confirm_title(), + body: m.persons_review_delete_confirm_text(), + confirmLabel: m.persons_review_delete_confirm_button(), + destructive: true + }); + if (!ok) cancel(); + }} + > + + +
    +
    +
    + + {#if mode === 'rename'} +
    { + return () => { + mode = 'idle'; + }; + }} + class="flex flex-wrap items-end gap-2" + > + + + + + + +
    + {/if} + + {#if mode === 'merge'} +
    { + return () => { + mode = 'idle'; + }; + }} + class="flex flex-wrap items-end gap-2" + > + + +
    + (mergeTargetId = value)} + /> +
    + + +
    + {/if} +
  • diff --git a/frontend/src/routes/persons/review/+page.server.ts b/frontend/src/routes/persons/review/+page.server.ts new file mode 100644 index 00000000..cb51eab4 --- /dev/null +++ b/frontend/src/routes/persons/review/+page.server.ts @@ -0,0 +1,109 @@ +import { error, fail } from '@sveltejs/kit'; +import { createApiClient, extractErrorCode } from '$lib/shared/api.server'; +import { getErrorMessage } from '$lib/shared/errors'; + +const PAGE_SIZE = 50; + +export async function load({ url, fetch, locals }) { + const canWrite = + (locals.user as { groups?: { permissions: string[] }[] } | undefined)?.groups?.some((g) => + g.permissions.includes('WRITE_ALL') + ) ?? false; + + const page = Math.max(0, Number.parseInt(url.searchParams.get('page') ?? '0', 10) || 0); + const api = createApiClient(fetch); + + const result = await api.GET('/api/persons', { + params: { query: { provisional: true, review: true, page, size: PAGE_SIZE } } + }); + + if (!result.response.ok) { + throw error(result.response.status, getErrorMessage(undefined)); + } + + const data = result.data!; + + return { + persons: data.items, + totalElements: data.totalElements, + totalPages: data.totalPages, + pageNumber: data.pageNumber, + canWrite + }; +} + +export const actions = { + confirm: async ({ request, fetch }) => { + const id = (await request.formData()).get('id') as string; + const api = createApiClient(fetch); + const result = await api.PATCH('/api/persons/{id}/confirm', { + params: { path: { id } } + }); + if (!result.response.ok) { + return fail(result.response.status, { + error: getErrorMessage(extractErrorCode(result.error)) + }); + } + return { success: true }; + }, + + delete: async ({ request, fetch }) => { + const id = (await request.formData()).get('id') as string; + const api = createApiClient(fetch); + const result = await api.DELETE('/api/persons/{id}', { + params: { path: { id } } + }); + if (!result.response.ok) { + return fail(result.response.status, { + error: getErrorMessage(extractErrorCode(result.error)) + }); + } + return { success: true }; + }, + + merge: async ({ request, fetch }) => { + const formData = await request.formData(); + const id = formData.get('id') as string; + const targetPersonId = formData.get('targetPersonId') as string; + if (!targetPersonId) { + return fail(400, { error: getErrorMessage('INVALID_INPUT') }); + } + const api = createApiClient(fetch); + const result = await api.POST('/api/persons/{id}/merge', { + params: { path: { id } }, + body: { targetPersonId } + }); + if (!result.response.ok) { + return fail(result.response.status, { + error: getErrorMessage(extractErrorCode(result.error)) + }); + } + return { success: true }; + }, + + rename: async ({ request, fetch }) => { + const formData = await request.formData(); + const id = formData.get('id') as string; + const firstName = (formData.get('firstName') as string)?.trim() || undefined; + const lastName = (formData.get('lastName') as string)?.trim(); + const personType = (formData.get('personType') as string) || 'PERSON'; + if (!lastName) { + return fail(400, { error: getErrorMessage('INVALID_INPUT') }); + } + const api = createApiClient(fetch); + const result = await api.PUT('/api/persons/{id}', { + params: { path: { id } }, + body: { + firstName, + lastName, + personType: personType as 'PERSON' | 'INSTITUTION' | 'GROUP' | 'UNKNOWN' + } + }); + if (!result.response.ok) { + return fail(result.response.status, { + error: getErrorMessage(extractErrorCode(result.error)) + }); + } + return { success: true }; + } +}; diff --git a/frontend/src/routes/persons/review/+page.svelte b/frontend/src/routes/persons/review/+page.svelte new file mode 100644 index 00000000..0fc0cc3e --- /dev/null +++ b/frontend/src/routes/persons/review/+page.svelte @@ -0,0 +1,56 @@ + + + + {m.persons_review_heading()} + + +
    + + +
    +

    {m.persons_review_heading()}

    +

    {m.persons_review_intro()}

    +
    + + {#if form?.error} + + {/if} + + {#if !hasResults} +
    +

    {m.persons_review_empty()}

    +
    + {:else} + + + + {/if} +