feat: select users in file access explanations

This commit is contained in:
2026-08-20 06:16:43 +02:00
parent a9c7a7a40c
commit 95aef18955
3 changed files with 53 additions and 9 deletions
+6 -2
View File
@@ -10,11 +10,15 @@ import {
type FilesManagedFileLinkTarget,
type ReferenceOptionProvider
} from "@govoplan/core-webui";
export { fetchResourceAccessExplanation } from "@govoplan/core-webui";
export {
fetchResourceAccessExplanation,
fetchResourceAccessExplanationSubjects
} from "@govoplan/core-webui";
export type {
AccessDecisionProvenanceItem,
ResourceAccessExplanationUser as AccessExplanationUser,
ResourceAccessExplanationResponse
ResourceAccessExplanationResponse,
ResourceAccessExplanationSubjectsResponse
} from "@govoplan/core-webui";
export type FileSpace = {
+44 -5
View File
@@ -29,6 +29,7 @@ import {
downloadFile,
downloadFilesAsZip,
fetchResourceAccessExplanation,
fetchResourceAccessExplanationSubjects,
listFilesDelta,
listFilesByProperties,
listFileConnectorProfiles,
@@ -52,7 +53,8 @@ import {
type FileSpace,
type ManagedFile,
type RenameResponse,
type ResourceAccessExplanationResponse } from
type ResourceAccessExplanationResponse,
type ResourceAccessExplanationSubjectsResponse } from
"../../api/files";
import { EMPTY_FILES, EMPTY_FOLDERS, EMPTY_SPACES, INTERNAL_DRAG_TYPE } from "./constants";
import { FileConflictDialog, FileContextMenu, FileDialog, FolderTree, RenamePreviewList, TransferFolderSelector } from "./components/FileManagerComponents";
@@ -176,6 +178,8 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut
const [error, setError] = useState("");
const [accessExplanationTarget, setAccessExplanationTarget] = useState<FileAccessExplanationTarget | null>(null);
const [resourceAccessExplanation, setResourceAccessExplanation] = useState<ResourceAccessExplanationResponse | null>(null);
const [resourceAccessSubjects, setResourceAccessSubjects] = useState<ResourceAccessExplanationSubjectsResponse | null>(null);
const [resourceAccessSubjectId, setResourceAccessSubjectId] = useState("");
const [resourceAccessLoading, setResourceAccessLoading] = useState(false);
const [shareDialogFile, setShareDialogFile] = useState<ManagedFile | null>(null);
const { setDragActive, internalDrag, setInternalDrag, dropTargetKey, setDropTargetKey, clearDropState: clearDragDropState } = useFileDragDropState();
@@ -1911,14 +1915,25 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut
}
async function openAccessExplanation(target: FileAccessExplanationTarget): Promise<void> {
if (!auth.user?.id) return;
const currentUserId = auth.principal?.membership_id || auth.user?.id;
if (!currentUserId) return;
setAccessExplanationTarget(target);
setResourceAccessExplanation(null);
setResourceAccessSubjects(null);
setResourceAccessLoading(true);
setError("");
try {
const subjects = await fetchResourceAccessExplanationSubjects(settings, {
tenantId: (auth.active_tenant ?? auth.tenant)?.id
});
const initialSubjectId = subjects.users.some((user) => user.id === currentUserId)
? currentUserId
: subjects.users[0]?.id;
if (!initialSubjectId) throw new Error("No permitted access-explanation subject is available.");
setResourceAccessSubjects(subjects);
setResourceAccessSubjectId(initialSubjectId);
setResourceAccessExplanation(await fetchResourceAccessExplanation(settings, {
userId: auth.user.id,
userId: initialSubjectId,
resourceType: target.resourceType,
resourceId: target.resourceId,
action: target.action,
@@ -1932,6 +1947,27 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut
}
}
async function selectAccessExplanationSubject(userId: string): Promise<void> {
if (!accessExplanationTarget || userId === resourceAccessSubjectId) return;
setResourceAccessLoading(true);
setError("");
try {
const explanation = await fetchResourceAccessExplanation(settings, {
userId,
resourceType: accessExplanationTarget.resourceType,
resourceId: accessExplanationTarget.resourceId,
action: accessExplanationTarget.action,
tenantId: (auth.active_tenant ?? auth.tenant)?.id
});
setResourceAccessExplanation(explanation);
setResourceAccessSubjectId(userId);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setResourceAccessLoading(false);
}
}
function openTransferDialogForContext(menu: ContextMenuState | null, mode: TransferMode) {
const sets = selectedSetsForContext(menu);
const space = spaceForContext(menu);
@@ -2595,13 +2631,16 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut
}
{accessExplanationTarget &&
<FileDialog title="i18n:govoplan-core.access_explanation.75ee7f62" onClose={() => {if (!resourceAccessLoading) {setAccessExplanationTarget(null);setResourceAccessExplanation(null);}}}>
<FileDialog title="i18n:govoplan-core.access_explanation.75ee7f62" onClose={() => {if (!resourceAccessLoading) {setAccessExplanationTarget(null);setResourceAccessExplanation(null);setResourceAccessSubjects(null);setResourceAccessSubjectId("");}}}>
<ResourceAccessExplanation
loading={resourceAccessLoading}
explanation={resourceAccessExplanation}
subjects={resourceAccessSubjects}
selectedUserId={resourceAccessSubjectId}
onSelectedUserIdChange={(userId) => void selectAccessExplanationSubject(userId)}
fallbackResourceLabel={accessExplanationTarget.label} />
<div className="button-row compact-actions align-end">
<Button onClick={() => {setAccessExplanationTarget(null);setResourceAccessExplanation(null);}} disabled={resourceAccessLoading}>i18n:govoplan-files.close.bbfa773e</Button>
<Button onClick={() => {setAccessExplanationTarget(null);setResourceAccessExplanation(null);setResourceAccessSubjects(null);setResourceAccessSubjectId("");}} disabled={resourceAccessLoading}>i18n:govoplan-files.close.bbfa773e</Button>
</div>
</FileDialog>
}