File size: 8,274 Bytes
4b1a31e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
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 }) => {
// Extract unique values
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();
// Default selections
const defaultTypes = ["LS out", "LS in", "pCR", "CR"];
const defaultStatuses = ["noted", "revised", "approved", "agreed"];
// State
const [selectedTypes, setSelectedTypes] = useState<string[]>([]);
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]);
const [selectedAgendas, setSelectedAgendas] = useState<string[]>([]);
// Initialize defaults when docs change
useEffect(() => {
if (docs.length > 0) {
const initialTypes = allTypes.filter(t => defaultTypes.includes(t));
// If no default types match, maybe select all or none? Let's stick to defaults.
// If the default list is empty (none found), user can select manually.
setSelectedTypes(initialTypes.length > 0 ? initialTypes : []);
const initialStatuses = allStatuses.filter(s => defaultStatuses.includes(s));
setSelectedStatuses(initialStatuses.length > 0 ? initialStatuses : []);
// Select all agendas by default
setSelectedAgendas(allAgendas);
}
}, [docs]); // Re-run when docs source changes
// Filter logic
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;
|