# New Knowledge Base

This document captures new learnings and knowledge gained while developing this project.

---

## 2026-08-17

### Capture litre fields keep a typing draft

`type="number"` + `value={n.toFixed(2)}` resets the caret on every key (`0.00` → fight the decimals). Use `LitresInput`: text + `inputMode="decimal"`, string draft while focused (empty when value is 0), commit number on change, format to two decimals on blur. Used on Step 1 volumes, wine-type strip, and Step 2 matrix.

**Save draft** always PUTs `saved-totals` when the API accepts the body. Client mismatch checks (wine-type strip vs Step 1, incomplete breakdown, lump back sync) are **advisory** — they append notes after a successful save and must not abort it. **Submit** still uses `submitBlockReason` as a hard gate (over-allocated, needs breakdown, strip mismatch).

### Plain-language UI messages

User-facing errors and notices live in `src/utils/userFacingMessages.js`. Prefer plain terms: **return** not submission, **breakdown** not back matrix / TOTAAL, **package size** not front volume. API `message` still wins when the server sends one; fallbacks use the helpers.

### Capture local draft (localStorage)

`ProducerCapturePanel` debounces edits to `localStorage` key `sawis_capture_draft_v1_{email}_{submissionId}` (volumes, wine-type strip, Step 2 matrix, selection). On reload, draft restores over server hydrate when context matches and status is editable. Step 2 volumes marked `dirty` skip server refetch so local matrix edits are not overwritten. Cleared on **Submit**; **logout** wipes all `localStorage`. Server **Save draft** remains source of truth across devices.

### Review submission compares package volumes to cultivar totals

Left **Total volume captured** is the sum of saved package/size litres. Right **Grand total** is the sum of cultivar line-item `TOTAAL`. Do not reuse `saved_totals.grand_total` for both — that hid mismatches.

### Super Admin types any producer code

Role **4** does not need a PRODKODE pool. Capture shows a text input; commit on blur/Enter, then `GET …/submissions?PRODKODE&period&country`. Show `data.message` from a failed lookup (including JSON 404). Roles 0–3 keep the assigned-code dropdown. `domesticRequest` treats JSON 404 with a `message` as a real error, not a missing-route gap.

### Capture modal layout

Admin capture uses `bi-modal--domestic-capture` at **90vw**. Modal variant hides the Capture tab. Help and close share `.domestic-capture-toolbar-actions` (do not use absolute `.modal-close-btn` — it overlaps Help).

### User assignment sends the logged-in user id

`PUT auth/domestic/admin/users/{id}/assignment` body is `{ domestic_producer, domestic_producer_codes, admin_email }`. `domestic_producer` is the **assigner’s** numeric user id, not the managed row’s `domestic_producer` (often `0` when unlinked). Resolve from session `user.id`, login/JWT fields, or the users list row matching the signed-in email. Do not send `0` — show an error and ask them to re-login. Persist `id` on the auth session so it survives refresh. Super Users (role 3) pick PRODKODE from their pool chips only — no free-text codes field. Drawer actions sit under the fields (`margin-top` not `auto`).

### Admin submissions: cancel vs open up roles

Use role-specific helpers for admin submission state actions instead of the broader approve helper. Roles **2 / 3 / 4** may call `POST auth/domestic/admin/submissions/{id}/cancel` from **Open** or **Submitted**, which locks the submission for editing. Only roles **2 / 4** may call `POST .../{id}/reopen` from **Cancelled** or **Missed**, which returns the status to **Open**.

### Capture litre format is two decimals with `.`

`formatLitres` used `toLocaleString("en-ZA")` then replaced commas with spaces. In `en-ZA` the comma is the decimal, so `1200.5` became `1 200 5`. Format with `toFixed(2)` and an ASCII `.`; space thousands stay. Number inputs use `step="0.01"` and `0.00`.

### Capture does not auto-create on context change

`GET auth/domestic/submissions?period&PRODKODE&country` looks up a return. No match → empty state (“no submission open…”) and **Open a submission** (`POST …/submissions`). Locked context (admin update / past View) still POSTs immediately.

