diff options
Diffstat (limited to 'makima/frontend/src/components/files')
| -rw-r--r-- | makima/frontend/src/components/files/FileDetail.tsx | 143 | ||||
| -rw-r--r-- | makima/frontend/src/components/files/FileList.tsx | 96 |
2 files changed, 239 insertions, 0 deletions
diff --git a/makima/frontend/src/components/files/FileDetail.tsx b/makima/frontend/src/components/files/FileDetail.tsx new file mode 100644 index 0000000..643f35e --- /dev/null +++ b/makima/frontend/src/components/files/FileDetail.tsx @@ -0,0 +1,143 @@ +import { useState } from "react"; +import type { FileDetail as FileDetailType } from "../../lib/api"; + +interface FileDetailProps { + file: FileDetailType; + loading: boolean; + onBack: () => void; + onSave: (id: string, name: string, description: string) => void; + onDelete: (id: string) => void; +} + +export function FileDetail({ + file, + loading, + onBack, + onSave, + onDelete, +}: FileDetailProps) { + const [isEditing, setIsEditing] = useState(false); + const [name, setName] = useState(file.name); + const [description, setDescription] = useState(file.description || ""); + + const handleSave = () => { + onSave(file.id, name, description); + setIsEditing(false); + }; + + const handleCancel = () => { + setName(file.name); + setDescription(file.description || ""); + setIsEditing(false); + }; + + if (loading) { + return ( + <div className="panel h-full flex items-center justify-center"> + <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> + </div> + ); + } + + return ( + <div className="panel h-full flex flex-col"> + {/* Header */} + <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]"> + <div className="flex items-center justify-between mb-3"> + <button + onClick={onBack} + className="font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors" + > + ← Back to list + </button> + <div className="flex gap-2"> + {isEditing ? ( + <> + <button + onClick={handleCancel} + className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors uppercase" + > + Cancel + </button> + <button + onClick={handleSave} + className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" + > + Save + </button> + </> + ) : ( + <> + <button + onClick={() => setIsEditing(true)} + className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] border border-[#0f3c78] hover:border-[#3f6fb3] transition-colors uppercase" + > + Edit + </button> + <button + onClick={() => onDelete(file.id)} + className="px-3 py-1.5 font-mono text-xs text-red-400 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase" + > + Delete + </button> + </> + )} + </div> + </div> + + {isEditing ? ( + <div className="space-y-3"> + <input + type="text" + value={name} + onChange={(e) => setName(e.target.value)} + className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]" + placeholder="File name" + /> + <textarea + value={description} + onChange={(e) => setDescription(e.target.value)} + className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none" + rows={2} + placeholder="Description (optional)" + /> + </div> + ) : ( + <> + <h2 className="font-mono text-lg text-[#dbe7ff] mb-1"> + {file.name} + </h2> + {file.description && ( + <p className="font-mono text-sm text-[#9bc3ff]"> + {file.description} + </p> + )} + </> + )} + </div> + + {/* Transcript */} + <div className="flex-1 overflow-y-auto p-4 space-y-3"> + {file.transcript.length === 0 ? ( + <div className="text-center text-[#9bc3ff] text-sm font-mono opacity-60 py-8"> + No transcript entries. + </div> + ) : ( + file.transcript.map((entry) => ( + <div key={entry.id} className="font-mono text-sm"> + <div className="flex items-baseline gap-2 mb-1"> + <span className="text-[#75aafc] text-xs"> + [{entry.start.toFixed(2)}s - {entry.end.toFixed(2)}s] + </span> + <span className="text-[#9bc3ff] text-xs font-bold"> + {entry.speaker} + </span> + </div> + <p className="m-0 text-[#dbe7ff] leading-relaxed">{entry.text}</p> + </div> + )) + )} + </div> + </div> + ); +} diff --git a/makima/frontend/src/components/files/FileList.tsx b/makima/frontend/src/components/files/FileList.tsx new file mode 100644 index 0000000..7e1eea4 --- /dev/null +++ b/makima/frontend/src/components/files/FileList.tsx @@ -0,0 +1,96 @@ +import type { FileSummary } from "../../lib/api"; + +interface FileListProps { + files: FileSummary[]; + loading: boolean; + onSelect: (id: string) => void; + onDelete: (id: string) => void; +} + +function formatDuration(seconds: number | null): string { + if (seconds === null) return "-"; + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs.toString().padStart(2, "0")}`; +} + +function formatDate(dateStr: string): string { + const date = new Date(dateStr); + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} + +export function FileList({ + files, + loading, + onSelect, + onDelete, +}: FileListProps) { + if (loading) { + return ( + <div className="panel h-full flex items-center justify-center"> + <div className="font-mono text-[#9bc3ff] text-sm">Loading files...</div> + </div> + ); + } + + return ( + <div className="panel h-full flex flex-col"> + <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase p-4 pb-2 border-b border-dashed border-[rgba(117,170,252,0.35)]"> + FILES// + </div> + + <div className="flex-1 overflow-y-auto"> + {files.length === 0 ? ( + <div className="text-center text-[#9bc3ff] text-sm font-mono opacity-60 py-8"> + No saved files yet. Start recording to create one. + </div> + ) : ( + <div className="divide-y divide-[rgba(117,170,252,0.15)]"> + {files.map((file) => ( + <div + key={file.id} + className="p-4 hover:bg-[rgba(117,170,252,0.05)] transition-colors" + > + <div className="flex items-start justify-between gap-4"> + <button + onClick={() => onSelect(file.id)} + className="flex-1 text-left" + > + <h3 className="font-mono text-sm text-[#dbe7ff] mb-1"> + {file.name} + </h3> + {file.description && ( + <p className="font-mono text-xs text-[#9bc3ff] mb-2 line-clamp-2"> + {file.description} + </p> + )} + <div className="flex gap-4 font-mono text-[10px] text-[#75aafc]"> + <span>{file.transcriptCount} segments</span> + <span>{formatDuration(file.duration)}</span> + <span>{formatDate(file.createdAt)}</span> + </div> + </button> + <button + onClick={(e) => { + e.stopPropagation(); + onDelete(file.id); + }} + className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase" + > + Delete + </button> + </div> + </div> + ))} + </div> + )} + </div> + </div> + ); +} |
