- Add all new Frontend source files (pages, components, hooks, utils) - Add selenium_MHBatchPaymentCheckWorker.py and MHSinglePaymentCheckWorker.py - Add install-steps-5-13.sh setup script - Update .gitignore to exclude runtime/sensitive data (backups, uploads, chat-history, keys, downloads, generated .d.ts files) while keeping folders - Add .gitkeep to preserve empty runtime folders in git Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
120 lines
5.0 KiB
JavaScript
120 lines
5.0 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
|
import FolderSection from "@/components/cloud-storage/folder-section";
|
|
import FilesSection from "@/components/cloud-storage/files-section";
|
|
import { apiRequest } from "@/lib/queryClient";
|
|
import { Breadcrumbs } from "./bread-crumb";
|
|
export default function FolderPanel({ folderId, onClose, onViewChange, }) {
|
|
const [currentFolderId, setCurrentFolderId] = useState(folderId ?? null);
|
|
const [path, setPath] = useState([]);
|
|
const [isLoadingPath, setIsLoadingPath] = useState(false);
|
|
// When the panel opens to a different initial folder, sync and notify parent
|
|
useEffect(() => {
|
|
setCurrentFolderId(folderId ?? null);
|
|
onViewChange?.(folderId ?? null);
|
|
}, [folderId, onViewChange]);
|
|
// notify parent when viewed folder changes
|
|
useEffect(() => {
|
|
onViewChange?.(currentFolderId);
|
|
}, [currentFolderId, onViewChange]);
|
|
// whenever currentFolderId changes we load the ancestor path
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
async function buildPath(fid) {
|
|
setIsLoadingPath(true);
|
|
try {
|
|
// We'll build path from root -> ... -> current. Since we don't know
|
|
// if backend provides a single endpoint for ancestry, we'll fetch
|
|
// current folder and walk parents until null. If fid is null then path is empty.
|
|
if (fid == null) {
|
|
if (mounted)
|
|
setPath([]);
|
|
return;
|
|
}
|
|
const collected = [];
|
|
let cursor = fid;
|
|
// keep a safety cap to avoid infinite loop in case of cycles
|
|
const MAX_DEPTH = 50;
|
|
let depth = 0;
|
|
while (cursor != null && depth < MAX_DEPTH) {
|
|
const res = await apiRequest("GET", `/api/cloud-storage/folders/${cursor}`);
|
|
const json = await res.json();
|
|
if (!res.ok)
|
|
throw new Error(json?.message || "Failed to fetch folder");
|
|
const folder = json?.data ?? json ?? null;
|
|
// normalize
|
|
const meta = {
|
|
id: folder?.id ?? null,
|
|
name: folder?.name ?? null,
|
|
parentId: folder?.parentId ?? null,
|
|
};
|
|
// prepend (we are walking up) then continue with parent
|
|
collected.push(meta);
|
|
cursor = meta.parentId;
|
|
depth += 1;
|
|
}
|
|
// collected currently top-down from current -> root. We need root->...->current
|
|
const rootToCurrent = collected.slice().reverse();
|
|
// we don't include the root (null) as an explicit item; Breadcrumbs shows "My Cloud Storage"
|
|
if (mounted)
|
|
setPath(rootToCurrent);
|
|
}
|
|
catch (err) {
|
|
console.error("buildPath error", err);
|
|
if (mounted)
|
|
setPath([]);
|
|
}
|
|
finally {
|
|
if (mounted)
|
|
setIsLoadingPath(false);
|
|
}
|
|
}
|
|
buildPath(currentFolderId);
|
|
return () => {
|
|
mounted = false;
|
|
};
|
|
}, [currentFolderId]);
|
|
// handler when child folder is clicked inside FolderSection
|
|
function handleChildSelect(childFolderId) {
|
|
// if user re-clicks current folder id as toggle, we still want to navigate into it.
|
|
setCurrentFolderId(childFolderId);
|
|
onViewChange?.(childFolderId); // keep page in sync
|
|
}
|
|
// navigate via breadcrumb (id may be null for root)
|
|
function handleNavigateTo(id) {
|
|
setCurrentFolderId(id);
|
|
onViewChange?.(id);
|
|
}
|
|
return (<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-2xl font-semibold">
|
|
{currentFolderId == null
|
|
? "My Cloud Storage"
|
|
: `Folder : ${path[path.length - 1]?.name ?? currentFolderId}`}
|
|
</h2>
|
|
<div>
|
|
{onClose && (<button onClick={onClose} className="inline-flex items-center px-3 py-1.5 rounded-md text-sm hover:bg-gray-100">
|
|
Close
|
|
</button>)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Breadcrumb / path strip */}
|
|
<div>
|
|
{/* show breadcrumbs even if loading; breadcrumbs show 'My Cloud Storage' + path */}
|
|
<Breadcrumbs path={path} onNavigate={handleNavigateTo}/>
|
|
</div>
|
|
|
|
{/* stacked vertically: folders on top, files below */}
|
|
<div className="flex flex-col gap-6">
|
|
<div className="w-full">
|
|
{/* pass onSelect so FolderSection can tell the panel to navigate into a child */}
|
|
<FolderSection parentId={currentFolderId} onSelect={handleChildSelect}/>
|
|
</div>
|
|
|
|
<div className="w-full">
|
|
<FilesSection parentId={currentFolderId}/>
|
|
</div>
|
|
</div>
|
|
</div>);
|
|
}
|