busy-office-ui
Menu

Pattern: staging / batch result

Who uses it: whoever maintains data in bulk — an uploaded file, an interface run, a mass posting. How often: month-end and whenever a supplier sends a spreadsheet. What "done" looks like: every submitted row either became a record or is still on screen saying why it didn't. This is the landing place that makes the bulk action contract work for data the user has not seen yet. Not for a handful of records — three or four rows do not need a validate/apply split with per-row verdicts; edit them one at a time in a plain detail form. This pattern earns its complexity only once the count is large enough that a human cannot eyeball every row before committing.

The state after validation

#Vendor Cost centre Amount Result
1 Acme Supply Co. CC-4021 $4,200.00 Ready Ready to import
2 Globex Industrial CC-1180 $56,000.00 Check Matched "Globex" to Globex Industrial
3 Nobody Ltd CC-1180 $900.00 Cannot import No vendor matches "Nobody Ltd"
4 Initech GmbH CC-99 $100.00 Cannot import Cost centre "CC-99" is not CC-nnnn
Start over

Zero new components. The row tints are the data table's existing data-row-state; the reason on each row is a badge plus words, so the tint is never the only signal. There is no "ok" tint — a row with nothing wrong is a normal row, and a third colour would make the two that need attention harder to find.

Anatomy

  1. Intake — a paste box or file upload. Whatever the source, the rows arrive as text the server can parse.
  2. Validate action — explicitly separate from apply, because the whole point is seeing the outcome before committing to it.
  3. Summary — an alert counting all three outcomes in one sentence.
  4. Staging table — one row per submitted line, in the order submitted, each carrying its own verdict.
  5. Apply action — names the number it will act on ("Apply 2 valid rows"), and is disabled when that number is zero.

Data contract

POST /import   action=validate   csv=<pasted rows>
  -> 200, the SAME screen with every row classified. Nothing is created.

POST /import   action=apply      csv=<the validated rows>
  -> 200, a success alert with the count APPLIED, and the rows that could
     not be applied still listed. Never a bare "done".

Row classification is the SERVER's, and only two of the three states put a
tint on the row:
  ok      -> no data-row-state. A normal row; its confirmation is the badge.
  warning -> data-row-state="warning"   applied, with a caveat
  error   -> data-row-state="error"     not applied, ever

Two posts to one endpoint, distinguished by an action field — not two endpoints, because validate and apply must agree on the parse exactly, and the cheapest way to guarantee that is one code path. The validated rows travel back in the second request, so the server never holds per-user staging state between them.

Deliberately no 4xx for a bad row. Both actions always return 200 — a row failing validation is DATA (data-row-state="error" in the classified result), never a transport-level error, which is what lets one response carry a mix of ok/warning/error rows in the same screen. A genuinely malformed request (unparseable file, missing action field) is the one case that IS a 4xx, and gets a plain error banner rather than a partial result.

States

StateWhat ships
Nothing pasted yetjust the intake box — no empty table, no zero-count summary
Validated, all readya success alert; the apply button names the count
Validated, mixedcounts for all three outcomes, and the two that need attention are tinted
Validated, none applicablethe apply button is disabled — the one place disabled beats absent, because the control's whole job is to tell you the count is zero
Empty submissionthe "nothing to validate" empty state, not a table with no rows
After applya success alert with the number APPLIED, and every un-appliable row still listed to fix and re-submit
Re-validatedverdicts are recomputed from scratch. Like bulk actions, per-row results are TRANSIENT — a stale "cannot import" on a row the user just fixed is worse than no message
No JSfully functional — this page has no JS dependency. Every row's tint (data-row-state) and verdict badge are server-rendered classification, not JS state; the "Apply" and "Start over" controls shown here are static illustration (type="button", no script anywhere on the page), since a real staging screen posts the validate/apply actions from the Data contract above regardless of JS

Why this instead of an editable grid

Bulk data maintenance splits into four patterns, and only one of them wants a grid. Row-swap inline edit covers config tables (inline editing); master-detail covers records with more fields than fit a row; mass change covers "set field X for N rows"; and this pattern covers true bulk work, where the editing happens in the user's spreadsheet — which beats any web grid at being a spreadsheet — and the web's job is to validate and report. Reaching for a grid to avoid building this one is how a screen ends up with 200 hand-edits and no audit of what failed.

Components used

complexity 3 of 4

↑↓ move Enter open Esc close