Vite React Back Office
apps/back-office replaces the Nuxt 2 / Vue 2 / Vuetify 2 UI in apps/back-office-old. The legacy source remains intact. The new workspace is @bsr/back-office, with Vite, React, TypeScript and custom responsive CSS inspired by the Vuexy semi-dark reference. It does not bundle the commercial template's source or assets.
Start and review
From the repository root, use Node 22.12+ and Yarn Classic:
yarn install --frozen-lockfile
yarn backoffice:dev
Open http://127.0.0.1:8090 and sign in with an operating account. All application data comes from the configured API environment; there is no demo mode. Stored sessions from the retired demo mode are discarded on load.
Real sessions allow the existing create/edit/delete/status actions by default, subject to API authorization. The instruction not to change live data applies to testing behavior. backoffice:safe remains an optional read-only command that blocks business POST/PATCH/DELETE while permitting authentication.
VITE_BACK_OFFICE_READ_ONLY=0 or an unset value enables normal operation. Set it to 1 in .env.local only to opt into read-only mode, then restart yarn backoffice:dev or rebuild. Vite variables are compiled into the build. Changing deployment environment variables after producing dist does not change that build.
Menus and legacy parity
Click a row or its eye action to open a modal. Filtering and pagination use the server; clicking a column sorts the currently loaded page. The table scrolls horizontally on small screens, and detail dialogs become full-screen on mobile.
| Route | Data and filters | Detail and supported actions |
|---|---|---|
/, /dashboard | Counts by branch, order status and recent orders | Live counts from response totals; missing totals show a dash. No invented revenue or trends. |
/orders | Orders; branch, status, shipping type, date range, search | Customer, line items, amounts; edit shipping type and coordinates. Status remains disabled as in legacy. |
/orders-check | Same order request and filters | Shows only rows whose refKey !== null within the fetched page. Not Mobile's waiting-payment workflow. Same detail actions as orders. |
/invoices | Invoices; branch, canceled status, shipping type, Incoming, dates, search | Cancellation notes, B1 errors, items; edit shipping and coordinates. Status remains disabled. |
/cn | Credit notes; branch, status, dates, search | Returned items, discount total, warehouse Q1/Q2/Q3 and photos. Read-only. |
/transmission-line | Deliveries; branch, status, transportation flag, dates, search | Invoice B1 outcomes, grouped products; update delivery status. |
/store-line | Invoices with fixed shippingType=2; branch, status, dates, search | Storefront picking; update NEW / CONFIRMED / CANCELED. No shipping/coordinate tabs. |
/transportation | Transports; branch, status, dates, search | Route, driver, deliveries, customer payments, photos by customer and delivery coordinates. Status remains disabled. |
/users | Employees; search | Eight permission flags, existing FCM token preserved; Excel export of all pages. |
/vehicles | Master vehicles; BFP/NFF/PCC company, capacity, search | Type, chassis, engine, color, year and capacities. Legacy inactive fields retain their reset contract. |
/pick-up-point | Master branch addresses | Add/edit/delete pickup points, shared Thai address lookup, Google Maps or typed coordinates. |
/signin, /signout | Authentication/session | Login and local logout; direct signout clears only this application's session. |
The sidebar makes the previously direct-access order review route discoverable. No new order creation, stock transitions or payment operations have been introduced.
Shared packages and boundaries
| Package | Usage |
|---|---|
@bsr/api | Central environment/hosts, Master key, web Maps key and existing POST guard |
@bsr/types | Auth user, employee and permission types |
@bsr/utils | Thai province/district/subdistrict/postcode dataset, lazy loaded |
rn-ui remains native-only. The web application owns its DOM components, CSS theme, web session and heterogeneous legacy API adapters. No native UI, Mobile rule, central environment setting or old app file was changed for this migration.
Request contracts
The request layer uses Accept: application/json, POS Authorization: Bearer or Master x-api-key, JSON bodies, a 40-second timeout, cancellation and backend error text. List responses must be arrays. content-range supplies the total; absent totals do not become a false zero. A 401 clears this app's session and returns to login.
Common list fields are page, pageLimit, sort=DESC, sortBy=CreatedAt, search. POS document lists add branch/date filters. Orders use orderStatus; invoices use invoiceStatus, reqCancel='' and toB1; deliveries use deliveryStatus and toTransportation; transports/CN use status. Vehicle lists use companyCode (default BFPDB) and capacity, without POS branch/date fields. Pickup points come from a branch's addresses array.
| Action | Method and path | Payload |
|---|---|---|
| Login | POST login, then GET branches?userCode=... | email, password; returned token authorizes the branch request |
| Shipping | PATCH orders/:id/shipping-type or invoices equivalent | shippingType as a string |
| Coordinates | PATCH orders/:id/lat-lng or invoices equivalent | U_BFP_Latitude, U_BFP_Longitude as strings |
| Storefront status | PATCH invoices/:id/status | invoiceStatus |
| Route picking status | PATCH deliveries/:id/status | deliveryStatus |
| Employee permissions | PATCH users/:id | Eight numeric 0/1 flags plus unchanged fcmToken |
| Vehicle | Master PATCH vehicles/:id | Active editable fields plus legacy inactive defaults below |
| Pickup create/edit | Master POST branches/:id/addresses, PATCH branches/:id/addresses/:addressId | Exact legacy address field whitelist |
| Pickup delete | Master DELETE branches/:id/addresses/:addressId | No body |
There is no active mutation for ordinary order/invoice status, transport status or credit-note status, because the old UI disabled them.
Calculations and fields that must not drift
- Order cancellation mismatch compares floored
amountandinvoiceAmountfor CONFIRMED/WAITING_PAYMENT. Invoice badge priority is canceled, missing SAP ref/retry, Incoming sent, then cancellation request or unsent. - Delivery header totals include every invoice. Grouped products exclude CANCELED invoices, combine by
productCodeand sort descending by quantity. - A transport customer without
paymentTypedisplaysamount; after payment it displayscash + transfer. Photo requests include customer code and both coordinates so separate stops are not conflated. - CN returned quantity/weight includes only
isReturn=1; the discount total includes everypriceCN. Warehouse Q1/Q2/Q3 is read-only. - Employee flags are
fillWeight,editPrice,delDelivery,newCustomer,approveCustomer,resetBooking,editDeliveryDate,isAdmin. Excel keeps the original 15 explicit columns, excludes tokens and fetches all pages instead of truncating at 999. Values are literal cells, never formulas. - Vehicle payload sends
vehicleTypeId,chassisNumber,engineNumber,vehicleColor,yearOfManuf, numericcapacityOfPayloadandmaxCapacityOfPayload. As in legacy, it resetscapacityOfFuel=0,status=1, and inactive fuel/date/insurance/license/remark fields to null. This consequential legacy behavior is intentionally retained; changing it needs a separate business decision. - Pickup payload allows only
addressType,checkAddressName,addressName,street,block,city,country,zipCode,visOrder,U_BFP_Amphur,U_BFP_Tambon,U_ISS_RouteCode,U_ISS_RouteName,U_BFP_Latitude,U_BFP_Longitude. Type isbo_ShipTo. Latitude/longitude must be finite and within geographic bounds.
Theme, dialogs and sessions
The semi-dark sidebar works with both light and dark content. Theme preference is stored under bsr.back-office.theme, initially following the OS. A drawer serves smaller screens; desktop navigation can collapse. Ctrl/Cmd K opens menu search. Native dialogs trap focus, use labelled headings and support nested confirmation/gallery dialogs. Unsaved edits require explicit discard before closing; saving prevents closure and refreshes both table and detail after success.
Session storage is namespaced bsr.back-office.<environment>.session, separate from the old application. Closing the browser tab ends that session according to browser session-storage behavior. Logout removes only this key. The interface does not invent client-only permission gates absent in legacy; API authorization remains the authority.
PRD and UAT on one domain
Run yarn backoffice:build:all to produce apps/back-office/dist-prd for / and apps/back-office/dist-uat for /uat/. The explicit builds select their own API environment and Master key without editing Mobile/Terminal configuration. They call the matching POS and Master APIs directly from the browser. Development still uses Vite proxies.
Upload PRD at the web root and UAT inside uat/, including each generated .htaccess. The root rules preserve UAT routing; its fallback is /uat/index.html. API servers must allow CORS from the website origin (the same for both paths). UAT preflight checks currently require an API-side CORS fix; see apps/back-office/deploy/README.md for the deployment guide and recorded results. Legacy gateway build commands below remain available.
Build and deployment
yarn backoffice:test
yarn backoffice:typecheck
yarn backoffice:build
yarn workspace @bsr/back-office preview
The preview server is at 127.0.0.1:8091. Vite's development/preview gateway proxies /pos-api and /mst-api to hosts selected by shared configuration. Static hosting needs its own reverse proxy and SPA fallback; apps/back-office/deploy/nginx.uat.example.conf is a ready-to-adapt example for an existing HTTPS ingress. It is not an installed server configuration. Keep the API host environment consistent with the compiled shared environment.
Deploy apps/back-office/dist, preserve index.html fallback for every client route, avoid caching API responses, and serve hashed assets with long caching. The Maps web key requires the deployment origin to be allowed by its existing Google configuration. Browser-visible values are not a place for server secrets.
Root workspaces explicitly list the three maintained apps and packages/*, excluding ignored Nuxt legacy dependency installation. ExcelJS and the Thai address database are separate lazy chunks, so their large-chunk build warnings do not mean they load on the dashboard. Vite currently bundles the shared TypeScript config; its notice about a future native config loader does not prevent the current build.
Validation and remaining operating checks
Automated tests exercise read-only request blocking, authentication headers, response errors, retired-session cleanup, mutation payloads, query filters, export above 999 records, amount rules and product aggregation. Tests mock the network. See apps/back-office/QA.md for the historical migration review and current removal checks.
No real/UAT business writes, actual login credentials or live Maps authorization were used during migration testing. Before an operational rollout, verify read-only login/data visibility with the intended user and deployed gateway/Maps origin, then perform business acceptance of enabled writes under the team's normal change process. These operating checks are distinct from the completed local implementation and simulated verification.