# Purchase orders

A React block (a whole screen) in [Beamline](https://beamline.io/)'s [Shop](https://beamline.io/components/apps/shop) app. Live demo: https://beamline.io/components/purchase-orders

A salon's purchase orders, received: the orders down the side, the chosen one beside them. Each line still due reads as a strip of received, this delivery and still due, with this delivery's count as text that edits where clicked (inline-edit) and the stock beside it rising by exactly that; one save carries one key, so a retry after a lost answer is counted once and said so; receiving everything still due fills the draft and saves nothing; closing is its own step, asked in place with what will never arrive, and a failed close is said apart from a failed delivery; each line's deliveries unfold as its quantity river. One more part travels with it: OrderPlanner, a supplier's next order planned to par: each product's shelf drawn as what is in stock (solid), what other open orders bring (hatched), this order and what is still short, the suggestion said as its sum (par − stock − on order), a count set by hand kept and marked as your own through a recount, and saving on your records and sending to the supplier as two actions with two receipts.

## Use it for

- Receiving supplier deliveries against an order, often in parts: a salon's back bar, a spa's retail shelf, a café's stock room, a clinic's consumables.
- Anywhere a retried save must never count stock twice, and closing an order short must be a deliberate step.
- OrderPlanner: reordering a back bar or retail shelf to par levels from one supplier, with what is already on order counted.

## Not for

- Forecasting what to order over weeks ([stock-horizon](https://beamline.io/components/stock-horizon)).
- One order line's full fulfilment story with shipments and returns ([quantity-river](https://beamline.io/components/quantity-river)).
- Customer orders and their fulfilment (order screens).

## Anatomy

- header: the product's mark and the title
- order list (nav): each order's supplier, number and where it stands
- sheet head: number and supplier, when it was ordered and where, its status in words
- lines: each product with its size and the stock (a rolling figure; "→ 13" in strong ink while a delivery is counted); a line still due is a strip (received in the success tone, this delivery in ink, still due empty or hatched as won't arrive while closing) and its words: received of ordered · this delivery's count (text that edits where clicked) · still due; a finished line, or any line when looking only or closed, is one quiet line: a slim record bar, "All 12 received" (or received of ordered with what is still due or never arrived) and its deliveries
- a line's deliveries: a quiet action opening the line's quantity river in place (its own Close folds it back)
- deliveries record (side): newest first, each delivery's date and who received it, then one row per line with its quantity (+3); the closing on top
- notice (status or alert): what was saved (green, the outcome), a duplicate counted once, a failed or unanswered save
- bar (sticky): Receive all still due (secondary button) and Clear (ghost), Close order (confirm-morph, neutral) and Save this delivery (primary, with the item count); on a phone the order's actions (Receive all, Close order) sit under its head and the bar keeps Clear and Save on one row
- OrderPlanner: the order's number and supplier, the location, Count stock again; a row per product: the name and size, the sum (par − ▪ in stock − ▨ on PO-… = n, or Stock not counted), the shelf against par (in stock solid, on other orders hatched, this order in the accent, short of par empty; the par mark labelled; past it only what the order adds above par), the grip at this order's end and the field as its exact twin, and the words (Suggested n or Your own count · Suggested n, +n to order, n short of par / fills it to par / at par / n above par); the foot: items, lines and cost (rolling), Not saved yet / Changed since saved / Saved, not sent / Sent, Save, Send to <supplier> (asked in place with items and cost), what was said, and the receipts (Saved … by …, Sent to … …)

## States

- awaiting delivery, partly received, all received and still open, closed: said beside the number and in the list
- counting: the delivery drawn on each line, the stock's new figure, Save counts the items
- saving: Save pending, the rows held
- saved: the counted units settle into the record, the stock figure rolls to its new count, the words of what came in; the draft clears; a new key
- duplicate: the app answers duplicate; the block applies it once and says it was already saved
- no answer (an Error named TimeoutError): the draft and its key stay; Save becomes Try again
- failed: the draft stays, nothing added, Try again
- closing: asked in place (what never arrived, stock unchanged); pending; closed, or refused with its deliveries said saved
- look only (canReceive false): the rows drawn, no bar, the permission said
- loading: shaped skeletons; no orders: an empty state
- phone: the list, then the order full width with Back
- right to left: rows, arrows and the bar mirror
- reduced motion: through the tokens
- OrderPlanner: not counted (no stock drawn, no suggestion)
- OrderPlanner: none needed (at par already)
- OrderPlanner: your own count (kept through Count stock again; the others follow their new suggestion, said)
- OrderPlanner: saved, changed since saved, sent (an open order now; the rows hold)
- OrderPlanner: a send refused (saved on your records, not sent, said apart; Retry)
- OrderPlanner: look only, loading, nothing planned

## Keyboard

- Tab through the list, each line's delivery count, the deliveries action and the bar
- On a line's delivery count: Enter or Space edits it in place, Enter saves, Escape puts it back; more than was ordered is refused with the reason
- Escape closes the close question
- OrderPlanner: each row's grip is a slider (←/→ one unit, Shift ten), the field its exact twin; Send asks in place (Enter confirms, Escape backs out)

## Motion

A line's strip slides to its new count while the stock figure rolls to its new count (slot-text); the close question morphs in place (confirm-morph); the river unfolds as quantity-river does. Nothing runs at rest. OrderPlanner: units change tone as the grip moves; the totals roll; receipts fade in.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orders` | `PurchaseOrder[]` | — | The orders, controlled with onOrdersChange; or defaultOrders. |
| `defaultOrders` | `PurchaseOrder[]` | — | The starting orders when uncontrolled. |
| `onOrdersChange` | `(orders: PurchaseOrder[]) => void` | — | After a delivery is applied or an order closed. |
| `open` | `string` | — | The open order's id. |
| `defaultOpen` | `string` | — | Initial open state when uncontrolled; defaults to false. |
| `onOpenChange` | `(id: string) => void` | — | Reports an open-state change; no callback by default. |
| `onReceive` | `(draft: DeliveryDraft) => Promise<ReceiveResult \| void>` | — | Save one delivery. Reject with an Error to keep the draft and its key (said with Try again). |
| `onClose` | `(orderId: string) => Promise<unknown>` | — | Close an order: nothing more is expected. Reject to keep it open (said apart from a failed delivery). |
| `by` | `string` | — | Who is receiving, written on the receipts the block records itself. |
| `defaultDrafts` | `Record<string, Record<string, number>>` | — | A delivery counted but not saved, restored from your own storage: per order id, each line's units this delivery. |
| `now` | `number` | — | The clock, for the receipts and the closing the block records itself. Default the device's. |
| `canReceive` | `boolean` | — | false: the person may look but not receive or close (receiving stock needs its own permission). |
| `loading` | `boolean` | — | Shaped skeletons while the orders load. |
| `product` | `string` | — | Your product's name, shown as its mark. |
| `className` | `string` | — | Classes on the documented visual root; no override by default. |
| `ref` | `Ref<HTMLDivElement>` | — | Receives the documented native target or imperative handle; omitted by default. |
| `OrderPlanner: plan / defaultPlan / onPlanChange / onSave / onSend / onRefresh / by / canOrder` | `OrderPlan \| null / (id, quantities) => Promise<{ at }> / (id) => Promise<{ at, to }> / (id) => Promise<PlanLine[]> / string / boolean` | — | The plan (lines with unit cost, stock or null, par and what other open orders bring; the quantities set by hand; the last save; sent). Save keeps the quantities on your records; Send sends what is saved (an unsaved change is saved first, its own receipt); a rejected send keeps the save. onRefresh returns the lines counted again; counts set by hand stay. planning.ts has the rule (suggested, quantityOf, totals, unsaved) for your server. |

## Built from

- [Avatar](https://beamline.io/components/avatar.md)
- [Button](https://beamline.io/components/button.md)
- [Confirm morph](https://beamline.io/components/confirm-morph.md)
- [Empty state](https://beamline.io/components/empty-state.md)
- [Inline edit](https://beamline.io/components/inline-edit.md)
- [Label](https://beamline.io/components/label.md)
- [Number field](https://beamline.io/components/number-field.md)
- [Quantity river](https://beamline.io/components/quantity-river.md)
- [Segmented control](https://beamline.io/components/segmented-control.md)
- [Select](https://beamline.io/components/select.md)
- [Skeleton](https://beamline.io/components/skeleton.md)
- [Slider](https://beamline.io/components/slider.md)
- [Slot text](https://beamline.io/components/slot-text.md)
- [Spinner](https://beamline.io/components/spinner.md)
- [Text morph](https://beamline.io/components/text-morph.md)
- [Tooltip](https://beamline.io/components/tooltip.md)
- [Value grip](https://beamline.io/components/value-grip.md)

## Dependencies

`lucide-react`

## Import

```tsx
import { PurchaseOrders, OrderPlanner } from "@beamline/purchase-orders";
```

## Get it

Purchase orders 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 Shop

- [Product editor](https://beamline.io/components/product-editor): A product being made in a shop's back office, every value read as text at rest. The variant grid, grouped by the first option, lists each combination…
- [Category manager](https://beamline.io/components/category-manager): A shop's product categories, nested and ranked. Each category moves up, down or into another from its menu, and every move gets its own receipt (saving…
- [Inventory item](https://beamline.io/components/inventory-item): One stock item in a shop's back office, apart from the variants that sell it (how many each uses per unit). Stock per location as stocked, reserved and…
- [Plan comparison](https://beamline.io/components/plan-comparison): Plans side by side against every feature: prices that roll between monthly and yearly, the current and recommended plan marked, a header that stays in…
- [Step form](https://beamline.io/components/step-form): The finished frame for any multi-step form you compose — onboarding, intake brief, questionnaire, booking, application: a wide card with progress, the…
- [Orders admin](https://beamline.io/components/orders-admin): A shop's order desk: today's orders and revenue, quick filters for what needs doing (to fulfil, unpaid, returns), a virtualised orders grid, and the…
- [Order detail](https://beamline.io/components/order-detail): One shop order with payment and fulfilment as two separate lanes. The header says where each stands (paid, partly paid, awaiting, refunded; not…
- [Order board](https://beamline.io/components/order-board): A shop's orders as cards in its columns, on the shared kanban with drag and keyboard moves. Each card: number, estimate or work order, customer, vehicle…
- [Order composer](https://beamline.io/components/order-composer): An order made inside a support ticket: products found by name, each variant with its stock drawn and said (in stock, none, oversold, not known) and the…
- [Draft orders](https://beamline.io/components/draft-orders): A draft order made for a customer in a shop's back office that reads like the order it will become. The lines lead: each with its variant and SKU or…
- [Order changes](https://beamline.io/components/order-changes): What happens to a shop order after it's placed. Edit: the order as it stands beside the requested version, each line marked new, changed (old → new…
- [Special orders](https://beamline.io/components/special-orders): Customers' special orders as demand to buy for, per outlet: To order apart from Ordered; each product's demand, what is reserved, incoming and on order…
- [Order history and fills](https://beamline.io/components/order-history): Every order the account placed and every fill it got, for a period: totals for volume, fees, fill rate and maker share; orders with their status and how…
- [Order ledger](https://beamline.io/components/order-ledger): Purchase orders as money. Each order's spend is drawn against its approved total (paid, billed, pending, what is left, and any overrun past the end…
- [Ticket orders](https://beamline.io/components/ticket-orders): A customer's orders inside a support ticket, each action's effect said before it's done: orders and drafts kept apart, a paid draft shown once as its…
- [Record search](https://beamline.io/components/record-search): One search across connected records (customers, vehicles, orders) by name, phone, plate, VIN or order number. What the words look like is said (info)…
- [Return queue](https://beamline.io/components/return-queue): A queue of return cases. Search by return, order or customer, exactly (the default) or loosely; status tabs with counts, Requires attention first; a date…
- [Return case](https://beamline.io/components/return-case): One return as one place where every act is its own. The return, its order (linked out) and the customer on three cards; the stage said on its tone. Items…
- [Return request](https://beamline.io/components/return-request): Staff start a return from an order, and edit the reasons customers pick. New return: the order's lines as a checklist, each with its variant, price and…
- [Print quote](https://beamline.io/components/print-quote): A print shop's quoting in three places. Quote: a group of garments that share one imprint and one quantity break, the group's pieces counted together and…
- [Service estimate](https://beamline.io/components/service-estimate): An estimate for one vehicle. Services added from the catalogue (one not listed for this vehicle said on warning, fit unknown said); each with its lines…
- [Job card](https://beamline.io/components/job-card): A technician's repair order, phone-first. The vehicle, the customer and when it's due in words (due at, due in, overdue by how long); an offline copy…
- [Parts tracker](https://beamline.io/components/parts-tracker): Parts across a shop's work orders: each with its number, the order and vehicle, supplier, quantity and cost (not known said), and where it is: ordered…
- [Visit board](https://beamline.io/components/visit-board): The staff board for a walk-in queue: visits move from waiting through called and serving to done today. Arrival (here or on the way), the place in the…

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).
