eduardo4547's picture
Upload 150 files
cb5d9d0 verified
import {
type PointerEvent,
type SyntheticEvent,
type RefObject,
} from "react";
import {
ArrowLeft,
Share2,
Download,
MapPin,
ShoppingCart,
RefreshCw,
RotateCcw,
Paintbrush,
Loader2,
} from "lucide-react";
import type { Product } from "../../types";
import type { SegmentMeta } from "../../hooks/useSegmentCanvas";
interface RoomPreviewPanelProps {
previewImage?: string | null;
offset: { x: number; y: number };
zoom: number;
imageSize: { width: number; height: number };
wrapperRef: RefObject<HTMLDivElement | null>;
canvasRef: RefObject<HTMLCanvasElement | null>;
selectedProduct: Product | null;
selectedMasks: Set<number>;
hoveredMask: number;
segmentMeta: Map<number, SegmentMeta>;
isApplying: boolean;
onBack: () => void;
onPointerDown: (event: PointerEvent<HTMLDivElement>) => void;
onPointerMove: (event: PointerEvent<HTMLDivElement>) => void;
onPointerUp: (event: PointerEvent<HTMLDivElement>) => void;
updateImageSize: (img: HTMLImageElement) => void;
onCanvasMouseMove: (e: React.MouseEvent<HTMLCanvasElement>) => void;
onCanvasMouseLeave: () => void;
onCanvasClick: (e: React.MouseEvent<HTMLCanvasElement>) => void;
onApplyTexture: () => void;
onReset: () => void;
onDownload: () => Promise<void>;
onShare: () => Promise<void>;
}
export function RoomPreviewPanel({
previewImage,
offset,
zoom,
imageSize,
wrapperRef,
canvasRef,
selectedProduct,
selectedMasks,
hoveredMask,
segmentMeta,
isApplying,
onBack,
onPointerDown,
onPointerMove,
onPointerUp,
updateImageSize,
onCanvasMouseMove,
onCanvasMouseLeave,
onCanvasClick,
onApplyTexture,
onReset,
onDownload,
onShare,
}: RoomPreviewPanelProps) {
const canApply = selectedMasks.size > 0 && selectedProduct != null;
const getLabel = (index: number) =>
segmentMeta.get(index)?.label ?? `Zona ${index}`;
return (
<div className="w-full h-full bg-white overflow-hidden">
<div className="relative h-full overflow-hidden lg:rounded-lg bg-[#f4f8ff]">
{/* ── Mobile top bar ───────────────────────────────────────── */}
{/* pr-12 reserva espacio a la derecha para el botΓ³n .hr-close del padre */}
<div className="lg:hidden absolute left-0 right-0 top-0 z-10 h-12 bg-white shadow-sm flex items-center justify-between px-3 pr-12">
<button
onClick={onBack}
className="p-2 rounded-full hover:bg-gray-100 transition-colors"
>
<ArrowLeft className="h-5 w-5 text-[#333]" />
</button>
<div className="flex items-center gap-1">
<button
onClick={onShare}
className="p-2 rounded-full hover:bg-[#eaf1ff] transition-colors"
>
<Share2 className="h-5 w-5 text-[#0047AB]" />
</button>
<button
onClick={onDownload}
className="p-2 rounded-full hover:bg-[#eaf1ff] transition-colors"
>
<Download className="h-5 w-5 text-[#0047AB]" />
</button>
</div>
</div>
{/* ── Desktop top bar ──────────────────────────────────────── */}
<div className="pointer-events-none hidden lg:flex absolute left-1/2 top-0 z-10 -translate-x-1/2 w-[80%] h-16 rounded-b-md bg-white shadow-sm items-center justify-center gap-4 px-3">
<button
onClick={onBack}
className="pointer-events-auto rounded-full bg-[#333333] px-4 py-2 text-sm font-semibold text-white hover:bg-black flex items-center gap-2"
>
<ArrowLeft className="h-4 w-4" /> Cambiar de HabitaciΓ³n
</button>
<span className="text-gray-400">|</span>
<button
onClick={onShare}
className="pointer-events-auto rounded-full bg-transparent px-4 py-2 text-sm font-medium text-[#0047AB] hover:bg-[#eaf1ff] flex items-center gap-2"
>
<Share2 className="h-4 w-4 text-[#0047AB]" />
Compartir
</button>
<button
onClick={onDownload}
className="pointer-events-auto rounded-full bg-transparent px-4 py-2 text-sm font-medium text-[#0047AB] hover:bg-[#eaf1ff] flex items-center gap-2"
>
<Download className="h-4 w-4 text-[#0047AB]" /> Descargar
</button>
<a
href="https://nauffargermany.com/gt/sucursales-2/"
target="_blank"
rel="noopener noreferrer"
className="pointer-events-auto rounded-full bg-transparent px-4 py-2 text-sm font-medium text-[#0047AB] hover:bg-[#eaf1ff] flex items-center gap-2"
>
<MapPin className="h-4 w-4 text-[#0047AB]" /> Encuentra tu tienda
</a>
{selectedProduct?.detailUrl ? (
<a
href={selectedProduct.detailUrl}
target="_blank"
rel="noopener noreferrer"
className="pointer-events-auto rounded-full bg-transparent px-4 py-2 text-sm font-medium text-[#0047AB] hover:bg-[#eaf1ff] flex items-center gap-2"
>
<ShoppingCart className="h-4 w-4 text-[#0047AB]" /> Ir a la pΓ‘gina del producto
</a>
) : (
<button
disabled
className="pointer-events-auto rounded-full bg-transparent px-4 py-2 text-sm font-medium text-gray-300 flex items-center gap-2 cursor-default"
>
<ShoppingCart className="h-4 w-4 text-gray-300" /> Ir a la pΓ‘gina del producto
</button>
)}
</div>
{/* ── Área de imagen + canvas ──────────────────────────────── */}
<div
ref={wrapperRef}
className="absolute inset-x-0 top-12 lg:top-16 bottom-12 lg:bottom-16 flex items-center justify-center bg-[#edf4ff] overflow-hidden"
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
>
{previewImage ? (
<div
style={{
transform: `translate(${offset.x}px, ${offset.y}px) scale(${zoom})`,
transformOrigin: "center center",
position: "relative",
display: "inline-flex",
lineHeight: 0,
...(imageSize.width > 0
? { width: imageSize.width, height: imageSize.height }
: {}),
}}
>
<img
src={previewImage}
alt="Vista previa de la habitaciΓ³n"
draggable={false}
onDragStart={(e) => e.preventDefault()}
onLoad={(event: SyntheticEvent<HTMLImageElement>) =>
updateImageSize(event.currentTarget)
}
style={{
display: "block",
width: imageSize.width > 0 ? "100%" : "auto",
height: imageSize.height > 0 ? "100%" : "auto",
maxWidth: imageSize.width > 0 ? "none" : "100%",
maxHeight: imageSize.height > 0 ? "none" : "100%",
objectFit: "contain",
}}
/>
<canvas
ref={canvasRef}
style={{
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
cursor: "crosshair",
}}
onMouseMove={onCanvasMouseMove}
onMouseLeave={onCanvasMouseLeave}
onClick={onCanvasClick}
/>
</div>
) : (
<div className="flex h-full w-full items-center justify-center text-[#707070] text-sm px-6 text-center">
No hay vista previa disponible aΓΊn.
</div>
)}
</div>
{/* ── Hint de selecciΓ³n ────────────────────────────────────── */}
{previewImage && selectedMasks.size === 0 && (
<div className="pointer-events-none absolute top-14 lg:top-20 left-1/2 -translate-x-1/2 z-10 bg-black/50 text-white text-xs px-3 py-1.5 rounded-full whitespace-nowrap">
{hoveredMask > 0
? `${getLabel(hoveredMask)} β€” haz clic para seleccionar`
: "Haz clic sobre una zona de la imagen para seleccionarla"}
</div>
)}
{/* ── Mobile bottom bar ────────────────────────────────────── */}
<div className="pointer-events-none lg:hidden absolute left-0 right-0 bottom-0 z-10 h-12 bg-white border-t border-gray-100 shadow-sm flex items-center px-3 gap-2">
{selectedProduct && (
<div className="flex items-center gap-2 min-w-0 flex-1">
<img
src={selectedProduct.image}
alt={selectedProduct.name}
className="w-8 h-8 object-cover rounded-md border border-gray-200 shrink-0"
/>
<div className="min-w-0">
<p className="text-[10px] text-[#707070] truncate">{selectedProduct.brand}</p>
<p className="text-xs font-semibold text-[#333] truncate leading-tight">{selectedProduct.name}</p>
</div>
</div>
)}
{!selectedProduct && selectedMasks.size > 0 && (
<p className="text-xs text-[#0047AB] font-medium truncate flex-1">
{[...selectedMasks].map(getLabel).join(", ")}
</p>
)}
{!selectedProduct && selectedMasks.size === 0 && <div className="flex-1" />}
<div className="flex items-center gap-1 ml-auto shrink-0">
{canApply && (
<button
onClick={onApplyTexture}
disabled={isApplying}
className="pointer-events-auto flex items-center gap-1.5 bg-[#0047AB] text-white px-3 py-1.5 rounded-full text-xs font-semibold hover:bg-[#003a94] disabled:opacity-60 transition-colors"
>
{isApplying ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<Paintbrush className="h-3 w-3" />
)}
{isApplying ? "Aplicando..." : "Aplicar"}
</button>
)}
<button
onClick={onReset}
className="pointer-events-auto p-2 rounded-full hover:bg-[#eaf1ff] transition-colors"
>
<RefreshCw className="h-4 w-4 text-[#0047AB]" />
</button>
</div>
</div>
{/* ── Desktop bottom bar ───────────────────────────────────── */}
<div className="pointer-events-none hidden lg:flex absolute left-1/2 bottom-0 z-10 -translate-x-1/2 w-[80%] h-16 rounded-t-md bg-white border border-[#0047AB]/10 shadow-sm items-center justify-start gap-4 px-4">
{selectedProduct && (
<div className="pointer-events-none flex items-center gap-3">
<img
src={selectedProduct.image}
alt={selectedProduct.name}
className="w-10 h-10 object-cover rounded-md border border-gray-200"
/>
<div>
<p className="text-[#707070] text-xs">{selectedProduct.brand}</p>
<p className="font-semibold text-[#333333] text-sm leading-tight">
{selectedProduct.name}
</p>
</div>
</div>
)}
{selectedMasks.size > 0 && (
<p className="text-xs text-[#0047AB] font-medium truncate max-w-[260px]">
{[...selectedMasks].map(getLabel).join(", ")}
</p>
)}
<div className="flex-1" />
{canApply && (
<button
onClick={onApplyTexture}
disabled={isApplying}
className="pointer-events-auto flex items-center gap-2 bg-[#0047AB] text-white px-4 py-2 rounded-full text-sm font-semibold hover:bg-[#003a94] disabled:opacity-60 transition-colors"
>
{isApplying ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Paintbrush className="h-4 w-4" />
)}
{isApplying ? "Aplicando..." : "Aplicar textura"}
</button>
)}
<button
onClick={onReset}
className="pointer-events-auto rounded-full bg-transparent px-4 py-2 text-sm font-medium text-[#0047AB] hover:bg-[#eaf1ff] flex items-center gap-2"
>
<span className="inline-flex items-center justify-center rounded-full bg-[#eaf1ff] p-2">
<RefreshCw className="h-4 w-4 text-[#0047AB]" />
</span>
Reiniciar
</button>
<button className="pointer-events-auto rounded-full bg-transparent px-4 py-2 text-sm font-medium text-[#0047AB] hover:bg-[#eaf1ff] flex items-center gap-2">
<span className="inline-flex items-center justify-center rounded-full bg-[#eaf1ff] p-2">
<RotateCcw className="h-4 w-4 text-[#0047AB]" />
</span>
Girar
</button>
</div>
</div>
</div>
);
}