Download components/DocFilterBar.tsx from OrganizedProgrammers/3GPP-Innovation-Extractor: direct link, hf CLI and curl.
- Browser
- Download file 8.27 kB
-
https://huggingface.co/spaces/OrganizedProgrammers/3GPP-Innovation-Extractor/resolve/main/components/DocFilterBar.tsx
- Command line
-
hf download hf://spaces/OrganizedProgrammers/3GPP-Innovation-Extractor/components/DocFilterBar.tsx
-
curl -L -o DocFilterBar.tsx https://huggingface.co/spaces/OrganizedProgrammers/3GPP-Innovation-Extractor/resolve/main/components/DocFilterBar.tsx
8.27 kB
| 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; | |