# HerbaCommunity Wellness Dashboard — Project Handoff Notes

> **Purpose of this file:** You are picking up an existing project in Claude Cowork (or Codex/Claude Code). This document is your full briefing. Read it top to bottom before touching code. The app already works as a front-end prototype; the next job is making it **live and persistent**.

---

## 1. What this app is

A daily-operations dashboard for **HerbaCommunity Kolhapur**, a Herbalife wellness center in Kolhapur, India. Members buy a **Gold Card** (30 scan-days) and visit daily to scan their card in exchange for a protein shake + afresh tea. Staff run the center from this dashboard on both **iPhone and desktop**.

Core daily flow: member walks in → staff scans their card (4-digit ID or manual) → attendance logged → shake served. When 30 scans are used, the card expires and must be recharged.

**The front-end is ONE file:** `herbalife-dashboard.html` (~9,800 lines, vanilla JS, no build step, no framework, no dependencies). Open it in any browser and it runs.

As of **03-08-2026** the app is a **multi-club network** — one head-quarter community can open clubs beneath it, those clubs can open clubs beneath them, and so on. See Section 5a and 6a. A Laravel backend and PWA layer have also been written (Sections 7a and 9).

> **Note on file location:** the working copy is `Documents\HerbaCommunity\herbalife-dashboard.html`. If you ever find a much smaller version there, it is a stale export — the real app has always been the large one. Check the line count before editing.

---

## 2. Current status — IMPORTANT

**This is a front-end prototype with NO backend.** Everything is hardcoded JavaScript arrays in the HTML file. Specifically:

- ❌ **No data persistence** — refresh the page and any new members/scans/recharges you added are GONE. Only the hardcoded demo data reloads.
- ❌ **No real authentication** — the PIN login is client-side only. Anyone who opens the file can bypass it by editing the JS.
- ❌ **No multi-device sync** — each device/browser has its own independent copy of the data.
- ❌ **No server** — nothing is saved anywhere outside the browser session.

**This means the app is currently good for: demos, testing, showing stakeholders.**
**It is NOT ready for: real center operations with real member data and money.**

The #1 next task is building a backend (PHP/MySQL was the agreed plan) so data persists and syncs. See Section 7.

---

## 3. Tech stack

| Layer | What's used |
|---|---|
| Structure | Single `.html` file |
| Styling | Inline CSS + CSS variables (`--green`, `--border`, etc.) in a `<style>` block |
| Logic | Vanilla JavaScript (no React/Vue/jQuery) |
| Fonts | Google Fonts: 'Outfit' (UI), 'JetBrains Mono' (numbers) |
| Data | Hardcoded JS arrays/objects (see Section 5) |
| Persistence | **NONE** (this is the gap to fill) |
| Backend | **NOT BUILT YET** — planned PHP/MySQL, skeleton zip exists but unwired |

No `npm`, no bundler, no transpiler. Edits are made directly to the HTML file. To test: open the file in a browser (or use a local static server).

---

## 4. Layout & navigation

**Mobile (width < 1024px):**
- Top bar: brand name + action buttons (Add Member / New Guest / Scan)
- Scrolling content pages
- Bottom nav with SVG icons: Home | Members | Guests | Trial | More
- "More" opens a hamburger menu (Reports, Transformations, Celebrations, Coaches, Referrals, Settings, Add, Guest)

**Desktop (width ≥ 1024px):**
- Green left sidebar (220px, injected via JS — `injectDesktopSidebar()`)
- Fixed top bar (54px)
- Content area, max-width 1440px

The desktop UI is built by JS at boot in `bootApp()` — it injects the sidebar and topbar, then hides the mobile bottom nav. Detection via `IS_DESKTOP` (checks `window.innerWidth`).

---

## 5. Data model (the hardcoded arrays)

All defined near the top of the `<script>` block. When you build the backend, these become database tables.

### `SETTINGS` (object) — center configuration
```js
{
  centerName, perDayPrice:100, goldCardPrice:3000, trialDays:4, trialPrice:300,
  giftEligibleDays:26, maxGraceDays:4, currency:'₹',
  priceHistory:[], batches:[], holidays:[],
  tiers:[ {name,min,max,color,bg}, ... ]   // 7 Ambassador tiers
}
```

