mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 10:55:25 +02:00
Add dropdown filters to Missing Data view with unique values
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { ExcelRow, COLUMNS } from '../types';
|
||||
import { Search, ChevronDown, ChevronUp, X, Edit2, Save, Filter, XCircle } from 'lucide-react';
|
||||
import { Search, ChevronDown, ChevronUp, X, Edit2, Save, Filter, XCircle, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface MissingDataViewProps {
|
||||
@@ -54,6 +54,7 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
const [editing, setEditing] = useState<EditingState | null>(null);
|
||||
const [columnFilters, setColumnFilters] = useState<Record<number, string>>({});
|
||||
const [showFilterMenu, setShowFilterMenu] = useState<number | null>(null);
|
||||
const [filterSearch, setFilterSearch] = useState<Record<number, string>>({});
|
||||
const pageSize = 100;
|
||||
|
||||
const launchDateCol = useMemo(() =>
|
||||
@@ -64,6 +65,33 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
const launchHeader = launchDateCol >= 0 ? headers[launchDateCol] : 'Launch Date';
|
||||
const readyHeader = readyToOrderCol >= 0 ? headers[readyToOrderCol] : 'Ready to Order';
|
||||
|
||||
const columnUniqueValues = useMemo(() => {
|
||||
const cols = columns.map(c => c.col);
|
||||
const result: Record<number, Set<string>> = {};
|
||||
cols.forEach(col => result[col] = new Set());
|
||||
|
||||
data.forEach(row => {
|
||||
cols.forEach(col => {
|
||||
let val: any = row[col];
|
||||
if (col === launchDateCol || col === readyToOrderCol) {
|
||||
val = formatDateValue(val) || String(val ?? '');
|
||||
} else {
|
||||
val = String(val ?? '');
|
||||
}
|
||||
if (val) result[col].add(val);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}, [data, launchDateCol, readyToOrderCol]);
|
||||
|
||||
const getDisplayValues = (col: number) => {
|
||||
const values = columnUniqueValues[col];
|
||||
if (!values) return [];
|
||||
const searchVal = filterSearch[col]?.toLowerCase() || '';
|
||||
const arr = Array.from(values).sort();
|
||||
return searchVal ? arr.filter(v => v.toLowerCase().includes(searchVal)) : arr;
|
||||
};
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
let result = data.map((row, index) => ({ row, index }));
|
||||
|
||||
@@ -214,7 +242,11 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
<table className="w-full text-left text-xs">
|
||||
<thead className="bg-slate-900/80 text-slate-400 sticky top-0 z-10">
|
||||
<tr>
|
||||
{columns.map(({ col, label, width }) => (
|
||||
{columns.map(({ col, label, width }) => {
|
||||
const isOpen = showFilterMenu === col;
|
||||
const hasFilter = columnFilters[col];
|
||||
const displayValues = getDisplayValues(col);
|
||||
return (
|
||||
<th
|
||||
key={col}
|
||||
style={{ width, minWidth: width }}
|
||||
@@ -230,32 +262,56 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
||||
)}
|
||||
</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>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setShowFilterMenu(isOpen ? null : col); }}
|
||||
className={cn(
|
||||
"w-full flex items-center justify-between px-1.5 py-0.5 bg-slate-800 border rounded text-[10px] transition-colors",
|
||||
hasFilter ? "border-indigo-500 text-white" : "border-slate-600 text-slate-400 hover:border-slate-500"
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{hasFilter || 'Filter...'}</span>
|
||||
<ChevronDown className={cn("w-3 h-3 transition-transform", isOpen && "rotate-180")} />
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div className="absolute top-full left-0 right-0 mt-1 bg-slate-800 border border-slate-600 rounded-md shadow-lg z-50 max-h-64 flex flex-col">
|
||||
<div className="p-1.5 border-b border-slate-700">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={filterSearch[col] || ''}
|
||||
onChange={(e) => setFilterSearch(prev => ({ ...prev, [col]: e.target.value }))}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="w-full px-2 py-1 bg-slate-900 border border-slate-700 rounded text-xs text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setColumnFilters(prev => { const n = { ...prev }; delete n[col]; return n; }); setPage(1); }}
|
||||
className="w-full px-2 py-1.5 text-left text-xs text-slate-300 hover:bg-slate-700 hover:text-white"
|
||||
>
|
||||
(All)
|
||||
</button>
|
||||
{displayValues.map((val) => (
|
||||
<button
|
||||
key={val}
|
||||
onClick={(e) => { e.stopPropagation(); setColumnFilters(prev => ({ ...prev, [col]: val })); setShowFilterMenu(null); setPage(1); }}
|
||||
className={cn(
|
||||
"w-full px-2 py-1.5 text-left text-xs hover:bg-slate-700 hover:text-white",
|
||||
columnFilters[col] === val ? "text-indigo-400 bg-indigo-500/10" : "text-slate-300"
|
||||
)}
|
||||
>
|
||||
{val}
|
||||
</button>
|
||||
))}
|
||||
{displayValues.length === 0 && (
|
||||
<div className="px-2 py-2 text-xs text-slate-500 text-center">No values</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
);})}
|
||||
<th className="px-2 py-2 font-medium text-right" style={{ width: 60 }}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
Reference in New Issue
Block a user