Files
DentalManagementMH07/apps/Frontend/src/components/database-management/folder-browser-modal.tsx
2026-05-04 00:52:42 -04:00

129 lines
3.9 KiB
TypeScript

import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { apiRequest } from "@/lib/queryClient";
import { Folder, FolderOpen, ChevronLeft, Loader2 } from "lucide-react";
interface BrowseResult {
current: string;
parent: string | null;
dirs: { name: string; path: string }[];
}
interface FolderBrowserModalProps {
open: boolean;
onClose: () => void;
onSelect: (path: string) => void;
}
export function FolderBrowserModal({ open, onClose, onSelect }: FolderBrowserModalProps) {
const [browsePath, setBrowsePath] = useState("/");
const [selected, setSelected] = useState<string>("/");
const { data, isLoading, isError } = useQuery<BrowseResult>({
queryKey: ["/db/browse", browsePath],
queryFn: async () => {
const res = await apiRequest(
"GET",
`/api/database-management/browse?path=${encodeURIComponent(browsePath)}`
);
if (!res.ok) throw new Error((await res.json()).error);
return res.json();
},
enabled: open,
});
const handleNavigate = (path: string) => {
setSelected(path);
setBrowsePath(path);
};
const handleConfirm = () => {
onSelect(selected);
onClose();
};
return (
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>Select Folder</DialogTitle>
</DialogHeader>
{/* Current path breadcrumb */}
<div className="text-xs text-gray-500 bg-gray-50 rounded px-3 py-2 font-mono break-all">
{data?.current ?? browsePath}
</div>
{/* Back button */}
{data?.parent && (
<Button
variant="ghost"
size="sm"
className="justify-start text-gray-600"
onClick={() => handleNavigate(data.parent!)}
>
<ChevronLeft className="h-4 w-4 mr-1" />
Back
</Button>
)}
{/* Directory list */}
<div className="border rounded-md overflow-y-auto max-h-64">
{isLoading && (
<div className="flex items-center justify-center py-8 text-gray-400">
<Loader2 className="h-5 w-5 animate-spin mr-2" />
Loading...
</div>
)}
{isError && (
<p className="text-sm text-red-500 p-4">Cannot read this directory.</p>
)}
{!isLoading && !isError && data?.dirs.length === 0 && (
<p className="text-sm text-gray-400 p-4">No sub-folders here.</p>
)}
{!isLoading &&
!isError &&
data?.dirs.map((dir) => (
<button
key={dir.path}
className={`w-full flex items-center gap-2 px-3 py-2 text-sm text-left hover:bg-gray-50 transition-colors ${
selected === dir.path ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700"
}`}
onClick={() => setSelected(dir.path)}
onDoubleClick={() => handleNavigate(dir.path)}
>
{selected === dir.path ? (
<FolderOpen className="h-4 w-4 shrink-0 text-blue-500" />
) : (
<Folder className="h-4 w-4 shrink-0 text-yellow-500" />
)}
{dir.name}
</button>
))}
</div>
<p className="text-xs text-gray-400">
Single-click to select · Double-click to open
</p>
<DialogFooter>
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button onClick={handleConfirm}>
Select Folder
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}