import { Button, Dialog, Heading, Select, Table } from "@digdir/designsystemet-react"; import { getLocalizedString } from "@olenbetong/appframe-core"; import { type FilterPermission, useDebounce, useFilterPermissions } from "@olenbetong/appframe-react"; import { Combobox } from "@olenbetong/appframe-ds"; import { PersonGroupIcon, PersonIcon, TrashIcon } from "@navikt/aksel-icons"; import { useEffect, useState } from "react"; import "./FilterShareDialog.css"; type PermissionCandidate = { primKey: string; name: string; login: string; type: string; }; export type FilterShareDialogProps = { open: boolean; onClose: () => void; /** `FilterID` of the filter to manage sharing for. */ filterId: number | null; /** Shown in the dialog heading. */ filterName?: string; "data-size"?: "sm" | "md" | "lg"; }; /** * Manages who a saved filter is shared with. * * Only a manager of the filter can load or change the grants — for anyone else * `sstp_WebSiteCMS_FilterBuilder_GetPermissions` returns nothing, so the list * simply stays empty. * * @example * ```jsx * setSharing(false)} /> * ``` */ export function FilterShareDialog({ open, onClose, filterId, filterName, "data-size": size = "sm", }: FilterShareDialogProps) { let { permissions, loading, error, setPermission, removePermission, search } = useFilterPermissions( open ? filterId : null, ); let [candidates, setCandidates] = useState([]); let [searchText, setSearchText] = useState(""); let query = useDebounce(searchText, 300); useEffect(() => { if (!open || query.length < 2) { setCandidates([]); return; } let isCancelled = false; search(query) .then((results) => { if (isCancelled) return; // Anyone who already has a grant is not a candidate for a new one. let granted = new Set(permissions.map((permission) => permission.groupRef)); setCandidates(results.filter((result) => !granted.has(result.primKey))); }) .catch(() => { if (!isCancelled) setCandidates([]); }); return () => { isCancelled = true; }; }, [open, query, search, permissions]); function handleAdd(candidate: PermissionCandidate | null) { if (!candidate) return; setSearchText(""); void setPermission(candidate.primKey, "Reader"); } return ( {filterName ? getLocalizedString("Sharing - {0}").replace("{0}", filterName) : getLocalizedString("Sharing")} options={candidates} value={null} inputValue={searchText} loading={loading} data-size={size} label={getLocalizedString("Add user or group")} placeholder={getLocalizedString("Search...")} getOptionLabel={(option) => option.name} getOptionKey={(option) => option.primKey} // The procedure searches both name and login, so filtering again on // the label would drop everything matched by login. filterOptions={(options) => [...options]} noOptionsText={query.length < 2 ? getLocalizedString("Type to search") : getLocalizedString("No matches")} renderOption={(option) => ( {option.type === "User" ? : } {option.name} {option.login} )} // The picker never keeps a selection; picking an option adds a grant. onInputChange={(_event, value) => setSearchText(value)} onChange={(_event, value) => handleAdd(value)} /> {error &&

{String(error.message ?? error)}

} {getLocalizedString("Name")} {getLocalizedString("Login")} {getLocalizedString("Access")} {permissions.length === 0 && ( {loading ? getLocalizedString("Loading...") : getLocalizedString("Not shared with anyone")} )} {permissions.map((permission) => ( void setPermission(permission.groupRef, accessLevel)} onRemove={() => void removePermission(permission.groupRef)} /> ))}
); } type PermissionRowProps = { permission: FilterPermission; size: "sm" | "md" | "lg"; onChangeAccess: (accessLevel: "Reader" | "Manager") => void; onRemove: () => void; }; function PermissionRow({ permission, size, onChangeAccess, onRemove }: PermissionRowProps) { return ( {permission.type === "User" ? : } {permission.name} {permission.login} ); }