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
|
|
|
|
// ─── Pipeline Stages ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export const PIPELINE_STAGES = [
|
|
|
|
|
|
'open',
|
|
|
|
|
|
'details_sent',
|
|
|
|
|
|
'in_communication',
|
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
|
|
|
|
'eoi_sent',
|
|
|
|
|
|
'eoi_signed',
|
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
|
|
|
|
'deposit_10pct',
|
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
|
|
|
|
'contract_sent',
|
|
|
|
|
|
'contract_signed',
|
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
|
|
|
|
'completed',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type PipelineStage = (typeof PIPELINE_STAGES)[number];
|
|
|
|
|
|
|
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 const STAGE_LABELS: Record<PipelineStage, string> = {
|
|
|
|
|
|
open: 'Open',
|
|
|
|
|
|
details_sent: 'Details Sent',
|
|
|
|
|
|
in_communication: 'In Comms',
|
|
|
|
|
|
eoi_sent: 'EOI Sent',
|
|
|
|
|
|
eoi_signed: 'EOI Signed',
|
|
|
|
|
|
deposit_10pct: 'Deposit 10%',
|
|
|
|
|
|
contract_sent: 'Contract Sent',
|
|
|
|
|
|
contract_signed: 'Contract Signed',
|
|
|
|
|
|
completed: 'Completed',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
fix(ux): batch UX audit fixes across spine pages
Comprehensive audit findings rolled up into one pass.
Bugs:
- dialog.tsx — sm-breakpoint centering classes (sm:left-[50%] /
sm:top-[50%]) were being silently stripped by tailwind-merge because
the base inset-0 + sm:inset-auto pair counted as a conflict. Replaced
with explicit per-side utilities (top-0 right-0 bottom-0 left-0 +
sm:right-auto sm:bottom-auto). Every Dialog instance now centers
correctly on desktop. (Affected 16 dialog consumers.)
- interest-documents-tab.tsx — useQuery shared the queryKey
['interests', interestId] with the parent InterestDetail's query but
returned a different shape ({ data: ... } envelope vs unwrapped).
They clobbered each other's cache on tab mount, degenerating the
parent header to "Unknown Client" / "Open" briefly. Unified the
queryFn shape so the cache stays consistent.
- interest-tabs.tsx — milestone steps now derive done-state from
PIPELINE_STAGES.indexOf(currentStage) >= step.advanceStage_idx as
well as from the date stamp. Stage truth > date truth. Seeded /
imported interests that arrived past `open` without per-step dates
now correctly show their milestone steps as checked.
- interest-detail.tsx — wires useMobileChrome so the mobile topbar
shows the client name instead of the interest UUID.
- interest-documents-tab.tsx — empty state restructured to a centered
"No documents yet — Generate EOI" CTA card instead of a small
primary button floating in the corner.
- timeline/route.ts — synthesizes a "Created at <stage>" event when
no audit-log rows exist for the interest, so the Activity tab
isn't empty for seeded interests.
- lead-source-chart.tsx — pie radii switched from fixed 90px/50px
to "70%"/"40%" so the pie scales with the container instead of
being clipped at narrow widths; reserved 40px for the legend.
Visual / clarity:
- interest-detail-header.tsx — Won/Lost rendered as branded text
buttons on desktop ("Mark won", "Close as lost") and icon-only on
mobile via `hidden sm:inline`. Edit/Archive stay icon-only. Reopen
promoted to a labeled button when the interest is closed. Added
"Last contact Xd ago" to the meta row.
- detail-header-strip.tsx — py-4 → py-3 (tighter strip).
- interest-tabs.tsx — milestone cards: the next pending milestone
gets a brand-blue ring + "NEXT" pill so the user can see at a
glance which lifecycle to act on. Its primary action gets the
filled button variant.
- interest-tabs.tsx — Deposit milestone: invoice flow promoted to
primary CTA ("Create deposit invoice"), manual stage advance
demoted to a small text link ("Mark received manually"). Reflects
the actual recommended path now that recordPayment auto-advances
on payment.
- inline-editable-field.tsx — pencil affordance shown faintly
(opacity-20) at rest so users discover that fields are editable
without having to hover-test every label. Lifts to opacity-60 on
hover.
- constants.ts — STAGE_SHORT_LABELS map for cramped contexts;
pipeline-chart.tsx + pipeline-funnel-chart.tsx use them on mobile
via useIsMobile, so the rotated 9-stage axis isn't a wall of
overlap on a 393px screen.
- client-pipeline-summary.tsx — StageStepper rebuilt as a single
segmented progress bar instead of 9 micro-dots + connectors that
rendered inconsistently at tight widths. Each stage is an equal
slice that lights up as the interest reaches it; tooltips on hover
give the full stage name. Also dropped a pre-existing dead `br`
variable.
- dashboard empty states — Lead Source, Revenue Breakdown, Pipeline
Funnel, and Recent Activity now have helpful descriptions explaining
what populates them, instead of bare "No interests in range".
- use-paginated-query.ts — reuses `&` when the endpoint already has
`?`, so callers like the documents hub don't generate
`…?tab=eoi_queue&signatureOnly=true?page=1&limit=25` (which the API
rejected as 400). Caught while testing the now-removed EOI route
but applies broadly.
tsc clean. vitest 832/832 pass. eslint 0 errors (down from 1
pre-existing) on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 01:24:15 +02:00
|
|
|
|
// Compact labels for cramped contexts (mobile chart axes, dense tables).
|
|
|
|
|
|
export const STAGE_SHORT_LABELS: Record<PipelineStage, string> = {
|
|
|
|
|
|
open: 'Open',
|
|
|
|
|
|
details_sent: 'Details',
|
|
|
|
|
|
in_communication: 'Comms',
|
|
|
|
|
|
eoi_sent: 'EOI →',
|
|
|
|
|
|
eoi_signed: 'EOI ✓',
|
|
|
|
|
|
deposit_10pct: 'Dep.',
|
|
|
|
|
|
contract_sent: 'Ctr →',
|
|
|
|
|
|
contract_signed: 'Ctr ✓',
|
|
|
|
|
|
completed: 'Done',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
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 const STAGE_BADGE: Record<PipelineStage, string> = {
|
|
|
|
|
|
open: 'bg-slate-100 text-slate-700',
|
|
|
|
|
|
details_sent: 'bg-blue-100 text-blue-700',
|
|
|
|
|
|
in_communication: 'bg-sky-100 text-sky-700',
|
|
|
|
|
|
eoi_sent: 'bg-indigo-100 text-indigo-700',
|
|
|
|
|
|
eoi_signed: 'bg-amber-100 text-amber-700',
|
|
|
|
|
|
deposit_10pct: 'bg-orange-100 text-orange-700',
|
|
|
|
|
|
contract_sent: 'bg-yellow-100 text-yellow-700',
|
|
|
|
|
|
contract_signed: 'bg-green-100 text-green-700',
|
|
|
|
|
|
completed: 'bg-emerald-100 text-emerald-700',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const STAGE_DOT: Record<PipelineStage, string> = {
|
|
|
|
|
|
open: 'bg-slate-400',
|
|
|
|
|
|
details_sent: 'bg-blue-500',
|
|
|
|
|
|
in_communication: 'bg-sky-500',
|
|
|
|
|
|
eoi_sent: 'bg-indigo-500',
|
|
|
|
|
|
eoi_signed: 'bg-amber-500',
|
|
|
|
|
|
deposit_10pct: 'bg-orange-500',
|
|
|
|
|
|
contract_sent: 'bg-yellow-500',
|
|
|
|
|
|
contract_signed: 'bg-green-500',
|
|
|
|
|
|
completed: 'bg-emerald-500',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Default revenue-forecast probability weights per stage (0–1).
|
|
|
|
|
|
// Editable per port via settings (`pipeline_weights`); these are the fallbacks.
|
|
|
|
|
|
export const STAGE_WEIGHTS: Record<PipelineStage, number> = {
|
|
|
|
|
|
open: 0.05,
|
|
|
|
|
|
details_sent: 0.1,
|
|
|
|
|
|
in_communication: 0.2,
|
|
|
|
|
|
eoi_sent: 0.4,
|
|
|
|
|
|
eoi_signed: 0.6,
|
|
|
|
|
|
deposit_10pct: 0.75,
|
|
|
|
|
|
contract_sent: 0.85,
|
|
|
|
|
|
contract_signed: 0.95,
|
|
|
|
|
|
completed: 1.0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Allowed transitions out of each stage. Used by changeInterestStage to guard
|
|
|
|
|
|
// against accidental skips (e.g. dragging a card from Completed back to Open,
|
|
|
|
|
|
// or jumping Open straight to Completed). Forward moves of 1-2 stages are
|
|
|
|
|
|
// permitted; backward moves are limited to the immediate predecessor unless
|
|
|
|
|
|
// the lifecycle (EOI/contract chain) needs an explicit rewind.
|
|
|
|
|
|
export const STAGE_TRANSITIONS: Record<PipelineStage, readonly PipelineStage[]> = {
|
|
|
|
|
|
open: ['details_sent', 'in_communication', 'eoi_sent', 'eoi_signed'],
|
|
|
|
|
|
details_sent: ['open', 'in_communication', 'eoi_sent', 'eoi_signed'],
|
|
|
|
|
|
in_communication: ['open', 'details_sent', 'eoi_sent', 'eoi_signed'],
|
|
|
|
|
|
eoi_sent: ['in_communication', 'eoi_signed', 'deposit_10pct'],
|
|
|
|
|
|
eoi_signed: ['eoi_sent', 'deposit_10pct', 'contract_sent', 'contract_signed'],
|
|
|
|
|
|
deposit_10pct: ['eoi_signed', 'contract_sent', 'contract_signed'],
|
|
|
|
|
|
contract_sent: ['eoi_signed', 'deposit_10pct', 'contract_signed'],
|
|
|
|
|
|
contract_signed: ['contract_sent', 'deposit_10pct', 'completed'],
|
|
|
|
|
|
completed: ['contract_signed'],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export function canTransitionStage(from: string, to: string): boolean {
|
|
|
|
|
|
if (from === to) return true;
|
|
|
|
|
|
const fromStage = safeStage(from);
|
|
|
|
|
|
const toStage = safeStage(to);
|
|
|
|
|
|
return STAGE_TRANSITIONS[fromStage].includes(toStage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function safeStage(value: string | null | undefined): PipelineStage {
|
|
|
|
|
|
return PIPELINE_STAGES.includes(value as PipelineStage) ? (value as PipelineStage) : 'open';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function stageLabel(stage: string | null | undefined): string {
|
|
|
|
|
|
return STAGE_LABELS[safeStage(stage)];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function stageBadgeClass(stage: string | null | undefined): string {
|
|
|
|
|
|
return STAGE_BADGE[safeStage(stage)];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function stageDotClass(stage: string | null | undefined): string {
|
|
|
|
|
|
return STAGE_DOT[safeStage(stage)];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
// ─── Berth Statuses ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_STATUSES = ['available', 'under_offer', 'sold'] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type BerthStatus = (typeof BERTH_STATUSES)[number];
|
|
|
|
|
|
|
feat(berths): full NocoDB field parity, numeric types, sales edit access
Aligns the berths schema with the 117 production rows in NocoDB and exposes
every field for editing via the BerthForm sheet.
Schema (migration 0020):
- power_capacity / voltage / nominal_boat_size / nominal_boat_size_m: text -> numeric
(NocoDB stores plain numbers; text was wrong shape and broke filter/sort)
- ADD status_override_mode text (1/117 legacy rows have a value; carried
forward for parity but not yet wired into the UI)
- USING NULLIF(TRIM(...), '')::numeric so legacy whitespace and empty
strings convert cleanly
Validator + service:
- updateBerthSchema / createBerthSchema use z.coerce.number() for the
four numeric fields
- berths.service stringifies numeric values for Drizzle's numeric type
Form (src/components/berths/berth-form.tsx):
- adds: nominal boat size (ft/m), water depth (ft/m) + "is minimum" flag,
side pontoon, cleat type/capacity, bollard type/capacity, bow facing
- converts to typed selects (with NocoDB option lists in src/lib/constants):
area, side pontoon, mooring type, cleat type/capacity, bollard type/capacity,
access
- power capacity / voltage become numeric inputs (with kW / V hints)
Permissions (seed.ts + dev DB):
- sales_manager and sales_agent: berths.edit false -> true
("sales will sometimes have to update these and I cannot be the only one")
- super_admin / director already had it; viewer stays read-only
- dev DB updated in-place via UPDATE roles ... jsonb_set
Verification:
- pnpm exec vitest run: 858/858 passing
- pnpm exec tsc --noEmit: same 36 errors as baseline (all pre-existing
on feat/mobile-foundation, none introduced)
- lint clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:30:32 +02:00
|
|
|
|
// ─── Berth single-select catalogues (mirror NocoDB) ──────────────────────────
|
|
|
|
|
|
// Stored as free text in the DB so legacy values still load, but the form
|
|
|
|
|
|
// presents only the canonical options below.
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_AREAS = ['A', 'B', 'C', 'D', 'E'] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_SIDE_PONTOON_OPTIONS = [
|
|
|
|
|
|
'No',
|
|
|
|
|
|
'Quay SB',
|
|
|
|
|
|
'Quay PT',
|
|
|
|
|
|
'Quay SB, Yes PT',
|
|
|
|
|
|
'Quay PT, Yes SB',
|
|
|
|
|
|
'Yes SB',
|
|
|
|
|
|
'Yes PT',
|
|
|
|
|
|
'Yes SB, PT',
|
|
|
|
|
|
'Finger SB',
|
|
|
|
|
|
'Finger PT',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_MOORING_TYPES = [
|
|
|
|
|
|
'Side Pier / Med Mooring',
|
|
|
|
|
|
'2x Med Mooring',
|
|
|
|
|
|
'Side Pier / Finger',
|
|
|
|
|
|
'Finger / Med Mooring',
|
|
|
|
|
|
'2x Finger',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_CLEAT_TYPES = ['A3', 'A5'] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_CLEAT_CAPACITIES = ['10-14 ton break load', '20-24 ton break load'] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_BOLLARD_TYPES = ['Bull bollard type A', 'Bull bollard type B'] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_BOLLARD_CAPACITIES = ['20 ton break load', '40 ton break load'] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export const BERTH_ACCESS_OPTIONS = [
|
|
|
|
|
|
'Car to Vessel',
|
|
|
|
|
|
'Car to Quai, Cart to Vessel',
|
|
|
|
|
|
'Cart to Vessel',
|
|
|
|
|
|
'Car (3t) to Vessel',
|
|
|
|
|
|
'Car (3.5t) to Vessel',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
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
|
|
|
|
// ─── Lead Categories ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
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 const LEAD_CATEGORIES = ['general_interest', 'specific_qualified', 'hot_lead'] as const;
|
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 type LeadCategory = (typeof LEAD_CATEGORIES)[number];
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Document Types ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
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 const DOCUMENT_TYPES = ['eoi', 'contract', 'nda', 'reservation_agreement', 'other'] as const;
|
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 type DocumentType = (typeof DOCUMENT_TYPES)[number];
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Document Statuses ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export const DOCUMENT_STATUSES = [
|
|
|
|
|
|
'draft',
|
|
|
|
|
|
'sent',
|
|
|
|
|
|
'partially_signed',
|
|
|
|
|
|
'completed',
|
|
|
|
|
|
'expired',
|
|
|
|
|
|
'cancelled',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type DocumentStatus = (typeof DOCUMENT_STATUSES)[number];
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Expense Categories ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export const EXPENSE_CATEGORIES = [
|
|
|
|
|
|
'fuel',
|
|
|
|
|
|
'maintenance',
|
|
|
|
|
|
'cleaning',
|
|
|
|
|
|
'docking',
|
|
|
|
|
|
'insurance',
|
|
|
|
|
|
'utilities',
|
|
|
|
|
|
'marina_fees',
|
|
|
|
|
|
'repairs',
|
|
|
|
|
|
'equipment',
|
|
|
|
|
|
'crew',
|
|
|
|
|
|
'administration',
|
|
|
|
|
|
'marketing',
|
|
|
|
|
|
'travel',
|
|
|
|
|
|
'entertainment',
|
|
|
|
|
|
'other',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type ExpenseCategory = (typeof EXPENSE_CATEGORIES)[number];
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Payment Methods ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export const PAYMENT_METHODS = [
|
|
|
|
|
|
'bank_transfer',
|
|
|
|
|
|
'credit_card',
|
|
|
|
|
|
'debit_card',
|
|
|
|
|
|
'cash',
|
|
|
|
|
|
'cheque',
|
|
|
|
|
|
'crypto',
|
|
|
|
|
|
'other',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type PaymentMethod = (typeof PAYMENT_METHODS)[number];
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Notification Types ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export const NOTIFICATION_TYPES = [
|
|
|
|
|
|
// Interest / pipeline
|
|
|
|
|
|
'interest_stage_changed',
|
|
|
|
|
|
'interest_created',
|
|
|
|
|
|
'interest_assigned',
|
|
|
|
|
|
// Documents
|
|
|
|
|
|
'document_sent',
|
|
|
|
|
|
'document_signed',
|
|
|
|
|
|
'document_completed',
|
|
|
|
|
|
'document_expired',
|
|
|
|
|
|
'document_reminder',
|
|
|
|
|
|
// Reminders
|
|
|
|
|
|
'reminder_due',
|
|
|
|
|
|
'reminder_overdue',
|
|
|
|
|
|
'reminder_assigned',
|
|
|
|
|
|
// Financial
|
|
|
|
|
|
'invoice_sent',
|
|
|
|
|
|
'invoice_paid',
|
|
|
|
|
|
'invoice_overdue',
|
|
|
|
|
|
// Notes
|
|
|
|
|
|
'mention',
|
|
|
|
|
|
// Email
|
|
|
|
|
|
'email_received',
|
|
|
|
|
|
// System
|
|
|
|
|
|
'system_alert',
|
|
|
|
|
|
'job_failed',
|
|
|
|
|
|
'bulk_operation_complete',
|
|
|
|
|
|
'export_ready',
|
|
|
|
|
|
// Berths
|
|
|
|
|
|
'berth_status_changed',
|
|
|
|
|
|
'berth_waiting_list_update',
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export type NotificationType = (typeof NOTIFICATION_TYPES)[number];
|