mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 13:45:24 +02:00
fix: resolve white screen crash in PricingView filters by adding comprehensive safety checks
This commit is contained in:
@@ -21,6 +21,7 @@ interface ColumnFilterPopoverProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
zIndex?: number;
|
zIndex?: number;
|
||||||
|
triggerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ColumnFilterPopover({
|
export function ColumnFilterPopover({
|
||||||
@@ -73,8 +74,10 @@ export function ColumnFilterPopover({
|
|||||||
|
|
||||||
const isDraggingRef = React.useRef(false);
|
const isDraggingRef = React.useRef(false);
|
||||||
const dragStartRef = React.useRef<number | null>(null);
|
const dragStartRef = React.useRef<number | null>(null);
|
||||||
|
const safeUniqueValues = uniqueValues || [];
|
||||||
|
const safeSelectedValues = selectedValues || [];
|
||||||
const hoveredIndexRef = React.useRef<number | null>(null);
|
const hoveredIndexRef = React.useRef<number | null>(null);
|
||||||
const selectedValuesRef = React.useRef<string[]>(selectedValues);
|
const selectedValuesRef = React.useRef<string[]>(safeSelectedValues);
|
||||||
const onSelectAllRef = React.useRef(onSelectAll);
|
const onSelectAllRef = React.useRef(onSelectAll);
|
||||||
const filteredValuesRef = React.useRef<string[]>([]);
|
const filteredValuesRef = React.useRef<string[]>([]);
|
||||||
const didDragRef = React.useRef(false);
|
const didDragRef = React.useRef(false);
|
||||||
@@ -85,16 +88,16 @@ export function ColumnFilterPopover({
|
|||||||
const listRef = React.useRef<HTMLDivElement>(null);
|
const listRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const filteredValues = useMemo(() => {
|
const filteredValues = useMemo(() => {
|
||||||
return uniqueValues.filter(v =>
|
return safeUniqueValues.filter(v =>
|
||||||
String(v || '').toLowerCase().includes(search.toLowerCase())
|
String(v || '').toLowerCase().includes(search.toLowerCase())
|
||||||
);
|
);
|
||||||
}, [uniqueValues, search]);
|
}, [safeUniqueValues, search]);
|
||||||
|
|
||||||
filteredValuesRef.current = filteredValues;
|
filteredValuesRef.current = filteredValues;
|
||||||
selectedValuesRef.current = selectedValues;
|
selectedValuesRef.current = safeSelectedValues;
|
||||||
onSelectAllRef.current = onSelectAll;
|
onSelectAllRef.current = onSelectAll;
|
||||||
|
|
||||||
const isAllSelected = selectedValues.length === uniqueValues.length && uniqueValues.length > 0;
|
const isAllSelected = safeSelectedValues.length === safeUniqueValues.length && safeUniqueValues.length > 0;
|
||||||
|
|
||||||
const applyCondition = () => {
|
const applyCondition = () => {
|
||||||
const results: string[] = [];
|
const results: string[] = [];
|
||||||
@@ -221,7 +224,7 @@ export function ColumnFilterPopover({
|
|||||||
activeTab === 'values' ? "bg-blue-600 text-white" : "text-slate-400 hover:text-white"
|
activeTab === 'values' ? "bg-blue-600 text-white" : "text-slate-400 hover:text-white"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Values ({selectedValues.length})
|
Values ({safeSelectedValues.length})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('condition')}
|
onClick={() => setActiveTab('condition')}
|
||||||
@@ -277,7 +280,7 @@ export function ColumnFilterPopover({
|
|||||||
<div
|
<div
|
||||||
key={val}
|
key={val}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={selectedValues.includes(val)}
|
aria-checked={safeSelectedValues.includes(val)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onMouseDown={(e) => { e.preventDefault(); handleMouseDown(idx); }}
|
onMouseDown={(e) => { e.preventDefault(); handleMouseDown(idx); }}
|
||||||
onMouseEnter={() => handleMouseEnter(idx)}
|
onMouseEnter={() => handleMouseEnter(idx)}
|
||||||
@@ -287,7 +290,7 @@ export function ColumnFilterPopover({
|
|||||||
const start = Math.min(lastClickedIndex, idx);
|
const start = Math.min(lastClickedIndex, idx);
|
||||||
const end = Math.max(lastClickedIndex, idx);
|
const end = Math.max(lastClickedIndex, idx);
|
||||||
const itemsToSelect = filteredValues.slice(start, end + 1);
|
const itemsToSelect = filteredValues.slice(start, end + 1);
|
||||||
const newSelected = new Set([...selectedValues, ...itemsToSelect]);
|
const newSelected = new Set([...safeSelectedValues, ...itemsToSelect]);
|
||||||
onSelectAll(Array.from(newSelected));
|
onSelectAll(Array.from(newSelected));
|
||||||
} else {
|
} else {
|
||||||
onToggle(val);
|
onToggle(val);
|
||||||
@@ -304,7 +307,7 @@ export function ColumnFilterPopover({
|
|||||||
"w-4 h-4 rounded border flex items-center justify-center shrink-0 transition-colors",
|
"w-4 h-4 rounded border flex items-center justify-center shrink-0 transition-colors",
|
||||||
selectedValues.includes(val) ? "bg-blue-600 border-blue-600" : "border-slate-600 bg-slate-900 group-hover:border-slate-500"
|
selectedValues.includes(val) ? "bg-blue-600 border-blue-600" : "border-slate-600 bg-slate-900 group-hover:border-slate-500"
|
||||||
)}>
|
)}>
|
||||||
{selectedValues.includes(val) && <Check className="w-3 h-3 text-white" />}
|
{safeSelectedValues.includes(val) && <Check className="w-3 h-3 text-white" />}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-slate-300 truncate" title={val}>{val || '(Empty)'}</span>
|
<span className="text-xs text-slate-300 truncate" title={val}>{val || '(Empty)'}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -322,7 +325,7 @@ export function ColumnFilterPopover({
|
|||||||
if (isAllSelected) {
|
if (isAllSelected) {
|
||||||
onSelectAll([]);
|
onSelectAll([]);
|
||||||
} else {
|
} else {
|
||||||
onSelectAll(uniqueValues);
|
onSelectAll(safeUniqueValues);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="text-[10px] font-black text-indigo-400 hover:text-indigo-300 transition-colors uppercase tracking-tight"
|
className="text-[10px] font-black text-indigo-400 hover:text-indigo-300 transition-colors uppercase tracking-tight"
|
||||||
|
|||||||
@@ -378,26 +378,26 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
: activeTerms.some(term => sku.includes(term));
|
: activeTerms.some(term => sku.includes(term));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (lineMultiFilter.length > 0) {
|
if ((lineMultiFilter || []).length > 0) {
|
||||||
result = result.filter(r => lineMultiFilter.includes(String(r.row[COLUMNS.LINE] || '')));
|
result = result.filter(r => (lineMultiFilter || []).includes(String(r.row[COLUMNS.LINE] || '')));
|
||||||
}
|
}
|
||||||
if (classificationFilter.length > 0) {
|
if ((classificationFilter || []).length > 0) {
|
||||||
result = result.filter(r => classificationFilter.includes(String(r.row[COLUMNS.CLASSIFICATION] || '')));
|
result = result.filter(r => (classificationFilter || []).includes(String(r.row[COLUMNS.CLASSIFICATION] || '')));
|
||||||
}
|
}
|
||||||
if (productTypeFilter.length > 0) {
|
if ((productTypeFilter || []).length > 0) {
|
||||||
result = result.filter(r => productTypeFilter.includes(String(r.row[COLUMNS.PRODUCT_TYPE] || '')));
|
result = result.filter(r => (productTypeFilter || []).includes(String(r.row[COLUMNS.PRODUCT_TYPE] || '')));
|
||||||
}
|
}
|
||||||
if (unitsOuterFilter.length > 0) {
|
if ((unitsOuterFilter || []).length > 0) {
|
||||||
result = result.filter(r => unitsOuterFilter.includes(String(r.row[unitsOuterIdx] || '')));
|
result = result.filter(r => (unitsOuterFilter || []).includes(String(r.row[unitsOuterIdx] || '')));
|
||||||
}
|
}
|
||||||
if (outerWFilter.length > 0) {
|
if ((outerWFilter || []).length > 0) {
|
||||||
result = result.filter(r => outerWFilter.includes(String(r.row[COLUMNS.OUTER_W] || '')));
|
result = result.filter(r => (outerWFilter || []).includes(String(r.row[COLUMNS.OUTER_W] || '')));
|
||||||
}
|
}
|
||||||
if (outerLFilter.length > 0) {
|
if ((outerLFilter || []).length > 0) {
|
||||||
result = result.filter(r => outerLFilter.includes(String(r.row[COLUMNS.OUTER_L] || '')));
|
result = result.filter(r => (outerLFilter || []).includes(String(r.row[COLUMNS.OUTER_L] || '')));
|
||||||
}
|
}
|
||||||
if (outerHFilter.length > 0) {
|
if ((outerHFilter || []).length > 0) {
|
||||||
result = result.filter(r => outerHFilter.includes(String(r.row[COLUMNS.OUTER_H] || '')));
|
result = result.filter(r => (outerHFilter || []).includes(String(r.row[COLUMNS.OUTER_H] || '')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic column filters (SRP, container)
|
// Dynamic column filters (SRP, container)
|
||||||
@@ -1241,7 +1241,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'line' ? null : 'line'); }}
|
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'line' ? null : 'line'); }}
|
||||||
className={cn(
|
className={cn(
|
||||||
'p-0.5 rounded hover:bg-slate-700 transition-colors shrink-0 filter-trigger-btn',
|
'p-0.5 rounded hover:bg-slate-700 transition-colors shrink-0 filter-trigger-btn',
|
||||||
lineMultiFilter.length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
|
(lineMultiFilter || []).length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Filter className="w-3 h-3" />
|
<Filter className="w-3 h-3" />
|
||||||
@@ -1250,8 +1250,8 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
{openFilter === 'line' && (
|
{openFilter === 'line' && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
triggerId="filter-trigger-line"
|
triggerId="filter-trigger-line"
|
||||||
uniqueValues={uniqueLines}
|
uniqueValues={uniqueLines || []}
|
||||||
selectedValues={lineMultiFilter}
|
selectedValues={lineMultiFilter || []}
|
||||||
onToggle={val => setLineMultiFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
onToggle={val => setLineMultiFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
||||||
onSelectAll={vals => setLineMultiFilter(vals)}
|
onSelectAll={vals => setLineMultiFilter(vals)}
|
||||||
onClear={() => { setLineMultiFilter([]); setOpenFilter(null); }}
|
onClear={() => { setLineMultiFilter([]); setOpenFilter(null); }}
|
||||||
@@ -1279,7 +1279,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'classification' ? null : 'classification'); }}
|
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'classification' ? null : 'classification'); }}
|
||||||
className={cn(
|
className={cn(
|
||||||
'p-0.5 rounded hover:bg-slate-700 transition-colors shrink-0 filter-trigger-btn',
|
'p-0.5 rounded hover:bg-slate-700 transition-colors shrink-0 filter-trigger-btn',
|
||||||
classificationFilter.length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
|
(classificationFilter || []).length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Filter className="w-3 h-3" />
|
<Filter className="w-3 h-3" />
|
||||||
@@ -1288,8 +1288,8 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
{openFilter === 'classification' && (
|
{openFilter === 'classification' && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
triggerId="filter-trigger-class"
|
triggerId="filter-trigger-class"
|
||||||
uniqueValues={uniqueClassifications}
|
uniqueValues={uniqueClassifications || []}
|
||||||
selectedValues={classificationFilter}
|
selectedValues={classificationFilter || []}
|
||||||
onToggle={val => setClassificationFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
onToggle={val => setClassificationFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
||||||
onSelectAll={vals => setClassificationFilter(vals)}
|
onSelectAll={vals => setClassificationFilter(vals)}
|
||||||
onClear={() => { setClassificationFilter([]); setOpenFilter(null); }}
|
onClear={() => { setClassificationFilter([]); setOpenFilter(null); }}
|
||||||
@@ -1310,7 +1310,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<button
|
<button
|
||||||
id="filter-trigger-type"
|
id="filter-trigger-type"
|
||||||
onClick={(e: React.MouseEvent) => { e.stopPropagation(); setOpenFilter(openFilter === 'productType' ? null : 'productType'); }}
|
onClick={(e: React.MouseEvent) => { e.stopPropagation(); setOpenFilter(openFilter === 'productType' ? null : 'productType'); }}
|
||||||
className={cn('ml-1 p-0.5 rounded transition-colors filter-trigger-btn', productTypeFilter.length > 0 ? 'text-purple-400 bg-purple-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100')}
|
className={cn('ml-1 p-0.5 rounded transition-colors filter-trigger-btn', (productTypeFilter || []).length > 0 ? 'text-purple-400 bg-purple-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100')}
|
||||||
>
|
>
|
||||||
<Filter className="w-3 h-3" />
|
<Filter className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -1318,8 +1318,8 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
{openFilter === 'productType' && (
|
{openFilter === 'productType' && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
triggerId="filter-trigger-type"
|
triggerId="filter-trigger-type"
|
||||||
uniqueValues={uniqueProductTypes}
|
uniqueValues={uniqueProductTypes || []}
|
||||||
selectedValues={productTypeFilter}
|
selectedValues={productTypeFilter || []}
|
||||||
onToggle={(val: string) => setProductTypeFilter((prev: string[]) => prev.includes(val) ? prev.filter((v: string) => v !== val) : [...prev, val])}
|
onToggle={(val: string) => setProductTypeFilter((prev: string[]) => prev.includes(val) ? prev.filter((v: string) => v !== val) : [...prev, val])}
|
||||||
onSelectAll={vals => setProductTypeFilter(vals)}
|
onSelectAll={vals => setProductTypeFilter(vals)}
|
||||||
onClear={() => { setProductTypeFilter([]); setOpenFilter(null); }}
|
onClear={() => { setProductTypeFilter([]); setOpenFilter(null); }}
|
||||||
@@ -1380,7 +1380,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
triggerId={`filter-trigger-${colKey}`}
|
triggerId={`filter-trigger-${colKey}`}
|
||||||
uniqueValues={dynamicColUniqueValues[col.index] || []}
|
uniqueValues={dynamicColUniqueValues[col.index] || []}
|
||||||
selectedValues={dynamicColFilters[col.index] || []}
|
selectedValues={dynamicColFilters[col.index] || []}
|
||||||
onToggle={val => setDynamicColFilters(prev => {
|
onToggle={val => setDynamicColFilters(prev => {
|
||||||
const current = prev[col.index] || [];
|
const current = prev[col.index] || [];
|
||||||
return current.includes(val)
|
return current.includes(val)
|
||||||
@@ -2150,7 +2150,8 @@ function TextFilterPopover({ value, onChange, onClose, triggerId }: {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
document.body && document.body);
|
document.body
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Sort Icon Helper ───────────────────────────────────────────────────
|
// ── Sort Icon Helper ───────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user