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>
This commit is contained in:
91
src/components/berths/berth-status-suggestion-dialog.tsx
Normal file
91
src/components/berths/berth-status-suggestion-dialog.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { ArrowRight, Loader2 } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
|
||||
interface BerthStatusSuggestionDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
berthId: string;
|
||||
currentStatus: string;
|
||||
suggestedStatus: string;
|
||||
reason: string;
|
||||
onApplied: () => void;
|
||||
}
|
||||
|
||||
export function BerthStatusSuggestionDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
berthId,
|
||||
currentStatus,
|
||||
suggestedStatus,
|
||||
reason,
|
||||
onApplied,
|
||||
}: BerthStatusSuggestionDialogProps) {
|
||||
const applyMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
apiFetch(`/api/v1/berths/${berthId}/status`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ status: suggestedStatus, reason }),
|
||||
}),
|
||||
onSuccess: () => {
|
||||
onApplied();
|
||||
onOpenChange(false);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Suggested Status Change</DialogTitle>
|
||||
<DialogDescription>
|
||||
Based on recent activity, a berth status update is recommended.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex items-center justify-center gap-4 py-4">
|
||||
<Badge variant="outline" className="text-base px-4 py-1.5">
|
||||
{currentStatus.replace(/_/g, ' ')}
|
||||
</Badge>
|
||||
<ArrowRight className="h-5 w-5 text-muted-foreground" />
|
||||
<Badge variant="default" className="text-base px-4 py-1.5">
|
||||
{suggestedStatus.replace(/_/g, ' ')}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{reason && (
|
||||
<p className="text-sm text-muted-foreground text-center px-4">{reason}</p>
|
||||
)}
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Dismiss
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => applyMutation.mutate()}
|
||||
disabled={applyMutation.isPending}
|
||||
>
|
||||
{applyMutation.isPending && (
|
||||
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
Apply Change
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user