- PATCH /api/users/:id route to update user permissions
- Auth session stores and returns allowedMuseums/allowedChannels
- User type gains AllowedMuseums/AllowedChannels (JSON string fields)
- parseAllowed() with fail-closed semantics (empty string → null → no data)
- Dashboard/Comparison apply permission base filter before user filters
- Filter dropdowns (museums, channels, years, districts) derived from
permission-filtered data — restricted users only see their allowed options
- Settings UserRow component with inline checkbox pickers for access config
- Access badges in users table showing current restriction summary
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update deploy.yml to rsync server/, install deps, write .env from
Gitea secrets, and restart hihala-dashboard.service
- Move tsx to regular dependencies for production use
- Remove unused SESSION_SECRET from config
- Accept PORT env var as fallback for SERVER_PORT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Server checks PIN against env (super admin) + NocoDB Users table
- Session stores name + role (admin/viewer)
- Admin: sees Settings page (seasons + users management)
- Viewer: sees Dashboard + Comparison only, no Settings
- Users CRUD on Settings page: add name + PIN + role, delete
- Settings link + nav hidden for non-admin users
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Server: POST /auth/login (verify PIN, set httpOnly cookie)
- Server: GET /auth/check, POST /auth/logout
- Client: Login page shown when not authenticated
- Session persists 7 days via httpOnly cookie
- PIN stored server-side only (ADMIN_PIN env var)
- Dashboard loads data only after successful auth
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The catch-all /api proxy was swallowing /api/seasons requests before
the specific proxy rule could match. Narrowing to /api/v2 fixes this
since all NocoDB REST calls use /api/v2/ paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Seasons that overlap the current comparison period appear as
colored bands on the Revenue Trend chart, same as Dashboard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dashboard:
- Season dropdown filter (filters data by season date range)
- Revenue trend chart shows colored annotation bands for each season
- All downstream memos use season-filtered data
Comparison:
- Season presets in period selector (optgroup)
- Auto-compares with same season from previous hijri year if defined
- Season preset persists start/end dates in URL
Added chartjs-plugin-annotation for chart bands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Server: seasons CRUD routes + generic NocoDB helpers
- Client: Settings page at /settings with inline add/edit/delete
- Seasons stored in NocoDB Seasons table
- Vite proxy: /api/seasons routed to Express server
- Nav links added (desktop + mobile)
- Locale keys for EN + AR
- Seasons loaded non-blocking on app mount
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Visitors by Event and Revenue by Event are now horizontal bar charts
- Both always visible (no longer hidden when events are filtered)
- Free attractions (Trail To Hira Cave, Makkah Greets Us) now visible
- Removed Doughnut chart and unused import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New MultiSelect component with checkbox dropdown
- Event and channel filters now accept multiple selections
- Empty array = all selected (no filter applied)
- URL params store selections as comma-separated values
- District and quarter remain single-select
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
B2C generates one PDF ticket per person, so UnitQuantity = visitors.
Other channels (POS, Safiyyah POS, etc.) use PeopleCount for visitors
since group tickets cover multiple people.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ETL writes District column to NocoDB DailySales
- Museums mapped: Hiraa (Revelation, Holy Quraan, Trail, Makkah, VIP)
AsSaffiyah (Creation Story, Best of Creation)
- District filter added to Dashboard and Comparison (cascades to museum)
- District Performance chart added (desktop + mobile)
- Locale keys added for both EN and AR
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Combo tickets (matching multiple museums) split revenue/visits evenly
- Each museum gets its own row tagged with TicketType=combo, ComboWith
- Added Best of Creation (متحف خير الخلق) to museum mapping
- Holy Quraan Museum now shows 3.3M total (was 971K without combo share)
- ComboMuseums column tracks split factor for auditing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove Slides route, import, and mobile nav link from App.tsx
- Remove Salla route mounting and console output from server
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ERP API can't handle concurrent requests — switch from batched
parallel (4 at a time) to sequential fetching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace NocoDB museum data (Districts/Museums/DailyStats) with ERP API
- Client fetches via server proxy (/api/erp/sales) — no credentials in browser
- Aggregate transaction-level ERP data into daily/museum/channel records
- Replace "district" dimension with "channel" (B2C/HiHala, POS, B2B, etc.)
- Add product-to-museum mapping (46 products → 6 museums)
- NocoDB retained only for PilgrimStats
- Remove old server/index.js (replaced by modular TS in server/src/)
- Update all components, types, and locale files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Preserves current NocoDB-based state before switching museum
sales data source to the Hono ERP API.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default to light theme instead of system preference, and update
dashboard subtitle to reflect VivaTicket as the data source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Stat cards lift on hover (translateY -2px + shadow elevation)
- Metric cards lift on hover
- Chart cards fade-up with staggered delays on mount
- All animations respect prefers-reduced-motion (already in place)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add prefers-color-scheme: dark media query for automatic dark mode
- Add data-theme attribute for manual override (persisted to localStorage)
- 3-state cycle: system → dark → light → system
- Theme toggle button in nav with contextual icon (sun/moon/half)
- Dark palette: slate-900 bg, slate-800 surfaces, adjusted text/accent/success/danger
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add aria-labels to icon-only buttons (refresh, language toggle)
- Add aria-hidden to decorative SVGs
- Add aria-label to data source select
- Replace outline:none with visible focus rings on all inputs/selects
- Add <main> landmark for screen reader navigation
- Add prefers-reduced-motion: disable all animations for vestibular safety
- Move error message inline style to CSS class
- Add aria-label to both nav landmarks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>