diff options
| author | soryu <soryu@soryu.co> | 2025-12-23 02:14:58 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2025-12-23 14:47:18 +0000 |
| commit | a32dc56d2e5447ef8988cb98b8686476cc94e70c (patch) | |
| tree | 61307503c4af82103cea2360fe95d3ea324968d6 /makima/frontend/src/routes/files.tsx | |
| parent | 73649d135efccda7e446775db773e21b458de202 (diff) | |
| download | soryu-a32dc56d2e5447ef8988cb98b8686476cc94e70c.tar.gz soryu-a32dc56d2e5447ef8988cb98b8686476cc94e70c.zip | |
Add Postgres for persistence and File cabinet
Migrations are local only currently, and must be run manually by setting POSTGRES_CONNECTION_URI
Diffstat (limited to 'makima/frontend/src/routes/files.tsx')
| -rw-r--r-- | makima/frontend/src/routes/files.tsx | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/makima/frontend/src/routes/files.tsx b/makima/frontend/src/routes/files.tsx new file mode 100644 index 0000000..86a24b8 --- /dev/null +++ b/makima/frontend/src/routes/files.tsx @@ -0,0 +1,95 @@ +import { useState, useCallback, useEffect } from "react"; +import { useParams, useNavigate } from "react-router"; +import { Masthead } from "../components/Masthead"; +import { FileList } from "../components/files/FileList"; +import { FileDetail } from "../components/files/FileDetail"; +import { useFiles } from "../hooks/useFiles"; +import type { FileDetail as FileDetailType } from "../lib/api"; + +export default function FilesPage() { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const { files, loading, error, fetchFile, editFile, removeFile } = useFiles(); + const [fileDetail, setFileDetail] = useState<FileDetailType | null>(null); + const [detailLoading, setDetailLoading] = useState(false); + + // Load file detail when URL has an id + useEffect(() => { + if (id) { + setDetailLoading(true); + fetchFile(id).then((detail) => { + setFileDetail(detail); + setDetailLoading(false); + }); + } else { + setFileDetail(null); + } + }, [id, fetchFile]); + + const handleSelectFile = useCallback( + (fileId: string) => { + navigate(`/files/${fileId}`); + }, + [navigate] + ); + + const handleBack = useCallback(() => { + navigate("/files"); + }, [navigate]); + + const handleDelete = useCallback( + async (fileId: string) => { + if (confirm("Are you sure you want to delete this file?")) { + const success = await removeFile(fileId); + if (success && id === fileId) { + navigate("/files"); + } + } + }, + [removeFile, id, navigate] + ); + + const handleSave = useCallback( + async (fileId: string, name: string, description: string) => { + await editFile(fileId, { name, description }); + const detail = await fetchFile(fileId); + setFileDetail(detail); + }, + [editFile, fetchFile] + ); + + return ( + <div className="relative z-10 h-screen flex flex-col overflow-hidden"> + <Masthead showTicker={false} showNav /> + + <main className="flex-1 p-4 md:p-6 min-h-0 overflow-hidden"> + {error && ( + <div className="mb-4 p-3 border border-red-400/50 bg-red-400/10 text-red-400 font-mono text-sm"> + {error} + </div> + )} + + {id && fileDetail ? ( + <FileDetail + file={fileDetail} + loading={detailLoading} + onBack={handleBack} + onSave={handleSave} + onDelete={handleDelete} + /> + ) : id && detailLoading ? ( + <div className="panel h-full flex items-center justify-center"> + <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> + </div> + ) : ( + <FileList + files={files} + loading={loading} + onSelect={handleSelectFile} + onDelete={handleDelete} + /> + )} + </main> + </div> + ); +} |
