feat: table view with sorting, faceted filters, and bulk delete for templates, workflows, and campaigns#396
Open
taniasanz7 wants to merge 4 commits into
Open
Conversation
added 4 commits
May 31, 2026 11:37
…te for templates
Adopt @tanstack/react-table as the table framework and add a card/table
view switcher to the templates list, structured to shadcn data-table
conventions but built on the existing @plunk/ui primitives.
Backend
- Add a shared list-sort helper (apps/api/src/utils/listSort.ts) exposing
a ListSort type + parseListSort() that validates ?sort=name|createdAt|
updatedAt&dir=asc|desc against allow-lists, silently falling back to the
existing createdAt desc default. Thread it through GET /templates,
GET /workflows and GET /campaigns and their service list() methods into
Prisma orderBy. Existing call shapes stay backward-compatible.
- Add POST /templates/bulk-update (TemplateSchemas.bulkUpdate) accepting
{ ids: string[] (1..1000), delete?: boolean }. Bulk delete runs the
ownership/project-scope check (404 on a foreign id), the workflow-step
reference check (409, mirroring single delete), and deleteMany inside one
prisma.$transaction so a partial delete is impossible. The schema is kept
open-ended for future bulk modes. Covers it with TemplateService tests.
Frontend (generic, reusable shared components under components/data-table)
- DataTable: presentation-only tanstack renderer on @plunk/ui Table, owns
per-th aria-sort.
- DataTableColumnHeader: sortable header (asc -> desc -> unsorted) with
chevron icons and an optional in-header filter slot.
- DataTableFacetedFilter: Excel-style per-column dropdown (Popover+Command)
for fixed-value columns.
- DataTableViewOptions: column-visibility "Columns" selector.
- DataTableViewSwitcher: card/table icon toggle.
- BulkActionBar: selection action bar.
- hooks: useColumnVisibility (localStorage), usePersistentState (view),
useShiftClickSelection (anchor+range selection).
Templates page
- Card view is the default and is unchanged from before (same search +
Type filter pills + card grid). Table view adds header sort, a Type
faceted filter in the column header (no pills, no sort-by dropdown), a
localStorage-persisted column-visibility menu (Name + Actions locked),
and a bulk-delete bar with shift-click range selection. Sorting and the
Type facet both feed the SWR query string so the server stays
authoritative (manualSorting). Selection clears on page/search/filter
changes and after a successful delete. View and column choices persist in
localStorage (plunk:templates:view, plunk:templates:columns).
…low status+steps, header casing
Address four review findings on the new shadcn data-table list-page UX.
1. No-results vs first-run empty state (templates/workflows/campaigns)
Previously, when a search/facet filter matched zero rows, the page fell
back to the first-run "No X yet" empty state and hid the table (and, in
table view, its header facets) with no way to recover. Add a distinct
"No results match your filters" state with a one-click "Clear filters"
button (new components/data-table/NoResultsState) that resets search + all
active facet/status filters + pagination. The first-run state now only
shows when there are genuinely zero items AND no active filter. Each page
derives a hasActiveFilters flag and a clearFilters handler from its
existing filter state; the search bar / view switcher / card-view pills
stay rendered above the empty state so manual recovery also remains
reachable.
2. Bulk-action button sizing (BulkActionBar)
Outline triggers read smaller than the solid "Delete selected" button.
Pin every action button in the bar to a uniform height/padding
([&>button]:h-8 px-3 text-xs) so the bar renders as one consistent group
regardless of variant border math.
3. Workflows: Status filter + Steps sorting
- Status facet (Active / Disabled) on the Status column header, mirroring
the campaigns Status facet, plus the equivalent card-view pill row.
Wired to a new ?status=active|disabled query param the controller
resolves to the enabled boolean in WorkflowService.list's Prisma where
(covered by the existing @@index([projectId, enabled])).
- Steps column is now sortable by step count. ?sort=steps maps onto
Prisma orderBy: {steps: {_count}}; parseListSort gains an opt-in
extraFields arg so steps is accepted for workflows without widening the
shared allow-list. name/createdAt/updatedAt sorting unchanged.
4. Column header casing (DataTableColumnHeader)
Sortable headers rendered Capitalized while plain <th> headers were
UPPERCASE. Apply the same text-xs font-medium uppercase tracking-wider
type styling to the sortable label in DataTableColumnHeader so every
column header is uniform.
Also URL-encode the search query on templates/workflows lists (the campaigns
list already did this) so values containing special chars round-trip cleanly.
Tests: extend WorkflowService.list suite with enabled-status filtering and
step-count sorting (asc/desc) coverage.
Contributor
Author
|
@driaug did you get a chance to have a look? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds an opt-in table view to the Templates, Workflows, and Campaigns list pages, built on
@tanstack/react-tableand styled to match the existing shadcn/Radix components. The card grid becomes unusable past a few dozen rows; this gives a scannable, sortable, filterable table for large accounts.Card view remains the default and is unchanged - the table is opt-in via a per-page card/table switcher (persisted in localStorage), so existing users see no difference unless they switch.
Table view adds:
?sort=&dir=query params (name/createdAt/updatedAt, plusstepson workflows by relation count)header) for fixed-value columns: template Type, campaign Status, workflow Status (active/disabled). Free-text stays in the existing search box.
deleteMan yrun in one transaction, so partial deletes are impossible (max 1000 ids/request).Backend: a small shared
parseListSorthelper adds?sort=&dir=to the three list endpoints;POST /{templates,workflows,cam paigns}/bulk-updateaccepts{ ids, delete? }. All additive —existing API shapes and defaults are unchanged. No schema migration
stepson workflows by relation count); the client only mirrors sort state into the request.header) for fixed-value columns: template Type, campaign Status, workflow Status (active/disabled). Free-text stays in the existing search box.
Testing
tsc --noEmitclean acrossapps/apiandapps/webnext buildsucceeds;/templates,/workflows,/campaignsprerenderand CampaignService 32/32 (incl. bulk-delete: project-scope 404, guard 409/400 rollback, dedup, no-op)
es / ~400 campaigns: switcher, header sort, faceted filters, column visibility, and bulk delete with shift-select; confirmed card view is unchanged
Checklist
Related Issues
If this is merged I fill follow up with also migrating the existing contact table