### `M` (array) — Members. Each member:
```js
{
  id:'HBL-0001', name, ph (phone), coach, sc (scans used), mw, st (streak),
  b (batch), jn (join date ISO), inv (referrer's card id), isNew, dte (days left on card),
  bday, anniv, trans:{wt,sugar,bp,skin,back,other}, monthlyScans, grace, expired,
  why, health, currentWeight, targetWeight, notes, convertedFromGuest,
  isTrial, isPartial, cardMaxDays,
  migrated, cardStartDate, priorRecharges, recharges
}
```

### `GUESTS` (array) — walk-in visitors not yet members:
```js
{ guestId:'G-001', name, phone, dob, branch, invBy (referrer card id), why, notes,
  date (ISO), time, converted, convertedCardId, convertedDate,
  repeat, repeatOf:[guestIds], priorClubs:[branchIds] }
```
See Section 5a — `GUESTS` is a scoped view of `GUESTS_ALL`.

### `USERS` (array) — staff login accounts:
```js
{ id:'u1', name, role, pin:'123456', phone, coachLink, active }
```
Roles: `admin` / `manager` / `coach` / `frontdesk`. Permissions enforced via `can(action)` and `applyRoleUI()`.

**Seeded demo users & PINs (CHANGE THESE BEFORE GOING LIVE):**
| Role | PIN |
|---|---|
| Admin | 123456 |
| Manager | 654321 |
| Coach Priya | 111111 |
| Coach Anita | 222222 |
| Coach Rahul | 333333 |
| Coach Suresh | 444444 |
| Front Desk | 000000 |

### Other arrays
- `COACHES` / `COACH_REGISTRY` — coach list + detailed profiles
- `SLOG` — today's scan log
- `ATT_HIST` — attendance history `{memberId: [{d:date, t:time}]}`
- `GRACE_LOG` — grace-day usage
- `COLLECTION_LOG` — every recharge/payment (revenue tracking)
- `ACTIVITY_LOG` — audit trail (max 500 entries, in-memory)
- `DEMO` — demo members flagged so they can be excluded from real reports

### Referral & tier tracking (recently added)
- `REFERRAL_REWARDS` — fuel rewards from referrals `[{ts, date, referrerId, referrerName, referrerType, referredMemberId, referredMemberName, conversionType, fuelAwarded, appliedTo, status, note}]`
- `COACH_BANK` — `{coachName: fuelOwed}` — fuel banked for coaches
- `REWARD_LEDGER` — `{guestId: {trial:bool, gold:bool}}` — idempotency guard
- `AMBASSADOR_EVENTS` — every gold-card event for tier calc `[{ts, month:'YYYY-MM', type:'new'|'recharge', memberId, memberName, coachName, referrerId, referrerName}]`

---

## 5a. Club network data model (the pyramid)

### `BRANCHES` (array) — every club/community in the network
```js
{ id:'HC-ICH',            // also the club code — unique
  name:'HerbaCommunity Ichalkaranji',
  parent:'HC-KOP',        // null for the root/HQ — this is what forms the pyramid
  city, owner, phone, since,
  block:4000, blockEnd:4499,   // reserved HBL-XXXX card range so IDs never clash
  active:true, icon:'🏢' }
```

Tree helpers: `gB(id)`, `childBranches(id)`, `descendantIds(id)` (includes self, recursive),
`branchDepth(id)`, `branchPath(id)`, `isDescendantOf(id, ancestorId)`, `lvlStyle(id)` (HQ / L1 / L2 / L3 colour badge).

### Scoping — how `M` and `GUESTS` now work

This is the key change. Two globals decide what the whole app sees:

| Global | Meaning |
|---|---|
| `ACTIVE_BRANCH` | the club you are operating as |
| `SCOPE_MODE` | `'own'` = this club only · `'net'` = this club **+ its entire downline** |

- `M_ALL` / `GUESTS_ALL` — the master arrays holding **every** member/guest in the network.
- `M` / `GUESTS` — the **scoped view**. `applyScope()` empties them and refills them in place from the masters.

Because the arrays are refilled *in place* (`M.length=0; M.push(...)`), every pre-existing screen kept working untouched — they still read `M`, they just see a different slice. **When you add data, push to `M_ALL` / `GUESTS_ALL` and then call `applyScope()`** — never push to `M` / `GUESTS` directly.

`gM(id)` deliberately searches `M_ALL`, so a referrer or a scanned card from another club still resolves.

Members gained `branch` and `dobFull`. Guests gained `branch`, `dob`, `repeat`, `repeatOf[]`, `priorClubs[]`.