### Submission status pill colours

The capture **Return status** badge sits with Producer Code / Period / Territory (not the page title). `.domestic-form-shell--*` tints the form and uses a left border in the status colour so the badge is clearly about that return.

Use `SubmissionStatusPill` on capture (header, footer, past table) and admin list/review. Tones: **Open** blue, **Submitted** yellow, **Approved** green, **Synced** gray, **Cancelled** red. API renamed New → Open; `New` / `Draft` still display as Open.

### Domestic reports filters live inside the table card

`/domestic/reports` no longer has a separate “Report filters” accordion above the table. Pass the filter form as `filtersSlot` on `GenericPivotTable` (grape / vineyard omit it). Layout matches exports Price Range: Filters button, collapsible wrap-grid dropdowns (`flexBasis: 15%`), green Reset + Apply with icons. Apply still goes through `handleFilterChange`; the table regenerates from `selectedFilters`. After generate, the card heading is API `title` and the period is `period_label`. The filters panel has no `shadow-sm`.

---

## 2026-08-13

### Admin portal: DAFF (role 1) is Tools-only

Role 1 sees **Tools** only in the admin sidebar. `canAccessAdminSubmissions` and `canManageDomesticUsers` are false; `/admin/submissions` and `/admin/users` redirect to `/admin/tools`.

### Admin portal: Super User (role 3) sees User management

Role 3 can open **User management** (pool-scoped producers) and **SAWIS 7 Submissions**. Tools stay hidden (`canAccessAdminTools` false). `canManageDomesticUsers` is true for role 3 even if login `permissions.can_manage_users` is false. Default `/admin` → `/admin/submissions`.

### Vineyard Status report URLs are GET

`getVineyardStatusReports` returns paths like `vineyardStatus/totalHectares`. Those endpoints are GET, not POST. `GenericPivotTable` detects `Type === "VineyardStatus"` (or a `vineyardStatus/` URL) and uses GET with sidebar filters as query params. Grape Production still POSTs. Also proxy `/vineyardStatus` in `setupProxy.js` — otherwise CRA returns `Cannot POST/GET /vineyardStatus/...`.

---

## 2026-08-12

### Logout and invalid session wipe client storage

`logout()` (sign-out and **401** via `checkUnauthorizedAndLogout`) expires all JS-visible cookies, then `localStorage.clear()` and `sessionStorage.clear()`. **403** and bodies containing “denied” are not session expiry (permission / profile gaps). Persist login with named cookie `sawisAuth` (`path=/`) and `localStorage` key `sawis_auth_v1` — do not write a raw `jwtToken:::token+user:::email` blob; JWT `+`/`=` breaks `document.cookie` on refresh.

### Capture Step 1 headings are accordions

Container subheads (Cans, Carton Packs, Glass, …) toggle their size rows. Still and sparkling share those package types — each type appears once (no “Glass sparkling” duplicate). Wine-type split is the totals strip; Step 1 still+sparkling litres must match strip still+sparkling combined. Fortified / wine-component stay strip-only. Selecting a size re-opens its package subheading.

### Capture Step 2 is hidden until a volume is selected

Step 2 (back matrix) stays visible. With no Step 1 size selected it shows “Select a package size in Step 1…”. Filter chips animate the matrix when switching. **← Volumes** or **Save & back to volumes** returns to the prompt.

### Past submissions View

Past table columns: Period, Status, Producer Code, Territory, Actions (View). View opens a **modal** with the capture panel locked to that row (`initialContext` + id via `GET …/submissions/{id}` when available).

### Producer capture wine-type totals map to wine_class_totals

