mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 11:55:23 +02:00
fix: improve filter popover positioning and reliability across all views
This commit is contained in:
@@ -265,6 +265,7 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
|
id={`details-filter-trigger-${col}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setOpenFilterCol(openFilterCol === col ? null : col);
|
setOpenFilterCol(openFilterCol === col ? null : col);
|
||||||
@@ -286,6 +287,7 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
|||||||
|
|
||||||
{openFilterCol === col && (
|
{openFilterCol === col && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
|
triggerId={`details-filter-trigger-${col}`}
|
||||||
uniqueValues={getUniqueValues(col)}
|
uniqueValues={getUniqueValues(col)}
|
||||||
selectedValues={columnFilters[col] || []}
|
selectedValues={columnFilters[col] || []}
|
||||||
onToggle={(val) => toggleColumnFilter(col, val)}
|
onToggle={(val) => toggleColumnFilter(col, val)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useMemo, useEffect, useRef } from 'react';
|
import React, { useState, useMemo, useEffect, useRef, useCallback } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Search, Check, X, Filter } from 'lucide-react';
|
import { Search, Check, X, Filter } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
@@ -43,34 +43,44 @@ export function ColumnFilterPopover({
|
|||||||
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
||||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||||
|
|
||||||
|
const updatePosition = useCallback(() => {
|
||||||
|
if (!triggerId) return;
|
||||||
|
const trigger = document.getElementById(triggerId);
|
||||||
|
if (!trigger) return;
|
||||||
|
|
||||||
|
const rect = trigger.getBoundingClientRect();
|
||||||
|
setPosition({
|
||||||
|
top: rect.bottom + window.scrollY + 4,
|
||||||
|
left: Math.min(rect.left + window.scrollX, window.innerWidth + window.scrollX - 300)
|
||||||
|
});
|
||||||
|
}, [triggerId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.id = 'filter-portal-' + Math.random().toString(36).substr(2, 9);
|
container.id = 'filter-portal-' + Math.random().toString(36).substring(2, 9);
|
||||||
container.style.position = 'fixed';
|
container.style.position = 'absolute';
|
||||||
container.style.zIndex = '9999';
|
container.style.zIndex = '9999';
|
||||||
container.style.top = '0';
|
container.style.top = '0';
|
||||||
container.style.left = '0';
|
container.style.left = '0';
|
||||||
|
container.style.width = '100%';
|
||||||
container.style.pointerEvents = 'none';
|
container.style.pointerEvents = 'none';
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
setPortalContainer(container);
|
setPortalContainer(container);
|
||||||
|
|
||||||
if (triggerId) {
|
updatePosition();
|
||||||
const trigger = document.getElementById(triggerId);
|
|
||||||
if (trigger) {
|
// Update on scroll and resize
|
||||||
const rect = trigger.getBoundingClientRect();
|
window.addEventListener('scroll', updatePosition, true);
|
||||||
setPosition({
|
window.addEventListener('resize', updatePosition);
|
||||||
top: rect.bottom + window.scrollY + 4,
|
|
||||||
left: Math.min(rect.left + window.scrollX, window.innerWidth - 300)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
window.removeEventListener('scroll', updatePosition, true);
|
||||||
|
window.removeEventListener('resize', updatePosition);
|
||||||
if (document.body.contains(container)) {
|
if (document.body.contains(container)) {
|
||||||
document.body.removeChild(container);
|
document.body.removeChild(container);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [updatePosition]);
|
||||||
|
|
||||||
const isDraggingRef = React.useRef(false);
|
const isDraggingRef = React.useRef(false);
|
||||||
const dragStartRef = React.useRef<number | null>(null);
|
const dragStartRef = React.useRef<number | null>(null);
|
||||||
@@ -202,8 +212,8 @@ export function ColumnFilterPopover({
|
|||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
top: position.top - window.scrollY,
|
top: position.top,
|
||||||
left: position.left - window.scrollX
|
left: position.left
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
|
id="dims-filter-trigger-line"
|
||||||
onClick={() => setOpenFilter(openFilter === 'line' ? null : 'line')}
|
onClick={() => setOpenFilter(openFilter === 'line' ? null : 'line')}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 px-3 py-2 rounded-md border text-sm transition-colors",
|
"flex items-center gap-2 px-3 py-2 rounded-md border text-sm transition-colors",
|
||||||
@@ -465,6 +466,7 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
</button>
|
</button>
|
||||||
{openFilter === 'line' && (
|
{openFilter === 'line' && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
|
triggerId="dims-filter-trigger-line"
|
||||||
uniqueValues={uniqueLines}
|
uniqueValues={uniqueLines}
|
||||||
selectedValues={lineFilter}
|
selectedValues={lineFilter}
|
||||||
onToggle={val => setLineFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
onToggle={val => setLineFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
||||||
@@ -479,6 +481,7 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
|
id="dims-filter-trigger-class"
|
||||||
onClick={() => setOpenFilter(openFilter === 'class' ? null : 'class')}
|
onClick={() => setOpenFilter(openFilter === 'class' ? null : 'class')}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 px-3 py-2 rounded-md border text-sm transition-colors",
|
"flex items-center gap-2 px-3 py-2 rounded-md border text-sm transition-colors",
|
||||||
@@ -490,6 +493,7 @@ export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureStat
|
|||||||
</button>
|
</button>
|
||||||
{openFilter === 'class' && (
|
{openFilter === 'class' && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
|
triggerId="dims-filter-trigger-class"
|
||||||
uniqueValues={uniqueClasses}
|
uniqueValues={uniqueClasses}
|
||||||
selectedValues={classFilter}
|
selectedValues={classFilter}
|
||||||
onToggle={val => setClassFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
onToggle={val => setClassFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
|
id={`matrix-filter-trigger-${index}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setOpenFilterCol(openFilterCol === index ? null : index);
|
setOpenFilterCol(openFilterCol === index ? null : index);
|
||||||
@@ -276,6 +277,7 @@ export function MatrixView({ data, headers, rowStatuses }: MatrixViewProps) {
|
|||||||
|
|
||||||
{openFilterCol === index && (
|
{openFilterCol === index && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
|
triggerId={`matrix-filter-trigger-${index}`}
|
||||||
uniqueValues={getUniqueValues(index)}
|
uniqueValues={getUniqueValues(index)}
|
||||||
selectedValues={columnFilters[index] || []}
|
selectedValues={columnFilters[index] || []}
|
||||||
onToggle={(val) => toggleColumnFilter(index, val)}
|
onToggle={(val) => toggleColumnFilter(index, val)}
|
||||||
|
|||||||
@@ -422,6 +422,7 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
id={`missing-filter-trigger-${col}`}
|
||||||
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === col ? null : col); }}
|
onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === col ? null : col); }}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full flex items-center justify-between px-1.5 py-0.5 bg-slate-800 border rounded text-[10px] transition-colors",
|
"w-full flex items-center justify-between px-1.5 py-0.5 bg-slate-800 border rounded text-[10px] transition-colors",
|
||||||
@@ -433,6 +434,7 @@ export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: Mi
|
|||||||
</button>
|
</button>
|
||||||
{openFilter === col && (
|
{openFilter === col && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
|
triggerId={`missing-filter-trigger-${col}`}
|
||||||
uniqueValues={allValues}
|
uniqueValues={allValues}
|
||||||
selectedValues={selectedFilters}
|
selectedValues={selectedFilters}
|
||||||
onToggle={(val) => setColumnFilters(prev => {
|
onToggle={(val) => setColumnFilters(prev => {
|
||||||
|
|||||||
@@ -2067,16 +2067,27 @@ function TextFilterPopover({ value, onChange, onClose, triggerId }: {
|
|||||||
}) {
|
}) {
|
||||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||||
|
|
||||||
useEffect(() => {
|
const updatePosition = useCallback(() => {
|
||||||
const trigger = document.getElementById(triggerId);
|
const trigger = document.getElementById(triggerId);
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
const rect = trigger.getBoundingClientRect();
|
const rect = trigger.getBoundingClientRect();
|
||||||
setPosition({
|
setPosition({
|
||||||
top: rect.bottom + window.scrollY + 4,
|
top: rect.bottom + window.scrollY + 4,
|
||||||
left: Math.min(rect.left + window.scrollX, window.innerWidth - 300)
|
left: Math.min(rect.left + window.scrollX, window.innerWidth + window.scrollX - 300)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [triggerId]);
|
}, [triggerId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updatePosition();
|
||||||
|
window.addEventListener('scroll', updatePosition, true);
|
||||||
|
window.addEventListener('resize', updatePosition);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', updatePosition, true);
|
||||||
|
window.removeEventListener('resize', updatePosition);
|
||||||
|
};
|
||||||
|
}, [updatePosition]);
|
||||||
|
|
||||||
const addTerm = () => {
|
const addTerm = () => {
|
||||||
if (value.terms.length < 5) {
|
if (value.terms.length < 5) {
|
||||||
onChange({ ...value, terms: [...value.terms, ''] });
|
onChange({ ...value, terms: [...value.terms, ''] });
|
||||||
@@ -2101,10 +2112,10 @@ function TextFilterPopover({ value, onChange, onClose, triggerId }: {
|
|||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
className="fixed z-[9999] w-72 bg-slate-800 border border-slate-700 rounded-lg shadow-2xl p-4 flex flex-col gap-4 animate-in fade-in zoom-in-95 duration-100"
|
className="absolute z-[9999] w-72 bg-slate-800 border border-slate-700 rounded-lg shadow-2xl p-4 flex flex-col gap-4 animate-in fade-in zoom-in-95 duration-100"
|
||||||
style={{
|
style={{
|
||||||
top: position.top - window.scrollY,
|
top: position.top,
|
||||||
left: position.left - window.scrollX
|
left: position.left
|
||||||
}}
|
}}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -326,6 +326,7 @@ export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, ro
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
|
id={`desc-filter-trigger-${col}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setOpenFilterCol(openFilterCol === col ? null : col);
|
setOpenFilterCol(openFilterCol === col ? null : col);
|
||||||
@@ -347,6 +348,7 @@ export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, ro
|
|||||||
|
|
||||||
{openFilterCol === col && (
|
{openFilterCol === col && (
|
||||||
<ColumnFilterPopover
|
<ColumnFilterPopover
|
||||||
|
triggerId={`desc-filter-trigger-${col}`}
|
||||||
uniqueValues={getUniqueValues(col)}
|
uniqueValues={getUniqueValues(col)}
|
||||||
selectedValues={columnFilters[col] || []}
|
selectedValues={columnFilters[col] || []}
|
||||||
onToggle={(val) => toggleColumnFilter(col, val)}
|
onToggle={(val) => toggleColumnFilter(col, val)}
|
||||||
|
|||||||
Reference in New Issue
Block a user