# Submittal register

A React block (a whole screen) in [Beamline](https://beamline.io/)'s [Construction](https://beamline.io/components/apps/construction) app. Live demo: https://beamline.io/components/submittal-register

The documents a trade owes a project before its material is bought, on the clocks that matter: a log whose rows each draw their way to site on one shared time axis (the trade's submission, the review, the supplier's lead time, against the day it is needed), three counts that keep apart who is late (trades with submissions, reviewers with answers, deliveries that will miss site), and the open record with who holds it, the lead time and site date that re-plan it, the review round with Send responses, Send for review to project people or an outside email, and the evidence by revision, and Bring in past to file a job's history (one company per batch, numbers proposed from each file's spec section, the day each was really received kept, a refused file retried alone).

## Use it for

- A builder's or project engineer's submittal log: shop drawings, product data and samples that trades submit and architects and engineers review before material is ordered.
- Any register of documents that pass between two parties on two deadlines (owed by one side, answered by the other) and feed a delivery date: procurement approvals, design sign-offs before manufacture, event or print production proofs.

## Not for

- Commenting on and approving one document's pages ([document-review](https://beamline.io/components/document-review)).
- A plan of tasks with dependencies ([gantt-chart](https://beamline.io/components/gantt-chart), [tension-timeline](https://beamline.io/components/tension-timeline)).
- A support queue on one reply clock ([helpdesk-inbox](https://beamline.io/components/helpdesk-inbox)).

## Anatomy

- head: the title, the project, and three clock buttons (Submissions late, Answers late, Late on site), each a count and its meaning in words; pressing one filters the log to it
- toolbar: search over number and name only, status chips with counts, Columns (the person's own columns, shown and ordered)
- log: a data grid, the number pinned; Submittal with its spec section, Status, Ball in court (who acts next and what they owe), Submission due and Answer due (each with its own clock words), Runway to site (the chain on the shared axis, today's line, the day it is needed as a flag, an overrun past it in red) with its verdict (N days late, N days spare, Not known); optional Trade, Type, Submission received (entered later marked), Sent for review, Answer received, Review took, Reviewers, Lead time, Needed on site
- record (beside the log, a sheet in a narrow room): number, revision, status, name, spec, type, trade; the ball line in words; the way to site: the verdict sentence, the large runway with Today and On site and two grips (the end of the lead time, the site flag), a key naming its pieces (Trade submits, Review, Lead time, Late), Needed on site and Lead time as the exact fields (scrub or type, weeks or days), Approve by and Submit by back-scheduled from them
- next step, one per state: Request submission (draft), Log as received with the day (requested), Send for review (received), the review round (in review), Close submittal (answered)
- review round: every reviewer with their answer, day and notes or Not answered, the lead marked; my own answer (choose, notes, Save my answer, Change my answer); Send responses for the lead, with who is still missing
- Send for review dialog: Project people (lead reviewer, other reviewers who also watch, watchers) or Email address (to, copies), Answer due (required), message, reminder; the recipients in words beside Send
- evidence: the current revision's files and notes, older revisions folded and read-only; spec section, type, reference, description
- Bring in past (a sheet, when the app passes onImport): Submitted by for the batch; For every file without one (an answer, a received day); the dropzone; per file its proposed number (the spec section read from the name, continuing after the register's own, in name order), the file, its state, and Name, Spec section, Type, Answer it got, Received on, Reference; the footer with what is still missing, the receipt, and File N submittals or Try the N that failed
- close (answered): the list of who gets the answer (trade contact, reviewers, outside reviewer, watchers; add a person or an address, remove anyone) and the sentence of who it goes to before Close; after, Answer sent to: each copy Delivered or Not delivered with its reason and Send to … again
- an outside reviewer (sent to an address): who it went to, the copies and when their answer is due; Record their answer: answer, received on (not after today), on behalf of, notes, files; once recorded, Received day from whom · entered day by whom
- answer choices (toolbar): each choice a line (words, what it means, uses, retired) that opens in place: words, what it means (fixed once used), Retire or Offer it again; a rename says how many past answers keep the old words
- export (toolbar, with chosen rows): Export N; the three scopes with what each holds; a packet's gaps named before; the result names what it could not hold
- watching: one group per company, people as chips; the person it is with and the creator's company stay and say why on a remove; Add someone

## States

- draft without a number (No number) and draft without a spec section (No spec section, in the warning ink)
- submission late: requested and past its submission due; counted only by Submissions late and said as N days late in Submission due
- answer late: in review and past its answer due; counted only by Answers late (never a late submission) and said as N days late in Answer due
- late on site: at this pace it arrives after it is needed; counted by Late on site; the runway's overrun is red past the flag and the verdict says N days late
- received date entered later: Submission received shows the day it came in and the day it was entered
- unknown lead time: the lead stretch hatched, verdict Not known, Approve by and Submit by Not known; no site date: verdict No site date
- closed and delivered: the plan is history; the log says On site (or Came N days late), the record On site since the day, N days before it was needed; never spare runway
- a grip held: the axis stays where it was at the press, the chain follows the hand without gliding, the record, the log row and the three counts change live; letting go keeps it (sent once, with a trace), Escape puts back the value from the press
- unknown review time: Review took is Not known until both sent and answered
- custom columns: Columns says Yours, Back to the usual columns resets
- no match: the empty state says search covers number and name only, or what the pressed clock would hold, with Clear filters
- loading (the grid's skeleton rows) and failed to load (the error with Try again, rows kept)
- plan edit saving (aria-busy) and refused (the last saved values put back, the reason under the plan)
- request: who and the submission due required; files attached now said to stay in the revision's history; refused with the draft kept
- routing: project people or email; a reviewer already watches and is not listed twice; email not allowed shows its reason; the answer due required and the first invalid field focused; sending; refused with every field kept; moved to review but the mail failed (said on the record with Send the email again)
- review round: waiting reviewers, partial answers, editing my own answer, everyone answered, Send with answers missing (a dialog names who can no longer answer, Wait for them or Send N answers), sending refused, answered (ready to close)
- closed: read-only answer and delivery; older submission revisions folded and read-only
- narrow room: the record opens in a sheet over the log; the clocks drop their explanations
- import: uploading (the dropzone's progress), upload failed with Try again, a file still missing something (counted per file and in the footer, each field naming its gap after File), no company for the batch, a file with no section has No number yet, filing, filed (locked, joined to the register as closed with its received day and answer, no reviewer invented), not filed with its reason, Try the N that failed sends only those, the whole batch refused with everything kept, the receipt (N filed today, received days kept)
- closing: the list named; closed with every copy delivered; closed with a copy not delivered (its reason, sent again); close refused (stays answered)
- sent to an outside address: waiting (and late); their answer entered on its day, or entered later (both days said)
- answer choices: renamed (past answers keep their words), retired (not offered, past kept), added, a used choice's meaning fixed, a duplicate or empty name refused
- export: rows chosen; summary, spreadsheet, packet; a packet with gaps before; ready, or ready without N attachments; refused
- watchers: removed, kept with why (the person it is with; the creator's company), added, refused

## Keyboard

- the log is the data grid: arrows between cells, Enter on a row opens its record
- the clocks and status chips are toggle buttons (Tab, Space)
- Columns: each column a checkbox with Move earlier and Move later buttons, the keyboard twin of dragging a header
- the record's close button returns to the log; in a sheet Escape closes it
- dialogs trap focus; Send with a missing field focuses the first one
- the lead time's end and the site flag are sliders: Left and Right (mirrored right to left) move one step (a week for a lead time in weeks, a day otherwise), Shift a week (four for a lead time in weeks); the value is said with what it does to the arrival
- Lead time is also a number field: arrows step, Page Up/Down by ten, or drag its label sideways; Needed on site is also a date field
- import: every field is a labelled control; File and Try again are buttons; the Select fields take typeahead
- the log's choose boxes are the data grid's (Space on a row); Export, Answer choices and each choice line are buttons; dialogs trap focus; Escape closes an open choice before its dialog

## Motion

A re-plan from a field or a key glides each runway piece, the flag and today's line to their new places with the considered duration (transforms only); while a grip is held nothing glides and the chain follows the hand in the same frame, the axis held still; on release a trace runs from the grip to the log row's verdict and, when the submittal joins or leaves it, to Late on site. The clock counts roll; rows that leave or join a filter slide (the grid's own); the record arrives beside the log with a short fade and travel. Nothing moves at rest; reduced motion places everything at once and the trace becomes a still ring. The new dialogs and lists place at once; nothing added here moves at rest.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `submittals` | `Submittal[]` | — |  |
| `defaultSubmittals` | `Submittal[]` | — |  |
| `onSubmittalsChange` | `((submittals: Submittal[]) => void)` | — | Every change arrives as the whole new list (after a confirmed action, or a refused one put back). |
| `people` (required) | `SubmittalPerson[]` | — |  |
| `me` (required) | `string` | — | The signed-in person's id. |
| `answers` | `AnswerOption[]` | — | The project's answer choices. Default: Approved, Approved as noted, Revise and resubmit, Rejected. |
| `reviewDays` | `number` | — | The project's usual review time in calendar days, used for a submittal not yet sent for review. Default 14. |
| `today` | `string` | — | "2026-11-04": the day the clocks count from. Default: today in the browser. |
| `project` | `string` | — |  |
| `open` | `string \| null` | — | The open submittal's id (controlled or not). |
| `defaultOpen` | `string \| null` | — | Initial open state when uncontrolled; defaults to false. |
| `onOpenChange` | `((id: string \| null) => void)` | — | Reports an open-state change; no callback by default. |
| `filters` | `Record<string, string[]>` | — | `status`: statuses shown; `clock`: one of "submission-late", "answer-late", "site-late". |
| `defaultFilters` | `Record<string, string[]>` | — |  |
| `onFiltersChange` | `((filters: Record<string, string[]>) => void)` | — |  |
| `query` | `string` | — | Search over the number and the name only. |
| `defaultQuery` | `string` | — |  |
| `onQueryChange` | `((query: string) => void)` | — |  |
| `view` | `SubmittalView` | — |  |
| `defaultView` | `SubmittalView` | — |  |
| `onViewChange` | `((view: SubmittalView) => void)` | — | The columns changed: keep it for this person (it is theirs, not the project's). |
| `loading` | `boolean` | — |  |
| `error` | `string \| Error \| null` | — | The log could not load; rows already shown stay. |
| `onRetry` | `(() => void)` | — |  |
| `className` | `string` | — | Classes on the documented visual root; no override by default. |
| `style` | `CSSProperties` | — | Inline styles on the documented visual root; no overrides by default. |
| `ref` | `Ref<HTMLDivElement>` | — | Receives the documented native target or imperative handle; omitted by default. |
| `onPlanChange` | `((id: string, patch: { leadTime?: { amount: number; unit: "days" \| "weeks"; } \| null \| …` | — | The day needed on site or the lead time changed (sent once the person stops changing it). A rejection puts the last saved value back and says why. |
| `onRequestSubmission` | `((id: string, request: { assignee: string; due: string; message: string; }) => void \| P…` | — | Draft → requested: ask the trade's person for it by `due`. |
| `onReceive` | `((id: string, received: string) => void \| Promise<unknown>)` | — | Requested → received, on `received` (a day before today when it is logged after the fact). |
| `onRoute` | `((id: string, route: SubmittalRoute) => void \| Promise<void \| RouteOutcome>)` | — | Received → in review. Resolve `{ mail: "failed" }` when the record moved on but the mail did not go out. |
| `onResendMail` | `((id: string) => void \| Promise<unknown>)` | — | Send the routing mail again after it failed. |
| `onAnswer` | `((id: string, answer: { answer: string; notes: string; }) => void \| Promise<unknown>)` | — | Save the signed-in reviewer's own answer while in review. |
| `onSendResponses` | `((id: string, missing: string[]) => void \| Promise<unknown>)` | — | The lead reviewer sends the round's answers: in review → answered. `missing`: reviewers who had not answered. |
| `onCloseSubmittal` | `((id: string) => void \| Promise<unknown>)` | — | Answered → closed, and the answer sent to `distribution` (person ids or outside addresses). The record closes even when a copy does not go out: resolve each copy's outcome (`sent: false` with the reason); nothing resolved means every copy went out. A rejection keeps it answered. |
| `externalRouting` | `string \| true` | — | Whether a submittal may be sent to an outside email address: true, or the reason it may not. Default true. |
| `onImport` | `((batch: ImportBatch) => Promise<ImportResult[]>)` | — | Bring in past submittals: files the app files as closed records, each with its own outcome. Without it the register offers no import. |
| `onUploadFile` | `FileDropzoneUpload` | — | Uploads each file as it is added (progress, cancel, retry in the list); its answer travels as `rows[].uploaded`. |
| `importOpen` | `boolean` | — |  |
| `defaultImportOpen` | `boolean` | — |  |
| `onImportOpenChange` | `((open: boolean) => void)` | — |  |
| `defaultImportFiles` | `FileDropzoneItem[]` | — | Files already in the import when it opens (a resumed batch). |
| `defaultAnswers` | `AnswerOption[]` | — |  |
| `onAnswersChange` | `((answers: AnswerOption[]) => void)` | — |  |
| `onAnswersSave` | `((answers: AnswerOption[]) => void \| Promise<unknown>)` | — | The answer choices edited (renamed, retired, added). A rejection keeps them as they were. Without it they are not edited here. Past answers keep their words: a rename stamps the old words on answers that had none. |
| `onExport` | `((ids: string[], format: ExportFormat) => Promise<{ file: string; href?: string \| undef…` | — | Make a summary, a spreadsheet or a packet of the chosen rows. Resolve with the file's name (and where to fetch it) and any attachment the app could not put in. Without it the log has no choose boxes. |
| `onRetryDelivery` | `((id: string, to: string) => void \| Promise<unknown>)` | — | Send a copy of the closing answer again to one recipient whose copy did not go out. |
| `onRecordDecision` | `((id: string, decision: { answer: string; receivedOn: string; forWhom: string; notes: s…` | — | An outside reviewer's answer, entered for them: in review → answered, its response received on `receivedOn`. Resolve with the uploaded files (their ids and names) when files were attached. |
| `onWatchersChange` | `((id: string, watchers: string[]) => void \| Promise<unknown>)` | — | The watchers changed (a person added or removed). A rejection puts them back and says why. |

## Built from

- alert
- attachment
- avatar
- badge
- button
- checkbox
- [chip-group](https://beamline.io/components/chip-group)
- combobox
- [data-grid](https://beamline.io/components/data-grid)
- [date-picker](https://beamline.io/components/date-picker)
- dialog
- [empty-state](https://beamline.io/components/empty-state)
- [file-dropzone](https://beamline.io/components/file-dropzone)
- input
- [multi-select](https://beamline.io/components/multi-select)
- [number-field](https://beamline.io/components/number-field)
- popover
- [search-field](https://beamline.io/components/search-field)
- [segmented-control](https://beamline.io/components/segmented-control)
- select
- sheet
- [slot-text](https://beamline.io/components/slot-text)
- spinner
- textarea

## Dependencies

`lucide-react`

## Import

```tsx
import { SubmittalRegister, DEFAULT_COLUMNS } from "@beamline/submittal-register";
```

## Get it

Submittal register is part of Beamline UI. Install the parts you need in your React app as compiled packages or editable source. [Get Beamline](https://beamline.io/checkout?pack=complete) · [Connect your agent](https://beamline.io/connect)

## More in Construction

- [Change orders](https://beamline.io/components/change-orders): Change orders against a contract, with the money drawn where it stands: one bar of the original contract, each approved change, a line at the current…
- [Inspection review](https://beamline.io/components/inspection-review): A unit's inspection, reviewed in three views. Findings: rooms and features with their condition, answers, notes and photos, and each feature's…
- [Work orders](https://beamline.io/components/work-orders): A maintenance work order that coordinates subtasks: the children's progress drawn by status with cancelled ones counted apart, the parent completed on…
- [Action center](https://beamline.io/components/action-center): Everything waiting on a person across projects: a strip of every open item on the days ahead, one lane per kind of owing (You submit, You answer, Tasks…
- [Company access](https://beamline.io/components/company-access): A project's companies as one board: No company first (office access waits for a company, and says so), then each company with its code and its people…
- [Condition checklist](https://beamline.io/components/condition-checklist): An inspection by area. Each item marked good (success), soon (warning) or needs work now (danger), or not checked yet and kept apart below; what was…
- [Corrective actions](https://beamline.io/components/corrective-actions): A corrective action raised from a failed inspection answer, for food safety, facilities or quality teams. The answer, its photo and note are pinned above…
- [Quality checks](https://beamline.io/components/quality-checks): Quality checks by picture and the control points that make them. A check asks for one photo: an upload that fails keeps its file with Try again, and…
- [Maintenance plan](https://beamline.io/components/maintenance-plan): Maintenance of shared equipment. The instruments, most urgent first, each with its most pressing kind and how due it is (overdue, due soon, fine, never…
- [Maintenance plans](https://beamline.io/components/maintenance-plans): An operations team's maintenance plans. The queue lists every asset-plan pair (one machine can appear under two plans) with its last service, next due…
- [Equipment register](https://beamline.io/components/equipment-register): A business's machines and vehicles: a register filtered by status (in use, in repair, sold, each counted) with each machine's photo or type icon…
- [Asset workspace](https://beamline.io/components/asset-workspace): One physical asset: a machine, vehicle or piece of plant. Its header keeps the system id apart from the name and serial, with type, site, status and the…
- [Asset packs](https://beamline.io/components/asset-packs): Packs of assets (sounds, presets, fonts), the providers they come from and the storage they take. Download and install are two phases with their own…
- [Instrument use](https://beamline.io/components/instrument-use): Shared instruments. The slot search reads as one sentence (how long, which instrument type, the dates and the working hours with the lab's time zone)…
- [Job booking](https://beamline.io/components/job-booking): A customer books a home or field-service job on the company's own site, phone first. Services with how many units, each unit adding its time and price…
- [Job booking setup](https://beamline.io/components/job-booking-setup): Online booking for a field-service company, set up from three sides that feed one booking form. Services: each priced and timed per unit with whether it…
- [Rental availability](https://beamline.io/components/rental-availability): A rental shop's availability: a product's month for the quantity a customer wants (each day available, some left with the count, booked out or not known…
- [Rooms & equipment](https://beamline.io/components/resource-setup): A salon's or clinic's rooms and equipment and what its services need of them: resources by group at each location, how many appointments each holds at…
- [System pages](https://beamline.io/components/system-page): The four pages a product shows when the normal one cannot: page not found (search and the likely places), something broke (try again, an error id to…

Part of [Beamline UI: polished, editable React components and complete screens for custom applications](https://beamline.io/). [Build your own style from any look; tokens, fonts, radii, motion; not only colours](https://beamline.io/docs/customize).
