mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 13:35:25 +02:00
fix: refactor tooltips to use global state to prevent crash and duplicate function error
This commit is contained in:
@@ -53,63 +53,12 @@ function findCol(headers: string[], ...keywords: string[]): number {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NoteTooltip({ note, isAnna, children }: { note: string, isAnna?: boolean, children: React.ReactNode }) {
|
|
||||||
const [show, setShow] = useState(false);
|
|
||||||
const [pos, setPos] = useState({ top: 0, left: 0 });
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
if (!note) {
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (ref.current) {
|
|
||||||
const rect = ref.current.getBoundingClientRect();
|
|
||||||
setPos({
|
|
||||||
top: rect.bottom + window.scrollY + 8,
|
|
||||||
left: rect.left + window.scrollX + rect.width / 2
|
|
||||||
});
|
|
||||||
setShow(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleScroll = () => setShow(false);
|
|
||||||
if (show) {
|
|
||||||
window.addEventListener('scroll', handleScroll, true);
|
|
||||||
}
|
|
||||||
return () => window.removeEventListener('scroll', handleScroll, true);
|
|
||||||
}, [show]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="relative flex items-center"
|
|
||||||
ref={ref}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={() => setShow(false)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{show && createPortal(
|
|
||||||
<div
|
|
||||||
style={{ top: pos.top, left: pos.left, transform: 'translateX(-50%)' }}
|
|
||||||
className={cn(
|
|
||||||
"absolute w-48 p-2 border rounded text-xs text-white shadow-xl z-[99999] pointer-events-none animate-in fade-in zoom-in-95 duration-100",
|
|
||||||
isAnna ? "bg-pink-900/95 border-pink-500/50" : "bg-amber-900/95 border-amber-500/50"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{note}
|
|
||||||
</div>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, rowStatuses }: PricingViewProps) {
|
export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, rowStatuses }: PricingViewProps) {
|
||||||
const COLUMNS = useColumns();
|
const COLUMNS = useColumns();
|
||||||
const [filterMode, setFilterMode] = usePersistentState<FilterMode>('pricing-filterMode', 'all_errors');
|
const [filterMode, setFilterMode] = usePersistentState<FilterMode>('pricing-filterMode', 'all_errors');
|
||||||
const [search, setSearch] = usePersistentState('pricing-search', '');
|
const [search, setSearch] = usePersistentState('pricing-search', '');
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [hoveredNote, setHoveredNote] = useState<{ text: string, pos: { top: number, left: number }, isAnna: boolean } | null>(null);
|
||||||
const pageSize = 100;
|
const pageSize = 100;
|
||||||
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({
|
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({
|
||||||
articleNo: 100,
|
articleNo: 100,
|
||||||
@@ -928,6 +877,20 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ── Tooltip Portal ── */}
|
||||||
|
{hoveredNote && createPortal(
|
||||||
|
<div
|
||||||
|
style={{ top: hoveredNote.pos.top, left: hoveredNote.pos.left, transform: 'translateX(-50%)' }}
|
||||||
|
className={cn(
|
||||||
|
"fixed w-48 p-2 border rounded text-xs text-white shadow-xl z-[99999] pointer-events-none animate-in fade-in zoom-in-95 duration-100",
|
||||||
|
hoveredNote.isAnna ? "bg-pink-900/95 border-pink-500/50" : "bg-amber-900/95 border-amber-500/50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{hoveredNote.text}
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
{/* ── Search bar with Dropdown ── */}
|
{/* ── Search bar with Dropdown ── */}
|
||||||
<div className="flex-1 max-w-md relative" ref={searchDropdownRef}>
|
<div className="flex-1 max-w-md relative" ref={searchDropdownRef}>
|
||||||
@@ -1980,19 +1943,27 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
onChange={(e) => handleToggleCheck(dataIndex, e.target.checked)}
|
onChange={(e) => handleToggleCheck(dataIndex, e.target.checked)}
|
||||||
className="w-4 h-4 rounded border-slate-600 bg-slate-900 text-blue-600 focus:ring-blue-500 focus:ring-offset-slate-800 cursor-pointer"
|
className="w-4 h-4 rounded border-slate-600 bg-slate-900 text-blue-600 focus:ring-blue-500 focus:ring-offset-slate-800 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<NoteTooltip note={note}>
|
<button
|
||||||
<button
|
onClick={() => setNoteEditor({ rowIndex: dataIndex, text: note })}
|
||||||
onClick={() => setNoteEditor({ rowIndex: dataIndex, text: note })}
|
onMouseEnter={(e) => {
|
||||||
className={cn(
|
if (!note) return;
|
||||||
"p-1.5 rounded-md transition-all shrink-0",
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
note
|
setHoveredNote({
|
||||||
? "bg-amber-500/10 text-amber-400 border border-amber-500/20 hover:bg-amber-500/20"
|
text: note,
|
||||||
: "text-slate-500 hover:text-slate-300 hover:bg-slate-700/50"
|
isAnna: false,
|
||||||
)}
|
pos: { top: rect.bottom + window.scrollY + 8, left: rect.left + window.scrollX + rect.width / 2 }
|
||||||
>
|
});
|
||||||
<MessageSquare className={cn("w-3.5 h-3.5", note && "fill-amber-400/20")} />
|
}}
|
||||||
</button>
|
onMouseLeave={() => setHoveredNote(null)}
|
||||||
</NoteTooltip>
|
className={cn(
|
||||||
|
"p-1.5 rounded-md transition-all shrink-0",
|
||||||
|
note
|
||||||
|
? "bg-amber-500/10 text-amber-400 border border-amber-500/20 hover:bg-amber-500/20"
|
||||||
|
: "text-slate-500 hover:text-slate-300 hover:bg-slate-700/50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<MessageSquare className={cn("w-3.5 h-3.5", note && "fill-amber-400/20")} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -2009,19 +1980,27 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleToggleAnnaCheck(dataIndex, e.target.checked)}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleToggleAnnaCheck(dataIndex, e.target.checked)}
|
||||||
className="w-4 h-4 rounded border-slate-600 bg-slate-900 text-pink-600 focus:ring-pink-500 focus:ring-offset-slate-800 cursor-pointer"
|
className="w-4 h-4 rounded border-slate-600 bg-slate-900 text-pink-600 focus:ring-pink-500 focus:ring-offset-slate-800 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<NoteTooltip note={annaNote} isAnna>
|
<button
|
||||||
<button
|
onClick={() => setAnnaEditor({ rowIndex: dataIndex, text: annaNote })}
|
||||||
onClick={() => setAnnaEditor({ rowIndex: dataIndex, text: annaNote })}
|
onMouseEnter={(e) => {
|
||||||
className={cn(
|
if (!annaNote) return;
|
||||||
"p-1.5 rounded-md transition-all shrink-0",
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
annaNote
|
setHoveredNote({
|
||||||
? "bg-pink-500/10 text-pink-400 border border-pink-500/20 hover:bg-pink-500/20"
|
text: annaNote,
|
||||||
: "text-slate-500 hover:text-slate-300 hover:bg-slate-700/50"
|
isAnna: true,
|
||||||
)}
|
pos: { top: rect.bottom + window.scrollY + 8, left: rect.left + window.scrollX + rect.width / 2 }
|
||||||
>
|
});
|
||||||
<MessageSquare className={cn("w-3.5 h-3.5", annaNote && "fill-pink-400/20")} />
|
}}
|
||||||
</button>
|
onMouseLeave={() => setHoveredNote(null)}
|
||||||
</NoteTooltip>
|
className={cn(
|
||||||
|
"p-1.5 rounded-md transition-all shrink-0",
|
||||||
|
annaNote
|
||||||
|
? "bg-pink-500/10 text-pink-400 border border-pink-500/20 hover:bg-pink-500/20"
|
||||||
|
: "text-slate-500 hover:text-slate-300 hover:bg-slate-700/50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<MessageSquare className={cn("w-3.5 h-3.5", annaNote && "fill-pink-400/20")} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user