The strip is SoT for `wine_class_totals`. Inputs are one per `cultivars_by_heading` label from GET lookups (e.g. Still Wine Red / White, Rosé, Perlé, Wine Component, Sparkling, Fortified, DE ALCOHOLISED). Saved keys are those heading strings. Fortified / wine-component headings still map to lump classes (`TH: T`, `HOUGRO: TOTAL` / `WCTOTAL`); the other headings share the Step 1 package grid. API requires **`grand_total === sum(wine_class_totals)`**, **`sum(size-level sub_totals) === grand_total`**, and **each size’s litres === sum of `domestic_sawis_7_data.TOTAAL` for that TH+HOUGRO**. Legacy saves keyed `still` / `sparkling` / `fortified` / `wine_component` hydrate onto the first matching heading.

### Producer submit uses POST …/submit

“Submit for verification” calls `POST auth/domestic/submissions/{id}/submit` (empty body) after saving front totals. It does **not** PUT `status: submitted` on the submission resource. Optional pre-check: `GET …/reconciliation` (`can_submit` / `ok`).

---

## 2026-08-05

### npm audit: React Router 7.18 vs RSC CSRF

After clearing most CRA transitive vulns with `npm audit fix` + overrides, `react-router-dom@7.18.x` still reports GHSA-qwww-vcr4-c8h2 (RSC CSRF). Patched router line is `react-router@≥8.3`, which peers React `≥19.2.7`. `npm audit fix --force` suggests **downgrading** to 7.11, which reopens the open-redirect fix that required ≥7.18. For a CRA SPA (no RSC), keep 7.18.x until a React 19 upgrade is planned.

### AuthRouteGuard for login-only routes

For routes that only need “any logged-in user” (not admin role), use `AuthRouteGuard` — same pattern as `AdminRouteGuard` but checks `useAuthStore().user` and redirects to `/` with `<Navigate replace />`. Hide matching nav/Home entry points when `!user` so users are not bounced after clicking a visible link.

### Domestic Sales tab bar vs can_capture

`DomesticSalesTabs` should return `null` when `!canCaptureDomestic(user)`. Showing only the Reports tab is noise on `/domestic/reports`; the bar exists to switch between Reports and SAWIS 7 capture.

### CRA proxy must cover export report paths

Phase 6 narrowed `setupProxy` to a allowlist. Export overview calls (`POST /getChartData`, `/getExports`, `/tables/...`, etc.) were omitted, so CRA returned 404 HTML, `res.json()` failed, and `filterStore` treated every catch as “session expired” and logged the user out. Proxy all `/get*` + `/tables`, and only logout on real 401/403. Chart/table payloads remain POST; `getFilters` stays GET.

---

## 2026-03-05

### Fixing npm audit vulnerabilities without breaking changes

When `npm audit fix` suggests `npm audit fix --force` (which would downgrade e.g. react-scripts and cause breaking changes), use **package.json overrides** to force patched versions of the vulnerable transitive dependencies instead:

- Add an `overrides` entry for the vulnerable package at the version that fixes the advisory (e.g. `"@tootallnate/once": "^3.0.1"`, `"svgo": "^2.8.1"`).
- Run `npm install` so the lockfile is updated; `npm audit` should then report 0 vulnerabilities while keeping your direct dependencies (e.g. react-scripts@5) unchanged.
- For SVGO, the vulnerable range was 2.1.0–2.8.0; the fix is in 2.8.1+. For @tootallnate/once, versions before 3.0.1 were vulnerable (GHSA-vpq2-c234-7xj6); use ^3.0.1.

---

## 2026-01-23

### Missing manifest.json Console Error

If you see a console error like `Manifest: Line: 1, column: 1, Syntax error`, this typically means:

1. The `index.html` has a `<link rel="manifest" href="manifest.json">` tag
2. But the `manifest.json` file doesn't exist in the `public` folder
3. The browser receives an HTML error page (404) instead of valid JSON
4. Trying to parse HTML as JSON causes the syntax error at line 1, column 1

**Solution**: Create a valid `manifest.json` file in the `public` folder with proper JSON structure:

```json
{
  "short_name": "App Name",
  "name": "Full Application Name",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}
```

---

## 2025-12-12

### Mapper Processing Sheet API Structure

The backend mapper processing sheet history endpoint returns data with the following structure:

