Item Action Interfaces
The ItemActionsMenu component serves as the primary interface for managing individual resources within the application's data tables. It aggregates multiple management tasks—specifically editing and deleting—into a single, compact dropdown menu, providing a clean user experience for row-level operations.
Core Implementation
The ItemActionsMenu is built using the DropdownMenu primitive from the UI library (based on Radix UI). It acts as a container that coordinates the lifecycle of action-specific dialogs.
// frontend/src/components/Items/ItemActionsMenu.tsx
export const ItemActionsMenu = ({ item }: ItemActionsMenuProps) => {
const [open, setOpen] = useState(false)
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<EditItem item={item} onSuccess={() => setOpen(false)} />
<DeleteItem id={item.id} onSuccess={() => setOpen(false)} />
</DropdownMenuContent>
</DropdownMenu>
)
}
The component requires an item object of type ItemPublic, which contains the necessary data (like id, title, and description) to perform actions.
State Coordination and Callbacks
A key feature of this implementation is how it manages the visibility of the dropdown menu in relation to the action dialogs it contains.
- Manual Open State: The menu maintains a local
openstate. This state is passed to theDropdownMenucomponent to allow programmatic control over when the menu closes. - The
onSuccessPattern: BothEditItemandDeleteItemaccept anonSuccessprop. When an API mutation completes successfully (e.g., an item is updated or deleted), the child component calls this function, which in turn setsopentofalsein the parentItemActionsMenu. This ensures the dropdown closes automatically only after a successful operation. - Preventing Default Closure: In the child components (
EditItem.tsxandDeleteItem.tsx), theDropdownMenuItemusese.preventDefault()on theonSelectevent. This is a critical implementation detail that prevents the Radix UI dropdown from closing immediately when a user clicks "Edit" or "Delete," allowing the subsequentDialogto remain visible.
// Example from frontend/src/components/Items/EditItem.tsx
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
onClick={() => setIsOpen(true)}
>
<Pencil />
Edit Item
</DropdownMenuItem>
Table Integration
The ItemActionsMenu is designed to be integrated directly into TanStack Table column definitions. In frontend/src/components/Items/columns.tsx, it is assigned to the "actions" column, where it receives the row's data via row.original.
// frontend/src/components/Items/columns.tsx
export const columns: ColumnDef<ItemPublic>[] = [
// ... other columns
{
id: "actions",
header: () => <span className="sr-only">Actions</span>,
cell: ({ row }) => (
<div className="flex justify-end">
<ItemActionsMenu item={row.original} />
</div>
),
},
]
Action Components
The menu delegates the actual logic and UI for resource modification to specialized components:
EditItem: Renders aDialogcontaining a form (managed byreact-hook-formandzod) to update the item's title and description. It usesItemsService.updateItemfor the API call.DeleteItem: Renders a confirmationDialogto prevent accidental deletions, triggering theItemsService.deleteItemmutation upon confirmation.
Both components utilize useMutation from TanStack Query to handle API interactions and useQueryClient to invalidate the "items" cache, ensuring the table reflects the latest data after an action is performed.