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';
|
|
|
|
|
|
|
|
|
|
import { useEffect } from 'react';
|
|
|
|
|
import { useForm } from 'react-hook-form';
|
|
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
|
|
import { Loader2, ChevronsUpDown, Check } from 'lucide-react';
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { Input } from '@/components/ui/input';
|
|
|
|
|
import { Label } from '@/components/ui/label';
|
|
|
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from '@/components/ui/select';
|
2026-04-24 15:36:27 +02:00
|
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetFooter } from '@/components/ui/sheet';
|
|
|
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
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 {
|
|
|
|
|
Command,
|
|
|
|
|
CommandEmpty,
|
|
|
|
|
CommandGroup,
|
|
|
|
|
CommandInput,
|
|
|
|
|
CommandItem,
|
|
|
|
|
CommandList,
|
|
|
|
|
} from '@/components/ui/command';
|
|
|
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
|
|
|
import { Separator } from '@/components/ui/separator';
|
|
|
|
|
import { TagPicker } from '@/components/shared/tag-picker';
|
2026-04-24 15:36:27 +02:00
|
|
|
import { YachtPicker } from '@/components/yachts/yacht-picker';
|
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';
|
|
|
|
|
import { useEntityOptions } from '@/hooks/use-entity-options';
|
|
|
|
|
import { createInterestSchema, type CreateInterestInput } from '@/lib/validators/interests';
|
refactor(sales): consolidate pipeline stages + wire EOI auto-advance
The 8→9 stage refresh from earlier today only updated constants.ts and the DB —
20 component/service files still hardcoded the old enum, leaving labels blank,
filter dropdowns wrong, kanban columns mismatched, and the analytics funnel
silently dropping new-stage rows. The platform also never advanced
pipelineStage on EOI lifecycle events: documents.service.ts wrote eoiStatus
but left the user-visible stage stuck.
This commit closes both gaps:
1. Single source of truth in src/lib/constants.ts — adds STAGE_LABELS,
STAGE_BADGE, STAGE_DOT, STAGE_WEIGHTS, STAGE_TRANSITIONS plus
stageLabel / stageBadgeClass / stageDotClass / safeStage /
canTransitionStage helpers. components/clients/pipeline-constants.ts
becomes a re-export shim so existing imports keep working.
2. 18 stale-enum surfaces migrated — interest list (table, card, filters,
form, stage picker), pipeline board, client card, berth interests tab,
portal client interests page, dashboard pipeline / funnel / revenue-
forecast charts, settings pipeline_weights default, dashboard.service
weights, analytics.service funnel stages, alert-rules stale-interest
filter, interest-scoring stage rank.
3. Documents tab wired into interest detail — replaced the placeholder in
interest-tabs.tsx with InterestDocumentsTab + InterestFilesTab so the
EOI launcher is back where salespeople work.
4. Auto-advance — new advanceStageIfBehind() in interests.service.ts
(forward-only, no-op if interest is already past the target). Called
from documents.service.ts on send (→ eoi_sent), Documenso completed
webhook (→ eoi_signed), and manual signed-EOI upload (→ eoi_signed).
5. Transition guard — canTransitionStage() blocks egregious skips
(e.g. completed → open, open → contract_signed). Enforced in
changeInterestStage before the DB write.
Tests updated to reflect the 9-stage model. tsc clean, vitest 832/832,
ESLint clean on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:33:53 +02:00
|
|
|
import { PIPELINE_STAGES, STAGE_LABELS, LEAD_CATEGORIES } from '@/lib/constants';
|
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 { cn } from '@/lib/utils';
|
|
|
|
|
|
|
|
|
|
const CATEGORY_LABELS: Record<string, string> = {
|
|
|
|
|
general_interest: 'General Interest',
|
|
|
|
|
specific_qualified: 'Specific Qualified',
|
|
|
|
|
hot_lead: 'Hot Lead',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
interface InterestFormProps {
|
|
|
|
|
open: boolean;
|
|
|
|
|
onOpenChange: (open: boolean) => void;
|
refactor(sales): consolidate pipeline stages + wire EOI auto-advance
The 8→9 stage refresh from earlier today only updated constants.ts and the DB —
20 component/service files still hardcoded the old enum, leaving labels blank,
filter dropdowns wrong, kanban columns mismatched, and the analytics funnel
silently dropping new-stage rows. The platform also never advanced
pipelineStage on EOI lifecycle events: documents.service.ts wrote eoiStatus
but left the user-visible stage stuck.
This commit closes both gaps:
1. Single source of truth in src/lib/constants.ts — adds STAGE_LABELS,
STAGE_BADGE, STAGE_DOT, STAGE_WEIGHTS, STAGE_TRANSITIONS plus
stageLabel / stageBadgeClass / stageDotClass / safeStage /
canTransitionStage helpers. components/clients/pipeline-constants.ts
becomes a re-export shim so existing imports keep working.
2. 18 stale-enum surfaces migrated — interest list (table, card, filters,
form, stage picker), pipeline board, client card, berth interests tab,
portal client interests page, dashboard pipeline / funnel / revenue-
forecast charts, settings pipeline_weights default, dashboard.service
weights, analytics.service funnel stages, alert-rules stale-interest
filter, interest-scoring stage rank.
3. Documents tab wired into interest detail — replaced the placeholder in
interest-tabs.tsx with InterestDocumentsTab + InterestFilesTab so the
EOI launcher is back where salespeople work.
4. Auto-advance — new advanceStageIfBehind() in interests.service.ts
(forward-only, no-op if interest is already past the target). Called
from documents.service.ts on send (→ eoi_sent), Documenso completed
webhook (→ eoi_signed), and manual signed-EOI upload (→ eoi_signed).
5. Transition guard — canTransitionStage() blocks egregious skips
(e.g. completed → open, open → contract_signed). Enforced in
changeInterestStage before the DB write.
Tests updated to reflect the 9-stage model. tsc clean, vitest 832/832,
ESLint clean on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:33:53 +02:00
|
|
|
/**
|
|
|
|
|
* Pre-fill clientId when launching the form from a client detail page.
|
|
|
|
|
* Ignored when `interest` is provided (edit mode).
|
|
|
|
|
*/
|
|
|
|
|
defaultClientId?: string;
|
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
|
|
|
interest?: {
|
|
|
|
|
id: string;
|
|
|
|
|
clientId: string;
|
|
|
|
|
clientName?: string | null;
|
2026-04-24 15:36:27 +02:00
|
|
|
yachtId?: string | 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
|
|
|
berthId?: string | null;
|
|
|
|
|
berthMooringNumber?: string | null;
|
|
|
|
|
pipelineStage: string;
|
|
|
|
|
leadCategory?: string | null;
|
|
|
|
|
source?: string | null;
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
reminderEnabled?: boolean;
|
|
|
|
|
reminderDays?: number | null;
|
|
|
|
|
tags?: Array<{ id: string }>;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
refactor(sales): consolidate pipeline stages + wire EOI auto-advance
The 8→9 stage refresh from earlier today only updated constants.ts and the DB —
20 component/service files still hardcoded the old enum, leaving labels blank,
filter dropdowns wrong, kanban columns mismatched, and the analytics funnel
silently dropping new-stage rows. The platform also never advanced
pipelineStage on EOI lifecycle events: documents.service.ts wrote eoiStatus
but left the user-visible stage stuck.
This commit closes both gaps:
1. Single source of truth in src/lib/constants.ts — adds STAGE_LABELS,
STAGE_BADGE, STAGE_DOT, STAGE_WEIGHTS, STAGE_TRANSITIONS plus
stageLabel / stageBadgeClass / stageDotClass / safeStage /
canTransitionStage helpers. components/clients/pipeline-constants.ts
becomes a re-export shim so existing imports keep working.
2. 18 stale-enum surfaces migrated — interest list (table, card, filters,
form, stage picker), pipeline board, client card, berth interests tab,
portal client interests page, dashboard pipeline / funnel / revenue-
forecast charts, settings pipeline_weights default, dashboard.service
weights, analytics.service funnel stages, alert-rules stale-interest
filter, interest-scoring stage rank.
3. Documents tab wired into interest detail — replaced the placeholder in
interest-tabs.tsx with InterestDocumentsTab + InterestFilesTab so the
EOI launcher is back where salespeople work.
4. Auto-advance — new advanceStageIfBehind() in interests.service.ts
(forward-only, no-op if interest is already past the target). Called
from documents.service.ts on send (→ eoi_sent), Documenso completed
webhook (→ eoi_signed), and manual signed-EOI upload (→ eoi_signed).
5. Transition guard — canTransitionStage() blocks egregious skips
(e.g. completed → open, open → contract_signed). Enforced in
changeInterestStage before the DB write.
Tests updated to reflect the 9-stage model. tsc clean, vitest 832/832,
ESLint clean on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:33:53 +02:00
|
|
|
export function InterestForm({ open, onOpenChange, defaultClientId, interest }: InterestFormProps) {
|
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 queryClient = useQueryClient();
|
|
|
|
|
const isEdit = !!interest;
|
|
|
|
|
|
|
|
|
|
const [clientOpen, setClientOpen] = useState(false);
|
|
|
|
|
const [berthOpen, setBerthOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
register,
|
|
|
|
|
handleSubmit,
|
|
|
|
|
watch,
|
|
|
|
|
setValue,
|
|
|
|
|
reset,
|
|
|
|
|
formState: { errors, isSubmitting },
|
|
|
|
|
} = useForm<CreateInterestInput>({
|
|
|
|
|
resolver: zodResolver(createInterestSchema),
|
|
|
|
|
defaultValues: {
|
|
|
|
|
clientId: '',
|
2026-04-24 15:36:27 +02:00
|
|
|
yachtId: undefined,
|
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
|
|
|
pipelineStage: 'open',
|
|
|
|
|
reminderEnabled: false,
|
|
|
|
|
tagIds: [],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const tagIds = watch('tagIds') ?? [];
|
|
|
|
|
const reminderEnabled = watch('reminderEnabled');
|
|
|
|
|
const selectedClientId = watch('clientId');
|
|
|
|
|
const selectedBerthId = watch('berthId');
|
2026-04-24 15:36:27 +02:00
|
|
|
const selectedYachtId = watch('yachtId');
|
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-24 15:36:27 +02:00
|
|
|
const {
|
|
|
|
|
options: clientOptions,
|
|
|
|
|
isLoading: clientsLoading,
|
|
|
|
|
setSearch: setClientSearch,
|
|
|
|
|
} = useEntityOptions({
|
|
|
|
|
endpoint: '/api/v1/clients/options',
|
|
|
|
|
labelKey: 'fullName',
|
|
|
|
|
});
|
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-24 15:36:27 +02:00
|
|
|
const {
|
|
|
|
|
options: berthOptions,
|
|
|
|
|
isLoading: berthsLoading,
|
|
|
|
|
setSearch: setBerthSearch,
|
|
|
|
|
} = useEntityOptions({
|
|
|
|
|
endpoint: '/api/v1/berths/options',
|
|
|
|
|
labelKey: 'mooringNumber',
|
|
|
|
|
});
|
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
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (interest && open) {
|
|
|
|
|
reset({
|
|
|
|
|
clientId: interest.clientId,
|
2026-04-24 15:36:27 +02:00
|
|
|
yachtId: interest.yachtId ?? undefined,
|
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
|
|
|
berthId: interest.berthId ?? undefined,
|
2026-04-24 15:36:27 +02:00
|
|
|
pipelineStage: interest.pipelineStage as (typeof PIPELINE_STAGES)[number],
|
|
|
|
|
leadCategory: interest.leadCategory as (typeof LEAD_CATEGORIES)[number] | undefined,
|
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
|
|
|
source: interest.source ?? undefined,
|
|
|
|
|
notes: interest.notes ?? undefined,
|
|
|
|
|
reminderEnabled: interest.reminderEnabled ?? false,
|
|
|
|
|
reminderDays: interest.reminderDays ?? undefined,
|
|
|
|
|
tagIds: interest.tags?.map((t) => t.id) ?? [],
|
|
|
|
|
});
|
|
|
|
|
} else if (!interest && open) {
|
|
|
|
|
reset({
|
refactor(sales): consolidate pipeline stages + wire EOI auto-advance
The 8→9 stage refresh from earlier today only updated constants.ts and the DB —
20 component/service files still hardcoded the old enum, leaving labels blank,
filter dropdowns wrong, kanban columns mismatched, and the analytics funnel
silently dropping new-stage rows. The platform also never advanced
pipelineStage on EOI lifecycle events: documents.service.ts wrote eoiStatus
but left the user-visible stage stuck.
This commit closes both gaps:
1. Single source of truth in src/lib/constants.ts — adds STAGE_LABELS,
STAGE_BADGE, STAGE_DOT, STAGE_WEIGHTS, STAGE_TRANSITIONS plus
stageLabel / stageBadgeClass / stageDotClass / safeStage /
canTransitionStage helpers. components/clients/pipeline-constants.ts
becomes a re-export shim so existing imports keep working.
2. 18 stale-enum surfaces migrated — interest list (table, card, filters,
form, stage picker), pipeline board, client card, berth interests tab,
portal client interests page, dashboard pipeline / funnel / revenue-
forecast charts, settings pipeline_weights default, dashboard.service
weights, analytics.service funnel stages, alert-rules stale-interest
filter, interest-scoring stage rank.
3. Documents tab wired into interest detail — replaced the placeholder in
interest-tabs.tsx with InterestDocumentsTab + InterestFilesTab so the
EOI launcher is back where salespeople work.
4. Auto-advance — new advanceStageIfBehind() in interests.service.ts
(forward-only, no-op if interest is already past the target). Called
from documents.service.ts on send (→ eoi_sent), Documenso completed
webhook (→ eoi_signed), and manual signed-EOI upload (→ eoi_signed).
5. Transition guard — canTransitionStage() blocks egregious skips
(e.g. completed → open, open → contract_signed). Enforced in
changeInterestStage before the DB write.
Tests updated to reflect the 9-stage model. tsc clean, vitest 832/832,
ESLint clean on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:33:53 +02:00
|
|
|
clientId: defaultClientId ?? '',
|
2026-04-24 15:36:27 +02:00
|
|
|
yachtId: undefined,
|
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
|
|
|
pipelineStage: 'open',
|
|
|
|
|
reminderEnabled: false,
|
|
|
|
|
tagIds: [],
|
|
|
|
|
});
|
|
|
|
|
}
|
refactor(sales): consolidate pipeline stages + wire EOI auto-advance
The 8→9 stage refresh from earlier today only updated constants.ts and the DB —
20 component/service files still hardcoded the old enum, leaving labels blank,
filter dropdowns wrong, kanban columns mismatched, and the analytics funnel
silently dropping new-stage rows. The platform also never advanced
pipelineStage on EOI lifecycle events: documents.service.ts wrote eoiStatus
but left the user-visible stage stuck.
This commit closes both gaps:
1. Single source of truth in src/lib/constants.ts — adds STAGE_LABELS,
STAGE_BADGE, STAGE_DOT, STAGE_WEIGHTS, STAGE_TRANSITIONS plus
stageLabel / stageBadgeClass / stageDotClass / safeStage /
canTransitionStage helpers. components/clients/pipeline-constants.ts
becomes a re-export shim so existing imports keep working.
2. 18 stale-enum surfaces migrated — interest list (table, card, filters,
form, stage picker), pipeline board, client card, berth interests tab,
portal client interests page, dashboard pipeline / funnel / revenue-
forecast charts, settings pipeline_weights default, dashboard.service
weights, analytics.service funnel stages, alert-rules stale-interest
filter, interest-scoring stage rank.
3. Documents tab wired into interest detail — replaced the placeholder in
interest-tabs.tsx with InterestDocumentsTab + InterestFilesTab so the
EOI launcher is back where salespeople work.
4. Auto-advance — new advanceStageIfBehind() in interests.service.ts
(forward-only, no-op if interest is already past the target). Called
from documents.service.ts on send (→ eoi_sent), Documenso completed
webhook (→ eoi_signed), and manual signed-EOI upload (→ eoi_signed).
5. Transition guard — canTransitionStage() blocks egregious skips
(e.g. completed → open, open → contract_signed). Enforced in
changeInterestStage before the DB write.
Tests updated to reflect the 9-stage model. tsc clean, vitest 832/832,
ESLint clean on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:33:53 +02:00
|
|
|
}, [interest, defaultClientId, open, reset]);
|
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 mutation = useMutation({
|
|
|
|
|
mutationFn: async (data: CreateInterestInput) => {
|
|
|
|
|
if (isEdit) {
|
|
|
|
|
const { tagIds: tIds, ...rest } = data;
|
|
|
|
|
await apiFetch(`/api/v1/interests/${interest!.id}`, { method: 'PATCH', body: rest });
|
|
|
|
|
if (tIds) {
|
|
|
|
|
await apiFetch(`/api/v1/interests/${interest!.id}/tags`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
body: { tagIds: tIds },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await apiFetch('/api/v1/interests', { method: 'POST', body: data });
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onSuccess: () => {
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['interests'] });
|
|
|
|
|
onOpenChange(false);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const selectedClient = clientOptions.find((c) => c.value === selectedClientId);
|
|
|
|
|
const selectedBerth = berthOptions.find((b) => b.value === selectedBerthId);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
<SheetContent className="w-full sm:max-w-2xl overflow-y-auto">
|
|
|
|
|
<SheetHeader>
|
|
|
|
|
<SheetTitle>{isEdit ? 'Edit Interest' : 'New Interest'}</SheetTitle>
|
|
|
|
|
</SheetHeader>
|
|
|
|
|
|
2026-04-24 15:36:27 +02:00
|
|
|
<form onSubmit={handleSubmit((data) => mutation.mutate(data))} className="space-y-6 py-6">
|
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
|
|
|
{/* Client */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
|
|
|
|
|
Client & Berth
|
|
|
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<Label>Client *</Label>
|
|
|
|
|
<Popover open={clientOpen} onOpenChange={setClientOpen}>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
role="combobox"
|
|
|
|
|
aria-expanded={clientOpen}
|
|
|
|
|
className={cn(
|
|
|
|
|
'w-full justify-between',
|
|
|
|
|
!selectedClientId && 'text-muted-foreground',
|
|
|
|
|
)}
|
|
|
|
|
disabled={isEdit}
|
|
|
|
|
>
|
2026-04-24 15:36:27 +02:00
|
|
|
{selectedClient?.label ?? interest?.clientName ?? 'Select client...'}
|
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
|
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
|
|
|
</Button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-[400px] p-0">
|
|
|
|
|
<Command>
|
2026-04-24 15:36:27 +02:00
|
|
|
<CommandInput placeholder="Search clients..." onValueChange={setClientSearch} />
|
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
|
|
|
<CommandList>
|
|
|
|
|
<CommandEmpty>
|
|
|
|
|
{clientsLoading ? 'Loading...' : 'No clients found.'}
|
|
|
|
|
</CommandEmpty>
|
|
|
|
|
<CommandGroup>
|
|
|
|
|
{clientOptions.map((option) => (
|
|
|
|
|
<CommandItem
|
|
|
|
|
key={option.value}
|
|
|
|
|
value={option.value}
|
|
|
|
|
onSelect={(val) => {
|
|
|
|
|
setValue('clientId', val);
|
|
|
|
|
setClientOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Check
|
|
|
|
|
className={cn(
|
|
|
|
|
'mr-2 h-4 w-4',
|
|
|
|
|
selectedClientId === option.value ? 'opacity-100' : 'opacity-0',
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
{option.label}
|
|
|
|
|
</CommandItem>
|
|
|
|
|
))}
|
|
|
|
|
</CommandGroup>
|
|
|
|
|
</CommandList>
|
|
|
|
|
</Command>
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
{errors.clientId && (
|
|
|
|
|
<p className="text-xs text-destructive">{errors.clientId.message}</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<Label>Berth (optional)</Label>
|
|
|
|
|
<Popover open={berthOpen} onOpenChange={setBerthOpen}>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
role="combobox"
|
|
|
|
|
aria-expanded={berthOpen}
|
|
|
|
|
className={cn(
|
|
|
|
|
'w-full justify-between',
|
|
|
|
|
!selectedBerthId && 'text-muted-foreground',
|
|
|
|
|
)}
|
|
|
|
|
>
|
2026-04-24 15:36:27 +02:00
|
|
|
{selectedBerth?.label ?? interest?.berthMooringNumber ?? 'Select berth...'}
|
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
|
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
|
|
|
</Button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className="w-[400px] p-0">
|
|
|
|
|
<Command>
|
2026-04-24 15:36:27 +02:00
|
|
|
<CommandInput placeholder="Search berths..." onValueChange={setBerthSearch} />
|
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
|
|
|
<CommandList>
|
|
|
|
|
<CommandEmpty>
|
|
|
|
|
{berthsLoading ? 'Loading...' : 'No berths found.'}
|
|
|
|
|
</CommandEmpty>
|
|
|
|
|
<CommandGroup>
|
|
|
|
|
<CommandItem
|
|
|
|
|
value=""
|
|
|
|
|
onSelect={() => {
|
|
|
|
|
setValue('berthId', undefined);
|
|
|
|
|
setBerthOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Check
|
|
|
|
|
className={cn(
|
|
|
|
|
'mr-2 h-4 w-4',
|
|
|
|
|
!selectedBerthId ? 'opacity-100' : 'opacity-0',
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
None
|
|
|
|
|
</CommandItem>
|
|
|
|
|
{berthOptions.map((option) => (
|
|
|
|
|
<CommandItem
|
|
|
|
|
key={option.value}
|
|
|
|
|
value={option.value}
|
|
|
|
|
onSelect={(val) => {
|
|
|
|
|
setValue('berthId', val);
|
|
|
|
|
setBerthOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Check
|
|
|
|
|
className={cn(
|
|
|
|
|
'mr-2 h-4 w-4',
|
|
|
|
|
selectedBerthId === option.value ? 'opacity-100' : 'opacity-0',
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
{option.label}
|
|
|
|
|
</CommandItem>
|
|
|
|
|
))}
|
|
|
|
|
</CommandGroup>
|
|
|
|
|
</CommandList>
|
|
|
|
|
</Command>
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
</div>
|
2026-04-24 15:36:27 +02:00
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>Yacht</Label>
|
|
|
|
|
<YachtPicker
|
|
|
|
|
value={selectedYachtId ?? null}
|
|
|
|
|
onChange={(id) => setValue('yachtId', id ?? undefined)}
|
|
|
|
|
ownerFilter={
|
|
|
|
|
selectedClientId ? { type: 'client', id: selectedClientId } : undefined
|
|
|
|
|
}
|
|
|
|
|
disabled={!selectedClientId}
|
|
|
|
|
placeholder={selectedClientId ? 'Select yacht...' : 'Select a client first'}
|
|
|
|
|
/>
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
Required before the interest can leave the "Open" stage.
|
|
|
|
|
</p>
|
|
|
|
|
{/* TODO: also include company-owned yachts where client is a member — requires autocomplete owner=any|company filter */}
|
|
|
|
|
{/* TODO: add "Add new yacht" inline shortcut (requires YachtForm integration) */}
|
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
|
|
|
|
{/* Pipeline */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
|
|
|
|
|
Pipeline
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<Label>Stage</Label>
|
|
|
|
|
<Select
|
|
|
|
|
value={watch('pipelineStage') ?? 'open'}
|
2026-04-24 15:36:27 +02:00
|
|
|
onValueChange={(v) =>
|
|
|
|
|
setValue('pipelineStage', v as (typeof PIPELINE_STAGES)[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
|
|
|
>
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
<SelectValue placeholder="Select stage" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{PIPELINE_STAGES.map((s) => (
|
|
|
|
|
<SelectItem key={s} value={s}>
|
refactor(sales): consolidate pipeline stages + wire EOI auto-advance
The 8→9 stage refresh from earlier today only updated constants.ts and the DB —
20 component/service files still hardcoded the old enum, leaving labels blank,
filter dropdowns wrong, kanban columns mismatched, and the analytics funnel
silently dropping new-stage rows. The platform also never advanced
pipelineStage on EOI lifecycle events: documents.service.ts wrote eoiStatus
but left the user-visible stage stuck.
This commit closes both gaps:
1. Single source of truth in src/lib/constants.ts — adds STAGE_LABELS,
STAGE_BADGE, STAGE_DOT, STAGE_WEIGHTS, STAGE_TRANSITIONS plus
stageLabel / stageBadgeClass / stageDotClass / safeStage /
canTransitionStage helpers. components/clients/pipeline-constants.ts
becomes a re-export shim so existing imports keep working.
2. 18 stale-enum surfaces migrated — interest list (table, card, filters,
form, stage picker), pipeline board, client card, berth interests tab,
portal client interests page, dashboard pipeline / funnel / revenue-
forecast charts, settings pipeline_weights default, dashboard.service
weights, analytics.service funnel stages, alert-rules stale-interest
filter, interest-scoring stage rank.
3. Documents tab wired into interest detail — replaced the placeholder in
interest-tabs.tsx with InterestDocumentsTab + InterestFilesTab so the
EOI launcher is back where salespeople work.
4. Auto-advance — new advanceStageIfBehind() in interests.service.ts
(forward-only, no-op if interest is already past the target). Called
from documents.service.ts on send (→ eoi_sent), Documenso completed
webhook (→ eoi_signed), and manual signed-EOI upload (→ eoi_signed).
5. Transition guard — canTransitionStage() blocks egregious skips
(e.g. completed → open, open → contract_signed). Enforced in
changeInterestStage before the DB write.
Tests updated to reflect the 9-stage model. tsc clean, vitest 832/832,
ESLint clean on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:33:53 +02:00
|
|
|
{STAGE_LABELS[s]}
|
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
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<Label>Lead Category</Label>
|
|
|
|
|
<Select
|
|
|
|
|
value={watch('leadCategory') ?? ''}
|
|
|
|
|
onValueChange={(v) =>
|
2026-04-24 15:36:27 +02:00
|
|
|
setValue(
|
|
|
|
|
'leadCategory',
|
|
|
|
|
v ? (v as (typeof LEAD_CATEGORIES)[number]) : undefined,
|
|
|
|
|
)
|
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
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
<SelectValue placeholder="Select category" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{LEAD_CATEGORIES.map((c) => (
|
|
|
|
|
<SelectItem key={c} value={c}>
|
|
|
|
|
{CATEGORY_LABELS[c] ?? c}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<Label>Source</Label>
|
|
|
|
|
<Select
|
|
|
|
|
value={watch('source') ?? ''}
|
|
|
|
|
onValueChange={(v) => setValue('source', v || undefined)}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
<SelectValue placeholder="Select source" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="website">Website</SelectItem>
|
|
|
|
|
<SelectItem value="manual">Manual</SelectItem>
|
|
|
|
|
<SelectItem value="referral">Referral</SelectItem>
|
|
|
|
|
<SelectItem value="broker">Broker</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
|
|
|
|
{/* Notes */}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>Notes</Label>
|
|
|
|
|
<Textarea
|
|
|
|
|
{...register('notes')}
|
|
|
|
|
placeholder="Add notes about this interest..."
|
|
|
|
|
rows={3}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
|
|
|
|
{/* Reminder */}
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
|
|
|
|
|
Reminder
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Checkbox
|
|
|
|
|
id="reminderEnabled"
|
|
|
|
|
checked={reminderEnabled ?? false}
|
|
|
|
|
onCheckedChange={(v) => setValue('reminderEnabled', !!v)}
|
|
|
|
|
/>
|
|
|
|
|
<Label htmlFor="reminderEnabled">Enable reminder</Label>
|
|
|
|
|
</div>
|
|
|
|
|
{reminderEnabled && (
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<Label>Reminder Days</Label>
|
|
|
|
|
<Input
|
|
|
|
|
{...register('reminderDays', { valueAsNumber: true })}
|
|
|
|
|
type="number"
|
|
|
|
|
min={1}
|
|
|
|
|
placeholder="e.g. 7"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
|
|
|
|
{/* Tags */}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>Tags</Label>
|
2026-04-24 15:36:27 +02:00
|
|
|
<TagPicker selectedIds={tagIds} onChange={(ids) => setValue('tagIds', ids)} />
|
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>
|
|
|
|
|
|
|
|
|
|
<SheetFooter>
|
2026-04-24 15:36:27 +02:00
|
|
|
<Button type="button" variant="outline" onClick={() => onOpenChange(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
|
|
|
Cancel
|
|
|
|
|
</Button>
|
|
|
|
|
<Button type="submit" disabled={isSubmitting || mutation.isPending}>
|
|
|
|
|
{(isSubmitting || mutation.isPending) && (
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
)}
|
|
|
|
|
{isEdit ? 'Save Changes' : 'Create Interest'}
|
|
|
|
|
</Button>
|
|
|
|
|
</SheetFooter>
|
|
|
|
|
</form>
|
|
|
|
|
</SheetContent>
|
|
|
|
|
</Sheet>
|
|
|
|
|
);
|
|
|
|
|
}
|