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-04-26 13:42:08 +02:00
|
|
|
|
import { useMemo, useState } from 'react';
|
|
|
|
|
|
import { useQuery, useQueryClient } 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
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
Dialog,
|
|
|
|
|
|
DialogContent,
|
|
|
|
|
|
DialogDescription,
|
|
|
|
|
|
DialogFooter,
|
|
|
|
|
|
DialogHeader,
|
|
|
|
|
|
DialogTitle,
|
|
|
|
|
|
} from '@/components/ui/dialog';
|
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
2026-04-26 13:42:08 +02:00
|
|
|
|
import {
|
|
|
|
|
|
Select,
|
|
|
|
|
|
SelectContent,
|
|
|
|
|
|
SelectItem,
|
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
|
SelectValue,
|
|
|
|
|
|
} from '@/components/ui/select';
|
|
|
|
|
|
import { Label } from '@/components/ui/label';
|
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-02 03:11:14 +02:00
|
|
|
|
/** Required for the EOI's top paragraph (Section 2) — without these the
|
|
|
|
|
|
* document is unsignable, so generation is blocked. Yacht and berth fields
|
|
|
|
|
|
* belong to Section 3 and may be left blank. */
|
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 EoiPrerequisites {
|
|
|
|
|
|
hasName: boolean;
|
2026-05-02 03:11:14 +02:00
|
|
|
|
hasEmail: boolean;
|
|
|
|
|
|
hasAddress: boolean;
|
|
|
|
|
|
/** Optional — info-only checks. Generation proceeds without them. */
|
2026-04-26 13:42:08 +02:00
|
|
|
|
hasYacht: boolean;
|
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
|
|
|
|
hasBerth: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface EoiGenerateDialogProps {
|
|
|
|
|
|
interestId: string;
|
|
|
|
|
|
open: boolean;
|
|
|
|
|
|
onOpenChange: (open: boolean) => void;
|
|
|
|
|
|
prerequisites: EoiPrerequisites;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-02 03:11:14 +02:00
|
|
|
|
const REQUIRED_LABELS: { key: keyof EoiPrerequisites; label: string }[] = [
|
|
|
|
|
|
{ key: 'hasName', label: 'Client name' },
|
|
|
|
|
|
{ key: 'hasAddress', label: 'Client address' },
|
|
|
|
|
|
{ key: 'hasEmail', label: 'Client email' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const OPTIONAL_LABELS: { key: keyof EoiPrerequisites; label: string }[] = [
|
|
|
|
|
|
{ key: 'hasYacht', label: 'Yacht linked (name + dimensions)' },
|
|
|
|
|
|
{ key: 'hasBerth', label: 'Berth linked (mooring number)' },
|
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-04-26 13:42:08 +02:00
|
|
|
|
const DOCUMENSO_TEMPLATE_VALUE = 'documenso-template';
|
|
|
|
|
|
|
|
|
|
|
|
interface InAppTemplate {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
description?: string | null;
|
|
|
|
|
|
templateType: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ListResponse {
|
|
|
|
|
|
data: InAppTemplate[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
export function EoiGenerateDialog({
|
|
|
|
|
|
interestId,
|
|
|
|
|
|
open,
|
|
|
|
|
|
onOpenChange,
|
|
|
|
|
|
prerequisites,
|
|
|
|
|
|
}: EoiGenerateDialogProps) {
|
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
2026-04-26 13:42:08 +02:00
|
|
|
|
const [selectedTemplate, setSelectedTemplate] = useState<string>(DOCUMENSO_TEMPLATE_VALUE);
|
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-02 03:11:14 +02:00
|
|
|
|
const requiredMet = REQUIRED_LABELS.every(({ key }) => prerequisites[key]);
|
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-04-26 13:42:08 +02:00
|
|
|
|
// Load in-app EOI templates so the operator can pick one as an alternative
|
|
|
|
|
|
// to the Documenso external-signing flow.
|
|
|
|
|
|
const { data: templatesRes } = useQuery<ListResponse>({
|
|
|
|
|
|
queryKey: ['document-templates', { templateType: 'eoi', isActive: true }],
|
|
|
|
|
|
queryFn: () =>
|
|
|
|
|
|
apiFetch<ListResponse>('/api/v1/document-templates?templateType=eoi&isActive=true'),
|
|
|
|
|
|
enabled: open,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const inAppTemplates = useMemo(() => templatesRes?.data ?? [], [templatesRes]);
|
|
|
|
|
|
|
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 handleGenerate = async () => {
|
2026-05-02 03:11:14 +02:00
|
|
|
|
if (!requiredMet) return;
|
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
|
|
|
|
|
|
|
|
|
|
setIsGenerating(true);
|
|
|
|
|
|
setError(null);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-04-26 13:42:08 +02:00
|
|
|
|
const isDocumensoPath = selectedTemplate === DOCUMENSO_TEMPLATE_VALUE;
|
|
|
|
|
|
const url = `/api/v1/document-templates/${encodeURIComponent(selectedTemplate)}/generate-and-sign`;
|
|
|
|
|
|
await apiFetch(url, {
|
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
|
|
|
|
method: 'POST',
|
2026-04-26 13:42:08 +02:00
|
|
|
|
body: {
|
|
|
|
|
|
interestId,
|
|
|
|
|
|
pathway: isDocumensoPath ? 'documenso-template' : 'inapp',
|
|
|
|
|
|
// Signers are derived server-side from EOI context for both pathways
|
|
|
|
|
|
// when the template type is EOI, so the dialog doesn't collect them.
|
|
|
|
|
|
signers: [],
|
|
|
|
|
|
},
|
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-02 23:00:58 +02:00
|
|
|
|
// Invalidate all document list queries (hub counts + per-interest lists).
|
|
|
|
|
|
// The DocumentList component uses ['documents', { interestId, clientId }]
|
|
|
|
|
|
// and the hub uses ['documents', 'hub', ...] / ['documents', 'hub-counts'].
|
|
|
|
|
|
// Using a predicate avoids key-shape drift between callers.
|
|
|
|
|
|
queryClient.invalidateQueries({
|
|
|
|
|
|
predicate: (q) => q.queryKey[0] === 'documents',
|
|
|
|
|
|
});
|
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
|
|
|
|
onOpenChange(false);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
setError(err instanceof Error ? err.message : 'Failed to generate EOI');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsGenerating(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
|
<DialogContent className="sm:max-w-md">
|
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
|
<DialogTitle>Generate Expression of Interest</DialogTitle>
|
|
|
|
|
|
<DialogDescription>
|
2026-04-26 13:42:08 +02:00
|
|
|
|
Pick how to render the EOI. Documenso is the primary path; in-app templates use the same
|
|
|
|
|
|
source PDF but render and store the PDF locally before sending for signing.
|
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
|
|
|
|
</DialogDescription>
|
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
2026-04-26 13:42:08 +02:00
|
|
|
|
<div className="space-y-4 py-2">
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<Label htmlFor="eoi-template">Template</Label>
|
|
|
|
|
|
<Select value={selectedTemplate} onValueChange={setSelectedTemplate}>
|
|
|
|
|
|
<SelectTrigger id="eoi-template">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
<SelectItem value={DOCUMENSO_TEMPLATE_VALUE}>
|
|
|
|
|
|
Documenso Standard EOI (recommended)
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
{inAppTemplates.map((t) => (
|
|
|
|
|
|
<SelectItem key={t.id} value={t.id}>
|
|
|
|
|
|
{t.name}
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-02 03:11:14 +02:00
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<p className="text-xs font-medium text-muted-foreground">
|
|
|
|
|
|
Required (Section 2 of the EOI)
|
|
|
|
|
|
</p>
|
|
|
|
|
|
{REQUIRED_LABELS.map(({ key, label }) => (
|
|
|
|
|
|
<div key={key} className="flex items-center gap-3 text-sm">
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={`flex h-5 w-5 items-center justify-center rounded-full text-xs font-bold ${
|
|
|
|
|
|
prerequisites[key] ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{prerequisites[key] ? '✓' : '✗'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={prerequisites[key] ? 'text-foreground' : 'text-muted-foreground'}
|
|
|
|
|
|
>
|
|
|
|
|
|
{label}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<p className="text-xs font-medium text-muted-foreground">
|
|
|
|
|
|
Optional (Section 3 — left blank if absent)
|
|
|
|
|
|
</p>
|
|
|
|
|
|
{OPTIONAL_LABELS.map(({ key, label }) => (
|
|
|
|
|
|
<div key={key} className="flex items-center gap-3 text-sm">
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={`flex h-5 w-5 items-center justify-center rounded-full text-xs font-bold ${
|
|
|
|
|
|
prerequisites[key]
|
|
|
|
|
|
? 'bg-green-100 text-green-700'
|
|
|
|
|
|
: 'bg-muted text-muted-foreground'
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{prerequisites[key] ? '✓' : '–'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={prerequisites[key] ? 'text-foreground' : 'text-muted-foreground'}
|
|
|
|
|
|
>
|
|
|
|
|
|
{label}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{!requiredMet ? (
|
|
|
|
|
|
<p className="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
|
|
|
|
|
|
Add the missing required details on the client's record before generating the
|
|
|
|
|
|
EOI.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : null}
|
2026-04-26 13:42:08 +02:00
|
|
|
|
</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
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-26 13:42:08 +02:00
|
|
|
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
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
|
|
|
|
|
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
|
|
|
|
Cancel
|
|
|
|
|
|
</Button>
|
2026-05-02 03:11:14 +02:00
|
|
|
|
<Button onClick={handleGenerate} disabled={!requiredMet || isGenerating}>
|
2026-04-26 13:42:08 +02:00
|
|
|
|
{isGenerating ? 'Generating…' : 'Generate EOI'}
|
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
|
|
|
|
</Button>
|
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|