Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
'use client';
|
|
|
|
|
|
2026-05-13 11:50:07 +02:00
|
|
|
import { useState } from 'react';
|
2026-05-12 22:56:42 +02:00
|
|
|
import dynamic from 'next/dynamic';
|
2026-05-12 23:29:22 +02:00
|
|
|
import { ExternalLink, ZoomIn } from 'lucide-react';
|
2026-05-13 11:50:07 +02:00
|
|
|
import { useQuery } from '@tanstack/react-query';
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
2026-05-15 01:12:20 +02:00
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
} from '@/components/ui/dialog';
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import { apiFetch } from '@/lib/api/client';
|
|
|
|
|
|
2026-05-12 23:29:22 +02:00
|
|
|
// yet-another-react-lightbox is ~50kb, lazy-load it.
|
|
|
|
|
const Lightbox = dynamic(() => import('yet-another-react-lightbox'), { ssr: false });
|
|
|
|
|
import 'yet-another-react-lightbox/styles.css';
|
|
|
|
|
|
2026-05-12 22:56:42 +02:00
|
|
|
// pdfjs-dist is ~150kb gzip — lazy-load so routes that never preview
|
|
|
|
|
// PDFs don't ship it. ssr:false because the worker setup needs window.
|
|
|
|
|
const PdfViewer = dynamic(() => import('./pdf-viewer').then((m) => ({ default: m.PdfViewer })), {
|
|
|
|
|
ssr: false,
|
|
|
|
|
loading: () => (
|
|
|
|
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
|
|
|
|
Loading PDF viewer…
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
interface FilePreviewDialogProps {
|
|
|
|
|
open: boolean;
|
|
|
|
|
onOpenChange: (open: boolean) => void;
|
|
|
|
|
fileId?: string;
|
|
|
|
|
fileName?: string;
|
|
|
|
|
mimeType?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function FilePreviewDialog({
|
|
|
|
|
open,
|
|
|
|
|
onOpenChange,
|
|
|
|
|
fileId,
|
|
|
|
|
fileName,
|
|
|
|
|
mimeType,
|
|
|
|
|
}: FilePreviewDialogProps) {
|
2026-05-12 23:29:22 +02:00
|
|
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
2026-05-13 11:50:07 +02:00
|
|
|
// useQuery replaces the prior useEffect(fetch+setState) pattern. The
|
|
|
|
|
// request is gated on the dialog being open and a fileId being set.
|
|
|
|
|
const previewQuery = useQuery<{ data: { url: string } }>({
|
|
|
|
|
queryKey: ['file-preview', fileId],
|
|
|
|
|
queryFn: () => apiFetch(`/api/v1/files/${fileId}/preview`),
|
|
|
|
|
enabled: open && !!fileId,
|
|
|
|
|
});
|
|
|
|
|
const previewUrl = previewQuery.data?.data.url ?? null;
|
|
|
|
|
const loading = previewQuery.isLoading;
|
|
|
|
|
const error = previewQuery.error ? 'Failed to load preview' : null;
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
|
|
|
|
const isImage = mimeType?.startsWith('image/');
|
|
|
|
|
const isPdf = mimeType === 'application/pdf';
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
<DialogContent className="max-w-4xl w-full h-[80vh] flex flex-col">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle className="flex items-center gap-2 truncate">
|
|
|
|
|
<span className="truncate">{fileName ?? 'Preview'}</span>
|
|
|
|
|
{previewUrl && (
|
|
|
|
|
<a
|
|
|
|
|
href={previewUrl}
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
className="shrink-0 text-muted-foreground hover:text-foreground"
|
|
|
|
|
>
|
|
|
|
|
<ExternalLink className="h-4 w-4" />
|
|
|
|
|
</a>
|
|
|
|
|
)}
|
|
|
|
|
</DialogTitle>
|
2026-05-15 01:12:20 +02:00
|
|
|
{/* A6: screen-reader description; visually hidden because the
|
|
|
|
|
* title + preview surface tells sighted users what the dialog
|
|
|
|
|
* contains. Skips the Radix "missing aria-describedby" warning. */}
|
|
|
|
|
<DialogDescription className="sr-only">
|
|
|
|
|
Inline preview of {fileName ?? 'the selected file'}.
|
|
|
|
|
</DialogDescription>
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
<div className="flex-1 overflow-hidden rounded-lg border bg-muted/20">
|
|
|
|
|
{loading && (
|
|
|
|
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
|
|
|
|
Loading preview...
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{error && (
|
|
|
|
|
<div className="flex h-full items-center justify-center text-sm text-destructive">
|
|
|
|
|
{error}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{!loading && !error && previewUrl && isImage && (
|
2026-05-12 23:29:22 +02:00
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setLightboxOpen(true)}
|
|
|
|
|
className="flex h-full w-full items-center justify-center p-4 group"
|
|
|
|
|
aria-label="Open in lightbox"
|
|
|
|
|
>
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
|
|
|
<img
|
|
|
|
|
src={previewUrl}
|
|
|
|
|
alt={fileName ?? 'Preview'}
|
2026-05-12 23:29:22 +02:00
|
|
|
className="max-h-full max-w-full object-contain rounded transition-transform group-hover:scale-[1.02]"
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
/>
|
2026-05-12 23:29:22 +02:00
|
|
|
<ZoomIn className="absolute right-6 bottom-6 h-6 w-6 rounded-full bg-background/80 p-1 text-foreground opacity-0 transition-opacity group-hover:opacity-100" />
|
|
|
|
|
</button>
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{!loading && !error && previewUrl && isPdf && (
|
2026-05-12 22:56:42 +02:00
|
|
|
<PdfViewer url={previewUrl} fileName={fileName} />
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</DialogContent>
|
2026-05-12 23:29:22 +02:00
|
|
|
|
|
|
|
|
{/* Lightbox renders OUTSIDE the parent Dialog so the dialog's own
|
|
|
|
|
* bounds don't clip the fullscreen overlay. yet-another-react-
|
|
|
|
|
* lightbox handles zoom/pan/keyboard nav out of the box. */}
|
|
|
|
|
{previewUrl && isImage && (
|
|
|
|
|
<Lightbox
|
|
|
|
|
open={lightboxOpen}
|
|
|
|
|
close={() => setLightboxOpen(false)}
|
|
|
|
|
slides={[{ src: previewUrl, alt: fileName ?? 'Preview' }]}
|
|
|
|
|
controller={{ closeOnBackdropClick: true }}
|
|
|
|
|
carousel={{ finite: true }}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|