| 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; |
|
|