```json
{
  "success": true,
  "history": [
    {
      "id": "349",
      "status": "Success",
      "current_step": "Completed",
      "admin_email": "user@example.com",
      "uploaded_excel_doc_link": "http://localhost:8080/admin/getAdminFiles/uploaded/349",
      "debug_excel_doc_link": null,
      "final_mapper_excel_doc_link": null,
      "error_message": null,
      "sent_at": "2025-12-12 03:08:45"
    }
  ]
}
```

### Status Values & Styling
Status values are centralized in `src/lib/constants.js` with the `PROCESS_STATUS_STYLES` object and `getProcessStatusStyle()` function. Use the `StatusBadge` component from `src/components/common/StatusBadge.jsx` for consistent status display.

| Status | Color | Background |
|--------|-------|------------|
| Success/Completed | white | #28a745 (green) |
| Started | white | #17a2b8 (cyan) |
| Uploading/Processing | #212529 | #ffc107 (yellow) |
| Lookup Error | white | #fd7e14 (orange) |
| Error/Failed | white | #dc3545 (red) |
| Cancel/Cancelled | white | #6c757d (gray) |

Usage:
```jsx
import { StatusBadge } from "../common/StatusBadge";
<StatusBadge status={status} fontSize="10px" padding="3px 6px" />
```

### Admin API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/admin/mapper_processing_sheet/history` | GET | Fetch processing history |
| `/admin/process-thread/:id` | GET | Get process thread details |
| `/admin/mapper_processing_retry/:id` | POST | Retry a failed process |
| `/admin/mapper_processing_cancel` | POST | Cancel the currently running process |

### File Download Links
The API provides three types of file download links:
1. `uploaded_excel_doc_link` - The original uploaded file
2. `debug_excel_doc_link` - Debug/diagnostic file
3. `final_mapper_excel_doc_link` - Final processed mapper file

These links are only present when the respective file is available (otherwise null).

### Retry Functionality
Failed processes can be retried using the `mapper_processing_retry` endpoint. The endpoint accepts the process ID and returns:
```json
{
  "success": true,
  "message": "Retry initiated successfully"
}
```

### Unknown Matches CRUD Endpoints
The backend provides CRUD endpoints for managing unknown value mappings:

| Entity | GET | POST | PUT | DELETE |
|--------|-----|------|-----|--------|
| Cultivar | `/admin/unknown-cultivar-matches` | `/admin/unknown-cultivar-matches` | `/admin/unknown-cultivar-matches/:id` | `/admin/unknown-cultivar-matches/:id` |
| Country | `/admin/unknown-country-matches` | `/admin/unknown-country-matches` | `/admin/unknown-country-matches/:id` | `/admin/unknown-country-matches/:id` |
| Producer | `/admin/unknown-producer-matches` | `/admin/unknown-producer-matches` | `/admin/unknown-producer-matches/:id` | `/admin/unknown-producer-matches/:id` |

These endpoints require admin authentication via the `adminFilter`.

---

## 2025-12-17

### react-data-table-component Search Functionality

The `react-data-table-component` library supports a built-in subHeader for adding custom search functionality:

```jsx
const [searchText, setSearchText] = useState("");

// Filter data based on search text
const filteredData = data.filter((row) => {
  if (!searchText) return true;
  const searchLower = searchText.toLowerCase();
  return fields.some((field) => {
    const value = row[field.name];
    return value && String(value).toLowerCase().includes(searchLower);
  });
});

// In the DataTable component
<DataTable
  columns={columns}
  data={filteredData}
  subHeader
  subHeaderComponent={
    <div className="w-100 mb-2">
      <input
        type="text"
        className="form-control"
        placeholder="Search..."
        value={searchText}
        onChange={(e) => setSearchText(e.target.value)}
        style={{ fontSize: "12px", maxWidth: "300px" }}
      />
    </div>
  }
/>
```

Key props:
- `subHeader={true}` - Enables the subHeader section
- `subHeaderComponent` - Custom React component to render in the subHeader area

---