|
|
import React, { useState, useEffect } from 'react'; |
|
|
import { MeetingDoc } from '../types'; |
|
|
import { Filter, X } from 'lucide-react'; |
|
|
|
|
|
interface DocFilterBarProps { |
|
|
docs: MeetingDoc[]; |
|
|
onFilterChange: (filteredDocs: MeetingDoc[]) => void; |
|
|
} |
|
|
|
|
|
const DocFilterBar: React.FC<DocFilterBarProps> = ({ docs, onFilterChange }) => { |
|
|
|
|
|
const allTypes = Array.from(new Set<string>(docs.map(d => d.Type))).sort(); |
|
|
const allStatuses = Array.from(new Set<string>(docs.map(d => d['TDoc Status']))).sort(); |
|
|
const allAgendas = Array.from(new Set<string>(docs.map(d => d['Agenda item description']))).sort(); |
|
|
|
|
|
|
|
|
const defaultTypes = ["LS out", "LS in", "pCR", "CR"]; |
|
|
const defaultStatuses = ["noted", "revised", "approved", "agreed"]; |
|
|
|
|
|
|
|
|
const [selectedTypes, setSelectedTypes] = useState<string[]>([]); |
|
|
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]); |
|
|
const [selectedAgendas, setSelectedAgendas] = useState<string[]>([]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (docs.length > 0) { |
|
|
const initialTypes = allTypes.filter(t => defaultTypes.includes(t)); |
|
|
|
|
|
|
|
|
setSelectedTypes(initialTypes.length > 0 ? initialTypes : []); |
|
|
|
|
|
const initialStatuses = allStatuses.filter(s => defaultStatuses.includes(s)); |
|
|
setSelectedStatuses(initialStatuses.length > 0 ? initialStatuses : []); |
|
|
|
|
|
|
|
|
setSelectedAgendas(allAgendas); |
|
|
} |
|
|
}, [docs]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
const filtered = docs.filter(doc => { |
|
|
const typeMatch = selectedTypes.length === 0 || selectedTypes.includes(doc.Type); |
|
|
const statusMatch = selectedStatuses.length === 0 || selectedStatuses.includes(doc['TDoc Status']); |
|
|
const agendaMatch = selectedAgendas.length === 0 || selectedAgendas.includes(doc['Agenda item description']); |
|
|
return typeMatch && statusMatch && agendaMatch; |
|
|
}); |
|
|
onFilterChange(filtered); |
|
|
}, [selectedTypes, selectedStatuses, selectedAgendas, docs]); |
|
|
|
|
|
|
|
|
const toggleSelection = (item: string, current: string[], setter: (val: string[]) => void) => { |
|
|
if (current.includes(item)) { |
|
|
setter(current.filter(i => i !== item)); |
|
|
} else { |
|
|
setter([...current, item]); |
|
|
} |
|
|
}; |
|
|
|
|
|
const toggleAll = (all: string[], current: string[], setter: (val: string[]) => void) => { |
|
|
if (current.length === all.length) { |
|
|
setter([]); |
|
|
} else { |
|
|
setter(all); |
|
|
} |
|
|
}; |
|
|
|
|
|
if (docs.length === 0) return null; |
|
|
|
|
|
return ( |
|
|
<div className="bg-white p-4 rounded-xl shadow-sm border border-slate-200 mb-6 animate-fade-in"> |
|
|
<div className="flex items-center mb-3"> |
|
|
<Filter className="w-4 h-4 text-slate-500 mr-2" /> |
|
|
<h3 className="text-sm font-semibold text-slate-800">Filter Documents</h3> |
|
|
<span className="ml-auto text-xs text-slate-500">{docs.length} total docs</span> |
|
|
</div> |
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> |
|
|
|
|
|
{/* Type Filter */} |
|
|
<div> |
|
|
<div className="flex justify-between items-center mb-2"> |
|
|
<label className="text-xs font-medium text-slate-600 uppercase tracking-wider">Type</label> |
|
|
<button |
|
|
onClick={() => toggleAll(allTypes, selectedTypes, setSelectedTypes)} |
|
|
className="text-[10px] text-blue-600 hover:text-blue-800" |
|
|
> |
|
|
{selectedTypes.length === allTypes.length ? 'Clear' : 'All'} |
|
|
</button> |
|
|
</div> |
|
|
<div className="max-h-32 overflow-y-auto custom-scrollbar space-y-1"> |
|
|
{allTypes.map(type => ( |
|
|
<label key={type} className="flex items-center space-x-2 cursor-pointer hover:bg-slate-50 p-1 rounded"> |
|
|
<input |
|
|
type="checkbox" |
|
|
checked={selectedTypes.includes(type)} |
|
|
onChange={() => toggleSelection(type, selectedTypes, setSelectedTypes)} |
|
|
className="rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-3.5 w-3.5" |
|
|
/> |
|
|
<span className="text-xs text-slate-700 truncate" title={type}>{type}</span> |
|
|
</label> |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
{/* Status Filter */} |
|
|
<div> |
|
|
<div className="flex justify-between items-center mb-2"> |
|
|
<label className="text-xs font-medium text-slate-600 uppercase tracking-wider">Status</label> |
|
|
<button |
|
|
onClick={() => toggleAll(allStatuses, selectedStatuses, setSelectedStatuses)} |
|
|
className="text-[10px] text-blue-600 hover:text-blue-800" |
|
|
> |
|
|
{selectedStatuses.length === allStatuses.length ? 'Clear' : 'All'} |
|
|
</button> |
|
|
</div> |
|
|
<div className="max-h-32 overflow-y-auto custom-scrollbar space-y-1"> |
|
|
{allStatuses.map(status => ( |
|
|
<label key={status} className="flex items-center space-x-2 cursor-pointer hover:bg-slate-50 p-1 rounded"> |
|
|
<input |
|
|
type="checkbox" |
|
|
checked={selectedStatuses.includes(status)} |
|
|
onChange={() => toggleSelection(status, selectedStatuses, setSelectedStatuses)} |
|
|
className="rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-3.5 w-3.5" |
|
|
/> |
|
|
<span className="text-xs text-slate-700 truncate" title={status}>{status}</span> |
|
|
</label> |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
{/* Agenda Filter */} |
|
|
<div> |
|
|
<div className="flex justify-between items-center mb-2"> |
|
|
<label className="text-xs font-medium text-slate-600 uppercase tracking-wider">Agenda Item</label> |
|
|
<button |
|
|
onClick={() => toggleAll(allAgendas, selectedAgendas, setSelectedAgendas)} |
|
|
className="text-[10px] text-blue-600 hover:text-blue-800" |
|
|
> |
|
|
{selectedAgendas.length === allAgendas.length ? 'Clear' : 'All'} |
|
|
</button> |
|
|
</div> |
|
|
<div className="max-h-32 overflow-y-auto custom-scrollbar space-y-1"> |
|
|
{allAgendas.map(agenda => ( |
|
|
<label key={agenda} className="flex items-center space-x-2 cursor-pointer hover:bg-slate-50 p-1 rounded"> |
|
|
<input |
|
|
type="checkbox" |
|
|
checked={selectedAgendas.includes(agenda)} |
|
|
onChange={() => toggleSelection(agenda, selectedAgendas, setSelectedAgendas)} |
|
|
className="rounded border-slate-300 text-blue-600 focus:ring-blue-500 h-3.5 w-3.5" |
|
|
/> |
|
|
<span className="text-xs text-slate-700 truncate" title={agenda}>{agenda}</span> |
|
|
</label> |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default DocFilterBar; |
|
|
|