From c60d54efe4fcfa854a6408da4d18e79c4f453530 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Tue, 3 Dec 2024 11:45:43 +0100 Subject: [PATCH] :arrow_up: - chore: update @maykin-ui/admin-ui (and migrate code accordingly) --- frontend/src/App.tsx | 1 + .../DestructionListReviewer.tsx | 18 +-- .../DestructionListToolbar.tsx | 8 +- .../ProcessingStatusBadge.tsx | 4 +- frontend/src/hooks/useFields.test.ts | 1 + frontend/src/hooks/useFields.ts | 59 +++++--- frontend/src/hooks/useFilter.ts | 11 +- frontend/src/hooks/useZaakSelection.test.ts | 9 +- frontend/src/hooks/useZaakSelection.ts | 36 +++-- .../lib/fieldSelection/fieldSelection.test.ts | 8 +- .../src/lib/fieldSelection/fieldSelection.ts | 43 +++--- .../zaakSelection/backends/sessionStorage.ts | 12 +- frontend/src/lib/zaakSelection/types.d.ts | 22 +-- frontend/src/lib/zaakSelection/utils.ts | 11 +- .../src/lib/zaakSelection/zaakSelection.ts | 13 +- .../destructionlist/abstract/BaseListView.tsx | 131 +++++++++--------- .../detail/hooks/useSecondaryNavigation.tsx | 16 ++- .../DestructionListEditPage.tsx | 6 +- .../DestructionListProcessReviewPage.tsx | 11 +- .../DestructionListProcessZaakReviewModal.tsx | 13 +- .../review/DestructionListReview.tsx | 10 +- frontend/src/pages/destructionlist/utils.ts | 24 ---- frontend/src/pages/landing/Landing.tsx | 9 +- frontend/src/pages/login/Login.tsx | 11 +- frontend/src/pages/settings/Settings.tsx | 36 +++-- frontend/src/types.d.ts | 2 +- 26 files changed, 278 insertions(+), 247 deletions(-) delete mode 100644 frontend/src/pages/destructionlist/utils.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dad9b958d..2a36637cd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -125,6 +125,7 @@ function App() { /> } pad="v" + variant="transparent" > diff --git a/frontend/src/components/DestructionListReviewer/DestructionListReviewer.tsx b/frontend/src/components/DestructionListReviewer/DestructionListReviewer.tsx index 09ada63d0..9b55dfffc 100644 --- a/frontend/src/components/DestructionListReviewer/DestructionListReviewer.tsx +++ b/frontend/src/components/DestructionListReviewer/DestructionListReviewer.tsx @@ -3,7 +3,6 @@ import { Button, FormField, P, - SerializedFormData, Solid, useAlert, useFormDialog, @@ -34,6 +33,12 @@ export type DestructionListReviewerProps = { destructionList: DestructionList; }; +export type DestructionListReviewerFormType = { + comment: string; + reviewer?: string; + coReviewer?: string[]; +}; + /** * Allows viewing/assigning the reviewers of a destruction list. * @param destructionList @@ -45,7 +50,7 @@ export function DestructionListReviewer({ const { state } = useNavigation(); const revalidator = useRevalidator(); const alert = useAlert(); - const formDialog = useFormDialog(); + const formDialog = useFormDialog(); const coReviews = useCoReviews(destructionList); const reviewers = useReviewers(); const coReviewers = useCoReviewers(); @@ -55,12 +60,8 @@ export function DestructionListReviewer({ /** * Gets called when the change is confirmed. */ - const handleSubmit = (data: SerializedFormData) => { - const { coReviewer, reviewer, comment } = data as { - comment: string; - reviewer?: string; - coReviewer?: string[]; - }; + const handleSubmit = (data: DestructionListReviewerFormType) => { + const { coReviewer, reviewer, comment } = data; const promises: Promise[] = []; @@ -198,6 +199,7 @@ export function DestructionListReviewer({ {reviewer && ( <> {title} ) : ( destructionList && ( -

{field2Title(destructionList.name, { unHyphen: false })}

+

{string2Title(destructionList.name, { unHyphen: false })}

) )} {logItems?.length ? ( diff --git a/frontend/src/components/ProcessingStatusBadge/ProcessingStatusBadge.tsx b/frontend/src/components/ProcessingStatusBadge/ProcessingStatusBadge.tsx index 4b2ab094f..c2779e8c9 100644 --- a/frontend/src/components/ProcessingStatusBadge/ProcessingStatusBadge.tsx +++ b/frontend/src/components/ProcessingStatusBadge/ProcessingStatusBadge.tsx @@ -1,4 +1,4 @@ -import { Badge, Outline, field2Title } from "@maykin-ui/admin-ui"; +import { Badge, Outline, string2Title } from "@maykin-ui/admin-ui"; import React from "react"; import { ProcessingStatus } from "../../lib/api/processingStatus"; @@ -41,7 +41,7 @@ export const ProcessingStatusBadge: React.FC = ({ } return `Wordt vernietigd ${timeAgo(plannedDestructionDate, { shortFormat: true })}`; } - return field2Title(PROCESSING_STATUS_MAPPING[processingStatus], { + return string2Title(PROCESSING_STATUS_MAPPING[processingStatus], { unHyphen: false, }); }; diff --git a/frontend/src/hooks/useFields.test.ts b/frontend/src/hooks/useFields.test.ts index 2f088262f..7c2afe431 100644 --- a/frontend/src/hooks/useFields.test.ts +++ b/frontend/src/hooks/useFields.test.ts @@ -3,6 +3,7 @@ import { act, renderHook, waitFor } from "@testing-library/react"; import { selectieLijstKlasseFactory as mockSelectieLijstKlasseFactory } from "../fixtures/selectieLijstKlasseChoices"; import * as fieldSelection from "../lib/fieldSelection/fieldSelection"; +import { Zaak } from "../types"; import { useFields } from "./useFields"; jest.mock("./useSelectielijstKlasseChoices", () => ({ diff --git a/frontend/src/hooks/useFields.ts b/frontend/src/hooks/useFields.ts index 39dc46760..d877fd0be 100644 --- a/frontend/src/hooks/useFields.ts +++ b/frontend/src/hooks/useFields.ts @@ -1,4 +1,4 @@ -import { AttributeData, TypedField } from "@maykin-ui/admin-ui"; +import { TypedField, TypedSerializedFormData } from "@maykin-ui/admin-ui"; import { useCallback, useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; @@ -16,17 +16,35 @@ import { ExpandZaak, Zaak } from "../types"; import { useSelectielijstKlasseChoices } from "./useSelectielijstKlasseChoices"; import { useZaaktypeChoices } from "./useZaaktypeChoices"; +type FilterTransformReturnType = Record< + | "startdatum__gte" + | "startdatum__lte" + | "einddatum__gte" + | "einddatum__lte" + | "archiefactiedatum__gte" + | "archiefactiedatum__lte", + string | null +> & + Partial< + Omit< + TypedSerializedFormData, + "startdatum" | "einddatum" | "archiefactiedatum" + > + >; + /** - * Hook resolving zaaktype choices, returns: `[TypedField[], Function]` tuple. + * Hook resolving the base fields for lists. */ -export function useFields( +export function useFields( destructionList?: DestructionList, review?: Review, - extraFields?: TypedField[], + extraFields?: TypedField[], ): [ - TypedField[], - (fields: TypedField[]) => void, - (filterData: AttributeData) => AttributeData, + TypedField[], + (fields: TypedField[]) => void, + ( + filterData: Partial>, + ) => FilterTransformReturnType, ] { const [fieldSelectionState, setFieldSelectionState] = useState(); @@ -45,7 +63,7 @@ export function useFields( // The raw, unfiltered configuration of the available base fields. // NOTE: This get filtered by `getActiveFields()`. - const fields: TypedField[] = [ + const fields: TypedField[] = [ { name: "identificatie", filterLookup: "identificatie__icontains", @@ -190,7 +208,8 @@ export function useFields( const getActiveFields = useCallback(() => { return fields.map((field) => { - const isActiveFromStorage = fieldSelectionState?.[field.name]; + const isActiveFromStorage = + fieldSelectionState?.[field.name as keyof typeof fieldSelectionState]; const isActive = typeof isActiveFromStorage === "undefined" ? field.active !== false @@ -204,7 +223,7 @@ export function useFields( * Pass this to `filterTransform` of a DataGrid component. * @param fields */ - const setFields = async (fields: TypedField[]) => { + const setFields = async (fields: TypedField[]) => { const activeFields = fields.filter((f) => f.active !== false); const inActiveFields = fields.filter((f) => f.active === false); await addToFieldSelection(FIELD_SELECTION_STORAGE_KEY, activeFields); @@ -218,7 +237,9 @@ export function useFields( * Pass this to `filterTransform` of a DataGrid component. * @param filterData */ - const filterTransform = (filterData: AttributeData) => { + const filterTransform = ( + filterData: Partial>, + ): FilterTransformReturnType => { const { startdatum = "", einddatum = "", @@ -226,12 +247,16 @@ export function useFields( ..._filterData } = filterData; - const [startdatum__gte = "", startdatum__lte = ""] = - String(startdatum).split("/"); - const [einddatum__gte = "", einddatum__lte = ""] = - String(einddatum).split("/"); - const [archiefactiedatum__gte = "", archiefactiedatum__lte = ""] = - String(archiefactiedatum).split("/"); + const [startdatum__gte = null, startdatum__lte = null] = (startdatum && + (startdatum as Date[]).map((d) => formatDate(d, "iso"))) || [null, null]; + const [einddatum__gte = null, einddatum__lte = null] = (einddatum && + (einddatum as Date[]).map((d) => formatDate(d, "iso"))) || [null, null]; + const [archiefactiedatum__gte = null, archiefactiedatum__lte = null] = + (archiefactiedatum && + (archiefactiedatum as Date[]).map((d) => formatDate(d, "iso"))) || [ + null, + null, + ]; return { startdatum__gte, diff --git a/frontend/src/hooks/useFilter.ts b/frontend/src/hooks/useFilter.ts index 5c9c807ae..325567e98 100644 --- a/frontend/src/hooks/useFilter.ts +++ b/frontend/src/hooks/useFilter.ts @@ -1,11 +1,12 @@ -import { AttributeData } from "@maykin-ui/admin-ui"; - import { useCombinedSearchParams } from "./useCombinedSearchParams"; /** * Hook providing filter interaction, returns: `[RESERVED, Function]` tuple. */ -export function useFilter(): [object, (filterData: AttributeData) => void] { +export function useFilter(): [ + object, + (filterData: T) => void, +] { const [, setCombinedSearchParams] = useCombinedSearchParams(); // Reserved for possible future expansion (filter state) const RESERVED = {}; @@ -15,9 +16,9 @@ export function useFilter(): [object, (filterData: AttributeData) => void] { * Pass this to `onFilter` of a DataGrid component. * @param filterData */ - const setFilterField = (filterData: AttributeData) => { + const setFilterField = (filterData: T) => { setCombinedSearchParams({ - ...(filterData as AttributeData), + ...(filterData as object), page: "1", }); }; diff --git a/frontend/src/hooks/useZaakSelection.test.ts b/frontend/src/hooks/useZaakSelection.test.ts index 3282ea64f..7844b14eb 100644 --- a/frontend/src/hooks/useZaakSelection.test.ts +++ b/frontend/src/hooks/useZaakSelection.test.ts @@ -1,6 +1,7 @@ import { act, renderHook, waitFor } from "@testing-library/react"; import { ZaakSelectionContextProvider } from "../contexts"; +import { ZaakIdentifier } from "../lib/zaakSelection"; import { Zaak } from "../types"; import { useZaakSelection } from "./useZaakSelection"; @@ -10,10 +11,10 @@ jest.mock("react-router-dom", () => ({ })); describe("useZaakSelection hook", () => { - const zaken = [ - { url: "zaak-1" } as Zaak, - { url: "zaak-2" } as Zaak, - { url: "zaak-3" } as Zaak, + const zaken: ZaakIdentifier[] = [ + { url: "zaak-1" }, + { url: "zaak-2" }, + { url: "zaak-3" }, ]; beforeEach(() => { diff --git a/frontend/src/hooks/useZaakSelection.ts b/frontend/src/hooks/useZaakSelection.ts index 9888d96a1..0ade1b628 100644 --- a/frontend/src/hooks/useZaakSelection.ts +++ b/frontend/src/hooks/useZaakSelection.ts @@ -1,9 +1,9 @@ -import { AttributeData } from "@maykin-ui/admin-ui"; import { useContext, useEffect, useMemo, useState } from "react"; import { ZaakSelectionContext } from "../contexts"; import { SessionStorageBackend, + ZaakIdentifier, ZaakSelection, ZaakSelectionBackend, addToZaakSelection, @@ -15,12 +15,11 @@ import { removeFromZaakSelection, setAllZakenSelected, } from "../lib/zaakSelection"; -import { Zaak } from "../types"; export type ZaakSelectionClearer = () => Promise; export type ZaakSelectionSelectHandler = ( - attributeData: AttributeData[], + zaken: ZaakIdentifier[], selected: boolean, detail?: object, ) => Promise; @@ -34,22 +33,22 @@ export type ZaakSelectionSelectAllPagesHandler = ( * Can be used to filter zaken to set selection for. */ export type ZaakSelectionZaakFilter = ( - zaken: Zaak[], + zaken: ZaakIdentifier[], selected: boolean, pageSpecificZaakSelection: ZaakSelection, -) => Promise; +) => Promise; /** * Function returning `ZaakSelection` `detail` object for `zaak`. * Can be used allow looking up detail object. */ export type ZaakSelectionDetailGetter = ( - zaak: Zaak, + zaak: ZaakIdentifier, pageSpecificZaakSelection: ZaakSelection, ) => Promise; /** - * Hook implementing zaak selection, returns: `[AttributeData[], Function, Object]` tuple. + * Hook implementing zaak selection, returns: `[Zaak[], Function, Object]` tuple. * "Optimistic updates" are implemented meaning the state is updated ahead of the API calls to improve UX. * First items contains the page specific zaak selection. * Second item contains the onSelect update function. @@ -57,18 +56,18 @@ export type ZaakSelectionDetailGetter = ( */ export function useZaakSelection( storageKey: string, - zakenOnPage: Zaak[], + zakenOnPage: ZaakIdentifier[], filterSelectionZaken?: ZaakSelectionZaakFilter, getSelectionDetail?: ZaakSelectionDetailGetter, backend: ZaakSelectionBackend | null = SessionStorageBackend, ): [ - selectedZakenOnPage: Zaak[], + selectedZakenOnPage: ZaakIdentifier[], handleSelect: ZaakSelectionSelectHandler, extra: { hasSelection: boolean; allPagesSelected: boolean; selectionSize: number; - deSelectedZakenOnPage: Zaak[]; + deSelectedZakenOnPage: ZaakIdentifier[]; zaakSelectionOnPage: ZaakSelection; handleSelectAllPages: ZaakSelectionSelectAllPagesHandler; clearZaakSelection: ZaakSelectionClearer; @@ -179,7 +178,7 @@ export function useZaakSelection( * @private */ const _optimisticallyUpdatePageSpecificZaakSelectionState = ( - zaken: Zaak[], + zaken: ZaakIdentifier[], selected: boolean, ) => { const optimisticSelection = zaken.reduce((selection, zaak) => { @@ -264,39 +263,38 @@ export function useZaakSelection( /** * Pass this to `onSelect` of a DataGrid component. - * @param attributeData + * @param zaken * @param selected * @param detail If called directly (not as callback for DataGrid), `detail` * can be passed directly, use `getSelectionDetail` otherwise. */ const onSelect = async ( - attributeData: AttributeData[], + zaken: ZaakIdentifier[], selected: boolean, detail?: object, ) => { if (!backend) return; - const _zaken = attributeData as unknown as Zaak[]; - _optimisticallyUpdatePageSpecificZaakSelectionState(_zaken, selected); + _optimisticallyUpdatePageSpecificZaakSelectionState(zaken, selected); _optimisticallyUpdateSelectionSizeState( selected ? selectionSize + 1 : selectionSize - 1, ); const filter = filterSelectionZaken ? filterSelectionZaken - : (zaken: Zaak[]) => { + : (zaken: ZaakIdentifier[]) => { return zaken; }; const filteredZaken = selected ? await filter( - attributeData as unknown as Zaak[], + zaken, selected, pageSpecificZaakSelection as ZaakSelection, ) - : attributeData.length + : zaken.length ? await filter( - attributeData as unknown as Zaak[], + zaken, selected, pageSpecificZaakSelection as ZaakSelection, ) diff --git a/frontend/src/lib/fieldSelection/fieldSelection.test.ts b/frontend/src/lib/fieldSelection/fieldSelection.test.ts index 00874e269..69199cba5 100644 --- a/frontend/src/lib/fieldSelection/fieldSelection.test.ts +++ b/frontend/src/lib/fieldSelection/fieldSelection.test.ts @@ -1,6 +1,7 @@ import { TypedField } from "@maykin-ui/admin-ui"; import { + FieldSelection, addToFieldSelection, clearFieldSelection, getFieldSelection, @@ -11,7 +12,7 @@ import { describe("fieldSelection", () => { const testKey = "testKey"; - const mockFields: TypedField[] = [ + const mockFields: TypedField>[] = [ { name: "field1", type: "boolean" }, { name: "field2", type: "number" }, { name: "field3", type: "string" }, @@ -45,7 +46,10 @@ describe("fieldSelection", () => { }); test("should retrieve field selection", async () => { - const fieldSelection = { field1: true, field2: false }; + const fieldSelection: FieldSelection> = { + field1: true, + field2: false, + }; await setFieldSelection(testKey, fieldSelection); const result = await getFieldSelection(testKey); diff --git a/frontend/src/lib/fieldSelection/fieldSelection.ts b/frontend/src/lib/fieldSelection/fieldSelection.ts index 4247243a3..1e789198f 100644 --- a/frontend/src/lib/fieldSelection/fieldSelection.ts +++ b/frontend/src/lib/fieldSelection/fieldSelection.ts @@ -1,20 +1,18 @@ import { TypedField } from "@maykin-ui/admin-ui"; -export type FieldSelection = { - /** - * A `Field.name` mapped to a `boolean`. - * - `true`: The field is active - * - `false`: The field is inactive - */ - [index: string]: boolean; -}; +import { Zaak } from "../../types"; + +export type FieldSelection = Record; /** * Note: This function is async to accommodate possible future refactors. * @param key A key identifying the selection * @param fields An array containing either `TypedField` objects. */ -export async function addToFieldSelection(key: string, fields: TypedField[]) { +export async function addToFieldSelection( + key: string, + fields: TypedField[], +) { await _mutateFieldSelection(key, fields, true); } @@ -23,9 +21,9 @@ export async function addToFieldSelection(key: string, fields: TypedField[]) { * @param key A key identifying the selection * @param fields An array containing either `TypedField` objects. */ -export async function removeFromFieldSelection( +export async function removeFromFieldSelection( key: string, - fields: TypedField[], + fields: TypedField[], ) { await _mutateFieldSelection(key, fields, false); } @@ -35,10 +33,10 @@ export async function removeFromFieldSelection( * Note: This function is async to accommodate possible future refactors. * @param key A key identifying the selection */ -export async function getFieldSelection(key: string) { +export async function getFieldSelection(key: string) { const computedKey = _getComputedKey(key); const json = sessionStorage.getItem(computedKey) || "{}"; - return JSON.parse(json) as FieldSelection; + return JSON.parse(json) as FieldSelection; } /** @@ -47,9 +45,9 @@ export async function getFieldSelection(key: string) { * @param key A key identifying the selection * @param fieldSelection */ -export async function setFieldSelection( +export async function setFieldSelection( key: string, - fieldSelection: FieldSelection, + fieldSelection: FieldSelection, ) { const computedKey = _getComputedKey(key); const json = JSON.stringify(fieldSelection); @@ -72,9 +70,12 @@ export async function clearFieldSelection(key: string) { * @param key A key identifying the selection * @param field Either a `Field.name` or `Field` object. */ -export async function isFieldActive(key: string, field: TypedField) { +export async function isFieldActive( + key: string, + field: TypedField, +) { const fieldSelection = await getFieldSelection(key); - return fieldSelection[field.name]; + return fieldSelection[field.name as keyof typeof fieldSelection]; } /** @@ -84,20 +85,20 @@ export async function isFieldActive(key: string, field: TypedField) { * @param fields An array containing either `TypedField` objects. * @param active Indicating whether the selection should be added (`true) or removed (`false). */ -export async function _mutateFieldSelection( +export async function _mutateFieldSelection( key: string, - fields: TypedField[], + fields: TypedField[], active: boolean, ) { const currentFieldSelection = await getFieldSelection(key); const names = fields.map((f) => f.name); - const fieldSelectionOverrides = names.reduce( + const fieldSelectionOverrides = names.reduce>( (partialFieldSelection, url) => ({ ...partialFieldSelection, [url]: active, }), - {}, + {} as FieldSelection, ); const combinedFieldSelection = { diff --git a/frontend/src/lib/zaakSelection/backends/sessionStorage.ts b/frontend/src/lib/zaakSelection/backends/sessionStorage.ts index 1db46645e..558596ad3 100644 --- a/frontend/src/lib/zaakSelection/backends/sessionStorage.ts +++ b/frontend/src/lib/zaakSelection/backends/sessionStorage.ts @@ -1,11 +1,11 @@ import { + ZaakIdentifier, ZaakSelection, ZaakSelectionBackend, ZaakSelectionBackendMeta, _getZaakUrl, _getZaakUrls, } from "../"; -import { Zaak } from "../../../types"; export const SessionStorageBackend: ZaakSelectionBackend = { /** @@ -17,7 +17,7 @@ export const SessionStorageBackend: ZaakSelectionBackend = { */ async addToZaakSelection( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], detail?: DetailType, // eslint-disable-next-line @typescript-eslint/no-unused-vars _?: ZaakSelectionBackendMeta, @@ -33,7 +33,7 @@ export const SessionStorageBackend: ZaakSelectionBackend = { */ async removeFromZaakSelection( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], // eslint-disable-next-line @typescript-eslint/no-unused-vars _?: ZaakSelectionBackendMeta, ) { @@ -137,7 +137,7 @@ export const SessionStorageBackend: ZaakSelectionBackend = { */ async getZaakSelectionItems( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], selectedOnly = true, // eslint-disable-next-line @typescript-eslint/no-unused-vars _?: ZaakSelectionBackendMeta, @@ -176,12 +176,12 @@ export const SessionStorageBackend: ZaakSelectionBackend = { */ export async function _mutateZaakSelection( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], selected: boolean, detail?: DetailType | DetailType[], ) { if (Array.isArray(detail)) { - if (detail.length !== (zaken as Zaak[]).length) { + if (detail.length !== (zaken as ZaakIdentifier[]).length) { throw new Error( "Can't mutate Zaak selection, length of `zaken` is not equal to length of given `detail`!", ); diff --git a/frontend/src/lib/zaakSelection/types.d.ts b/frontend/src/lib/zaakSelection/types.d.ts index 9b690d265..8ce75e303 100644 --- a/frontend/src/lib/zaakSelection/types.d.ts +++ b/frontend/src/lib/zaakSelection/types.d.ts @@ -1,11 +1,17 @@ import { Zaak } from "../../types"; +/** + * A type describing an object with a `Zaak`'s url and optionally other `Zaak` + * attributes. + */ +export type ZaakIdentifier = { url: string } & Partial; + +/** + * A `Zaak.url` mapped to a `boolean`. + * - `true`: The zaak is added to the selection. + * - `false`: The zaak is removed from the selection. + */ export type ZaakSelection = { - /** - * A `Zaak.url` mapped to a `boolean`. - * - `true`: The zaak is added to the selection. - * - `false`: The zaak is removed from the selection. - */ [index: string]: { selected: boolean; detail?: DetailType; @@ -21,7 +27,7 @@ export type ZaakSelectionBackend = { */ addToZaakSelection( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], detail?: DetailType, meta?: ZaakSelectionBackendMeta, ): Promise; @@ -31,7 +37,7 @@ export type ZaakSelectionBackend = { */ removeFromZaakSelection( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], meta?: ZaakSelectionBackendMeta, ): Promise; @@ -75,7 +81,7 @@ export type ZaakSelectionBackend = { */ getZaakSelectionItems( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], selectedOnly?: boolean, meta?: ZaakSelectionBackendMeta, ): Promise>; diff --git a/frontend/src/lib/zaakSelection/utils.ts b/frontend/src/lib/zaakSelection/utils.ts index e125c6034..cbac10d42 100644 --- a/frontend/src/lib/zaakSelection/utils.ts +++ b/frontend/src/lib/zaakSelection/utils.ts @@ -1,10 +1,9 @@ import { isPrimitive } from "@maykin-ui/admin-ui"; -import { Zaak } from "../../types"; -import { ZaakSelection } from "./types"; +import { ZaakIdentifier, ZaakSelection } from "./types"; export function _zaken2zaakSelection( - zaken: Array, + zaken: Array, selected: boolean, detail?: DetailType, ): ZaakSelection { @@ -24,7 +23,7 @@ export function _zaken2zaakSelection( * @param zaken An array containing either `Zaak.url` or `Zaak` objects * @private */ -export function _getZaakUrls(zaken: Array) { +export function _getZaakUrls(zaken: Array) { return zaken.map(_getZaakUrl); } @@ -33,6 +32,6 @@ export function _getZaakUrls(zaken: Array) { * @param zaak Either a `Zaak.url` or `Zaak` object. * @private */ -export function _getZaakUrl(zaak: string | Zaak) { - return isPrimitive(zaak) ? zaak : (zaak.url as string); +export function _getZaakUrl(zaak: string | ZaakIdentifier) { + return isPrimitive(zaak) ? zaak : zaak.url; } diff --git a/frontend/src/lib/zaakSelection/zaakSelection.ts b/frontend/src/lib/zaakSelection/zaakSelection.ts index 2968dd8d2..7d6ed7dae 100644 --- a/frontend/src/lib/zaakSelection/zaakSelection.ts +++ b/frontend/src/lib/zaakSelection/zaakSelection.ts @@ -1,6 +1,9 @@ -import { Zaak } from "../../types"; import { SessionStorageBackend, _getZaakSelection } from "./backends"; -import { ZaakSelection, ZaakSelectionBackendMeta } from "./types"; +import { + ZaakIdentifier, + ZaakSelection, + ZaakSelectionBackendMeta, +} from "./types"; /** * Adds `zaken` to zaak selection identified by key. @@ -16,7 +19,7 @@ import { ZaakSelection, ZaakSelectionBackendMeta } from "./types"; */ export async function addToZaakSelection( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], detail?: DetailType, backend = SessionStorageBackend, meta?: ZaakSelectionBackendMeta, @@ -37,7 +40,7 @@ export async function addToZaakSelection( */ export async function removeFromZaakSelection( key: string, - zaken: (string | Zaak)[], + zaken: (string | ZaakIdentifier)[], backend = SessionStorageBackend, meta?: ZaakSelectionBackendMeta, ) { @@ -139,7 +142,7 @@ export async function getZaakSelectionSize( */ export async function getZaakSelectionItems( key: string, - zaak: (string | Zaak)[], + zaak: (string | ZaakIdentifier)[], selectedOnly = true, backend = SessionStorageBackend, meta?: ZaakSelectionBackendMeta, diff --git a/frontend/src/pages/destructionlist/abstract/BaseListView.tsx b/frontend/src/pages/destructionlist/abstract/BaseListView.tsx index f4955336f..950417e8a 100644 --- a/frontend/src/pages/destructionlist/abstract/BaseListView.tsx +++ b/frontend/src/pages/destructionlist/abstract/BaseListView.tsx @@ -1,5 +1,4 @@ import { - AttributeData, ButtonProps, DataGridProps, ListTemplate, @@ -12,15 +11,12 @@ import React, { useCallback, useMemo } from "react"; import { useNavigation } from "react-router-dom"; import { DestructionListToolbar } from "../../../components"; -import { useFields } from "../../../hooks/useFields"; -import { useFilter } from "../../../hooks/useFilter"; -import { usePage } from "../../../hooks/usePage"; -import { useSort } from "../../../hooks/useSort"; import { ZaakSelectionDetailGetter, ZaakSelectionZaakFilter, useZaakSelection, -} from "../../../hooks/useZaakSelection"; +} from "../../../hooks"; +import { useFields, useFilter, usePage, useSort } from "../../../hooks"; import { DestructionList } from "../../../lib/api/destructionLists"; import { Review } from "../../../lib/api/review"; import { PaginatedZaken } from "../../../lib/api/zaken"; @@ -33,7 +29,7 @@ import { Zaak } from "../../../types"; /** The template used to format urls to an external application providing zaak details. */ const REACT_APP_ZAAK_URL_TEMPLATE = process.env.REACT_APP_ZAAK_URL_TEMPLATE; -export type BaseListViewProps = React.PropsWithChildren<{ +export type BaseListViewProps = React.PropsWithChildren<{ storageKey: string; title?: string; errors?: string | string[]; @@ -50,19 +46,19 @@ export type BaseListViewProps = React.PropsWithChildren<{ initiallySelectedZakenOnPage?: Zaak[]; sortable?: boolean; - extraFields?: TypedField[]; + extraFields?: TypedField[]; filterSelectionZaken?: ZaakSelectionZaakFilter; getSelectionDetail?: ZaakSelectionDetailGetter; - dataGridProps?: Partial; + dataGridProps?: Partial>; enableUseZaakSelection?: boolean; selectionBackend?: ZaakSelectionBackend | null; onClearZaakSelection?: () => void; - onSelectionChange?: (rows: AttributeData[]) => void; + onSelectionChange?: (rows: T[]) => void; }>; -export function BaseListView({ +export function BaseListView({ storageKey, title, errors, @@ -89,24 +85,27 @@ export function BaseListView({ selectionBackend = SessionStorageBackend, onClearZaakSelection, onSelectionChange, -}: BaseListViewProps) { +}: BaseListViewProps) { const { state } = useNavigation(); const [page, setPage] = usePage(); - const [, setFilterField] = useFilter(); const [sort, setSort] = useSort(); // Object list. const objectList = paginatedZaken.results.map((zaak) => ({ ...zaak, href: formatMessage(REACT_APP_ZAAK_URL_TEMPLATE || "", zaak), - })); + })) as unknown as T[]; // Fields. - const [fields, setFields, filterTransform] = useFields( + const [fields, setFields, filterTransform] = useFields( destructionList, review, extraFields, ); + type FilterTransformData = ReturnType; + + // Filter. + const [, setFilterField] = useFilter(); // Selection. const [ @@ -182,58 +181,60 @@ export function BaseListView({ document.documentElement.clientHeight; return ( - errors={errors} secondaryNavigationItems={secondaryNavigationItems} - dataGridProps={{ - aProps: { - target: "_blank", - }, - boolProps: { - explicit: true, - }, - fieldsSelectable: true, - // If no vertical scrolling is applied, used (slower) 100% height to - // push paginator down at bottom of page. - // This triggers a "stickyfill" behaviour which is slower than native - // sticky which is not compatible with the percentage value. - height: hasVerticalOverflow ? undefined : "100%", - pageSize: 100, - showPaginator: true, - selectable: selectable === true, - filterable: true, - tableLayout: "fixed", - - allowSelectAllPages, - allPagesSelected, - count: paginatedZaken.count, - equalityChecker: (a, b) => - a && b && (a.uuid === b.uuid || a.url === b.url), - fields, - filterTransform, - loading: state !== "idle", - objectList: objectList, - page, - sort: sortable && sort, - selected: selectable - ? ([ - ...new Map( - selectedZakenOnPage.map((zaak) => [zaak["uuid"], zaak]), - ).values(), - ] as unknown as AttributeData[]) - : [], - selectionActions: getSelectionActions(), - - onFieldsChange: setFields, - onFilter: setFilterField, - onPageChange: setPage, - onSelect: handleSelect, - onSelectAllPages: handleSelectAllPages, - onSelectionChange: onSelectionChange, - onSort: setSort, - - ...dataGridProps, - }} + dataGridProps={ + { + aProps: { + target: "_blank", + }, + boolProps: { + explicit: true, + }, + fieldsSelectable: true, + // If no vertical scrolling is applied, used (slower) 100% height to + // push paginator down at bottom of page. + // This triggers a "stickyfill" behaviour which is slower than native + // sticky which is not compatible with the percentage value. + height: hasVerticalOverflow ? undefined : "100%", + pageSize: 100, + showPaginator: true, + selectable: selectable === true, + filterable: true, + tableLayout: "fixed", + + allowSelectAllPages, + allPagesSelected, + count: paginatedZaken.count, + equalityChecker: (a, b) => + a && b && (a.uuid === b.uuid || a.url === b.url), + fields: fields, + filterTransform, + loading: state !== "idle", + objectList: objectList, + page, + sort: sortable && sort, + selected: selectable + ? ([ + ...new Map( + selectedZakenOnPage.map((zaak) => [zaak["uuid"], zaak]), + ).values(), + ] as T[]) + : [], + selectionActions: getSelectionActions(), + + onFieldsChange: setFields, + onFilter: setFilterField, + onPageChange: setPage, + onSelect: handleSelect, + onSelectAllPages: handleSelectAllPages, + onSelectionChange: onSelectionChange, + onSort: setSort, + + ...dataGridProps, + } as DataGridProps + } > - formDialog( + formDialog( "Markeer als definitief", undefined, [ @@ -271,7 +279,7 @@ export function useSecondaryNavigation(): ToolbarItem[] { variant: "danger", pad: "h", onClick: () => - formDialog( + formDialog( "Zaken definitief vernietigen", `U staat op het punt om ${destructionListItems.count} zaken definitief te vernietigen`, [ @@ -298,7 +306,7 @@ export function useSecondaryNavigation(): ToolbarItem[] { ), }; - const validateDestroy = ({ name }: AttributeData) => { + const validateDestroy = ({ name }: DestroyFormType) => { // Name can be undefined at a certain point and will crash the entire page if ( (name as string | undefined)?.toLowerCase() === diff --git a/frontend/src/pages/destructionlist/detail/pages/DestructionListEditPage/DestructionListEditPage.tsx b/frontend/src/pages/destructionlist/detail/pages/DestructionListEditPage/DestructionListEditPage.tsx index 3459ba590..7cb80b92b 100644 --- a/frontend/src/pages/destructionlist/detail/pages/DestructionListEditPage/DestructionListEditPage.tsx +++ b/frontend/src/pages/destructionlist/detail/pages/DestructionListEditPage/DestructionListEditPage.tsx @@ -21,6 +21,8 @@ import { import { DestructionListDetailContext } from "../../DestructionListDetail.loader"; import { useSecondaryNavigation } from "../../hooks/useSecondaryNavigation"; +type DestructionListEditData = Zaak & { processingStatus: string }; + /** * Show items of a destruction list. * Allows viewing, adding and removing destruction list items. @@ -53,7 +55,7 @@ export function DestructionListEditPage() { ); // Whether extra fields should be rendered. - const extraFields: TypedField[] = useMemo( + const extraFields: TypedField[] = useMemo( () => !editingState && destructionList.processingStatus !== "new" ? [ @@ -173,7 +175,7 @@ export function DestructionListEditPage() { }; return ( - destructionList={destructionList} extraFields={extraFields} initiallySelectedZakenOnPage={initiallySelectedZakenOnPage} diff --git a/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/DestructionListProcessReviewPage.tsx b/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/DestructionListProcessReviewPage.tsx index 7a8274bfe..f76ffce89 100644 --- a/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/DestructionListProcessReviewPage.tsx +++ b/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/DestructionListProcessReviewPage.tsx @@ -1,9 +1,4 @@ -import { - AttributeData, - Outline, - Toolbar, - TypedField, -} from "@maykin-ui/admin-ui"; +import { Outline, Toolbar, TypedField } from "@maykin-ui/admin-ui"; import React, { useMemo, useState } from "react"; import { useRevalidator, useRouteLoaderData } from "react-router-dom"; @@ -170,7 +165,7 @@ export function DestructionListProcessReviewPage() { // // Remove the zaak from the selection in the background. if (!selected) { - await handleSelect([zaak] as unknown as AttributeData[], false); + await handleSelect([zaak], false); // Call the Route's loader function // @@ -206,7 +201,7 @@ export function DestructionListProcessReviewPage() { // // We add the selected zaak to the zaak selection and add our feedback as // details, this allows us to recover (and submit) the feedback later. - await handleSelect([{ url: zaakUrl }] as unknown as AttributeData[], true, { + await handleSelect([{ url: zaakUrl }], true, { action, selectielijstklasse, archiefactiedatum, diff --git a/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/components/DestructionListProcessZaakReviewModal/DestructionListProcessZaakReviewModal.tsx b/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/components/DestructionListProcessZaakReviewModal/DestructionListProcessZaakReviewModal.tsx index 58e59c183..2415821cd 100644 --- a/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/components/DestructionListProcessZaakReviewModal/DestructionListProcessZaakReviewModal.tsx +++ b/frontend/src/pages/destructionlist/detail/pages/DestructionListProcessReviewPage/components/DestructionListProcessZaakReviewModal/DestructionListProcessZaakReviewModal.tsx @@ -1,5 +1,4 @@ import { - AttributeData, Body, Column, ErrorMessage, @@ -25,6 +24,14 @@ export const LABEL_CHANGE_SELECTION_LIST_CLASS = export const LABEL_POSTPONE_DESTRUCTION = "Verlengen bewaartermijn"; export const LABEL_KEEP = "Afwijzen van het voorstel"; +type DestructionListProcessZaakReviewModalFormType = { + zaakUrl: string; + action: string; + selectielijstklasse: string; + archiefactiedatum: string; + comment: string; +}; + export type DestructionListProcessZaakReviewModalProps = { zaakModalDataState: { open: boolean; @@ -236,7 +243,7 @@ export const DestructionListProcessZaakReviewModal: React.FC< return [...baseFields, ...(_formState.action ? actionSelectedFields : [])]; }; - const validate = (values: AttributeData) => { + const validate = (values: DestructionListProcessZaakReviewModalFormType) => { const action = values.action; /** * Updates the form state, and validates the form. @@ -322,7 +329,7 @@ export const DestructionListProcessZaakReviewModal: React.FC< -
autoComplete="off" justify="stretch" noValidate diff --git a/frontend/src/pages/destructionlist/review/DestructionListReview.tsx b/frontend/src/pages/destructionlist/review/DestructionListReview.tsx index fc2549bcb..ffea767a8 100644 --- a/frontend/src/pages/destructionlist/review/DestructionListReview.tsx +++ b/frontend/src/pages/destructionlist/review/DestructionListReview.tsx @@ -1,5 +1,4 @@ import { - AttributeData, ButtonProps, P, Solid, @@ -24,6 +23,7 @@ import { } from "../../../lib/auth/permissions"; import { RestBackend, + ZaakIdentifier, ZaakSelection, addToZaakSelection, compareZaakSelection, @@ -276,7 +276,7 @@ export function DestructionListReviewPage() { * @param zaak */ async function handleApproveClick(zaak: Zaak) { - return handleSelect([zaak] as unknown as AttributeData[], true, { + return handleSelect([zaak], true, { approved: true, }); } @@ -302,7 +302,7 @@ export function DestructionListReviewPage() { "Zaak uitzonderen", "Annuleren", async (comment) => - handleSelect([zaak] as unknown as AttributeData[], true, { + handleSelect([zaak], true, { approved: false, comment, }), @@ -384,7 +384,7 @@ export function DestructionListReviewPage() { * Gets called when adding item to selection, filtering the selection. */ async function filterSelectionZaken( - zaken: Zaak[], + zaken: ZaakIdentifier[], selected: boolean, pageSpecificZaakSelection: ZaakSelection<{ approved: boolean; @@ -476,7 +476,7 @@ export function DestructionListReviewPage() { * Gets called when adding items to selection using "select all", returning * the detail value. */ - async function getSelectionDetail(zaak: Zaak) { + async function getSelectionDetail(zaak: ZaakIdentifier) { const approved = !((zaak.url as string) in excludedZaakSelection); return { approved, comment: "" }; } diff --git a/frontend/src/pages/destructionlist/utils.ts b/frontend/src/pages/destructionlist/utils.ts deleted file mode 100644 index 5b5fcfc2a..000000000 --- a/frontend/src/pages/destructionlist/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AttributeData } from "@maykin-ui/admin-ui"; - -import { - addToZaakSelection, - removeFromZaakSelection, -} from "../../lib/zaakSelection/zaakSelection"; -import { Zaak } from "../../types"; - -export async function updateSelectedZaken( - selected: boolean, - attributeData: AttributeData[], - destructionListKey: string, - zaken: Zaak[], -) { - selected - ? await addToZaakSelection( - destructionListKey, - attributeData as unknown as Zaak[], - ) - : await removeFromZaakSelection( - destructionListKey, - attributeData.length ? (attributeData as unknown as Zaak[]) : zaken, - ); -} diff --git a/frontend/src/pages/landing/Landing.tsx b/frontend/src/pages/landing/Landing.tsx index e033edf07..09bfae253 100644 --- a/frontend/src/pages/landing/Landing.tsx +++ b/frontend/src/pages/landing/Landing.tsx @@ -1,5 +1,4 @@ import { - AttributeData, Badge, FieldSet, KanbanTemplate, @@ -42,7 +41,7 @@ export type LandingKanbanEntry = { assignees: React.ReactNode; }; -export const STATUSES: FieldSet[] = [ +export const STATUSES: FieldSet[] = [ [ STATUS_MAPPING.new, { @@ -219,7 +218,7 @@ export const Landing = () => { }; return ( - kanbanProps={{ title: "Vernietigingslijsten", fieldsets: STATUSES, @@ -248,9 +247,7 @@ export const Landing = () => { }, ], }, - renderPreview: (object: AttributeData) => { - const entry = object as LandingKanbanEntry; - + renderPreview: (entry) => { if ( entry.processingStatus === "new" && !entry.plannedDestructionDate diff --git a/frontend/src/pages/login/Login.tsx b/frontend/src/pages/login/Login.tsx index 9b9e2e51f..abf748de2 100644 --- a/frontend/src/pages/login/Login.tsx +++ b/frontend/src/pages/login/Login.tsx @@ -1,5 +1,5 @@ import { - AttributeData, + FormProps, LoginTemplate, LoginTemplateProps, forceArray, @@ -26,6 +26,8 @@ const makeRedirectUrl = (oidcLoginUrl: string) => { return loginUrl.href; }; +type LoginFormType = { username: string; password: string }; + /** * Login page */ @@ -60,21 +62,20 @@ export function LoginPage({ ...props }: LoginProps) { ); const { detail, nonFieldErrors, ...errors } = formErrors; - const oidcProps: Partial = {}; + const oidcProps: Partial> = {}; if (oidcEnabled) { oidcProps.urlOidcLogin = makeRedirectUrl(oidcLoginUrl); oidcProps.labelOidcLogin = "Organisatie login"; } return ( - slotPrimaryNavigation={<>} // FIXME: Should be easier to override formProps={{ nonFieldErrors: nonFieldErrors || detail, errors, fields, - onSubmit: (_, data) => - submit(data as AttributeData, { method: "POST" }), + onSubmit: (_, data) => submit(data, { method: "POST" }), }} {...oidcProps} {...props} diff --git a/frontend/src/pages/settings/Settings.tsx b/frontend/src/pages/settings/Settings.tsx index 73a1627dc..b27741b2a 100644 --- a/frontend/src/pages/settings/Settings.tsx +++ b/frontend/src/pages/settings/Settings.tsx @@ -1,11 +1,4 @@ -import { - AttributeData, - Body, - H2, - ListTemplate, - Solid, - useAlert, -} from "@maykin-ui/admin-ui"; +import { Body, H2, ListTemplate, Solid, useAlert } from "@maykin-ui/admin-ui"; import { useCallback, useMemo, useState } from "react"; import { useLoaderData } from "react-router-dom"; @@ -14,7 +7,7 @@ import "./Settings.css"; import { UpdateSettingsAction } from "./settings.action"; import { SettingsContext } from "./settings.loader"; -interface ObjectListItem { +interface ShortProcedureSetting { zaaktype: string; value: string | number; verkorteProcedure: boolean; @@ -29,11 +22,11 @@ export function SettingsPage() { const submitAction = useSubmitAction(); const alert = useAlert(); - const objectList = useMemo( + const objectList = useMemo( () => zaaktypeChoices.map((zaaktype) => ({ - zaaktype: zaaktype.label, - value: zaaktype.value, + zaaktype: zaaktype.label.toString(), + value: zaaktype.value || "", verkorteProcedure: zaaktypesShortProcess.includes( String(zaaktype.value), ), @@ -91,12 +84,17 @@ export function SettingsPage() { alert, ]); - const onSelectionChange = useCallback((selectedRows: AttributeData[]) => { - const newSelected = new Set( - (selectedRows as unknown as ObjectListItem[]).map((row) => row.zaaktype), - ); - setSelectedZaaktypes(newSelected); - }, []); + const onSelectionChange = useCallback( + (selectedRows: ShortProcedureSetting[]) => { + const newSelected = new Set( + (selectedRows as unknown as ShortProcedureSetting[]).map( + (row) => row.zaaktype, + ), + ); + setSelectedZaaktypes(newSelected); + }, + [], + ); const selectedItems = useMemo( () => @@ -105,7 +103,7 @@ export function SettingsPage() { ); return ( - secondaryNavigationItems={[ { children: ( diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index 7aad7a4af..f8110f549 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -2879,7 +2879,7 @@ export interface Zaak { * URL-referentie naar dit object. Dit is de unieke identificatie en locatie van dit object. * @format uri */ - url?: string; + url: string; // FIXME: Manual fix here, removed optional flag. /** * Unieke resource identifier (UUID4) * @format uuid