`branchStats(id, deep)` returns the rollup for one club — `deep:true` walks the whole downline.
`nextCardId(branchId)` allocates from that club's reserved block.

Demo downline is generated by `seedNetwork()` at boot — deterministic (its own LCG, no `Math.random`), idempotent, and it seeds deliberate cross-club repeat guests so the duplicate alert can be demonstrated.

---

## 6. Features built (all working in the prototype)

### 6a. Multi-club network — pyramid structure
- **Network page** (🏢 in the nav) — hero card for the active club with own vs. downline totals, then the **downline pyramid**: an indented tree with connector rails, one node per club showing members / served today / guests / conversion % / gift-eligible, a relative-size bar, and expand-collapse on any branch.
- **Club performance leaderboard** — re-sortable by members, served today, guests, conversion %, or gift eligible.
- **Branch drill** — tap any club for its own-vs-downline breakdown, its direct child clubs, and its latest guest experiences. "Operate as this club" switches into it.
- **Club switcher** in the top bar — pick any club, and toggle *This Club Only* vs *Include All Downline*. Every dashboard KPI, member list, report and export repaints for the new scope (`refreshAll()` → `syncKPIs()`).
- **Open New Club** — creates a club under any club in your downline, validates a unique club code, and auto-reserves the next free card-number block.
- Dashboard KPIs are now **computed** from the current scope (they used to be hardcoded HTML numbers).
- Printed reports carry the club name and the scope they were run at.
- Scanning a card that belongs to another club now names that club instead of saying "not found".

### 6b. Guest experience — network-wide repeat detection
The guest experience is a one-time offer **across the whole network**, so guest entry now captures **name + mobile + date of birth** and checks every club before saving.

- Matching (`samePerson`): same 10-digit mobile → match; or same normalised name **and** same DOB → match (catches people who give a different number).
- `findGuestHistory(person)` searches `GUESTS_ALL` **and** `M_ALL` — never scoped, that is the whole point.
- `guestDupCheck()` fires **live** as staff type. If the person has been here before, a red/amber panel appears above the form listing every prior record: **which club, what date, what time**, guest ID, DOB, reason for visit, and whether they converted.
- If they are already an enrolled **member** anywhere, the alert turns red — "already a member in the network" with their card ID and home club.
- Saving requires an explicit confirm, and the record is stored with `repeat:true` plus the clubs they visited before.
- Repeat guests are badged 🔁 in Pending Guests and in the new **Network Guest Registry** (searchable by mobile / name / DOB) on the Network page.

---

### Core operations
- **Scan/attendance** — 4-digit card ID or manual entry (no real camera QR yet)
- **Add Member** — 3-step wizard (Identity → Coach & Health → Goals & Notes) with per-step validation
- **Guest entry** — log walk-ins, later convert to trial or gold member
- **Recharge** — Full (Gold, 30 days) / Partial (custom days) / Trial (4 days)
- **Grace days** — allow a few extra visits past expiry before recharge (max configurable)
- **CSV bulk import** — migrate existing members (3-step modal, template download, robust parser)

### Auth (Phase 1 — client-side only, NOT production-secure)
- Login overlay, 6-digit PIN numpad, Remember Me (30 days), 10-min auto-lock
- 4 roles with permission gating
- Wrong-PIN lockout (5 attempts → 30s), desktop keyboard support
- Preview Mode banner

### Reports (Reports page)
- **Daily Report** — collection/revenue, serves, members, guests, recharges + full detail
- **Gift Eligible** — members with 26+ scan days this month (attendance % capped at 100)
- **Ambassador Tier Achievers** — monthly, with month selector + CSV download (UTF-8 BOM, clean format)
- Export buttons: Gift Report, Attendance, Irregular, All Members, Body Analysis, Grace Log (open print → save as PDF, A4 landscape)

### Referral Fuel Rewards system
- Member refers someone who converts → member earns "fuel" (extra card days)
- Trial conversion = +1 fuel; Gold = +3 fuel; path-based (trial→gold = 4 total)
- Fallback chain: active referrer → referrer's coach bank → new member's coach bank → forfeit
- Idempotent (each guest triggers each reward type once)
- Referrals page has 2 tabs: **Network** and **Fuel Rewards** (summary KPIs, coach bank, reward log)

