Files
pn-new-crm/src/components/documents/cancel-document-dialog.tsx
Matt 221ae5784e chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:

- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
  never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
  after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
  redirects (ocr to ai, reports to dashboard, invitations to users),
  docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
  flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
  let-reassign), set-state-in-effect disables in CountryFlag and
  UploadForSigning preview-bytes effect, unused 'confirm' destructures in
  interest contract + reservation tabs, unescaped apostrophe in test-template
  card copy
2026-05-23 00:52:59 +02:00

147 lines
5.2 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Loader2, XCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Textarea } from '@/components/ui/textarea';
export type CancelMode = 'delete' | 'keep_remote';
interface CancelDocumentDialogProps {
open: boolean;
onOpenChange: (next: boolean) => void;
/** Label used in the dialog ("Cancel reservation", "Cancel contract", "Cancel EOI"). */
documentLabel: string;
/** Fires when the rep confirms. Caller invokes the mutation with the
* chosen `cancelMode` (and optional reason). The dialog stays open
* until `onOpenChange(false)` is called by the parent - typically on
* mutation success/failure. */
onConfirm: (params: { cancelMode: CancelMode; reason: string }) => void;
/** When true, disables the confirm action + shows a spinner. */
isSubmitting?: boolean;
}
/**
* Cancel-confirm dialog with an explicit "what to do with Documenso?"
* choice. Default `'delete'` mirrors the prior behaviour - DELETE the
* upstream envelope to keep the Documenso log uncluttered. `keep_remote`
* leaves the envelope intact so admins can later inspect it for audit /
* forensics; only the local CRM row flips to `cancelled`.
*
* Used by the Reservation / Contract / EOI tabs (any signing-doc
* surface that exposes a Cancel CTA). Replaces the previous
* `useConfirmation()` flow which had no way to surface this choice.
*/
export function CancelDocumentDialog({
open,
onOpenChange,
documentLabel,
onConfirm,
isSubmitting = false,
}: CancelDocumentDialogProps) {
const [cancelMode, setCancelMode] = useState<CancelMode>('delete');
const [reason, setReason] = useState('');
function reset() {
setCancelMode('delete');
setReason('');
}
return (
<Dialog
open={open}
onOpenChange={(next) => {
if (!next) reset();
onOpenChange(next);
}}
>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Cancel {documentLabel.toLowerCase()}</DialogTitle>
<DialogDescription>
Signers will no longer be able to sign. Choose how to handle the document on Documenso.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<RadioGroup
value={cancelMode}
onValueChange={(value) => setCancelMode(value as CancelMode)}
className="gap-3"
>
<label
htmlFor="cancel-mode-delete"
className="flex cursor-pointer items-start gap-3 rounded-md border p-3 hover:bg-accent/40"
>
<RadioGroupItem id="cancel-mode-delete" value="delete" className="mt-0.5" />
<span className="space-y-0.5">
<span className="block text-sm font-medium">Delete from Documenso</span>
<span className="block text-xs text-muted-foreground">
Frees the envelope slot upstream. Use this when the draft was abandoned and the
upstream record is no longer useful.
</span>
</span>
</label>
<label
htmlFor="cancel-mode-keep"
className="flex cursor-pointer items-start gap-3 rounded-md border p-3 hover:bg-accent/40"
>
<RadioGroupItem id="cancel-mode-keep" value="keep_remote" className="mt-0.5" />
<span className="space-y-0.5">
<span className="block text-sm font-medium">Keep on Documenso for audit</span>
<span className="block text-xs text-muted-foreground">
Marks the local copy cancelled but leaves the envelope visible on Documenso so an
admin can review it later.
</span>
</span>
</label>
</RadioGroup>
<div className="space-y-1.5">
<Label htmlFor="cancel-reason" className="text-xs font-medium text-muted-foreground">
Reason (optional)
</Label>
<Textarea
id="cancel-reason"
value={reason}
onChange={(e) => setReason(e.target.value)}
rows={2}
placeholder="What changed? Inlined into the cancellation audit log."
className="text-sm"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSubmitting}>
Keep open
</Button>
<Button
variant="destructive"
onClick={() => onConfirm({ cancelMode, reason: reason.trim() })}
disabled={isSubmitting}
>
{isSubmitting ? (
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />
) : (
<XCircle className="mr-1.5 h-4 w-4" aria-hidden />
)}
Cancel {documentLabel.toLowerCase()}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}