Dashboard: PeriodPicker replaces year + quarter dropdowns. Defaults to
current month. YoY stat card now compares same range vs previous year.
Comparison: two independent PeriodPicker blocks (Period A and Period B).
Changing Period A auto-updates Period B to same period previous year,
but Period B remains freely editable.
Both pages use filterDataByDateRange; Filters type drops year/quarter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
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>
- 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>
- 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>
- Enable strict: true in tsconfig.json (was false)
- Add proper interfaces for all component props (Dashboard, Comparison, Slides)
- Add SlideConfig, ChartTypeOption, MetricOption types
- Type all function parameters, callbacks, and state variables
- Fix dynamic property access with proper keyof assertions
- 233 type errors resolved across 5 files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Lazy-load Dashboard, Comparison, Slides via React.lazy + Suspense
- Main bundle reduced from 606KB to 256KB
- Replace full-screen spinner with skeleton cards during load
- Skeleton used for both initial data fetch and route transitions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Convert all .js files to .tsx/.ts
- Add types for data structures (MuseumRecord, Metrics, etc.)
- Add type declarations for react-chartjs-2
- Configure tsconfig with relaxed strictness for gradual adoption
- All components now use TypeScript
- Rename Revenue to GrossRevenue, add NetRevenue (excl. VAT)
- Add VAT toggle (Incl/Excl) on Dashboard and Comparison pages
- Add offline mode with localStorage caching (24h validity)
- Add refresh button and offline indicator in nav
- Remove Google Sheets fallback (archived to dataService.legacy.js)
- Add AR/EN translations for new UI elements
- Move export button outside header-actions to enable absolute positioning
- Button now positioned at top-right (LTR) / top-left (RTL)
- Switch from Tajawal to IBM Plex Sans Arabic (slicker, more technical)
- Add RTL-specific font-family in CSS
- Restored Arabic/English translations from overhaul
- Restored LanguageContext for language switching
- Restored updated components with i18n support
- Kept original CSS from f17e19f (working desktop styling)
- Added missing CSS for new components:
- .page-title-with-actions
- .toggle-with-label
- .period-display-banner
Desktop and Arabic translations both working.
- Track touch position and show live drag offset while swiping
- Add rubber band effect at edges (25% resistance)
- Disable CSS transition during drag for instant response
- Improve accessibility with role/aria attributes
Added CSS rules that were missing after restoring from older commit:
- .page-title-with-actions (flex layout for title + toggle)
- .toggle-with-label (Labels On/Off toggle styling)
- .period-display-banner (Comparison page period display)
- Added mobile-only CSS improvements at end of file
- Enhanced touch targets (min 44px for accessibility)
- Improved carousel with real-time drag feedback and rubber band effect
- Better visual feedback on touch interactions
- Bottom nav active indicator
- Improved stat/chart cards spacing on mobile
- Period banner stacks vertically on mobile
- Table scroll hint with fade gradient
- Extra small screen (≤375px) optimizations
- All changes scoped to @media (max-width: 768px)
Features:
- Slides page: build presentations with configurable charts per slide
- Export slides as HTML zip
- Data source dropdown (Museums active, Coffees/eCommerce placeholders)
- Renamed app to 'HiHala Data' with 'Museums' as subtitle
Fixes:
- Cross-year period labels now show 'Nov 25–Jan 26' instead of misleading year
- Period display boxes updated to use smart labels
UI:
- New nav link for Slides
- Mobile nav updated
- Data source select styled to match brand
- Dashboard filters (year, district, museum, quarter) sync to URL
- Comparison filters (preset, dates, district, museum) sync to URL
- Users can now share links with specific filter configurations
- Reset filters also clears URL parameters
- Add useUrlState hook (unused but available for future use)
- Global data labels toggle in header (works on Dashboard & Comparison pages)
- Labels show formatted values (K/M suffix, max 2 decimals) with white pill background
- Capture Rate chart now shows pilgrims as curved line on right Y-axis
- Revenue Trends toggle moved to top-right corner of chart container
- Mobile: bottom navigation bar with Dashboard, Compare, Labels toggle
- Mobile: top nav simplified to brand only, bottom nav is thumb-friendly