Adds complete initial codebase for the RJ Bulk Supplies fuel delivery backoffice: - Authentication pages and protected route layouts - Customer, supplier, fuel purchase, and delivery note management - Custom reporting and customer statement sending tools - Full UI component library and Tailwind styling - Server actions, API routes, and PocketBase integration - Development scripts and project configuration updates - Updated README and package metadata
137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useTransition } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { MoreHorizontal, Eye, Pencil, Trash2, CheckCheck } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { Select } from "@/components/ui/select";
|
|
import { deleteDeliveryNote, updateStatus } from "@/actions/delivery-notes";
|
|
import { DELIVERY_STATUSES, type DeliveryStatus } from "@/lib/types";
|
|
import { toast } from "sonner";
|
|
import Link from "next/link";
|
|
|
|
interface Props {
|
|
id: string;
|
|
status?: string;
|
|
}
|
|
|
|
export function DeliveryNotesTableActions({ id, status }: Props) {
|
|
const router = useRouter();
|
|
const [statusOpen, setStatusOpen] = useState(false);
|
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
const [newStatus, setNewStatus] = useState(status || "Pending");
|
|
const [pending, startTransition] = useTransition();
|
|
|
|
function onUpdateStatus() {
|
|
startTransition(async () => {
|
|
const res = await updateStatus(id, newStatus);
|
|
if (res.ok) {
|
|
toast.success("Status updated");
|
|
setStatusOpen(false);
|
|
router.refresh();
|
|
} else {
|
|
toast.error(res.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function onDelete() {
|
|
startTransition(async () => {
|
|
const res = await deleteDeliveryNote(id);
|
|
if (res.ok) {
|
|
toast.success("Note deleted");
|
|
setDeleteOpen(false);
|
|
router.refresh();
|
|
} else {
|
|
toast.error(res.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/delivery-notes/${id}`}>
|
|
<Eye className="mr-2 h-4 w-4" /> View
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/delivery-notes/${id}/edit`}>
|
|
<Pencil className="mr-2 h-4 w-4" /> Edit
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => setStatusOpen(true)}>
|
|
<CheckCheck className="mr-2 h-4 w-4" /> Update status
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={() => setDeleteOpen(true)} className="text-destructive focus:text-destructive">
|
|
<Trash2 className="mr-2 h-4 w-4" /> Delete
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<Dialog open={statusOpen} onOpenChange={setStatusOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Update status</DialogTitle>
|
|
<DialogDescription>Change the status of this delivery note.</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="py-2">
|
|
<Select
|
|
value={newStatus}
|
|
onChange={(e) => setNewStatus(e.target.value)}
|
|
options={DELIVERY_STATUSES.map((s) => ({ value: s, label: s }))}
|
|
/>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="ghost" onClick={() => setStatusOpen(false)} disabled={pending}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={onUpdateStatus} disabled={pending}>
|
|
{pending ? "Saving…" : "Update"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Delete this delivery note?</DialogTitle>
|
|
<DialogDescription>This cannot be undone.</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button variant="ghost" onClick={() => setDeleteOpen(false)} disabled={pending}>
|
|
Cancel
|
|
</Button>
|
|
<Button variant="destructive" onClick={onDelete} disabled={pending}>
|
|
{pending ? "Deleting…" : "Delete"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|