Files
pn-new-crm/src/components/interests/supplemental-info-request-button.tsx

208 lines
7.9 KiB
TypeScript
Raw Normal View History

feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
'use client';
import { useState } from 'react';
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { CheckCircle2, ClipboardCopy, Clock, Mail, Send } from 'lucide-react';
import { formatDistanceToNowStrict } from 'date-fns';
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { apiFetch } from '@/lib/api/client';
interface Props {
interestId: string;
/** Hide the button when EOI has already been sent / signed at that
* point the supplemental step is past its window. Caller passes the
* current eoiStatus so we can render contextually. */
eoiStatus?: string | null;
}
interface IssueResponse {
data: {
link: string;
expiresAt: string;
emailSent: boolean;
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
resent?: boolean;
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
};
}
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
interface TokenHistoryRow {
id: string;
token: string;
createdAt: string;
expiresAt: string;
consumedAt: string | null;
issuedBy: string | null;
expired: boolean;
}
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
/**
* One-click "Request more info" action. Fires the supplemental-info-
* request endpoint, which emails the client a public form pre-filled
* with what's on file. On success we display the generated link + a
* copy-to-clipboard button in case the rep needs to share it through
* another channel.
*
* Hidden once the EOI is `signed` the supplemental step only makes
* sense before the signed EOI freezes the data into the contract path.
*/
export function SupplementalInfoRequestButton({ interestId, eoiStatus }: Props) {
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
const qc = useQueryClient();
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
const [link, setLink] = useState<string | null>(null);
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
// History query — the latest 20 issuances. Refetched after every
// mutation so the rep sees the just-generated row appear immediately.
const history = useQuery({
queryKey: ['supplemental-info', interestId, 'history'],
queryFn: () =>
apiFetch<{ data: TokenHistoryRow[] }>(
`/api/v1/interests/${interestId}/supplemental-info-request`,
),
enabled: eoiStatus !== 'signed',
staleTime: 30_000,
});
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
const mutation = useMutation({
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
mutationFn: (vars: { sendEmail: boolean; tokenId?: string }) =>
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
apiFetch<IssueResponse>(`/api/v1/interests/${interestId}/supplemental-info-request`, {
method: 'POST',
body: vars,
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
}),
onSuccess: (res) => {
setLink(res.data.link);
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
if (res.data.resent) {
toast.success('Email re-sent using the existing link.');
} else if (res.data.emailSent) {
toast.success('Email sent. Link also shown below for sharing manually.');
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
} else {
toast.message(
'Link generated. Click "Send by email" to mail it, or copy it to share manually.',
);
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
}
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
void qc.invalidateQueries({ queryKey: ['supplemental-info', interestId, 'history'] });
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
},
onError: (err) =>
toast.error(err instanceof Error ? err.message : 'Failed to generate the form link.'),
});
if (eoiStatus === 'signed') return null;
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
// Pick the latest unconsumed + unexpired token, if any. That's the
// candidate for "Resend" — the rep wants the same link to land in the
// client's inbox again. Older or consumed tokens stay in history but
// can't be resent (consumed = form already submitted; expired = link
// dead).
const tokens = history.data?.data ?? [];
const resendableToken = tokens.find((t) => !t.consumedAt && !t.expired) ?? null;
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
return (
<Card>
fix(uat): batch — timeline overshoot, name-sync, reset-password, dashboard cleanup, queue/seed hygiene + alpha UAT findings doc UAT findings landed across the last few Playwright + React Grab passes; single grouped commit so the index doesn't fragment into 30 one-liners. User & auth: - `user-settings`: name now updates the avatar + topbar menu after save (was reading stale session). - `me/password-reset`: 3 bugs (token validation, error response shape, redirect chain). - Admin user permission-overrides route honours the same envelope as the rest of the admin surface. Dashboard: - Removed obsolete `revenue-breakdown-chart` + `dashboard-widgets-card` (replaced by the customisable widget grid). - Strip `revenue_breakdown` from analytics route + use-analytics + service + integration test so nothing renders an empty card. - Activity log timeline overshoot fix (`interest-timeline` + `entity-activity-feed`). - Tightened tiles: active-deals, berth-heat-widget, pipeline-value, kpi-tile. - `dev-mode-banner`: derive dismissed state synchronously instead of via an effect (set-state-in-effect lint rule). Forms & lists (assorted polish): - client / company / yacht / interest / reminder forms — validation + empty-state copy + tab transitions. - companies/yachts list tweaks; berth recommender panel; qualification checklist; supplemental info request button. Infra & misc: - Queue workers (ai / email / notifications) — log shape + per-job timeout consistency. - Auth / brochures / users schema small adjustments; seeds reflect permissions matrix changes. - Scan shell + scanner manifest + AI admin page small fixes. - `next.config.transpilePackages` adds `echarts`/`zrender`/`echarts-for-react` (recommended config from echarts-for-react inside Next). Docs: - `docs/superpowers/audits/alpha-uat-master.md` — single rolling cross-cutting UAT findings doc (per CLAUDE.md convention). - `docs/BACKLOG.md`: dashboard stats cards (§I) + activity-log normalization (§J). - 2026-05-18 audit log updated with this batch. - `CLAUDE.md` — small manual UAT scaffold notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:56:11 +02:00
{/* shadcn's default CardContent ships with `pt-0 sm:pt-0` because it
assumes a CardHeader sits above. This card is intentionally
header-less, so we restore symmetric padding (`pt-` matches `p-`)
at both base and `sm:` breakpoints. */}
<CardContent className="space-y-3 p-4 pt-4 sm:p-6 sm:pt-6">
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
<div className="space-y-1">
<h3 className="text-sm font-semibold">Need more info before drafting the EOI?</h3>
<p className="text-xs text-muted-foreground">
Email the client a one-time link to a public form pre-filled with what we have on file.
Submissions auto-update this client + interest record.
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
type="button"
size="sm"
variant={link ? 'outline' : 'default'}
onClick={() => mutation.mutate({ sendEmail: false })}
disabled={mutation.isPending}
>
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
{mutation.isPending
? 'Generating…'
: resendableToken
? 'Regenerate link'
: 'Generate link'}
</Button>
<Button
type="button"
size="sm"
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
variant={resendableToken ? 'outline' : 'default'}
onClick={() => mutation.mutate({ sendEmail: true })}
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
disabled={mutation.isPending}
>
<Mail className="mr-1.5 size-3.5" aria-hidden />
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
{resendableToken ? 'New link + email' : 'Generate + email'}
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
</Button>
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
{resendableToken ? (
<Button
type="button"
size="sm"
variant="default"
onClick={() => mutation.mutate({ sendEmail: true, tokenId: resendableToken.id })}
disabled={mutation.isPending}
title="Re-email the existing active link to the client. No new token is created."
>
<Send className="mr-1.5 size-3.5" aria-hidden />
Resend current
</Button>
) : null}
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
{link ? (
<>
<Input value={link} readOnly className="h-8 text-xs font-mono flex-1 min-w-[260px]" />
<Button
type="button"
size="sm"
variant="outline"
onClick={() => {
void navigator.clipboard.writeText(link);
toast.success('Link copied');
}}
>
<ClipboardCopy className="mr-1.5 size-3.5" aria-hidden />
Copy
</Button>
</>
) : null}
</div>
feat(uat-batch): Groups D + E — wizard polish + supplemental-info history D24 + D25 + E26 from the 2026-05-21 plan. All three shipped. Shipped now: D24 BulkAddBerthsWizard ft/m toggle. Step 2 header gets a small monospaced ft/m button that flips the dimension entry unit wizard-wide. Cell values stay as-typed; on submit a single `inputToFt(v)` helper converts m→ft (1 m = 3.28084 ft) before posting the canonical feet payload. Column headers update Length/Width/Draft labels to reflect the active unit. D25 BulkAddBerthsWizard dock-letter expansion. Replaced the Select-of-A–E with a chip group + free-text "Other…" input. Common letters (A-E) are quick-pick chips; reps can type any uppercase letter sequence (AA, BB, F, …) for ports whose dock layout extends past the five-letter shortlist. New `handleGenerate` validation rejects empty / non-uppercase inputs with a toast. Custom-input path uppercases + strips non-letters as the rep types so the canonical `^[A-Z]+\d+$` mooring regex always matches. E26 Supplemental-info Regenerate / Resend / history. Service: new `listTokensForInterest(portId, interestId)` returns the latest 20 issuances with expired/consumed flags; new `getTokenForResend(portId, interestId, tokenId)` snapshots a specific token back into the issue-shape so the route can re-email without minting a fresh token. Route: GET lists the issuances (gated on `interests.view`); POST accepts an optional `tokenId` for the Resend branch (forces `sendEmail=true` since the rep clicked with intent) and returns `resent: true/false` on the success payload. UI: button card now shows three actions — Generate / Regenerate link, Generate + email (or "New link + email" when a usable token exists), and Resend current (only when there's an active unconsumed unexpired token). Issuance history list shows Active / Submitted / Expired per row. Verified: tsc clean, vitest 1454/1454. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:30:22 +02:00
{/* Issuance history every past supplemental link for this
interest, newest first. Lets the rep see whether a previous
link is still outstanding (so they can Resend rather than
mint a fresh one) and confirm whether the client ever
submitted. Hidden when the list is empty. */}
{tokens.length > 0 ? (
<div className="space-y-1 border-t pt-2">
<div className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
Issuance history
</div>
<ul className="divide-y">
{tokens.map((t) => {
const created = new Date(t.createdAt);
const status = t.consumedAt
? { label: 'Submitted', tone: 'text-emerald-700', icon: CheckCircle2 }
: t.expired
? { label: 'Expired', tone: 'text-muted-foreground', icon: Clock }
: { label: 'Active', tone: 'text-amber-700', icon: Clock };
const StatusIcon = status.icon;
return (
<li key={t.id} className="flex items-center justify-between gap-2 py-1.5 text-xs">
<div className="flex items-center gap-1.5">
<StatusIcon className={`size-3 ${status.tone}`} aria-hidden />
<span className={`font-medium ${status.tone}`}>{status.label}</span>
<span className="text-muted-foreground">
{formatDistanceToNowStrict(created, { addSuffix: true })}
</span>
</div>
</li>
);
})}
</ul>
</div>
) : null}
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave Replaces the legacy 9-stage pipeline with 7 canonical stages (enquiry → qualified → eoi → reservation → deposit_paid → contract → nurturing) plus three doc sub-status columns (eoi_doc_status, reservation_doc_status, contract_doc_status) that track sent/signed within a single stage instead of branching it. Schema (migration 0062): - interests gains assigned_to, deposit_expected_amount/currency, three doc-status columns, two documenso-id columns, and date_reservation_signed. - New tables: qualification_criteria (per-port admin-configurable), interest_qualifications (per-interest state), payments (deposit / balance / refund records keyed to interest + client). - Default qualification criteria seeded for every existing port. - Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into the new stage + doc-status + outcome shape. Migration 0063 adds interest_contact_log.voice_transcript and template_used columns for v1.1-A/B (quick-template buttons + voice transcription via Web Speech API). v1.1 phase work bundled here: - A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on the contact-log compose dialog (useVoiceTranscription hook). - C: berth-rules-engine wraps state writes in pg_advisory_xact_lock with an idempotent re-read; emits rule_evaluated audit traces. - D: Documenso webhook: reservation/contract sub-status stamping moved out of the PDF-download try-block so a download failure no longer swallows the stamp. New integration test coverage. - E: /admin/qualification-criteria CRUD page + admin component. - F: default_new_interest_owner exposed in System Settings. - G: recentActivityCount + active_engagement deal-pulse signal surfaced as a chip on interests + hot-deals card. - H: interest_assigned notification on assignedTo change (skips self-assign, uses a dedupe key). Plus the supporting components: AssignedToChip, DealPulseChip, PaymentsSection, QualificationChecklist, MultiEoiChip, SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner, SupplementalInfoRequestButton, UserPicker. Tests: 1370/1370 vitest pass (added deal-health unit suite + expanded constants/validators/pipeline-transitions coverage). tsc clean, eslint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
</CardContent>
</Card>
);
}