diff --git a/frontend/src/lib/zaakSelection/zaakSelection.ts b/frontend/src/lib/zaakSelection/zaakSelection.ts new file mode 100644 index 000000000..01a67a0b3 --- /dev/null +++ b/frontend/src/lib/zaakSelection/zaakSelection.ts @@ -0,0 +1,135 @@ +import { isPrimitive } from "@maykin-ui/admin-ui"; + +import { Zaak } from "../../types"; + +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]: boolean; +}; + +/** + * Adds `zaken` to zaak selection identified by key. + * Note: only the `url` of selected `zaken` are stored. + * Note: This function is async to accommodate possible future refactors. + * @param key A key identifying the selection + * @param zaken An array containing either `Zaak.url` or `Zaak` objects + */ +export async function addToZaakSelection( + key: string, + zaken: string[] | Zaak[], +) { + await _mutateZaakSelection(key, zaken, true); +} + +/** + * Removes `zaken` from zaak selection identified by key. + * Note: only the `url` of selected `zaken` are stored. + * Note: This function is async to accommodate possible future refactors. + * @param key A key identifying the selection + * @param zaken An array containing either `Zaak.url` or `Zaak` objects + */ +export async function removeFromZaakSelection( + key: string, + zaken: string[] | Zaak[], +) { + await _mutateZaakSelection(key, zaken, false); +} + +/** + * Gets the zaak selection. + * Note: only the `url` of selected `zaken` are stored. + * Note: This function is async to accommodate possible future refactors. + * @param key A key identifying the selection + */ +export async function getZaakSelection(key: string) { + const computedKey = _getComputedKey(key); + const json = sessionStorage.getItem(computedKey) || "{}"; + return JSON.parse(json) as ZaakSelection; +} + +/** + * Sets zaak selection cache. + * Note: only the `url` of selected `zaken` are stored. + * Note: This function is async to accommodate possible future refactors. + * @param key A key identifying the selection + * @param zaakSelection + */ +export async function setZaakSelection( + key: string, + zaakSelection: ZaakSelection, +) { + const computedKey = _getComputedKey(key); + const json = JSON.stringify(zaakSelection); + sessionStorage.setItem(computedKey, json); +} + +/** + * Returns whether zaak is selected. + * @param key A key identifying the selection + * @param zaak Either a `Zaak.url` or `Zaak` object. + */ +export async function isZaakSelected(key: string, zaak: string | Zaak) { + const zaakSelection = await getZaakSelection(key); + const url = _getZaakUrl(zaak); + return zaakSelection[url]; +} + +/** + * Mutates the zaak selection + * Note: only the `url` of selected `zaken` are stored. + * Note: This function is async to accommodate possible future refactors. + * @param key A key identifying the selection + * @param zaken An array containing either `Zaak.url` or `Zaak` objects + * @param selected Indicating whether the selection should be added (`true) or removed (`false). + */ +export async function _mutateZaakSelection( + key: string, + zaken: string[] | Zaak[], + selected: boolean, +) { + const currentZaakSelection = await getZaakSelection(key); + const urls = _getZaakUrls(zaken); + + const zaakSelectionOverrides = urls.reduce( + (partialZaakSelection, url) => ({ + ...partialZaakSelection, + [url]: selected, + }), + {}, + ); + + const combinedZaakSelection = { + ...currentZaakSelection, + ...zaakSelectionOverrides, + }; + + await setZaakSelection(key, combinedZaakSelection); +} + +/** + * Computes the prefixed cache key. + * @param key A key identifying the selection + */ +function _getComputedKey(key: string): string { + return `oab.lib.zaakSelection.${key}`; +} + +/** + * Returns the urls based on an `Array` of `string`s or `Zaak` objects. + * @param zaken An array containing either `Zaak.url` or `Zaak` objects + */ +function _getZaakUrls(zaken: Array) { + return zaken.map(_getZaakUrl); +} + +/** + * Returns the url based on a `string` or `Zaak` object. + * @param zaak Either a `Zaak.url` or `Zaak` object. + */ +function _getZaakUrl(zaak: string | Zaak) { + return isPrimitive(zaak) ? zaak : (zaak.url as string); +} diff --git a/frontend/src/pages/destructionlist/DestructionListCreate.tsx b/frontend/src/pages/destructionlist/DestructionListCreate.tsx index e3625e418..3e71cb675 100644 --- a/frontend/src/pages/destructionlist/DestructionListCreate.tsx +++ b/frontend/src/pages/destructionlist/DestructionListCreate.tsx @@ -14,11 +14,20 @@ import { import { loginRequired } from "../../lib/api/loginRequired"; import { ZaaktypeChoice, listZaaktypeChoices } from "../../lib/api/private"; import { PaginatedZaken, listZaken } from "../../lib/api/zaken"; +import { + addToZaakSelection, + isZaakSelected, + removeFromZaakSelection, +} from "../../lib/zaakSelection/zaakSelection"; import { Zaak } from "../../types"; import "./DestructionListCreate.css"; +/** We need a key to store the zaak selection to, however we don't have a destruction list name yet. */ +const DESTRUCTION_LIST_CREATE_KEY = "tempDestructionList"; + export type DestructionListCreateContext = { zaken: PaginatedZaken; + selectedZaken: Zaak[]; zaaktypeChoices: ZaaktypeChoice[]; }; @@ -30,9 +39,21 @@ export const destructionListCreateLoader = loginRequired( async ({ request }) => { const searchParams = new URL(request.url).searchParams; searchParams.set("not_in_destruction_list", "true"); + + // Get zaken and zaaktypen. const zaken = await listZaken(searchParams); const zaaktypeChoices = await listZaaktypeChoices(); - return { zaken, zaaktypeChoices }; + + // Get zaak selection. + const isZaakSelectedPromises = zaken.results.map((zaak) => + isZaakSelected(DESTRUCTION_LIST_CREATE_KEY, zaak), + ); + const isZaakSelectedResults = await Promise.all(isZaakSelectedPromises); + const selectedZaken = zaken.results.filter( + (_, index) => isZaakSelectedResults[index], + ); + + return { zaken, selectedZaken, zaaktypeChoices }; }, ); @@ -47,7 +68,7 @@ export type DestructionListCreateProps = Omit< export function DestructionListCreatePage({ ...props }: DestructionListCreateProps) { - const { zaken, zaaktypeChoices } = + const { zaken, selectedZaken, zaaktypeChoices } = useLoaderData() as DestructionListCreateContext; const [searchParams, setSearchParams] = useSearchParams(); const objectList = zaken.results as unknown as AttributeData[]; @@ -133,8 +154,11 @@ export function DestructionListCreatePage({ }, ]; + /** + * Gets called when a filter value is change. + * @param filterData + */ const onFilter = (filterData: AttributeData) => { - // TODO: Fill filter fields with current value const combinedParams = { ...Object.fromEntries(searchParams), ...filterData, @@ -147,6 +171,28 @@ export function DestructionListCreatePage({ setSearchParams(activeParams); }; + /** + * Gets called when the selection is changed. + * @param attributeData + * @param selected + */ + const onSelect = async ( + attributeData: AttributeData[], + selected: boolean, + ) => { + selected + ? await addToZaakSelection( + DESTRUCTION_LIST_CREATE_KEY, + attributeData as unknown as Zaak[], + ) + : await removeFromZaakSelection( + DESTRUCTION_LIST_CREATE_KEY, + attributeData.length + ? (attributeData as unknown as Zaak[]) + : zaken.results, + ); + }; + return ( console.log(...args), - // }, - // ], - // TODO: Keep track of selected/unselected state. - onSelectionChange: (...args) => - console.log("onSelectionChange", args), + onSelect: onSelect, onPageChange: (page) => setSearchParams({ ...Object.fromEntries(searchParams),