### Ambassador Tier system (monthly recognition)
- Members earn tiers from **referrals**; coaches earn from **their members' gold activity**
- Only gold-card events count (new + recharge); NOT trial/partial; own recharge doesn't count for self
- Counter resets on the 1st (computed live from event timestamps)
- 7 default tiers, fully editable in **Settings → Tiers** (name, min, max, add/remove)
- Badges shown on member drill modal + coach registry cards
- Demo data seeded at boot (`seedDemoReports()`) so reports show realistic content — idempotent, becomes a no-op once real data exists

### Settings (tabbed)
General / Pricing / Batches / Holidays / Coaches / **Tiers** / Users / Activity
- User Management: full modal (role cards, coach link, PIN reset, active toggle, delete safeguards)
- Activity Log viewer (filter, colored chips, newest first)

### Other polish
- All dates display as **DD-MM-YYYY** (helpers: `fmtDate`, `fmtDateLong`, `fmtDateFull`, `fmtDateShort`, `_parseDate`)
- Click anywhere on a date input opens the calendar (global handler + `showPicker()`)
- Celebrations (birthdays/anniversaries) with WhatsApp "Send Wish" links
- Transformations tracking

---

## 7. The deployment plan (what to do next)

The agreed approach was **PHP + MySQL on Hostinger** with an 8-digit hybrid security model (daily-use PIN + admin password for sensitive actions).

### Reality check
Going from this prototype to a true live system is a **multi-day job (3–5 focused days)**, not a one-day flip. The main work is rewriting how the app loads/saves data: replacing every hardcoded array with `fetch()` calls to a real API. That's a substantial change touching most features.

### Two paths

**Path A — Quick live demo (≈1 hour)**
Just host the HTML file so it has a URL accessible on any device. Data still won't persist or sync. Good for showing people. Any static host works (Hostinger, Netlify, GitHub Pages, Vercel).

**Path B — Production-ready (the real goal, multi-day)**
Build the backend in 5 chunks, testing each before moving on:
1. `schema.sql` + `auth.php` + `db.php` + `config.php.example` + `.htaccess` + `setup.md`
2. API **read** endpoints (get members, guests, reports, settings)
3. API **write** endpoints (members, scans, recharges, BFA, guests, bulk import)
4. Settings + admin endpoints
5. Hardening (input validation, prepared statements, session security, HTTPS) + deployment package

### What the network model adds to the backend job
- `branches` table with a self-referencing `parent_id`. Rollups are recursive — either a MySQL 8 recursive CTE, or store a materialised `path` column (`HC-KOP/HC-ICH/HC-JAY`) and query with `LIKE 'path%'`.
- `members` and `guests` both need a `branch_id` FK; every read endpoint must filter by the caller's club **and its downline** — that authorisation check belongs on the server, never in the client.
- The guest-duplicate lookup must be a **network-wide** query (index on `phone`, and on `name + dob`) that deliberately ignores branch scoping.
- Card IDs: keep the per-branch reserved block, or switch to a DB sequence. Blocks are 500 wide today, so a club that ever exceeds 500 cards needs a second block.

### Prerequisites before Path B
- [ ] Sign up for Hostinger (hosting + MySQL database) — **not done yet**
- [ ] Choose a domain / app URL — **TBD**
- [ ] Decide backup strategy for member data

### Security must-dos before real use
- Change all default PINs
- Move authentication server-side (never trust the client)
- Use prepared statements everywhere (prevent SQL injection)
- Serve over HTTPS
- Never expose `config.php` (DB credentials) — `.htaccess` protection
- Test payment/revenue math thoroughly before trusting it with real money

---

## 8. How to work on this in Cowork

1. Keep `herbalife-dashboard.html` and this `PROJECT_NOTES.md` in the same folder (e.g. `Documents/HerbaCommunity/`).
2. In Claude Desktop, switch to **Cowork** mode and point it at that folder.
3. Good opening prompt:
   > "Read PROJECT_NOTES.md first. This is a single-file wellness-center dashboard with no backend — all data is hardcoded JS. I want to [deploy a demo / build the PHP+MySQL backend]. Start by [confirming the plan / setting up hosting]."
4. Cowork can edit the file directly and drive a browser to test — use that to preview changes and, later, to upload to hosting.

