| import React from "react"; |
| import { |
| FileText, |
| Plus, |
| Search, |
| X, |
| Trash2, |
| Wifi, |
| WifiOff, |
| RefreshCw |
| } from "lucide-react"; |
| import { Document } from "../../types"; |
|
|
| interface DocumentSidebarProps { |
| documents: Document[]; |
| activeDocId: string; |
| setActiveDocId: (id: string) => void; |
| setShowDocModal: (show: boolean) => void; |
| handleDeleteDocument: (id: string, e: React.MouseEvent) => void; |
| documentSearchTerm: string; |
| setDocumentSearchTerm: (term: string) => void; |
| syncStatus: "synced" | "syncing" | "offline" | "error"; |
| teamChangesEnabled: boolean; |
| activeCollaborators: number; |
| onClose?: () => void; |
| } |
|
|
| export default function DocumentSidebar({ |
| documents, |
| activeDocId, |
| setActiveDocId, |
| setShowDocModal, |
| handleDeleteDocument, |
| documentSearchTerm, |
| setDocumentSearchTerm, |
| syncStatus, |
| teamChangesEnabled, |
| activeCollaborators, |
| onClose |
| }: DocumentSidebarProps) { |
| |
| const filtered = documents.filter((doc) => |
| doc.title.toLowerCase().includes(documentSearchTerm.toLowerCase()) |
| ); |
|
|
| return ( |
| <div className="bg-[#15181E] border border-slate-800 rounded-xl p-4 space-y-3.5 shadow-md"> |
| |
| <div className="flex items-center justify-between"> |
| <h2 className="text-xs font-mono uppercase tracking-wider text-slate-505 flex items-center gap-1.5 font-bold"> |
| <FileText className="h-4 w-4 text-blue-500" /> Folder Documents |
| </h2> |
| <div className="flex items-center gap-1.5"> |
| <button |
| onClick={() => setShowDocModal(true)} |
| className="p-1 px-2.5 bg-[#0F1115] hover:bg-slate-800 text-slate-300 hover:text-white border border-slate-800 rounded-md text-xs font-semibold flex items-center gap-1 transition cursor-pointer" |
| > |
| <Plus className="h-3 w-3" /> New |
| </button> |
| {onClose && ( |
| <button |
| onClick={onClose} |
| className="p-1 bg-[#0F1115] hover:bg-slate-800 text-slate-400 hover:text-white border border-slate-800 rounded-md transition cursor-pointer" |
| title="Hide Sidebar" |
| > |
| <X className="h-3 w-3" /> |
| </button> |
| )} |
| </div> |
| </div> |
| |
| {/* Highly intuitive search input layout */} |
| <div className="relative"> |
| <span className="absolute inset-y-0 left-0 flex items-center pl-2.5 pointer-events-none"> |
| <Search className="h-3.5 w-3.5 text-slate-500" /> |
| </span> |
| <input |
| type="text" |
| placeholder="Search by title..." |
| value={documentSearchTerm} |
| onChange={(e) => setDocumentSearchTerm(e.target.value)} |
| className="w-full text-xs bg-[#0F1115] border border-slate-800 rounded-lg pl-8 pr-7 py-2 text-slate-205 placeholder:text-slate-650 focus:outline-none focus:border-slate-700 tracking-tight transition-colors" |
| /> |
| {documentSearchTerm && ( |
| <button |
| type="button" |
| onClick={() => setDocumentSearchTerm("")} |
| className="absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-500 hover:text-white transition-colors cursor-pointer" |
| title="Clear search query" |
| > |
| <X className="h-3.5 w-3.5" /> |
| </button> |
| )} |
| </div> |
| |
| {/* Documents List */} |
| <div className="space-y-1 max-h-[180px] overflow-y-auto pr-1"> |
| {filtered.length === 0 ? ( |
| <div className="text-center py-6 text-slate-500 text-xs font-mono"> |
| No matching segments |
| </div> |
| ) : ( |
| filtered.map((doc) => { |
| const isSelected = doc.id === activeDocId; |
| return ( |
| <div |
| key={doc.id} |
| onClick={() => setActiveDocId(doc.id)} |
| className={`w-full text-left px-3 py-2 rounded-lg transition cursor-pointer flex items-center justify-between gap-2 group ${ |
| isSelected |
| ? "bg-blue-600/10 border border-blue-500/20 text-white" |
| : "text-slate-400 hover:text-slate-200 hover:bg-[#0F1115]/50 border border-transparent" |
| }`} |
| > |
| <div className="font-mono text-xs truncate max-w-[80%]"> |
| {doc.title} |
| </div> |
| <button |
| onClick={(e) => handleDeleteDocument(doc.id, e)} |
| className="opacity-0 group-hover:opacity-100 hover:text-red-400 transition p-1 cursor-pointer" |
| > |
| <Trash2 className="h-3 w-3" /> |
| </button> |
| </div> |
| ); |
| }) |
| )} |
| </div> |
| |
| {/* Secure Offline Persistent Sync Status Banner */} |
| <div className="pt-3 border-t border-slate-800 space-y-2.5"> |
| <div className="flex items-center justify-between text-xs"> |
| <span className="font-mono text-[10px] text-slate-500">Sync Pipeline</span> |
| |
| {syncStatus === "synced" && ( |
| <span className="flex items-center gap-1 font-mono text-[10px] text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20 text-[9px] font-bold"> |
| <Wifi className="h-3 w-3" /> Online Synced |
| </span> |
| )} |
| {syncStatus === "syncing" && ( |
| <span className="flex items-center gap-1 font-mono text-[10px] text-amber-400 bg-amber-500/10 px-2 py-0.5 rounded-full border border-amber-500/20 text-[9px] font-bold"> |
| <RefreshCw className="h-3 w-3 animate-spin" /> Uploading Segment |
| </span> |
| )} |
| {syncStatus === "offline" && ( |
| <span className="flex items-center gap-1 font-mono text-[10px] text-slate-400 bg-slate-800/10 px-2 py-0.5 rounded-full border border-slate-750 text-[9px] font-bold"> |
| <WifiOff className="h-3 w-3" /> Offline Buffer |
| </span> |
| )} |
| {syncStatus === "error" && ( |
| <span className="flex items-center gap-1 font-mono text-[10px] text-red-405 bg-red-500/10 px-2 py-0.5 rounded-full border border-red-500/20 text-[9px] font-bold"> |
| <WifiOff className="h-3 w-3" /> Sync Failed |
| </span> |
| )} |
| </div> |
| |
| <div className="bg-[#0F1115]/60 border border-slate-800 p-2.5 rounded-lg space-y-1"> |
| <div className="flex justify-between items-center text-[9px] text-slate-500 font-mono"> |
| <span>Collaborators</span> |
| <span>{teamChangesEnabled ? activeCollaborators : "1"} Editor Online</span> |
| </div> |
| <div className="flex items-center gap-1 py-1"> |
| <div className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" /> |
| <span className="font-mono text-[9px] text-slate-400">Sarah and Liam joined review panel</span> |
| </div> |
| </div> |
| </div> |
| |
| </div> |
| ); |
| } |
|
|