import React, { useState } from 'react'; import { Clock, Plus, FileText, Calendar, Users, Award, Gavel, ShieldAlert, Search, Filter, Paperclip, MapPin, User, Edit2, Trash2, Download, X, CheckCircle, FileCode, Tag, ArrowUpDown, Sparkles, ExternalLink } from 'lucide-react'; import { ProjectStore, TimelineEvent, TimelineEventType, UserRole, Attachment } from '../types'; import { AttachmentUploader } from './AttachmentUploader'; interface TimelineViewProps { store: ProjectStore; onUpdateStore: (newStore: ProjectStore) => void; userRole?: UserRole; currentUserName?: string; } export const TimelineView: React.FC = ({ store, onUpdateStore, userRole = 'viewer', currentUserName = 'System User' }) => { const events = store.timelineEvents || []; const canEdit = userRole === 'admin' || userRole === 'editor'; // Search & Filter State const [searchQuery, setSearchQuery] = useState(''); const [typeFilter, setTypeFilter] = useState('all'); const [sortOrder, setSortOrder] = useState<'desc' | 'asc'>('desc'); // Modal State const [isModalOpen, setIsModalOpen] = useState(false); const [editingEvent, setEditingEvent] = useState | null>(null); // Modal Form Inputs const [title, setTitle] = useState(''); const [eventType, setEventType] = useState('circular'); const [category, setCategory] = useState(''); const [date, setDate] = useState(new Date().toISOString().split('T')[0]); const [time, setTime] = useState('10:00'); const [referenceNumber, setReferenceNumber] = useState(''); const [location, setLocation] = useState(''); const [author, setAuthor] = useState(currentUserName); const [description, setDescription] = useState(''); const [attachments, setAttachments] = useState([]); // Open modal to create a new event const handleOpenCreateModal = () => { setEditingEvent(null); setTitle(''); setEventType('circular'); setCategory('Official Circular'); setDate(new Date().toISOString().split('T')[0]); setTime('10:00'); setReferenceNumber(`PIN-${new Date().getFullYear()}-${Math.floor(100 + Math.random() * 900)}`); setLocation('Executive Office'); setAuthor(currentUserName); setDescription(''); setAttachments([]); setIsModalOpen(true); }; // Open modal to edit existing event const handleOpenEditModal = (event: TimelineEvent) => { setEditingEvent(event); setTitle(event.title); setEventType(event.type); setCategory(event.category || ''); setDate(event.date); setTime(event.time || ''); setReferenceNumber(event.referenceNumber || ''); setLocation(event.location || ''); setAuthor(event.author || currentUserName); setDescription(event.description); setAttachments(event.attachments || []); setIsModalOpen(true); }; // Save event (Create or Update) const handleSaveEvent = (e: React.FormEvent) => { e.preventDefault(); if (!title.trim() || !date) return; let updatedEvents = [...events]; if (editingEvent && editingEvent.id) { // Update existing updatedEvents = updatedEvents.map((evt) => evt.id === editingEvent.id ? { ...evt, title: title.trim(), type: eventType, category: category.trim() || undefined, date, time: time.trim() || undefined, referenceNumber: referenceNumber.trim() || undefined, location: location.trim() || undefined, author: author.trim() || currentUserName, description: description.trim(), attachments } : evt ); } else { // Create new const nextId = (store.ids.timelineEvent || events.length + 1) + 1; const newEvt: TimelineEvent = { id: nextId, title: title.trim(), type: eventType, category: category.trim() || getTypeLabel(eventType), date, time: time.trim() || undefined, referenceNumber: referenceNumber.trim() || undefined, location: location.trim() || undefined, author: author.trim() || currentUserName, description: description.trim(), attachments, createdAt: new Date().toISOString() }; updatedEvents.push(newEvt); } onUpdateStore({ ...store, timelineEvents: updatedEvents, ids: { ...store.ids, timelineEvent: (store.ids.timelineEvent || events.length + 1) + 1 }, logs: [ { id: store.ids.log || Date.now(), action: editingEvent ? 'TIMELINE_EVENT_UPDATED' : 'TIMELINE_EVENT_CREATED', type: 'timeline', details: `${editingEvent ? 'Updated' : 'Created'} timeline event: ${title.trim()} (${referenceNumber || eventType})`, user: currentUserName, timestamp: new Date().toISOString() }, ...store.logs ] }); setIsModalOpen(false); }; // Delete Event const handleDeleteEvent = (id: number) => { if (!window.confirm('Are you sure you want to delete this timeline event?')) return; const updatedEvents = events.filter((e) => e.id !== id); onUpdateStore({ ...store, timelineEvents: updatedEvents, logs: [ { id: store.ids.log || Date.now(), action: 'TIMELINE_EVENT_DELETED', type: 'timeline', details: `Deleted timeline event ID #${id}`, user: currentUserName, timestamp: new Date().toISOString() }, ...store.logs ] }); }; // Filter & Sort Events const filteredEvents = events .filter((e) => { if (typeFilter !== 'all' && e.type !== typeFilter) return false; if (searchQuery.trim()) { const q = searchQuery.toLowerCase(); return ( e.title.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || (e.referenceNumber && e.referenceNumber.toLowerCase().includes(q)) || (e.category && e.category.toLowerCase().includes(q)) || (e.author && e.author.toLowerCase().includes(q)) ); } return true; }) .sort((a, b) => { const dateA = new Date(`${a.date}T${a.time || '00:00'}`).getTime(); const dateB = new Date(`${b.date}T${b.time || '00:00'}`).getTime(); return sortOrder === 'desc' ? dateB - dateA : dateA - dateB; }); // Helpers for Type Colors & Icons function getTypeBadge(type: TimelineEventType) { switch (type) { case 'circular': return 'bg-cyan-500/10 text-cyan-400 border-cyan-500/30'; case 'meeting': return 'bg-amber-500/10 text-amber-400 border-amber-500/30'; case 'milestone': return 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30'; case 'decision': return 'bg-purple-500/10 text-purple-400 border-purple-500/30'; case 'release': case 'audit': return 'bg-rose-500/10 text-rose-400 border-rose-500/30'; default: return 'bg-slate-800 text-slate-300 border-slate-700'; } } function getTypeIcon(type: TimelineEventType) { switch (type) { case 'circular': return ; case 'meeting': return ; case 'milestone': return ; case 'decision': return ; case 'release': case 'audit': return ; default: return ; } } function getTypeLabel(type: TimelineEventType) { switch (type) { case 'circular': return 'Circular Published'; case 'meeting': return 'Committee Meeting'; case 'milestone': return 'Milestone Reached'; case 'decision': return 'Key Decision'; case 'release': return 'Official Release'; case 'audit': return 'Audit Review'; default: return 'Custom Event'; } } // Count Statistics const circularsCount = events.filter((e) => e.type === 'circular').length; const meetingsCount = events.filter((e) => e.type === 'meeting').length; const milestonesCount = events.filter((e) => e.type === 'milestone').length; const docsCount = events.reduce((acc, e) => acc + (e.attachments ? e.attachments.length : 0), 0); return (
{/* Page Header */}

Interactive Timeline & Circular Registry

Chronological record of official meetings, circulars published, milestone achievements, and executive directives.

{canEdit && ( )}
{/* Summary Analytics Cards */}
Total Timeline Events
{events.length}
Historical & scheduled entries
Circulars Issued
{circularsCount}
Official governance publications
Committee Meetings
{meetingsCount}
PMT & PSC sessions logged
Supporting Documents
{docsCount}
Attached files & circular PDFs
{/* Filter and Search Bar */}
{/* Search Box */}
setSearchQuery(e.target.value)} placeholder="Search timeline, circular ref, title..." className="w-full bg-slate-950 border border-slate-800 rounded-xl pl-9 pr-3 py-2 text-xs text-slate-200 focus:outline-none focus:border-amber-500 transition" /> {searchQuery && ( )}
{/* Type Filter Tabs */}
{[ { id: 'all', label: 'All Events' }, { id: 'circular', label: 'Circulars' }, { id: 'meeting', label: 'Meetings' }, { id: 'milestone', label: 'Milestones' }, { id: 'decision', label: 'Decisions' } ].map((tab) => ( ))} {/* Sort Toggle */}
{/* Vertical Interactive Timeline */}
{filteredEvents.length === 0 ? (
No timeline events match your search criteria

Try adjusting your search query, selecting "All Events", or creating a new timeline entry.

{canEdit && ( )}
) : (
{filteredEvents.map((evt) => (
{/* Pulsing Timeline Node */}
{/* Event Card */}
{/* Type Badge */} {getTypeIcon(evt.type)} {evt.category || getTypeLabel(evt.type)} {/* Reference Number if present */} {evt.referenceNumber && ( Ref: {evt.referenceNumber} )}

{evt.title}

{/* Controls for Admin / Editor */}
{canEdit && ( <> )}
{/* Metadata Bar */}
{evt.date} {evt.time && at {evt.time}}
{evt.location && (
{evt.location}
)} {evt.author && (
Published by: {evt.author}
)}
{/* Description Body */}

{evt.description}

{/* Supporting Attachments Display */} {evt.attachments && evt.attachments.length > 0 && (
Supporting Documents & Circular Files ({evt.attachments.length})
{evt.attachments.map((att) => ( {att.name} ({(att.size / 1024).toFixed(0)} KB) ))}
)}
))}
)}
{/* CREATE / EDIT TIMELINE EVENT MODAL */} {isModalOpen && (

{editingEvent ? 'Edit Timeline Event' : 'Create Custom Timeline Event'}

{/* Event Title */}
setTitle(e.target.value)} placeholder="e.g. Published Circular PIN-2026-003 or Mid-Year Strategy Review" required className="w-full bg-slate-950 border border-slate-800 rounded-xl p-2.5 text-slate-200 focus:outline-none focus:border-amber-500" />
{/* Event Type & Category */}
setCategory(e.target.value)} placeholder="e.g. Official Circular, Board Directive" className="w-full bg-slate-950 border border-slate-800 rounded-xl p-2.5 text-slate-200 focus:outline-none focus:border-amber-500" />
{/* Date, Time & Reference Number */}
setDate(e.target.value)} required className="w-full bg-slate-950 border border-slate-800 rounded-xl p-2.5 text-slate-200 focus:outline-none focus:border-amber-500" />
setTime(e.target.value)} className="w-full bg-slate-950 border border-slate-800 rounded-xl p-2.5 text-slate-200 focus:outline-none focus:border-amber-500" />
setReferenceNumber(e.target.value)} placeholder="e.g. PIN-2026-003" className="w-full bg-slate-950 border border-slate-800 rounded-xl p-2.5 text-amber-300 font-mono focus:outline-none focus:border-amber-500" />
{/* Location & Author */}
setLocation(e.target.value)} placeholder="e.g. Main Boardroom / Portal" className="w-full bg-slate-950 border border-slate-800 rounded-xl p-2.5 text-slate-200 focus:outline-none focus:border-amber-500" />
setAuthor(e.target.value)} placeholder="e.g. PMO Office" className="w-full bg-slate-950 border border-slate-800 rounded-xl p-2.5 text-slate-200 focus:outline-none focus:border-amber-500" />
{/* Description */}