App for the Mega Infrastructure AI Integration programme (A000) and Mega Plant (B110). 100 activities across 2 programmes, 06 Oct 2026 - 31 Aug 2027, parsed from the client PDF and frozen as an immutable baseline. Views: Dashboard, Timeline (month grid + week grid matching the PDF's Friday-anchored week numbering), Todo list, WBS, Data. Notes on the source data: - Hierarchy is derived from the PDF's indentation, not the activity code. Codes do not sort structurally (A490 sits under Workforce, A510 under Financials), so a code-ordered import would produce a wrong tree. - The schedule is built from the dates, not the PDF's "Durn" column, which disagrees with its own dates for 13 of the 100 activities. - Spec issues found in the PDF are surfaced in-app under Data: 2 unresolved "?" placeholders, 6 activities sharing one stretched 6-week window while declared 15 days, 13 duration mismatches, 17 spelling corrections (original wording preserved and viewable). Progress (status, % complete, note) is stored behind a pluggable adapter so the site can be static today and point at a shared JSON store later without touching the UI.
136 lines
6.7 KiB
Markdown
136 lines
6.7 KiB
Markdown
# Mega Infrastructure — Project Tracker
|
|
|
|
A project tracker seeded from `Mega Infrastructure AI intergration system.pdf` (a Candy 2.05p / P6
|
|
barchart printed 30 Sep 2026). **100 activities, 2 programmes, 06 Oct 2026 → 31 Aug 2027.**
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # http://localhost:4004
|
|
npm run build # -> dist/ (static, drop on any host)
|
|
```
|
|
|
|
Port **4004** on purpose — 3000 is taken by another local app.
|
|
|
|
## What's in it
|
|
|
|
| View | What it does |
|
|
|---|---|
|
|
| **Dashboard** | Weighted % complete, counts by status, overdue, starting-this-week, workstream health, build-load-by-month chart |
|
|
| **Timeline** | Collapsible WBS Gantt. **Month** grid for reading, **Week** grid matches the PDF (Friday-anchored, the SA convention Candy uses). Today-line, status colours, progress fill. |
|
|
| **Todo list** | The 78 deliverables as a checklist. Filter by window (open / next 7d / next 30d / in-flight / overdue / unconfirmed), status, workstream, free text. Group by workstream, status or start month. Inline status + % slider. |
|
|
| **WBS** | Full 100-row hierarchy matching the PDF, with rollup % and status. Toggle corrected names ↔ original PDF wording. |
|
|
| **Data** | Source provenance, sharing config, export/import, and the spec issues found in the PDF. |
|
|
|
|
Click any activity (bar, row or todo) to open the edit drawer: status, % complete, note, plus the
|
|
plan facts and any flags attached to that activity.
|
|
|
|
## Appearance
|
|
|
|
Dark/light mode plus four accent colours, set from the topbar.
|
|
|
|
- **Mode** — sun/moon toggle, **dark by default**. An explicit choice sticks from then on.
|
|
- **Accent** — Blue, Violet, Emerald, Amber. Each has a lighter variant tuned for dark mode
|
|
(`#2563eb` light / `#60a5fa` dark, and so on) so contrast holds in both.
|
|
|
|
Both persist to `localStorage`. An inline script in `index.html` resolves the theme *before* first
|
|
paint, so there's no flash of the wrong colours on reload — it mirrors `src/lib/appearance.ts`
|
|
(`DEFAULT_MODE` / `DEFAULT_ACCENT`), so keep the two in sync if you change the defaults or the key names.
|
|
|
|
All colour is token-driven from the top of `src/styles.css`. To add a fifth accent, add one entry
|
|
to `ACCENTS` in `src/lib/appearance.ts`, a `[data-accent="..."]` rule and its
|
|
`[data-theme="dark"][data-accent="..."]` counterpart in `styles.css`, and the value to the inline
|
|
script's whitelist.
|
|
|
|
|
|
## Deploying to Coolify
|
|
|
|
```bash
|
|
docker build -t mega-tracker .
|
|
```
|
|
|
|
Multi-stage: `node:22-alpine` builds, `nginx:alpine` serves the static output. Final image is nginx
|
|
plus ~215 KB of files — no Node runtime, no `node_modules`.
|
|
|
|
In Coolify: **New Resource → Dockerfile**, point it at this repo/directory. It finds the Dockerfile
|
|
automatically.
|
|
|
|
- **Port**: `80` (the Dockerfile `EXPOSE`s it; Coolify/Traefik routes to it)
|
|
- **Build args**: none needed
|
|
- **Domain**: set it on the resource; Coolify generates the Traefik route and TLS for you
|
|
- **Health check**: baked into the image (`wget --spider` every 30s)
|
|
|
|
**Use a subdomain, not a path.** The build emits absolute asset URLs (`/assets/…`). That is correct
|
|
for a dedicated hostname like `tracker.mega.co.za`, and wrong for a path mount like
|
|
`example.com/mega/`, which would 404 on every asset. If you ever need a subpath, set
|
|
`base: '/mega/'` in `vite.config.ts` and rebuild.
|
|
|
|
**Caching is deliberate.** Hashed assets under `/assets/` get `max-age=31536000, immutable`; the
|
|
app shell gets `no-store`. A new deploy emits new asset filenames, so this is always correct, and it
|
|
prevents the classic "the update didn't work" browser cache bug.
|
|
|
|
### If you ever need build-time config
|
|
|
|
Vite inlines `VITE_*` variables into the bundle at **build** time, so they must be set as build
|
|
arguments in Coolify, not runtime environment variables. There is currently nothing to configure —
|
|
no API base URL, no feature flags. If you add the shared progress store (below) and need a default
|
|
endpoint, add a `--build-arg VITE_PROGRESS_URL=…` and read it via `import.meta.env`.
|
|
|
|
## Data model
|
|
|
|
The plan (codes, names, dates, hierarchy) is baked into `src/data/baseline.ts` and is **never
|
|
edited by the app**. Only progress is recorded: status, % complete, and a note, keyed by activity
|
|
code.
|
|
|
|
Parent rollups are duration-weighted from the children, using `spanDays` — the working days the
|
|
activity's own dates actually span — rather than the PDF's `Durn` column, which disagrees with the
|
|
dates for 13 of the 100 activities.
|
|
|
|
## Storage, and the honest limitation
|
|
|
|
Progress defaults to `localStorage`. **That means a static deploy shows the project owner the plan
|
|
but not your progress** — the plan is in the bundle, the progress is in your browser. The owner
|
|
would see every activity at 0%.
|
|
|
|
To share, open **Data → Share progress with the project owner** and point it at any endpoint that
|
|
accepts `GET` and `PUT` of a JSON body. A single-row PocketBase record works and needs no schema
|
|
knowledge in the app:
|
|
|
|
```
|
|
GET https://your-host/api/collections/tracker_progress/records/<RECORD_ID>
|
|
PATCH https://your-host/api/collections/tracker_progress/records/<RECORD_ID>
|
|
```
|
|
|
|
The adapter is endpoint-agnostic (`src/lib/storage.ts` → `remoteBlobAdapter`), so it also works
|
|
against a tiny hand-rolled endpoint or a KV store. The auth token is kept in the browser only and
|
|
is never bundled into the deployed files.
|
|
|
|
Export/import JSON works regardless of adapter, so you always have a portable backup.
|
|
|
|
## Spec issues found in the PDF
|
|
|
|
Flagged in-app under **Data → Spec issues**, and derived at runtime from the baseline rather than
|
|
hardcoded:
|
|
|
|
- **2 activities still contain a `?`** — `B220 Payments ?`, `B340 Per Bank?`. Unfinished spec text.
|
|
- **6 activities share one identical 6-week window** (`A490, A140, A180, A220, A260, A320`, all
|
|
08 Dec 26 → 19 Jan 27) while each is declared 15 working days but occupies 31. Reads as a bulk
|
|
edit or a scheduling-constraint artefact in Candy, not a plan.
|
|
- **13 activities** have a duration that disagrees with their own dates.
|
|
- **17 spelling corrections** applied for display; the original wording is preserved and viewable
|
|
in the WBS tab.
|
|
- `A000` and `A010` cover the identical window — worth confirming whether "Site Dashboard" is a
|
|
module or the whole programme.
|
|
- The plan is heavily front-loaded: 33 of 78 deliverables start in the first 8 weeks, peaking at
|
|
10 concurrent in the week of 13 Oct 2026.
|
|
|
|
## Regenerating from a new PDF
|
|
|
|
```bash
|
|
pdftotext -layout "new-plan.pdf" /tmp/mega.txt
|
|
python3 scripts/parse_pdf.py # -> /tmp/mega_acts.json
|
|
python3 scripts/gen_baseline.py # -> src/data/baseline.ts
|
|
```
|
|
|
|
**Hierarchy is taken from the PDF's indentation, not the activity code.** Codes do not sort
|
|
structurally — `A490` sits under Workforce, `A510` under Financials, `A580`/`A590` under Health &
|
|
Safety. Re-importing from a spreadsheet that sorts by code will produce a wrong tree.
|