# Email Analytics Portal (MVP)

Internal Next.js portal for email analytics with:

- Supabase Auth login + role-aware routing (`admin`, `affiliate`)
- Country-scoped dashboards for affiliates
- Admin user/country access management
- Excel upload preview + commit flow
- Parser that scans multi-sheet workbooks and normalizes headers
- Merge/upsert by `sent_email_name` (`Sent Email Name`) as unique event key
- Metadata management (friendly title, brand, notes, campaign group, thumbnail)
- Import history/review page with error summaries

## Stack

- Next.js App Router (v16-compatible)
- TypeScript
- Tailwind CSS
- Supabase (Postgres, Auth, Storage)
- Recharts
- SheetJS (`xlsx`)
- Zod
- React Hook Form

## Routes

- `/login`
- `/dashboard`
- `/upload`
- `/metadata`
- `/admin/users`
- `/admin/imports`
- `/emails/[id]`

## Environment Variables

Create `.env.local` from `.env.example`:

```bash
cp .env.example .env.local
```

Required:

- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`

Optional:

- `SUPABASE_SERVICE_ROLE_KEY` (required for admin create-user form and bootstrap script)
- `NEXT_PUBLIC_APP_URL` (defaults to `http://localhost:3000`)
- `TOP_RANKING_MIN_SENT` (defaults to `10`)
- `DISABLE_AUTH` (defaults to `true` in current MVP; set to `false` to re-enable login)
- `LOCAL_VIEWER_ROLE` (`admin` or `affiliate`, used when `DISABLE_AUTH=true`)
- `LOCAL_VIEWER_COUNTRIES` (comma list like `ES,PT`, used for affiliate local view)
- `LOCAL_ACCESS_PASSWORD` (defaults to `password`, used by local login screen when `DISABLE_AUTH=true`)
- `LOCAL_ACCESS_PASSWORD_ADMIN` (optional override for admin login)
- `LOCAL_ACCESS_PASSWORD_REGIONAL` (optional default for all regional logins)
- `LOCAL_ACCESS_PASSWORD_<REGION>` (optional per-region override, e.g. `LOCAL_ACCESS_PASSWORD_DACH`, `LOCAL_ACCESS_PASSWORD_OPIT`)

## Database Setup

Run the SQL migration:

- `supabase/migrations/0001_init.sql`

Then seed countries:

- `supabase/seed.sql`

You can run these in Supabase SQL Editor or via Supabase CLI migration flow.

### What the migration creates

- Tables:
  - `profiles`
  - `countries`
  - `user_country_access`
  - `imports`
  - `raw_email_events`
  - `email_metadata`
  - `email_event_metadata_overrides`
- Trigger to auto-create `profiles` row on `auth.users` insert
- Merge RPC: `merge_raw_email_events(p_import_id, p_rows jsonb)`
- Views:
  - `email_events_enriched`
  - `country_overall_metrics`
  - `monthly_country_metrics`
  - `document_country_metrics`
  - `top_emails_by_country`
  - `worst_emails_by_country`
- RLS policies for role + country scoping
- Storage buckets:
  - `imports` (private)
  - `thumbnails` (public)

## Local Run

```bash
npm install
npm run dev
```

Open: `http://localhost:3000`

### Local Affiliate/User View (without Supabase Auth)

When `DISABLE_AUTH=true`, local access uses `/login` with a temporary password gate:

- Default password fallback: `password`
- Admin password precedence:
  - `LOCAL_ACCESS_PASSWORD_ADMIN`
  - else `LOCAL_ACCESS_PASSWORD`
  - else `password`
- Regional password precedence:
  - `LOCAL_ACCESS_PASSWORD_<REGION>` (e.g. `LOCAL_ACCESS_PASSWORD_DACH`)
  - else `LOCAL_ACCESS_PASSWORD_REGIONAL`
  - else `LOCAL_ACCESS_PASSWORD`
  - else `password`
- Pick `Regional manager` + scope for no-admin-bar regional view
- Pick `Top-level manager (admin)` for admin scope switcher

### Dashboard Scopes (Region Groups)

Affiliate country access is validated against a single dashboard scope:

- `OPSA`: `ES, PT`
- `DACH`: `AT, DE, CH`
- `OPSAB`: `FI, SE, NO, NL, DK, BE, LU`
- `OPUK`: `GB`
- `OPIT`: `IT`
- `OPFS`: `FR`
- `OPEL`: `BG, HR, CY, CZ, EE, GR, HU, IE, LV, LT, MT, PL, RO, SK, SI`

## Bootstrap Script

Optional helper script to seed countries and promote admins by email:

```bash
ADMIN_EMAILS=\"admin@company.com\" npm run bootstrap
```

Requires `SUPABASE_SERVICE_ROLE_KEY`.

## Import Flow

1. Go to `/upload`
2. Choose `.xlsx` file
3. Click `Preview`
4. Review parsed rows/warnings/countries
5. Click `Commit Import`

Commit behavior:

- Creates an `imports` record for every uploaded file
- Uploads original file to `imports` storage bucket
- Parses and normalizes recognized columns by header names
- Ignores empty rows and unknown right-side junk columns
- For affiliates, blocks countries outside assigned access
- Dedupe-merge within file, then upsert via RPC keyed by `sent_email_name`
- On conflicts, newer uploads win:
  - incoming metrics overwrite existing event metrics
  - incoming timestamps overwrite existing timestamps when present
  - incoming country/document fields overwrite existing when non-empty
- Extracts `Brand` from either:
  - row-level data (if a `Brand` column exists), or
  - document summary blocks (`Approved Document Name` + `Brand`)
- Upserts extracted brands into `email_metadata` by `approved_document_name`

Performance notes:

- Local mode import merge is map-indexed for large files (faster conflict checks on `sent_email_name`)
- Supabase mode uses bulk merge RPC (`merge_raw_email_events`) rather than row-by-row SQL loops

## Header Normalization

Parser supports aliases and scans all sheets. It only accepts sheets with required headers:

- `Primary Country [**]`
- `Approved Document Name`
- `Sent Email Name`

This avoids accidentally parsing dashboard summary tabs.

## Metadata Model Tradeoff

MVP uses two metadata layers:

- `email_metadata` keyed by `approved_document_name` (document-level, editable by admins)
- `email_event_metadata_overrides` keyed by `sent_email_name` (event-level overrides, editable by admins and region-scoped affiliates)

Dashboard views already coalesce event overrides over document metadata, so future event-level editing can be added without refactoring reporting queries.

## Quality Checks Run

- `npm run lint`
- `npm run typecheck`
- `npm run build` (with env vars set)

## Notes

- Admin create-user UI uses Supabase Admin API and needs `SUPABASE_SERVICE_ROLE_KEY`.
- If affiliates have no countries assigned, dashboard/upload views show empty scoped states.
- Metadata Studio works in both Supabase mode and local mode; local thumbnails are saved under `public/local-thumbnails`.
