2026-09-12 07:35:35 +00:00
|
|
|
import type { NextConfig } from "next";
|
|
|
|
|
import withPWAInit from "@ducanh2912/next-pwa";
|
|
|
|
|
|
|
|
|
|
const nextConfig: NextConfig = {
|
|
|
|
|
// No `env` block — NEXT_PUBLIC_ vars are picked up automatically at build time.
|
Round 4: regressions fixed + mobile standard
Part A — Round 3 regressions:
- A.1: verify-cost-reconciliation.mjs now loads .env.local, wraps in try/catch, exits 1 on any failure
- A.2: Vehicle Costs restored Santam — back to R 14,996.31 (matches row-by-row sum)
- A.3: incl<excl / VAT math / VAT>20% now FATAL — seed exits 2 with full report (4 anomalies on Charl-CCS + Buildsmart)
- A.4: Reconciliation target R 694,962.93 with both deltas itemised (Personnel +R19,800, Santam -R5,322.44, unexplained R 0.00)
- A.5: 'Lines' view uses actual lines (recharts LineChart), no fills; fills only in 'Cost stack'
Part B — Mobile standard:
- B.1: header wraps to 2 rows on mobile; YTD badge cards stack; page-wide overflow eliminated on /income, /charts, /overheads, /running, /expenses
- B.2: IncomeView card row layout stacked — name gets its own line on mobile, never truncated to single char
- B.3: Plant type filter is a <select> dropdown below lg (was 18 pills wrapping to 13 rows); same for sort on /income and /running
- B.4: Plant table first column sticky; -webkit-overflow-scrolling: touch; tables scroll inside their own container
- B.5: All buttons/links use min-h-[44px] tap targets; text below 12px is bumped where it matters
- B.6: Charts legend wraps below title; chart min-height h-44 → h-52 on mobile
- B.7: paddingTop env(safe-area-inset-top) on sticky header for notch/home indicator clearance
AGENTS.md created with the standing mobile checklist rule.
2026-09-12 10:05:43 +00:00
|
|
|
|
2026-10-01 13:59:51 +00:00
|
|
|
// Next.js blocks dev resources (HMR client, font optimizer, stack frames) for
|
|
|
|
|
// any origin that isn't localhost. When you open the dev server from another
|
|
|
|
|
// device on the LAN, the SSR HTML renders but /_next/hmr is refused, so the
|
|
|
|
|
// page never hydrates and every button/control is inert.
|
|
|
|
|
//
|
|
|
|
|
// Patterns are matched per dot-segment with `*` as a single-segment wildcard
|
|
|
|
|
// (same rules as `images.remotePatterns`) — CIDR ranges do NOT work here.
|
|
|
|
|
// Dev-only; `allowedDevOrigins` has no effect on `next build` / `next start`.
|
|
|
|
|
allowedDevOrigins: ["192.168.0.*", "100.88.155.*", "*.local"],
|
|
|
|
|
|
Round 4: regressions fixed + mobile standard
Part A — Round 3 regressions:
- A.1: verify-cost-reconciliation.mjs now loads .env.local, wraps in try/catch, exits 1 on any failure
- A.2: Vehicle Costs restored Santam — back to R 14,996.31 (matches row-by-row sum)
- A.3: incl<excl / VAT math / VAT>20% now FATAL — seed exits 2 with full report (4 anomalies on Charl-CCS + Buildsmart)
- A.4: Reconciliation target R 694,962.93 with both deltas itemised (Personnel +R19,800, Santam -R5,322.44, unexplained R 0.00)
- A.5: 'Lines' view uses actual lines (recharts LineChart), no fills; fills only in 'Cost stack'
Part B — Mobile standard:
- B.1: header wraps to 2 rows on mobile; YTD badge cards stack; page-wide overflow eliminated on /income, /charts, /overheads, /running, /expenses
- B.2: IncomeView card row layout stacked — name gets its own line on mobile, never truncated to single char
- B.3: Plant type filter is a <select> dropdown below lg (was 18 pills wrapping to 13 rows); same for sort on /income and /running
- B.4: Plant table first column sticky; -webkit-overflow-scrolling: touch; tables scroll inside their own container
- B.5: All buttons/links use min-h-[44px] tap targets; text below 12px is bumped where it matters
- B.6: Charts legend wraps below title; chart min-height h-44 → h-52 on mobile
- B.7: paddingTop env(safe-area-inset-top) on sticky header for notch/home indicator clearance
AGENTS.md created with the standing mobile checklist rule.
2026-09-12 10:05:43 +00:00
|
|
|
// Emits `.next/standalone/server.js` with a trimmed node_modules, so the
|
|
|
|
|
// runtime image doesn't need the full dependency tree. Required by the
|
|
|
|
|
// Dockerfile. Note it does NOT bundle `.next/static` or `public/` — the
|
|
|
|
|
// Dockerfile copies those in separately.
|
|
|
|
|
output: "standalone",
|
Invoices: the app ingests PDFs itself, with a problem-invoice inbox
Extraction was previously manual: an agent read each PDF, mapped the columns
by hand and wrote data/source/invoice-*.json. That was the only way anything
got in, and the upload button just filed the document without reading it.
That supplier's report is one fixed template — the columns sit at identical
x-positions on every invoice, and only the line count and page count vary —
so the figures are now read by coordinate instead. Deliberately not an AI
extractor: this input does not need one, and a deterministic parse that fails
loudly is worth more here than a flexible one that can hallucinate a total.
lib/invoice-parser.ts reads the header, the machine lines across all pages,
the "Plant Hire Total" row and the SUB TOTAL / Escalation / PLUS VAT /
INVOICE TOTAL block, then applies the same arithmetic gates the seed does:
lines must sum to the subtotal, each line must satisfy qty x rate = amount
and amount + VAT = total, and VAT must match the stated rate. Anything that
does not reconcile is refused with the reason — never partially imported.
Validated by parsing all five existing invoices and comparing every field
against the hand-extracted JSON: identical, including the R24,205.13-VAT
column that a plain text read gets wrong. PHI 169 was then deleted and
re-ingested through the HTTP endpoint alone and came back field-for-field
identical, with no agent involved.
- "Add Invoice" drops a PDF in and imports it; lines are matched to plant
items by the code the invoice prints, and an unmatched line (the
container codes MPLANT04/MPLANT05) is left unassigned rather than guessed.
- A "Problem invoices" inbox lists every PDF that failed, with the reason,
and Retry re-reads the stored document and re-runs the parse. Retry
re-reports rather than claiming success, and only files imported data.
- invoice_intake collection records each attempt.
pdfjs-dist is in serverExternalPackages: bundled, it rewrites its own lazy
worker import to a .next path absent from the standalone image and every
parse dies with "Setting up fake worker failed". The parser also copies the
buffer before handing it over, since pdfjs transfers and detaches it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-02 12:30:17 +00:00
|
|
|
|
|
|
|
|
// pdfjs-dist dynamically imports its own worker module at runtime. When Next bundles
|
|
|
|
|
// it, that import is rewritten to a path inside .next that does not exist in the
|
|
|
|
|
// standalone image, and every parse dies with "Setting up fake worker failed".
|
|
|
|
|
// Keeping it external means it is required from node_modules at runtime and traced
|
|
|
|
|
// into the image, so the worker resolves next to the library that needs it.
|
|
|
|
|
serverExternalPackages: ["pdfjs-dist"],
|
2026-09-12 07:35:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const withPWA = withPWAInit({
|
|
|
|
|
dest: "public",
|
2026-09-12 08:24:26 +00:00
|
|
|
// Off in dev. next-pwa writes sw.js + workbox-*.js into `dest` on every
|
|
|
|
|
// compile, and the dev server watches public/ — so leaving it on makes each
|
|
|
|
|
// build trigger the next one, forever. A dev service worker also serves
|
|
|
|
|
// stale assets against HMR. Production builds are unaffected.
|
|
|
|
|
disable: process.env.NODE_ENV === "development",
|
2026-09-12 07:35:35 +00:00
|
|
|
cacheOnFrontEndNav: true,
|
|
|
|
|
aggressiveFrontEndNavCaching: true,
|
|
|
|
|
reloadOnOnline: true,
|
|
|
|
|
register: true,
|
|
|
|
|
workboxOptions: { cleanupOutdatedCaches: true, clientsClaim: true, skipWaiting: true },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export default withPWA(nextConfig);
|