Manual Decision Node Editor

Admins handle pending decisions from Manual Decision nodes here. Browse processes that have queued items, open one to review user submissions, and approve or decline individually or in bulk. Lives at /manual-editor.


Overview

When a workflow reaches a Manual Decision node, the user sees a waiting message and the workflow pauses. The item lands in the Admin Approvals queue. An admin then reviews and decides. Once decided, the workflow resumes on the matching branch.

Trigger

Manual Decision node fires in a workflow

Access

Admins and super-admins only

Route

/admin/approvals

Process Selection

Entry screen. Each card shows a process that has at least one Manual Decision node. The badge counts pending items. Click any card to open the approval table.

Pending Approvals

20 total pending across 3 processes

Admissions

Application Review

12

Scholarships

Financial Aid Review

5

Interviews

Interview Decision

3

Mentoring

Mentor Approval

0

Property
Value

Card width

responsive, 2-col on sm+

Color stripe

4px topprocess accent color

Pending badge

36px circleprocess color, white text

Empty (zero pending)

muted cardgray400 stripe + opacity 0.7

Title font

18px600 weight

Decision label

14pxgray500

On click

Navigate to approval table for that process

Approval Table

Browse pending items for a single process. Core columns are checkbox, date, and user. Dynamic columns come from the workflow's configured context variables. Use the search field to filter by user name, email, or any other field—type to narrow the list.

Admissions: Application Review

5 pending
Created
User
Country
Birth year
Application
Action

Jan 15, 2024

John Smith

john@email.com

United States

1995

APP-2024-001

Jan 16, 2024

Jane Doe

jane@email.com

Canada

1992

APP-2024-002

Jan 17, 2024

Mike Johnson

mike@email.com

United Kingdom

1998

APP-2024-003

Jan 18, 2024

Sarah Wilson

sarah@email.com

Australia

2001

APP-2024-004

Jan 18, 2024

Carlos Rivera

carlos@email.com

Mexico

1996

APP-2024-005
Property
Value

Core columns

checkbox, created, user (name + email)

Dynamic columns

from workflow contextVariables setting

Search

TextField with icon, matches name, email, and all dynamic fields

Row select

Checkboxprimary tint when checked

Per-row action

Decide buttonoutlined small

Bulk actions

Approve / Declineappears when 1+ selected

Empty cells

show as em dash

Horizontal scroll

min-width 720pxmobile-friendly

Single Decision

Click Decide on any row above to open the decision modal. Choose Approve or Decline, optionally add a comment, and submit. The row leaves the queue and the workflow resumes on the matching branch.

Property
Value

Trigger

Decide button per row

Container

DialogMUI modal

Decision input

RadioGroupApprove / Decline

Comment field

TextField multilineoptional

Submit

primary contained

Cancel

outlined

On submit

Row removed, toast confirmation, workflow resumes

Error

stale data → refresh row, network → retry

Bulk Decision

Select multiple rows using the checkbox column to reveal the bulk actions. Choose Approve or Decline for the entire selection. Add one comment that applies to all items. Confirm before submitting.

Property
Value

Trigger

1+ rows selected

Toolbar

Selected count chip + Approve + Decline

Approve button

contained successgreen

Decline button

outlined errorred

Modal

Dialog with item count + comment field

Comment field

applies to all selectedsingle field

Confirmation

Review count before submit

Progress

loading state during submission

Empty & Loading States

Cover every condition the page can land in.

No pending processes

All caught up

No processes have pending approvals
Empty table

Queue cleared

All items in this process have been decided
Loading
Already decided (stale)

This item was already decided

Another admin acted on this item before you. Refresh to see the latest queue.

Information Architecture

Two screens, one decision flow. Always available back-navigation to the parent screen.

Process Selection

/admin/approvals

Approval Table

/admin/approvals/:processId

Decision (modal)

overlay

Code

Process card
<Paper
  onClick={() => router.push(`/admin/approvals/${process.id}`)}
  sx={{
    cursor: 'pointer',
    border: '1px solid',
    borderColor: 'divider',
    borderRadius: '10px',
    overflow: 'hidden',
    '&:hover': { borderColor: 'primary.main', boxShadow: 1 },
  }}
>
  <Box sx={{ height: 4, backgroundColor: process.color }} />
  <Box sx={{ p: 2.5, display: 'flex', justifyContent: 'space-between' }}>
    <Box>
      <Typography variant="h4">{process.name}</Typography>
      <Typography variant="body2" color="text.secondary">
        {process.decision}
      </Typography>
    </Box>
    <Avatar sx={{ bgcolor: process.color }}>{process.pending}</Avatar>
  </Box>
</Paper>
Approval table with dynamic columns
// Columns merge core (always present) with workflow.contextVariables
const coreColumns = [
  { field: 'select', width: 44, renderCell: SelectCheckbox },
  { field: 'created', headerName: 'Created', width: 120 },
  { field: 'user', headerName: 'User', flex: 1, renderCell: UserCell },
];

const dynamicColumns = workflow.contextVariables.map((v) => ({
  field: v,
  headerName: humanize(v),
  flex: 1,
  valueGetter: (params) => params.row.context[v] ?? '—',
}));

const actionColumn = {
  field: 'action',
  headerName: 'Action',
  width: 100,
  renderCell: (params) => (
    <Button size="small" variant="outlined" onClick={() => decide(params.row)}>
      Decide
    </Button>
  ),
};

<DataGrid
  rows={pendingItems}
  columns={[...coreColumns, ...dynamicColumns, actionColumn]}
  checkboxSelection
  onSelectionModelChange={setSelected}
/>
Decision modal
<Dialog open={!!decisionRow} onClose={() => setDecisionRow(null)} maxWidth="sm" fullWidth>
  <DialogTitle>Review application</DialogTitle>
  <DialogContent dividers>
    <RadioGroup
      value={decision}
      onChange={(e) => setDecision(e.target.value)}
    >
      <FormControlLabel value="approve" control={<Radio />} label="Approve" />
      <FormControlLabel value="decline" control={<Radio />} label="Decline" />
    </RadioGroup>
    <TextField
      label="Comment (optional)"
      value={comment}
      onChange={(e) => setComment(e.target.value)}
      multiline
      minRows={3}
      fullWidth
      sx={{ mt: 2 }}
    />
  </DialogContent>
  <DialogActions>
    <Button onClick={() => setDecisionRow(null)} variant="outlined">Cancel</Button>
    <Button onClick={handleSubmit} variant="contained" disabled={!decision}>
      Submit
    </Button>
  </DialogActions>
</Dialog>