Add column filters to Missing Data view and include Classification in editable columns

This commit is contained in:
Christian Vidal Wolf
2026-04-12 18:32:08 +02:00
parent 39869edcef
commit fd5d7458b9
2 changed files with 50 additions and 7 deletions
+1
View File
@@ -115,6 +115,7 @@ export default function App() {
const syncedData = await getAllSyncedRows(session?.access_token); const syncedData = await getAllSyncedRows(session?.access_token);
const editableColumns = new Set([ const editableColumns = new Set([
COLUMNS.CLASSIFICATION,
COLUMNS.LONG_DE, COLUMNS.LONG_EN, COLUMNS.LONG_DE, COLUMNS.LONG_EN,
COLUMNS.SHORT_DE, COLUMNS.SHORT_EN, COLUMNS.SHORT_DE, COLUMNS.SHORT_EN,
COLUMNS.DETAILS_DE, COLUMNS.DETAILS_EN, COLUMNS.DETAILS_DE, COLUMNS.DETAILS_EN,
+49 -7
View File
@@ -1,6 +1,6 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { ExcelRow, COLUMNS } from '../types'; import { ExcelRow, COLUMNS } from '../types';
import { Search, ChevronDown, ChevronUp, X, Edit2, Save } from 'lucide-react'; import { Search, ChevronDown, ChevronUp, X, Edit2, Save, Filter, XCircle } from 'lucide-react';
import { cn } from '../lib/utils'; import { cn } from '../lib/utils';
interface MissingDataViewProps { interface MissingDataViewProps {
@@ -52,6 +52,8 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
const [sortDesc, setSortDesc] = useState(false); const [sortDesc, setSortDesc] = useState(false);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [editing, setEditing] = useState<EditingState | null>(null); const [editing, setEditing] = useState<EditingState | null>(null);
const [columnFilters, setColumnFilters] = useState<Record<number, string>>({});
const [showFilterMenu, setShowFilterMenu] = useState<number | null>(null);
const pageSize = 100; const pageSize = 100;
const launchDateCol = useMemo(() => const launchDateCol = useMemo(() =>
@@ -87,6 +89,19 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
); );
} }
(Object.entries(columnFilters) as [string, string][]).forEach(([colIdx, filterValue]) => {
if (!filterValue) return;
const colIdxNum = parseInt(colIdx);
const filterLower = filterValue.toLowerCase();
result = result.filter(r => {
const val: any = r.row[colIdxNum];
const displayVal = colIdxNum === launchDateCol || colIdxNum === readyToOrderCol
? formatDateValue(val) || String(val ?? '')
: String(val ?? '');
return displayVal.toLowerCase().includes(filterLower);
});
});
if (sortCol !== null) { if (sortCol !== null) {
result.sort((a, b) => { result.sort((a, b) => {
const valA = a.row[sortCol]; const valA = a.row[sortCol];
@@ -101,7 +116,7 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
} }
return result; return result;
}, [data, activeTab, search, sortCol, sortDesc, launchDateCol]); }, [data, activeTab, search, sortCol, sortDesc, launchDateCol, columnFilters]);
const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
const start = (page - 1) * pageSize; const start = (page - 1) * pageSize;
@@ -203,18 +218,45 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
<th <th
key={col} key={col}
style={{ width, minWidth: width }} style={{ width, minWidth: width }}
className="px-3 py-3 font-medium border-r border-slate-700/30 select-none cursor-pointer hover:text-white" className="px-2 py-2 font-medium border-r border-slate-700/30 relative"
onClick={() => handleSort(col)}
> >
<span className="flex items-center gap-1"> <div
className="flex items-center gap-1 cursor-pointer select-none hover:text-white"
onClick={() => handleSort(col)}
>
{label} {label}
{sortCol === col && ( {sortCol === col && (
sortDesc ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" /> sortDesc ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" />
)} )}
</span> </div>
<div className="mt-1 relative">
<input
type="text"
placeholder="Filter..."
value={columnFilters[col] || ''}
onChange={(e) => {
setColumnFilters(prev => ({ ...prev, [col]: e.target.value }));
setPage(1);
}}
onClick={(e) => e.stopPropagation()}
className="w-full px-1.5 py-0.5 bg-slate-800 border border-slate-600 rounded text-[10px] text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500"
/>
{columnFilters[col] && (
<button
onClick={(e) => {
e.stopPropagation();
setColumnFilters(prev => { const n = { ...prev }; delete n[col]; return n; });
setPage(1);
}}
className="absolute right-1 top-1/2 -translate-y-1/2 text-slate-500 hover:text-white"
>
<XCircle className="w-3 h-3" />
</button>
)}
</div>
</th> </th>
))} ))}
<th className="px-3 py-3 font-medium text-right" style={{ width: 60 }}></th> <th className="px-2 py-2 font-medium text-right" style={{ width: 60 }}></th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-700/30"> <tbody className="divide-y divide-slate-700/30">