'use client'; // Vrstva pripomienok nad newsletterom (stránka s odkazom aj editor). // Kontajner musí mať position: relative a moduly v ňom atribút data-module-id. import React, { useLayoutEffect, useState } from 'react'; import { ReviewComment } from '@/lib/reviews'; interface ModuleLayout { width: number; tops: Record; } // Prepočet súradníc kliknutia na súradnice ukotvené k modulu export function pointToModuleCoords( container: HTMLElement, clientX: number, clientY: number, moduleId?: string ): { moduleId: string; x: number; y: number } | null { const rect = container.getBoundingClientRect(); const modules = Array.from(container.querySelectorAll('[data-module-id]')); const target = moduleId ? modules.find((m) => m.dataset.moduleId === moduleId) : modules.find((m) => { const r = m.getBoundingClientRect(); return clientY >= r.top && clientY <= r.bottom; }) || modules[modules.length - 1]; if (!target || !rect.width) return null; const top = target.getBoundingClientRect().top; return { moduleId: target.dataset.moduleId!, x: Math.min(1, Math.max(0, (clientX - rect.left) / rect.width)), y: Math.round(clientY - top) }; } // Poloha modulov v kontajneri; prepočíta sa pri zmene veľkosti a obsahu export function useModuleLayout(containerRef: React.RefObject, deps: unknown[]): ModuleLayout | null { const [layout, setLayout] = useState(null); useLayoutEffect(() => { const el = containerRef.current; if (!el) return; const measure = () => { const cr = el.getBoundingClientRect(); const tops: Record = {}; el.querySelectorAll('[data-module-id]').forEach((m) => { tops[m.dataset.moduleId!] = m.getBoundingClientRect().top - cr.top; }); setLayout({ width: cr.width, tops }); }; measure(); const ro = new ResizeObserver(measure); ro.observe(el); // obrázky sa načítavajú postupne a menia výšku modulov el.addEventListener('load', measure, true); return () => { ro.disconnect(); el.removeEventListener('load', measure, true); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [containerRef, ...deps]); return layout; } interface AnnotationLayerProps { layout: ModuleLayout | null; comments: ReviewComment[]; showDone?: boolean; selectedId?: string | null; onSelect?: (id: string | null) => void; // Akcie v bubline vybranej pripomienky (napr. Hotovo v editore) renderActions?: (c: ReviewComment) => React.ReactNode; } export const AnnotationLayer: React.FC = ({ layout, comments, showDone = true, selectedId, onSelect, renderActions }) => { if (!layout) return null; const visible = comments.filter((c) => (showDone || !c.done) && layout.tops[c.module_id] !== undefined); const pos = (moduleId: string, x: number, y: number) => ({ left: x * layout.width, top: layout.tops[moduleId] + y }); const commentNumbers = new Map(comments.filter((c) => c.kind === 'comment').map((c, i) => [c.id, i + 1])); return (
{/* Kresby */} {visible .filter((c) => c.kind === 'drawing' && c.path && c.path.length > 1) .map((c) => ( { const p = pos(c.module_id, x, y); return `${p.left},${p.top}`; }).join(' ')} fill="none" stroke={c.color} strokeWidth={selectedId === c.id ? 5 : 3} strokeLinecap="round" strokeLinejoin="round" opacity={c.done ? 0.3 : 0.9} className="pointer-events-auto cursor-pointer" onClick={(e) => { e.stopPropagation(); onSelect?.(selectedId === c.id ? null : c.id); }} /> ))} {/* Komentáre (špendlíky) a texty */} {visible .filter((c) => c.kind !== 'drawing') .map((c) => { const p = pos(c.module_id, c.x, c.y); const isSelected = selectedId === c.id; return (
{ e.stopPropagation(); onSelect?.(isSelected ? null : c.id); }} > {c.kind === 'comment' ? (
{c.done ? '✓' : commentNumbers.get(c.id)}
) : (
{c.text}
)}
); })} {/* Bublina vybranej pripomienky */} {(() => { const c = visible.find((v) => v.id === selectedId); if (!c || c.kind === 'text') { return c && renderActions ? (
{renderActions(c)}
) : null; } const anchor = c.kind === 'drawing' && c.path?.length ? c.path[c.path.length - 1] : [c.x, c.y]; const p = pos(c.module_id, anchor[0], anchor[1]); return (
e.stopPropagation()} >
{c.author || 'Anonym'} {new Date(c.created_at).toLocaleString('sk-SK')}
{c.text ? (

{c.text}

) : (

{c.kind === 'drawing' ? 'Kresba bez poznámky' : ''}

)} {c.done &&

✓ Hotovo

} {renderActions &&
{renderActions(c)}
}
); })()}
); };