### Working style that has worked well on this project
- **One focused change at a time**, tested before moving on.
- **After every code change, run a sanity test** — this project has been validated by extracting the `<script>`, mocking `document`/`window`/`localStorage` in Node, and asserting behavior (dates format right, no duplicate function names, exports produce clean CSV, etc.). Keep doing this; it catches regressions fast.
- **Check for duplicate function definitions** after edits (a real risk in an 8,000-line single file).
- Preserve the **z-index hierarchy** when touching modals/overlays (login 100000, lock 99999, report viewer 99999, hamburger 10001, edit member 800, BFA 700–850, sidebar 500, drill 400–600, modals 200, topbar/nav 40).

---

## 9. Known gaps / deferred features

*Audited against the code on 03-08-2026. Everything in Section 6 was verified
present; the list below is what genuinely remains.*

**Now built (was a gap):**
- ✅ **PWA** — manifest, service worker with offline write queue, 6 icons,
  offline page, install prompt (incl. the iOS Add-to-Home-Screen hint).
  Assets in `pwa\`; the registration is wired into the HTML. Requires the
  files to sit at the web root, and HTTPS (localhost excepted).
- ✅ **Backend** — Laravel API written (Sections 7a / `api\INTEGRATION.md`).
  Not yet executed.

**Still missing:**
- **Real QR camera scanning** — still 4-digit numpad / manual only. Needs
  `getUserMedia` + `BarcodeDetector` (or a JS decoder for iOS Safari, which
  lacks `BarcodeDetector`). Camera access requires HTTPS.
- **WhatsApp automation** — manual `wa.me` links work today. Auto-send needs the
  WhatsApp Business API and a BSP account; it cannot be done from the browser.
- **Auto-reminders** (missed scans, trial expiring) — now straightforward with
  the Laravel scheduler (`app/Console`), but not written.
- **Editable health conditions / transformation categories** — you asked for
  these to be editable in Settings like Tiers. Still hardcoded: the health
  chips are literal markup inside `#healthChips`, and `TRANS_CATS` is a `const`
  whose entries carry filter **functions**, so making it editable means
  replacing those closures with declarative rules the UI can build.
- **About / Version screen** — no version constant exists anywhere in the app.
  Worth adding before real deployment so you can tell which build a centre is
  running when something is reported.

---

## 10. File inventory

*72 files, ~820 KB. The folder is self-contained — no absolute paths, nothing
outside it. Copy the whole thing and everything works.*

| Path | What it is | Status |
|---|---|---|
| `herbalife-dashboard.html` | The entire front-end (~9,800 lines) | **Working, tested** |
| `PROJECT_NOTES.md` | This handoff file | — |
| `FEATURES.md` | **Full feature & functionality reference** — what every feature does, and how far each can be trusted | — |
| `SETUP.md` | Install + run book (Linux and Windows) | — |
| `api\` | Laravel backend: 10 migrations (21 tables), 19 models, 3 policies, `BranchScope`, 2 services, 6 controllers, 2 seeders, routes | **Written, NEVER executed** |
| `api\INTEGRATION.md` | How to drop `api\` into a fresh Laravel skeleton | — |
| `pwa\` | manifest, service worker, offline page, 6 PNG icons | **Written; registration is wired into the HTML** |
| `tests\` | 8 Node test suites + shared DOM harness + `run-all.js` | **8/8 passing** |

> The old `HerbaCommunity_Server_Package.zip` referenced in earlier revisions is
> superseded by `api\` and is no longer part of the project.

### First commands on the new machine

```bash
cd <HerbaCommunity>/tests && node run-all.js     # confirm nothing broke in transit
```

Then follow `SETUP.md` from section 2. The first genuinely new information will
come from `php artisan migrate` — that is the first time any of the PHP has run.

---

## 11. Quick glossary

- **Fuel** = extra scan-days added to a member's card as a referral reward (1 fuel = 1 day)
- **Gold Card** = the standard 30-scan-day membership card
- **Trial Card** = short intro card (default 4 days)
- **Partial Card** = custom-length card (priced per day)
- **Grace day** = an allowed extra visit after the card expires, before recharge
- **Gift eligible** = member who scanned 26+ days this month (qualifies for a monthly gift)
- **Ambassador tier** = monthly recognition badge based on gold-card events brought in
- **BFA** = Body Fat Analysis (health measurement record)
- **Batch** = a daily time slot (morning/evening session)

---

*End of handoff. When in doubt, read the code — it's all in one file and heavily structured with section comment banners (`// ═══ SECTION ═══`).*
