feat(admin): confirm activate/deactivate via modal popup across all tables
Replace immediate server action calls with ConfirmDialog modals for activate/deactivate on all 6 admin tables (users, vendors, vessels, sites, accounts, products). Delete already used DeleteConfirmDialog; this adds the same pattern for reversible toggle actions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d27ec9152c
commit
3f3e1e6423
7 changed files with 141 additions and 69 deletions
|
|
@ -1,12 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useTransition } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useTableControls } from "@/components/ui/use-table-controls";
|
import { useTableControls } from "@/components/ui/use-table-controls";
|
||||||
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
||||||
import { AddAccountButton, EditAccountButton } from "./account-form";
|
import { AddAccountButton, EditAccountButton } from "./account-form";
|
||||||
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
||||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { deleteAccount, toggleAccountActive } from "./actions";
|
import { deleteAccount, toggleAccountActive } from "./actions";
|
||||||
|
|
||||||
export type AccountRow = {
|
export type AccountRow = {
|
||||||
|
|
@ -20,23 +20,15 @@ export type AccountRow = {
|
||||||
const CHIPS = ["Active", "Inactive"];
|
const CHIPS = ["Active", "Inactive"];
|
||||||
|
|
||||||
function AccountActionsMenu({ account }: { account: AccountRow }) {
|
function AccountActionsMenu({ account }: { account: AccountRow }) {
|
||||||
const router = useRouter();
|
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [toggleOpen, setToggleOpen] = useState(false);
|
||||||
|
|
||||||
function handleToggle() {
|
|
||||||
startTransition(async () => {
|
|
||||||
await toggleAccountActive(account.id);
|
|
||||||
router.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RowActionsMenu>
|
<RowActionsMenu>
|
||||||
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
||||||
<RowActionsItem onClick={handleToggle} disabled={isPending}>
|
<RowActionsItem onClick={() => setToggleOpen(true)}>
|
||||||
{account.isActive ? "Deactivate" : "Activate"}
|
{account.isActive ? "Deactivate" : "Activate"}
|
||||||
</RowActionsItem>
|
</RowActionsItem>
|
||||||
<RowActionsSeparator />
|
<RowActionsSeparator />
|
||||||
|
|
@ -60,6 +52,14 @@ function AccountActionsMenu({ account }: { account: AccountRow }) {
|
||||||
label={account.name}
|
label={account.name}
|
||||||
onConfirm={() => deleteAccount(account.id)}
|
onConfirm={() => deleteAccount(account.id)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
open={toggleOpen}
|
||||||
|
onOpenChange={setToggleOpen}
|
||||||
|
title={account.isActive ? `Deactivate ${account.name}?` : `Activate ${account.name}?`}
|
||||||
|
description={account.isActive ? `${account.name} will be hidden from account selections.` : `${account.name} will become available for account selections.`}
|
||||||
|
confirmLabel={account.isActive ? "Deactivate" : "Activate"}
|
||||||
|
onConfirm={() => toggleAccountActive(account.id)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState, useTransition } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { formatCurrency, formatDate } from "@/lib/utils";
|
import { formatCurrency, formatDate } from "@/lib/utils";
|
||||||
import { useTableControls } from "@/components/ui/use-table-controls";
|
import { useTableControls } from "@/components/ui/use-table-controls";
|
||||||
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
||||||
import { AddProductButton } from "./product-form";
|
import { AddProductButton } from "./product-form";
|
||||||
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
||||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { deleteProduct, toggleProductActive } from "./actions";
|
import { deleteProduct, toggleProductActive } from "./actions";
|
||||||
|
|
||||||
export type ProductRow = {
|
export type ProductRow = {
|
||||||
|
|
@ -26,21 +26,13 @@ export type ProductRow = {
|
||||||
const CHIPS = ["Active", "Inactive"];
|
const CHIPS = ["Active", "Inactive"];
|
||||||
|
|
||||||
function ProductActionsMenu({ product }: { product: ProductRow }) {
|
function ProductActionsMenu({ product }: { product: ProductRow }) {
|
||||||
const router = useRouter();
|
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [toggleOpen, setToggleOpen] = useState(false);
|
||||||
|
|
||||||
function handleToggle() {
|
|
||||||
startTransition(async () => {
|
|
||||||
await toggleProductActive(product.id);
|
|
||||||
router.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RowActionsMenu>
|
<RowActionsMenu>
|
||||||
<RowActionsItem onClick={handleToggle} disabled={isPending}>
|
<RowActionsItem onClick={() => setToggleOpen(true)}>
|
||||||
{product.isActive ? "Deactivate" : "Activate"}
|
{product.isActive ? "Deactivate" : "Activate"}
|
||||||
</RowActionsItem>
|
</RowActionsItem>
|
||||||
<RowActionsSeparator />
|
<RowActionsSeparator />
|
||||||
|
|
@ -53,6 +45,14 @@ function ProductActionsMenu({ product }: { product: ProductRow }) {
|
||||||
label={product.name}
|
label={product.name}
|
||||||
onConfirm={() => deleteProduct(product.id)}
|
onConfirm={() => deleteProduct(product.id)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
open={toggleOpen}
|
||||||
|
onOpenChange={setToggleOpen}
|
||||||
|
title={product.isActive ? `Deactivate ${product.name}?` : `Activate ${product.name}?`}
|
||||||
|
description={product.isActive ? `${product.name} will be hidden from new purchase orders.` : `${product.name} will become available for new purchase orders.`}
|
||||||
|
confirmLabel={product.isActive ? "Deactivate" : "Activate"}
|
||||||
|
onConfirm={() => toggleProductActive(product.id)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState, useTransition } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useTableControls } from "@/components/ui/use-table-controls";
|
import { useTableControls } from "@/components/ui/use-table-controls";
|
||||||
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
||||||
import { AddSiteButton, EditSiteButton } from "./site-form";
|
import { AddSiteButton, EditSiteButton } from "./site-form";
|
||||||
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
||||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { deleteSite, toggleSiteActive } from "./actions";
|
import { deleteSite, toggleSiteActive } from "./actions";
|
||||||
|
|
||||||
export type SiteRow = {
|
export type SiteRow = {
|
||||||
|
|
@ -25,23 +25,15 @@ export type SiteRow = {
|
||||||
const CHIPS = ["Active", "Inactive"];
|
const CHIPS = ["Active", "Inactive"];
|
||||||
|
|
||||||
function SiteActionsMenu({ site }: { site: SiteRow }) {
|
function SiteActionsMenu({ site }: { site: SiteRow }) {
|
||||||
const router = useRouter();
|
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [toggleOpen, setToggleOpen] = useState(false);
|
||||||
|
|
||||||
function handleToggle() {
|
|
||||||
startTransition(async () => {
|
|
||||||
await toggleSiteActive(site.id);
|
|
||||||
router.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RowActionsMenu>
|
<RowActionsMenu>
|
||||||
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
||||||
<RowActionsItem onClick={handleToggle} disabled={isPending}>
|
<RowActionsItem onClick={() => setToggleOpen(true)}>
|
||||||
{site.isActive ? "Deactivate" : "Activate"}
|
{site.isActive ? "Deactivate" : "Activate"}
|
||||||
</RowActionsItem>
|
</RowActionsItem>
|
||||||
<RowActionsSeparator />
|
<RowActionsSeparator />
|
||||||
|
|
@ -67,6 +59,14 @@ function SiteActionsMenu({ site }: { site: SiteRow }) {
|
||||||
label={site.name}
|
label={site.name}
|
||||||
onConfirm={() => deleteSite(site.id)}
|
onConfirm={() => deleteSite(site.id)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
open={toggleOpen}
|
||||||
|
onOpenChange={setToggleOpen}
|
||||||
|
title={site.isActive ? `Deactivate ${site.name}?` : `Activate ${site.name}?`}
|
||||||
|
description={site.isActive ? `${site.name} will be hidden from cost centre selections.` : `${site.name} will become available for cost centre selections.`}
|
||||||
|
confirmLabel={site.isActive ? "Deactivate" : "Activate"}
|
||||||
|
onConfirm={() => toggleSiteActive(site.id)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
||||||
import { AddUserButton, EditUserButton } from "./user-form";
|
import { AddUserButton, EditUserButton } from "./user-form";
|
||||||
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
||||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { deleteUser, toggleUserActive } from "./actions";
|
import { deleteUser, toggleUserActive } from "./actions";
|
||||||
import { grantSuperUser } from "../superuser-requests/actions";
|
import { grantSuperUser } from "../superuser-requests/actions";
|
||||||
import { ShieldCheck } from "lucide-react";
|
import { ShieldCheck } from "lucide-react";
|
||||||
|
|
@ -37,16 +38,9 @@ function UserActionsMenu({ user }: { user: UserRow }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [toggleOpen, setToggleOpen] = useState(false);
|
||||||
const [grantPending, startGrantTransition] = useTransition();
|
const [grantPending, startGrantTransition] = useTransition();
|
||||||
|
|
||||||
function handleToggle() {
|
|
||||||
startTransition(async () => {
|
|
||||||
await toggleUserActive(user.id);
|
|
||||||
router.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleGrantSuperUser() {
|
function handleGrantSuperUser() {
|
||||||
startGrantTransition(async () => {
|
startGrantTransition(async () => {
|
||||||
await grantSuperUser(user.id);
|
await grantSuperUser(user.id);
|
||||||
|
|
@ -58,7 +52,7 @@ function UserActionsMenu({ user }: { user: UserRow }) {
|
||||||
<>
|
<>
|
||||||
<RowActionsMenu>
|
<RowActionsMenu>
|
||||||
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
||||||
<RowActionsItem onClick={handleToggle} disabled={isPending}>
|
<RowActionsItem onClick={() => setToggleOpen(true)}>
|
||||||
{user.isActive ? "Deactivate" : "Activate"}
|
{user.isActive ? "Deactivate" : "Activate"}
|
||||||
</RowActionsItem>
|
</RowActionsItem>
|
||||||
{user.role !== "SUPERUSER" && user.role !== "ADMIN" && (
|
{user.role !== "SUPERUSER" && user.role !== "ADMIN" && (
|
||||||
|
|
@ -89,6 +83,14 @@ function UserActionsMenu({ user }: { user: UserRow }) {
|
||||||
label={user.name}
|
label={user.name}
|
||||||
onConfirm={() => deleteUser(user.id)}
|
onConfirm={() => deleteUser(user.id)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
open={toggleOpen}
|
||||||
|
onOpenChange={setToggleOpen}
|
||||||
|
title={user.isActive ? `Deactivate ${user.name}?` : `Activate ${user.name}?`}
|
||||||
|
description={user.isActive ? `This will prevent ${user.name} from signing in.` : `This will restore ${user.name}'s access to PPMS.`}
|
||||||
|
confirmLabel={user.isActive ? "Deactivate" : "Activate"}
|
||||||
|
onConfirm={() => toggleUserActive(user.id)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
App/app/(portal)/admin/vendors/vendors-table.tsx
vendored
24
App/app/(portal)/admin/vendors/vendors-table.tsx
vendored
|
|
@ -1,13 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState, useTransition } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useTableControls } from "@/components/ui/use-table-controls";
|
import { useTableControls } from "@/components/ui/use-table-controls";
|
||||||
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
||||||
import { AddVendorButton, EditVendorButton } from "./vendor-form";
|
import { AddVendorButton, EditVendorButton } from "./vendor-form";
|
||||||
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
||||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { deleteVendor, toggleVendorActive } from "./actions";
|
import { deleteVendor, toggleVendorActive } from "./actions";
|
||||||
|
|
||||||
type ContactRow = {
|
type ContactRow = {
|
||||||
|
|
@ -34,23 +34,15 @@ export type VendorRow = {
|
||||||
const CHIPS = ["Verified", "Unverified", "Active", "Inactive"];
|
const CHIPS = ["Verified", "Unverified", "Active", "Inactive"];
|
||||||
|
|
||||||
function VendorActionsMenu({ vendor }: { vendor: VendorRow }) {
|
function VendorActionsMenu({ vendor }: { vendor: VendorRow }) {
|
||||||
const router = useRouter();
|
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [toggleOpen, setToggleOpen] = useState(false);
|
||||||
|
|
||||||
function handleToggle() {
|
|
||||||
startTransition(async () => {
|
|
||||||
await toggleVendorActive(vendor.id);
|
|
||||||
router.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RowActionsMenu>
|
<RowActionsMenu>
|
||||||
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
||||||
<RowActionsItem onClick={handleToggle} disabled={isPending}>
|
<RowActionsItem onClick={() => setToggleOpen(true)}>
|
||||||
{vendor.isActive ? "Deactivate" : "Activate"}
|
{vendor.isActive ? "Deactivate" : "Activate"}
|
||||||
</RowActionsItem>
|
</RowActionsItem>
|
||||||
<RowActionsSeparator />
|
<RowActionsSeparator />
|
||||||
|
|
@ -77,6 +69,14 @@ function VendorActionsMenu({ vendor }: { vendor: VendorRow }) {
|
||||||
label={vendor.name}
|
label={vendor.name}
|
||||||
onConfirm={() => deleteVendor(vendor.id)}
|
onConfirm={() => deleteVendor(vendor.id)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
open={toggleOpen}
|
||||||
|
onOpenChange={setToggleOpen}
|
||||||
|
title={vendor.isActive ? `Deactivate ${vendor.name}?` : `Activate ${vendor.name}?`}
|
||||||
|
description={vendor.isActive ? `${vendor.name} will be hidden from new purchase orders.` : `${vendor.name} will become available for new purchase orders.`}
|
||||||
|
confirmLabel={vendor.isActive ? "Deactivate" : "Activate"}
|
||||||
|
onConfirm={() => toggleVendorActive(vendor.id)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useTransition } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useTableControls } from "@/components/ui/use-table-controls";
|
import { useTableControls } from "@/components/ui/use-table-controls";
|
||||||
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
import { TableControls, SortableTh } from "@/components/ui/table-controls";
|
||||||
import { AddVesselButton, EditVesselButton } from "./vessel-form";
|
import { AddVesselButton, EditVesselButton } from "./vessel-form";
|
||||||
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu";
|
||||||
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog";
|
||||||
|
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
|
||||||
import { deleteVessel, toggleVesselActive } from "./actions";
|
import { deleteVessel, toggleVesselActive } from "./actions";
|
||||||
|
|
||||||
export type VesselRow = {
|
export type VesselRow = {
|
||||||
|
|
@ -20,23 +20,15 @@ export type VesselRow = {
|
||||||
const CHIPS = ["Active", "Inactive"];
|
const CHIPS = ["Active", "Inactive"];
|
||||||
|
|
||||||
function VesselActionsMenu({ vessel }: { vessel: VesselRow }) {
|
function VesselActionsMenu({ vessel }: { vessel: VesselRow }) {
|
||||||
const router = useRouter();
|
|
||||||
const [editOpen, setEditOpen] = useState(false);
|
const [editOpen, setEditOpen] = useState(false);
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [toggleOpen, setToggleOpen] = useState(false);
|
||||||
|
|
||||||
function handleToggle() {
|
|
||||||
startTransition(async () => {
|
|
||||||
await toggleVesselActive(vessel.id);
|
|
||||||
router.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RowActionsMenu>
|
<RowActionsMenu>
|
||||||
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
<RowActionsItem onClick={() => setEditOpen(true)}>Edit</RowActionsItem>
|
||||||
<RowActionsItem onClick={handleToggle} disabled={isPending}>
|
<RowActionsItem onClick={() => setToggleOpen(true)}>
|
||||||
{vessel.isActive ? "Deactivate" : "Activate"}
|
{vessel.isActive ? "Deactivate" : "Activate"}
|
||||||
</RowActionsItem>
|
</RowActionsItem>
|
||||||
<RowActionsSeparator />
|
<RowActionsSeparator />
|
||||||
|
|
@ -59,6 +51,14 @@ function VesselActionsMenu({ vessel }: { vessel: VesselRow }) {
|
||||||
label={vessel.name}
|
label={vessel.name}
|
||||||
onConfirm={() => deleteVessel(vessel.id)}
|
onConfirm={() => deleteVessel(vessel.id)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
open={toggleOpen}
|
||||||
|
onOpenChange={setToggleOpen}
|
||||||
|
title={vessel.isActive ? `Deactivate ${vessel.name}?` : `Activate ${vessel.name}?`}
|
||||||
|
description={vessel.isActive ? `${vessel.name} will be hidden from new purchase orders.` : `${vessel.name} will become available for new purchase orders.`}
|
||||||
|
confirmLabel={vessel.isActive ? "Deactivate" : "Activate"}
|
||||||
|
onConfirm={() => toggleVesselActive(vessel.id)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
App/components/ui/confirm-dialog.tsx
Normal file
70
App/components/ui/confirm-dialog.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useTransition } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { AdminDialog } from "@/components/ui/admin-dialog";
|
||||||
|
|
||||||
|
type ActionResult = { ok: true } | { error: string };
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (v: boolean) => void;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
confirmLabel: string;
|
||||||
|
onConfirm: () => Promise<ActionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConfirmDialog({ open, onOpenChange, title, description, confirmLabel, onConfirm }: Props) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
if (isPending) return;
|
||||||
|
setError("");
|
||||||
|
onOpenChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
setError("");
|
||||||
|
startTransition(async () => {
|
||||||
|
const result = await onConfirm();
|
||||||
|
if ("error" in result) {
|
||||||
|
setError(result.error);
|
||||||
|
} else {
|
||||||
|
onOpenChange(false);
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminDialog title={title} open={open} onClose={handleClose}>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-neutral-600">{description}</p>
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm text-danger-700 bg-danger-50 rounded-lg px-3 py-2">{error}</p>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-end gap-3 pt-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleClose}
|
||||||
|
disabled={isPending}
|
||||||
|
className="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 disabled:opacity-60"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleConfirm}
|
||||||
|
disabled={isPending}
|
||||||
|
className="rounded-lg bg-primary-600 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-700 disabled:opacity-60 transition-colors"
|
||||||
|
>
|
||||||
|
{isPending ? `${confirmLabel}…` : confirmLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AdminDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue