shadcn.io is not affiliated with official shadcn/ui
Expense Approval Queue CRUD Block for React Aria
An expense desk where requests over 500 dollars need two approvers: tabs with counts, a reason required to reject, no approving your own request and a dialog for new ones.
Installation
Overview
Add a rule to an approval list and show it, with this expense queue for React with React Aria. Tabs for Pending, Approved and Rejected carry their counts and re-derive as requests move. A typed rule is shown: over $500 needs two approvers, anything else one, so a row reads "1 of 2 approved" with the approvers' initials in Badges. Approve is a row button, and the first one that can be pressed is the one Shine Button; Reject opens a Popover with a required reason; you cannot approve your own request ("You can't approve your own request", aria-disabled with the reason in the row) or approve twice. Withdraw removes your own pending request. New request, an outline button in the header, opens a Dialog with a what-for and an amount that says how many approvals it needs. A table from the medium breakpoint, cards below, and Undo for every change. No bulk actions: those are in the reviews and tables blocks.
FAQ
How do I change the approval rule?
Edit THRESHOLD and needOf. Today needOf returns 2 above 500 dollars and 1 otherwise; the row text, the dialog's hint ("$1,240 needs 2 approvals") and the status transitions all call it, so a rule such as three approvers over 5,000 is one more line.
Why can't I approve my own request?
Requests you create carry own: true, and canApprove refuses them. Their Approve button is aria-disabled and stays focusable; pressing it says why in the polite line. Instead of Reject they show Withdraw, which removes the request and offers Undo.
What does Reject need?
A reason. The popover will not send an empty one: it shows the problem, keeps focus in the field and leaves the request pending. A rejected request moves to the Rejected tab with its reason shown in the row, and Undo puts it back to Pending.
How do I fill it with my requests?
Edit REQUESTS: each has an id, a title, a whole-dollar amount, a requester, approvals (initials of who approved, "You" for the viewer), a status and, when rejected, a reason. Counts, progress text and which button is the primary are derived.
What stays local, and how do I wire real approvals?
Requests live in state in this tab and a reload brings the eight originals back; nothing is sent to anyone. In your app call your API from approve, reject, withdraw and create, enforce the two-approver rule and the no-self-approval rule on the server, and make Undo a compensating call.