From 68a8dae0dc08dccdd9ced4ab930fc3da831eeec5 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sun, 7 Jun 2026 08:43:16 +0200 Subject: [PATCH] refactor(mui): migrate ProjectFileManager to MUI kit Replace legacy ConfirmModal with the kit ConfirmDialog (delete-confirm). Re-skin onto Card + DataTable (file/folder rows), Box toolbar/breadcrumb, EmptyState, LoadingState, kit Button/TextField/IconButton. The hidden multipart file input is kept (per-file NAS upload). All queries, mutations, invalidate keys (["projects", id, "files"]), NAS upload/ download/rename/create-folder/drag-drop logic, permissions, and Czech text preserved verbatim. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/components/ProjectFileManager.tsx | 788 ++++++++++++-------- 1 file changed, 465 insertions(+), 323 deletions(-) diff --git a/src/admin/components/ProjectFileManager.tsx b/src/admin/components/ProjectFileManager.tsx index 31125c6..3050a8a 100644 --- a/src/admin/components/ProjectFileManager.tsx +++ b/src/admin/components/ProjectFileManager.tsx @@ -1,8 +1,21 @@ import { useState, useRef } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import CircularProgress from "@mui/material/CircularProgress"; import { projectFilesOptions } from "../lib/queries/projects"; import { useAlert } from "../context/AlertContext"; -import ConfirmModal from "./ConfirmModal"; +import { + Card, + Button, + TextField, + ConfirmDialog, + EmptyState, + LoadingState, + DataTable, + type DataColumn, +} from "../ui"; import apiFetch from "../utils/api"; const API_BASE = "/api/admin"; @@ -143,7 +156,16 @@ function getFileIcon(type: string, extension?: string) { function SymlinkBadge({ target }: { target?: string }) { return ( - + - + ); } @@ -168,26 +190,53 @@ function FileNameCell({ }) { if (item.type === "folder") { return ( - - + {item.is_symlink && } {item.item_count !== undefined && ( - {item.item_count} + + {item.item_count} + )} - + ); } return ( - - {item.name} + + {item.name} {item.is_symlink && } - + ); } @@ -469,76 +518,271 @@ export default function ProjectFileManager({ }; if (filesLoading && items.length === 0 && !errorMessage) { - return ( -
-
-
- ); + return ; } if (errorMessage) { return ( -
-
-

Soubory

-
+ + + Soubory + + - {errorMessage} -
-
-
+ } + title={errorMessage} + /> + ); } - return ( -
-
-

Soubory

- - {/* Toolbar */} -
-
- {breadcrumb.map((segment, i) => ( - - {i > 0 && /} - - - ))} -
- - {fullPath && ( - - {fullPath} - - )} - - {canManage && ( -
- - - -
- )} -
- - {/* New folder input */} - {newFolderMode && ( -
- setNewFolderName(e.target.value)} - className="admin-form-input" - placeholder="Název složky..." - autoFocus - onKeyDown={(e) => { - if (e.key === "Enter") handleCreateFolder(); - if (e.key === "Escape") { - setNewFolderMode(false); - setNewFolderName(""); - } - }} - style={{ fontSize: "12px", padding: "6px 10px" }} - /> - - + + + + )} + + + {/* New folder input */} + {newFolderMode && ( + + setNewFolderName(e.target.value)} + placeholder="Název složky..." + autoFocus + sx={{ maxWidth: 320 }} + onKeyDown={(e) => { + if (e.key === "Enter") handleCreateFolder(); + if (e.key === "Escape") { setNewFolderMode(false); setNewFolderName(""); - }} + } + }} + /> + + + + )} + + {/* Drop zone + table */} + + {dragOver && ( + + - Zrušit - -
+ + + + + Přetáhněte soubory sem + )} - {/* Drop zone + table */} -
- {dragOver && ( -
+ {items.length === 0 && !filesLoading ? ( + - - - - - Přetáhněte soubory sem -
- )} - - {items.length === 0 && !filesLoading ? ( -
- - - {hasNasFolder - ? "Složka je prázdná" - : "Složka projektu zatím neexistuje"} - - {canManage && !hasNasFolder && ( - - Nahrání souboru ji automaticky vytvoří - - )} -
- ) : ( -
- - - - - - - - {canManage && ( - - )} - - - - {items.map((item) => ( - - - - - - {canManage && ( - - )} - - ))} - -
NázevVelikostZměněno - Akce -
- {getFileIcon(item.type, item.extension)} - - {renamingItem === item.name ? ( - setRenameValue(e.target.value)} - className="admin-form-input" - style={{ - fontSize: "11px", - padding: "3px 8px", - maxWidth: "300px", - }} - autoFocus - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - handleRename(item); - } - if (e.key === "Escape") { - e.preventDefault(); - isCancelling.current = true; - setRenamingItem(null); - setRenameValue(item.name); - setTimeout(() => { - isCancelling.current = false; - }, 0); - } - }} - onBlur={() => { - if (isCancelling.current) { - return; - } - handleRename(item); - }} - /> - ) : ( - - )} - - {item.type === "file" ? item.size_formatted : "\u2014"} - {item.modified || "\u2014"} -
- {item.type === "file" && ( - - )} - - -
-
-
- )} -
-
+ } + title={ + hasNasFolder + ? "Složka je prázdná" + : "Složka projektu zatím neexistuje" + } + description={ + canManage && !hasNasFolder + ? "Nahrání souboru ji automaticky vytvoří" + : undefined + } + /> + ) : ( + item.name} + /> + )} + - setDeleteTarget(null)} onConfirm={handleDelete} @@ -836,9 +978,9 @@ export default function ProjectFileManager({ message={`Opravdu chcete smazat "${deleteTarget?.name}"?${deleteTarget?.type === "folder" ? " Složka bude smazána včetně veškerého obsahu." : ""}`} confirmText="Smazat" cancelText="Zrušit" - type="danger" + confirmVariant="danger" loading={deleting} /> -
+ ); }