"use client"; import { type CSSProperties, useState } from "react"; import { Download, File, FileText, Loader2, Pencil, Plus, Users, } from "lucide-react"; import { HeaderSearchBtn } from "@/app/components/shared/HeaderSearchBtn"; import { RenameableTitle } from "@/app/components/shared/RenameableTitle"; import type { MikeProject } from "@/app/components/shared/types"; import type { MikeDocumentVersion } from "@/app/lib/mikeApi"; export type ProjectTab = "documents" | "assistant" | "reviews"; export type ProjectContextMenu = { x: number; y: number; docId?: string | null; folderId: string | null; showFolderActions: boolean; }; export const CHECK_W = "w-8 shrink-0"; export const NAME_COL_W = "w-[300px] shrink-0"; export const DOC_NAME_COL_W = "w-[260px] sm:w-[300px] md:w-[360px] lg:w-[420px] xl:w-[500px] 2xl:w-[560px] shrink-0"; const TREE_CONTROL_WIDTH_PX = 32; const TREE_NAME_PADDING_PX = 8; function treeControlWidth(depth: number) { return TREE_CONTROL_WIDTH_PX * (Math.max(0, depth) + 1); } export function treeControlCellStyle( depth: number, ): CSSProperties | undefined { if (depth <= 0) return undefined; const width = treeControlWidth(depth); return { justifyContent: "flex-start", minWidth: width, paddingLeft: TREE_NAME_PADDING_PX + depth * TREE_CONTROL_WIDTH_PX, width, }; } export function treeNameCellStyle(depth: number): CSSProperties | undefined { if (depth <= 0) return undefined; return { left: treeControlWidth(depth) }; } export function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } export function formatDate(iso: string) { return new Date(iso).toLocaleDateString(undefined, { day: "numeric", month: "short", year: "numeric", }); } export function DocIcon({ fileType }: { fileType: string | null }) { if (fileType === "pdf") return ; if (fileType === "docx" || fileType === "doc") return ; return ; } export function DocVersionHistory({ docId, filename, loading, versions, depth = 0, onDownloadVersion, onOpenVersion, onRenameVersion, }: { docId: string; filename: string; loading: boolean; versions: MikeDocumentVersion[]; depth?: number; onDownloadVersion: ( docId: string, versionId: string, filename: string, ) => void; onOpenVersion?: (versionId: string, versionLabel: string) => void; onRenameVersion?: ( versionId: string, displayName: string | null, ) => Promise | void; }) { const [editingVersionId, setEditingVersionId] = useState( null, ); const [editingValue, setEditingValue] = useState(""); const commit = async (versionId: string) => { const trimmed = editingValue.trim(); setEditingVersionId(null); const next = trimmed.length > 0 ? trimmed : null; await onRenameVersion?.(versionId, next); }; if (loading && versions.length === 0) { return (
Loading versions…
); } if (versions.length === 0) { return (
No version history.
); } const ordered = [...versions].reverse(); return ( <> {ordered.map((v) => { const numberLabel = typeof v.version_number === "number" && v.version_number >= 1 ? `${v.version_number}` : v.source === "upload" ? "Original" : "—"; const displayLabel = v.display_name?.trim() || numberLabel; const dt = new Date(v.created_at); const dateLabel = Number.isNaN(dt.valueOf()) ? "" : dt.toLocaleString(undefined, { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", }); const isEditing = editingVersionId === v.id; return (
{ if (isEditing) return; onOpenVersion?.(v.id, displayLabel); }} className="group flex items-center h-9 pr-8 border-b border-gray-50 bg-gray-50/60 text-xs text-gray-600 cursor-pointer hover:bg-gray-100/80 transition-colors" >
{isEditing ? ( e.stopPropagation()} onChange={(e) => setEditingValue(e.target.value) } onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); void commit(v.id); } else if (e.key === "Escape") { setEditingVersionId(null); } }} onBlur={() => void commit(v.id)} className="min-w-0 flex-1 max-w-[240px] border-b border-gray-300 bg-transparent text-xs text-gray-800 outline-none focus:border-gray-500" /> ) : ( {displayLabel} )} {!isEditing && onRenameVersion && ( )} {dateLabel} · {v.source}
); })} ); } export function ProjectPageSkeleton() { return (
Projects
{[1, 2, 3, 4, 5].map((i) => (
))}
); } export function ProjectPageHeader({ project, tab, search, creatingChat, creatingReview, docsCount, onBackToProjects, onOpenDocuments, onTitleCommit, onSearchChange, onOpenPeople, onNewChat, onNewReview, }: { project: MikeProject; tab: ProjectTab; search: string; creatingChat: boolean; creatingReview: boolean; docsCount: number; onBackToProjects: () => void; onOpenDocuments: () => void; onTitleCommit: (newName: string) => void | Promise; onSearchChange: (search: string) => void; onOpenPeople: () => void; onNewChat: () => void; onNewReview: () => void; }) { return (
{tab !== "documents" ? ( ) : ( (#{project.cm_number}) ) : null } /> )} {tab !== "documents" && ( <> {tab === "assistant" ? "Assistant" : "Tabular Reviews"} )}
{docsCount === 0 && (
Upload a document first
)}
); }