Files
DentalManagementMH07/apps/Frontend/src/pages/cloud-storage-page.jsx
Gitead 1edf73fdc8 feat: add new frontend components, MH batch worker, and gitignore rules
- 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>
2026-06-26 00:23:43 -04:00

148 lines
6.2 KiB
JavaScript

import { useEffect, useRef, useState } from "react";
import { useSearch } from "wouter";
import { useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Folder as FolderIcon } from "lucide-react";
import { apiRequest } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
import { NewFolderModal } from "@/components/cloud-storage/new-folder-modal";
import FolderPanel from "@/components/cloud-storage/folder-panel";
import { useAuth } from "@/hooks/use-auth";
import RecentTopLevelFoldersCard, { recentTopLevelFoldersQueryKey, } from "@/components/cloud-storage/recent-top-level-folder-modal";
import CloudSearchBar, { cloudSearchQueryKeyRoot, } from "@/components/cloud-storage/search-bar";
import FilePreviewModal from "@/components/cloud-storage/file-preview-modal";
import { cloudFilesQueryKeyRoot } from "@/components/cloud-storage/files-section";
export default function CloudStoragePage() {
const { toast } = useToast();
const { user } = useAuth();
const qc = useQueryClient();
const search = useSearch();
// panel open + initial folder id to show when opening
const [panelOpen, setPanelOpen] = useState(false);
const [panelInitialFolderId, setPanelInitialFolderId] = useState(null);
const panelRef = useRef(null);
// Deep-link: if navigated here with ?folderId=XXX, open that folder automatically
useEffect(() => {
const params = new URLSearchParams(search);
const id = Number(params.get("folderId"));
if (id > 0) {
setPanelInitialFolderId(id);
setPanelOpen(true);
}
}, [search]);
// key to remount recent card to clear its internal selection when needed
const [recentKey, setRecentKey] = useState(0);
// New folder modal
const [isNewFolderOpen, setIsNewFolderOpen] = useState(false);
// searchbar - file preview modal state
const [previewFileId, setPreviewFileId] = useState(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
// searchbar - handlers
function handleOpenFolder(folderId) {
setPanelInitialFolderId(folderId);
setPanelOpen(true);
setTimeout(() => {
panelRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
}, 50);
}
function handleSelectFile(fileId) {
setPreviewFileId(fileId);
setIsPreviewOpen(true);
}
// create folder handler (page-level)
async function handleCreateFolder(name) {
try {
const userId = user?.id;
if (!userId) {
toast({
title: "Sign in required",
description: "Please sign in to create a folder.",
});
return;
}
const res = await apiRequest("POST", `/api/cloud-storage/folders`, {
userId,
name,
parentId: null,
});
const json = await res.json();
if (!res.ok)
throw new Error(json?.message || "Failed to create folder");
toast({ title: "Folder created" });
// close modal
setIsNewFolderOpen(false);
// Invalidate recent folders page 1 so RecentFoldersCard will refresh.
qc.invalidateQueries({ queryKey: recentTopLevelFoldersQueryKey(1) });
}
catch (err) {
toast({ title: "Error", description: err?.message || String(err) });
}
}
return (<div className="container mx-auto space-y-6">
{/* Header / actions */}
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Cloud Storage</h1>
<p className="text-muted-foreground">
Manage Files and Folders in Cloud Storage.
</p>
</div>
<div className="flex gap-2 items-center">
<Button onClick={() => setIsNewFolderOpen(true)}>
<FolderIcon className="h-4 w-4 mr-2"/> New Folder
</Button>
</div>
</div>
<CloudSearchBar onOpenFolder={(folderId) => {
handleOpenFolder(folderId);
}} onSelectFile={(fileId) => {
handleSelectFile(fileId);
}}/>
{/* Recent folders card (delegated component) */}
<RecentTopLevelFoldersCard key={recentKey} pageSize={10} initialPage={1} onSelect={(folderId) => {
setPanelInitialFolderId(folderId);
setPanelOpen(true);
}}/>
{/* FolderPanel lives in page so it can be reused with other UI */}
<div ref={panelRef}/>
{panelOpen && (<FolderPanel folderId={panelInitialFolderId} onClose={() => setPanelOpen(false)} onViewChange={(viewedId) => {
// If the panel navigates back to root, clear recent card selection by remounting it
if (viewedId === null) {
setRecentKey((k) => k + 1);
// clear the panel initial id and close the panel so child folder/file sections hide
setPanelInitialFolderId(null);
setPanelOpen(false);
}
}}/>)}
{/* File preview modal */}
<FilePreviewModal fileId={previewFileId} isOpen={isPreviewOpen} onClose={() => {
setIsPreviewOpen(false);
setPreviewFileId(null);
}} onDeleted={() => {
// close preview (modal may already close, but be explicit)
setIsPreviewOpen(false);
setPreviewFileId(null);
// refresh the recent folders card
qc.invalidateQueries({ queryKey: recentTopLevelFoldersQueryKey(1) });
// invalidate file lists and search results
qc.invalidateQueries({
queryKey: cloudFilesQueryKeyRoot,
exact: false,
});
qc.invalidateQueries({
queryKey: cloudSearchQueryKeyRoot,
exact: false,
});
// remount the recent card so it clears internal selection (UX nicety)
setRecentKey((k) => k + 1);
}}/>
{/* New folder modal (reusable) */}
<NewFolderModal isOpen={isNewFolderOpen} onClose={() => setIsNewFolderOpen(false)} onSubmit={handleCreateFolder}/>
</div>);
}