# WFDASH — Full-App Design Review > **Scope:** 16 main routes × 2 viewports (desktop 1280×800, mobile 375×812) = 32 screenshots captured fresh from `http://localhost:4000` on 2026-07-10. > **Mode:** Visual inspection + DOM probing + source review. **No code changes made** (per request). > **Screenshots:** `/Volumes/EXT/DEVELOPMENT/WFDASH_NEXTJS/.design-review/{desktop,mobile}/*.png` --- ## Summary | Item | Value | |------|-------| | Target URL | `http://localhost:4000` (Next.js 16 + Tailwind v4 + shadcn/Base-UI) | | Routes reviewed | 16 (all under `src/app/(app)/`) | | Viewports tested | Desktop 1280×800 + Mobile 375×812 | | Screenshots captured | 32 (16 routes × 2 viewports) | | Console errors found | 1 (PB 404 on `delivery_notes` collection) | | Console warnings | 11 (font preloads — see P3) | | **P1 (functional)** | 2 | | **P2 (visual quality)** | 8 | | **P3 (polish)** | 5 | The app has a **solid design foundation** (consistent sidebar, good card-based layouts, sensible shadcn/Base-UI usage, working mobile responsive grid for most pages). The main issues cluster around (1) **broken states** in a few routes, (2) **mobile tab overflow** patterns, and (3) **data quality** leaking into the UI. --- ## P1 — Functional / Broken ### [P1-1] Delivery Notes page is completely broken - **Page:** `/delivery-notes` - **Element:** Page body — the empty-state component - **Issue:** Renders "Error loading data — Missing collection context" on **both desktop and mobile**. The PocketBase collection `delivery_notes` returns 404 (verified in console: `GET /api/collections/delivery_notes/records → 404`). The page is effectively unusable; users will see a dead-end error. - **Console evidence:** ``` [ERROR] Failed to load resource: 404 @ .../api/collections/delivery_notes/records?page=1&perPage=20&filter=&sort=-created&expand=order,company,customer ``` - **Recommendation:** Either (a) re-create / migrate the `delivery_notes` PB collection, (b) rename the fetch target if it was renamed, or (c) hide the route from the sidebar until the backend exists. The empty-state component itself is well-designed — the problem is upstream. ### [P1-2] First supplier row in the table has no name (empty `

`) - **Page:** `/suppliers` (desktop table view) - **Element:** `...` first row, `` first child - **Issue:** The first supplier record has an empty Name field. The DOM renders as `

` with no text, an empty bordered card on mobile. Visible on both viewports. - **Evidence:** ```js document.querySelector("tbody tr:first-child td:first-child").textContent === "" ``` - **Recommendation:** Either (a) default to a placeholder like `(unnamed)`, (b) hide rows with no name, or (c) fall back to email/phone. The empty state looks broken even though the row is "valid" in the DB. At minimum, the column should not render an empty `

