feat(externally-signed): mark contract/reservation as signed without file
Step 4 second slice. Adds the "Mark as signed without file" action to
contract + reservation tabs per PRE-DEPLOY-PLAN § 1.5.14.
Service: `markExternallySigned(interestId, portId, docType, reason)`
flips the relevant doc-status column ('contract_doc_status' /
'reservation_doc_status' / 'eoi_doc_status') to 'signed', writes an
audit log entry with `metadata.type='externally_signed'` capturing
the optional reason, and fires the appropriate berth-rule trigger
(eoi_signed / contract_signed) so downstream automation (berth
status flips, notifications) treats it identically to a Documenso-
signed completion.
Route: POST /api/v1/interests/[id]/mark-externally-signed gated on
interests.edit. Validates docType against the canonical 3-value enum.
UI: <MarkExternallySignedDialog> AlertDialog with optional reason
textarea + per-docType copy. Wired into EmptyContractState and
EmptyReservationState empty-state buttons. The action sits alongside
"Upload draft for signing" and "Upload paper-signed copy" as a third
option for reps whose canonical paper lives elsewhere.
EOI not yet wired into a UI surface — the eoi flow already has a
full upload pipeline. Service supports it for completeness.
Followup: quick brochure/PDF download buttons + per-user reminder
digest schedule still pending in Step 4 backlog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { ExternalEoiUploadDialog } from '@/components/interests/external-eoi-upload-dialog';
|
||||
import { MarkExternallySignedDialog } from '@/components/interests/mark-externally-signed-dialog';
|
||||
import { SigningProgress } from '@/components/documents/signing-progress';
|
||||
import { UploadForSigningDialog } from '@/components/documents/upload-for-signing-dialog';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
@@ -91,6 +92,7 @@ export function InterestContractTab({ interestId, clientId: _clientId }: Interes
|
||||
const portSlug = useUIStore((s) => s.currentPortSlug);
|
||||
const [uploadSignedOpen, setUploadSignedOpen] = useState(false);
|
||||
const [uploadForSigningOpen, setUploadForSigningOpen] = useState(false);
|
||||
const [markExternalOpen, setMarkExternalOpen] = useState(false);
|
||||
|
||||
const { data: docsRes, isLoading: docsLoading } = useQuery<{ data: DocumentRow[] }>({
|
||||
queryKey: ['documents', { interestId, documentType: 'contract' }],
|
||||
@@ -118,6 +120,7 @@ export function InterestContractTab({ interestId, clientId: _clientId }: Interes
|
||||
<EmptyContractState
|
||||
onUploadSigned={() => setUploadSignedOpen(true)}
|
||||
onUploadForSigning={() => setUploadForSigningOpen(true)}
|
||||
onMarkExternal={() => setMarkExternalOpen(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -181,6 +184,17 @@ export function InterestContractTab({ interestId, clientId: _clientId }: Interes
|
||||
documentType="contract"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* "Mark as signed externally" — flips the contract doc-status
|
||||
to 'signed' without uploading a file. Used when the rep is
|
||||
keeping the canonical copy elsewhere and just wants the CRM
|
||||
state to reflect the close. */}
|
||||
<MarkExternallySignedDialog
|
||||
open={markExternalOpen}
|
||||
onOpenChange={setMarkExternalOpen}
|
||||
interestId={interestId}
|
||||
docType="contract"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -336,9 +350,11 @@ function ActiveContractCard({
|
||||
function EmptyContractState({
|
||||
onUploadSigned,
|
||||
onUploadForSigning,
|
||||
onMarkExternal,
|
||||
}: {
|
||||
onUploadSigned: () => void;
|
||||
onUploadForSigning: () => void;
|
||||
onMarkExternal: () => void;
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-xl border border-dashed bg-muted/20 p-8 text-center">
|
||||
@@ -361,6 +377,9 @@ function EmptyContractState({
|
||||
<Upload className="size-4" aria-hidden />
|
||||
Upload paper-signed copy
|
||||
</Button>
|
||||
<Button onClick={onMarkExternal} variant="ghost" size="sm" className="gap-1.5">
|
||||
Mark as signed without file
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user