feat(webui): complete files quick access
This commit is contained in:
@@ -470,7 +470,7 @@ payload: {owner_type: "user" | "group";owner_id: string;path: string;recursive?:
|
||||
return apiFetch<FolderDeleteResponse>(settings, "/api/v1/files/folders/delete", { method: "POST", body: JSON.stringify({ recursive: true, ...payload }) });
|
||||
}
|
||||
|
||||
export function listFiles(settings: ApiSettings, params: {owner_type?: string;owner_id?: string;campaign_id?: string;path_prefix?: string;campaign_usage?: FileCampaignUsageFilter;audit_relevant?: boolean;page_size?: number;cursor?: string | null;} = {}): Promise<FileListResponse> {
|
||||
export function listFiles(settings: ApiSettings, params: {owner_type?: string;owner_id?: string;campaign_id?: string;path_prefix?: string;campaign_usage?: FileCampaignUsageFilter;audit_relevant?: boolean;sort?: "path" | "recent";page_size?: number;cursor?: string | null;} = {}): Promise<FileListResponse> {
|
||||
const search = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null && value !== "") search.set(key, String(value));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FileText, X } from "lucide-react";
|
||||
import { FileText, UploadCloud, X } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
import { Link } from "react-router";
|
||||
import {
|
||||
Button,
|
||||
DismissibleAlert,
|
||||
@@ -7,6 +8,8 @@ import {
|
||||
SelectionList,
|
||||
SelectionListItem,
|
||||
SelectionListItemContent,
|
||||
hasScope,
|
||||
quickAccessLaunchState,
|
||||
useDashboardWidgetData,
|
||||
type QuickAccessToolRenderContext
|
||||
} from "@govoplan/core-webui";
|
||||
@@ -14,7 +17,7 @@ import { listFiles } from "../../api/files";
|
||||
|
||||
type Props = Pick<
|
||||
QuickAccessToolRenderContext,
|
||||
"settings" | "launchContext" | "complete" | "cancel"
|
||||
"settings" | "auth" | "launchContext" | "complete" | "cancel" | "close"
|
||||
>;
|
||||
|
||||
/**
|
||||
@@ -23,15 +26,18 @@ type Props = Pick<
|
||||
*/
|
||||
export default function FileQuickAccess({
|
||||
settings,
|
||||
auth,
|
||||
launchContext,
|
||||
complete,
|
||||
cancel
|
||||
cancel,
|
||||
close
|
||||
}: Props) {
|
||||
const load = useCallback(
|
||||
async () => (await listFiles(settings, { page_size: 7 })).files,
|
||||
async () => (await listFiles(settings, { sort: "recent", page_size: 7 })).files,
|
||||
[settings]
|
||||
);
|
||||
const { data: files, loading, error } = useDashboardWidgetData(load, 0);
|
||||
const canUpload = hasScope(auth, "files:upload");
|
||||
|
||||
return (
|
||||
<LoadingFrame loading={loading} label="Loading recent files">
|
||||
@@ -74,7 +80,17 @@ export default function FileQuickAccess({
|
||||
<p className="muted">No authorized files are available.</p>
|
||||
) : null}
|
||||
<div className="button-row compact-actions">
|
||||
<Button onClick={() => cancel("user")}><X size={15} aria-hidden="true" /> Cancel selection</Button>
|
||||
{canUpload ? (
|
||||
<Link
|
||||
className="btn btn-secondary"
|
||||
to="/files?quickAction=upload"
|
||||
state={quickAccessLaunchState(launchContext)}
|
||||
onClick={close}
|
||||
>
|
||||
<UploadCloud size={15} aria-hidden="true" /> i18n:govoplan-files.upload.8bdf057f
|
||||
</Link>
|
||||
) : null}
|
||||
<Button onClick={() => cancel("user")}><X size={15} aria-hidden="true" /> i18n:govoplan-files.cancel.77dfd213</Button>
|
||||
</div>
|
||||
</LoadingFrame>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { FormGrid, ActionToolbar,
|
||||
type ApiSettings,
|
||||
type AuthInfo, i18nMessage } from
|
||||
"@govoplan/core-webui";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import {
|
||||
bulkDeleteFiles,
|
||||
bulkRenameFiles,
|
||||
@@ -114,12 +115,15 @@ const FILES_WORKFLOW_DOCUMENTATION = {
|
||||
} as const;
|
||||
|
||||
export default function FilesPage({ settings, auth }: {settings: ApiSettings;auth: AuthInfo;}) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const canDownload = hasScope(auth, "files:download");
|
||||
const canUpload = hasScope(auth, "files:upload");
|
||||
const canOrganize = hasScope(auth, "files:organize");
|
||||
const canDelete = hasScope(auth, "files:delete");
|
||||
const canShare = hasScope(auth, "files:file:share");
|
||||
const [spaces, setSpaces] = useState<FileSpace[]>(EMPTY_SPACES);
|
||||
const [spacesLoaded, setSpacesLoaded] = useState(false);
|
||||
const [activeSpaceId, setActiveSpaceId] = useState("");
|
||||
const activeSpace = spaces.find((space) => space.id === activeSpaceId) ?? spaces[0] ?? null;
|
||||
const activeSpaceIsConnector = activeSpace?.space_type === "connector";
|
||||
@@ -330,6 +334,7 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut
|
||||
}, [activeSpaceId, currentFolder, propertyFiltersActive, propertyFilterResults, searchActive, searchResults, sortColumn, sortDirection]);
|
||||
|
||||
async function loadSpaces() {
|
||||
setSpacesLoaded(false);
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
@@ -341,6 +346,7 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setSpacesLoaded(true);
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
@@ -434,6 +440,40 @@ export default function FilesPage({ settings, auth }: {settings: ApiSettings;aut
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [settings.apiBaseUrl, settings.apiKey, settings.accessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
const parameters = new URLSearchParams(location.search);
|
||||
if (parameters.get("quickAction") !== "upload" || !spacesLoaded) return;
|
||||
|
||||
const uploadSpace =
|
||||
activeSpace && !isConnectorSpace(activeSpace)
|
||||
? activeSpace
|
||||
: spaces.find((space) => !isConnectorSpace(space)) ?? null;
|
||||
if (canUpload && uploadSpace) {
|
||||
setActiveSpaceId(uploadSpace.id);
|
||||
openDialog("upload", { spaceId: uploadSpace.id, folderPath: "" });
|
||||
} else if (!canUpload) {
|
||||
setError("File upload permission is required.");
|
||||
} else {
|
||||
setError("No authorized managed file space is available for upload.");
|
||||
}
|
||||
|
||||
parameters.delete("quickAction");
|
||||
const search = parameters.toString();
|
||||
navigate(
|
||||
{ pathname: location.pathname, search: search ? `?${search}` : "" },
|
||||
{ replace: true, state: location.state }
|
||||
);
|
||||
}, [
|
||||
activeSpace,
|
||||
canUpload,
|
||||
location.pathname,
|
||||
location.search,
|
||||
location.state,
|
||||
navigate,
|
||||
spaces,
|
||||
spacesLoaded
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!contextMenu) return undefined;
|
||||
const close = () => setContextMenu(null);
|
||||
|
||||
Reference in New Issue
Block a user