Skip to main content

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.

  1. Manual Open State: The menu maintains a local open state. This state is passed to the DropdownMenu component to allow programmatic control over when the menu closes.
  2. The onSuccess Pattern: Both EditItem and DeleteItem accept an onSuccess prop. When an API mutation completes successfully (e.g., an item is updated or deleted), the child component calls this function, which in turn sets open to false in the parent ItemActionsMenu. This ensures the dropdown closes automatically only after a successful operation.
  3. Preventing Default Closure: In the child components (EditItem.tsx and DeleteItem.tsx), the DropdownMenuItem uses e.preventDefault() on the onSelect event. This is a critical implementation detail that prevents the Radix UI dropdown from closing immediately when a user clicks "Edit" or "Delete," allowing the subsequent Dialog to 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 a Dialog containing a form (managed by react-hook-form and zod) to update the item's title and description. It uses ItemsService.updateItem for the API call.
  • DeleteItem: Renders a confirmation Dialog to prevent accidental deletions, triggering the ItemsService.deleteItem mutation 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.