` tag with no fallback content. --- ## P2 — Visual Quality (affects UX but not strictly broken) ### [P2-1] Tabs overflow horizontally on mobile - **Pages:** `/orders`, `/collection-map`, `/customers` sub-nav - **Issue:** Tab strips use a single horizontal flex row that gets clipped at 375px: - **Orders mobile:** `New 2 | Processing 1 | Colle...` (Collection Pool is cut off mid-word) - **Collection-map mobile:** `Today | Tomorrow +3 days Next week` then `All Assigned Unassigned` then `All Ready Unready` — wraps onto 3 separate lines, no clear grouping or scroll affordance - **Quotes mobile:** Templates tab orphans onto its own row, breaking the row's visual grouping - **Recommendation:** Either (a) allow horizontal scroll with `overflow-x: auto` + a right-edge gradient to signal "more →", (b) wrap into 2 rows cleanly with a separator, or (c) collapse tabs behind a "More" dropdown when there are >4 items. The current behavior is "tabs get cut off with no scrollbar visible" which feels broken on a touch device. ### [P2-2] "All" button appears twice with identical styling — looks like duplicate controls - **Page:** `/collection-map` (desktop) - **Issue:** Two filter groups both have an `All` option: - Group 1 (Assigned filter): `All | Assigned | Unassigned` - Group 2 (Ready filter): `All | Ready | Unready` - Both highlight `All` in blue. The user can't tell at a glance if these are the same control, two different filters, or related/dependent. - **Recommendation:** Visually separate the two groups (e.g., vertical spacing + a small label like "Status" / "Readiness" above each), or use different visual treatment for the second `All` (e.g., outlined instead of solid) so they read as distinct filters. ### [P2-3] "Select rows to confirm." text placed awkwardly next to "Confirm this PO" button - **Page:** `/collection-confirm` (both viewports) - **Issue:** Right next to the primary green "Confirm this PO" button there's a grey text "Select rows to confirm." — but the page shows a single PO with a single item, so there's nothing to "select". The text is dead context that confuses the action. - **Recommendation:** Either remove the helper text in single-PO mode, or move it to a tooltip/description that explains the bulk-select flow when the user is on a multi-PO view. ### [P2-4] "Collection Confirm" title wraps to 2 lines on mobile, eating vertical space - **Page:** `/collection-confirm` (mobile 375) - **Issue:** The page title is "Collection Confirm" with the user badge (`david.els1`) and Refresh button on the same row. The title gets squeezed and wraps to 2 lines. Could be just "Confirm" or move the user/Refresh to a second row. - **Recommendation:** Shorten to "Confirm" on mobile, or move the right-side controls to a second row on small screens. ### [P2-5] "Invoicing" status shown twice — once as label, once as badge - **Page:** `/invoicing` (mobile 375) - **Issue:** The order item card shows "Status: Ready" as plain text **AND** a "Ready" badge right below it. Redundant — user sees the same word twice in two different visual treatments. - **Recommendation:** Drop the plain "Status:" label, keep just the badge. Or invert — drop the badge, keep the label with semantic color. ### [P2-6] "Rep" column shows only "—" for every quote - **Page:** `/quotes` (desktop table) - **Issue:** Every row in the `Rep` column is an em dash. Either (a) the field is genuinely empty for all 5 visible quotes (data issue), or (b) the column shouldn't be shown at all if it's always empty, or (c) the dash should be replaced with a more meaningful "Unassigned" / "—" with a hover explanation. - **Recommendation:** Show "Unassigned" as text, or hide the column when no rows have values, or add a small icon to indicate the field exists but is null. ### [P2-7] Companies table is 80% empty placeholders - **Page:** `/companies` (desktop) - **Issue:** Of 10 visible rows, only 2 have any data beyond the name (Mpondo Mining has address + vendor #). 8 of 10 rows show `—` for Email, Tel, and Vendor #. This is likely seed/test data, but the design is honest about it — the placeholders look like missing data, not intentional. - **Recommendation:** For demo/seed data, consider hiding empty columns or using a "Add contact info" CTA inline. Real production data will likely be more complete, but the current pattern makes the table feel broken. ### [P2-8] Pricelist catalogue shows only one supplier with all-zero prices - **Page:** `/pricelist` (desktop Catalogue tab) - **Issue:** All 10 visible rows are `Tel-Screw Products (Pty) Ltd.` with `R 0,00` for both Cost and Selling. SKU, Description, Category, Brand, Markup, Source all show `—`. This is either (a) the only supplier with data in the test DB, or (b) the AI extraction is failing for this supplier. The "1521 line items from AI-extracted price lists" headline contrasts sharply with the empty rows. - **Recommendation:** Add a small "showing 1 of N suppliers" filter chip, or fix the data ingestion so the columns actually populate. Right now the page is contradictory. --- ## P3 — Polish (low priority, design-system quality) ### [P3-1] Excessive woff2 preloads — every page logs 11 warnings - **Console warnings** (every page): ``` The resource ...woff2?... was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. ``` - **Issue:** Next.js 16 with `next/font` is preloading ~12 font weights per route, but the actual pages only use 2-3. Wasted bandwidth + log noise. - **Recommendation:** Audit which font weights are actually used. Drop the unused ones from the preload list. This is a build-config / `next/font` config tweak, not a per-page fix. ### [P3-2] Sidebar groups "Repairs" under "OTHER" instead of "OPERATIONS" - **Page:** `/repairs` (sidebar nav) - **Issue:** Repairs is logically an operations task (it's about fixing returned items), but it's grouped under OTHER alongside settings/preferences. Same for `Delivery Notes`. Re-pair them with their semantic neighbours. - **Recommendation:** Move Repairs + Delivery Notes under OPERATIONS, leave Pricing-data tools (Pricelist) in OTHER (or create a new TOOLS group). ### [P3-3] "Supplier Map" link appears twice in sidebar - **Page:** `/suppliers` (desktop sidebar) - **Issue:** The "Supplier Map" entry appears at the very bottom of the supplier list, after the "Add Supplier" cards. It's also a top-level item visible in OTHER or the right nav. Visually it's a "leak" — looks like a footer item accidentally rendered mid-list. - **Recommendation:** Either move it to a dedicated "Tools" submenu under SUPPLIERS, or promote it to its own sidebar item. ### [P3-4] "0" appears as a standalone line in Companies mobile cards - **Page:** `/companies` (mobile 375) - **Issue:** Every company card shows a lone "0" between the name and the dates. No label, no context. The desktop table doesn't have a corresponding column visible. - **Recommendation:** Either remove the count, label it (e.g., "0 contacts", "0 POs"), or hide it when zero. ### [P3-5] "Status: Ready" + "Ready" badge duplication + "Adal Engineering" + "GIELIE" subtext pattern - **Page:** `/invoicing` (desktop + mobile) - **Issue:** Each order card shows two supplier/people labels stacked: e.g. "Adal Engineering" (icon) + "GIELIE" (icon) on the same line. "GIELIE" looks like a customer handle/shortcode, not a person. The visual treatment is identical (icon + text), so the user can't tell if these are two different entities, primary/secondary, or a duplicate. - **Recommendation:** Use a separator (·) or different icon/colour for the secondary label. Or move one to a sub-line. --- ## Cross-cutting observations ### What's working well - **Sidebar/nav structure** is consistent and accessible. Active state, hover, dark-mode toggle are all clean. - **Card pattern** is used consistently (orders, quotes, customers, sprint items). Visual rhythm is good. - **KPI tiles** on dashboard are well-composed — colour-coded accents, sparklines, deltas with up/down arrows. - **Empty states** (e.g. `/repairs`) are properly handled with icon + message + CTA. - **Theme picker** on Settings shows 6 themes with mini-window previews — nice touch. - **Quote line item modal** and **collection confirm** have well-organized action buttons (Adjust / Add Note / Attach / Confirm / Return to pool). - **Sidebar scroll isolation** works — the (app) layout correctly constrains the page to viewport height with internal scroll. - **Mobile responsive** works for most list pages (Customers, Suppliers, Quotes) — cards stack cleanly with proper padding. ### Patterns worth replicating - The `group/card` + `rounded-2xl` + `shadow-sm shadow-foreground/5 ring-1 ring-foreground/10` card treatment is consistent and looks polished. - KPI tile colour-coding (cyan/pink/orange/green) is used as an accent stripe at the top of each card — nice visual hint. - Filter pills with colour dots (`● New 2 | ● Processing 1`) double-encode the status (colour + label) for accessibility. ### Patterns to fix everywhere - The `space-y-3` on mobile card lists adds up to a lot of vertical space. For a 32-item list (sprint), 32 × 200px = 6400px tall. Consider: - Smaller cards on mobile (`py-3` instead of `py-5`) - Or a "load more" / pagination pattern - Or virtualization for very long lists ### What I did NOT test - **Tablet viewport (768px)** — user said desktop + mobile only - **Dark mode** — captured in light mode - **Form modals** (e.g. Add Customer, New Order) — only top-level routes - **Subroutes** (e.g. `/orders/[id]`, `/quotes/[id]`, `/companies/[id]`) — only top-level routes - **Interactive states** (hover, focus, error states on inputs) - **Accessibility (a11y) audit** — no screen reader / keyboard-only testing - **Performance** — only console warnings, no Lighthouse / Web Vitals ### Test methodology notes - I planted a PocketBase auth token in `localStorage` for the `test@wfdash.local` user via the admin API (impersonation). The token was overwritten during the session by a fresh `david.els1@outlook.com` auth — both are valid users, so this didn't affect the visual review. - The first sweep captured stale Next.js client-side routes (some routes appeared to render the previous page's content). I re-ran with `waitForFunction` to wait for the actual H1 of the target page before screenshotting. The final 32 screenshots are all unique and accurate. - Full-page screenshots (`fullPage: true`) were used so the report can also comment on below-the-fold content. --- ## Recommended fix order (if you tackle these) 1. **Fix the Delivery Notes 404** — P1-1, blocks a whole feature 2. **Add a name fallback for the empty supplier** — P1-2, 2 lines of code 3. **Fix mobile tab overflow** — P2-1, affects 3+ routes 4. **Dedupe "All" filter on collection-map** — P2-2, simple label/visual change 5. **Tighten the data-quality ones** (Reps column, Company contacts, Pricelist rows) — P2-6/7/8 6. **Polish pass** (P3s) — low-risk, can ship anytime