SRM is a robust Field Incident Management System designed for ONEE (Office National de l'Electricité et de l'Eau Potable — Morocco). It empowers field agents to report and track electrical infrastructure incidents (BT/MT) in real-time, even in areas with limited connectivity, thanks to its offline-first synchronization engine.
- 📱 Native Mobile Experience — Built with Expo SDK 54, React 19, and the New Architecture for smooth, high-performance rendering.
- 🔄 Offline-First Synchronization — Local incident reporting via SQLite. Data syncs to Supabase when connectivity is restored.
- 🛠️ Structured Incident Reporting — Step-by-step wizard with dynamic sub-type pickers for 16 BT and 12 MT incident categories (e.g. Manque phase, Câble conducteur cisaillé, Transformateur avarié).
- 🎙️ Voice-to-Incident (AI) — Record audio in Darija/French; a Supabase Edge Function powered by Gemini 3 Flash transcribes and structures the data automatically.
- 🖼️ Image Attachments — Capture photos, auto-compress to JPEG (1200px, 70%), and queue for upload to Supabase Storage (
incident-mediabucket). Images are stored locally when offline and uploaded during the next sync window. - 📍 Location Tracking — Automatic capture of GPS coordinates (latitude/longitude) at incident creation.
- 🔐 Secure Authentication — Email/Password and OTP (Magic Link) login via Supabase Auth with
expo-secure-storefor token persistence.
- 📊 Live Dashboard — KPI cards (Total / En Cours / Résolus / Réclamations), 30-day resolution performance metrics (average and max), and a monthly incidents bar chart.
- 🔍 Advanced Incident Management — Full-text search, filterable by status, network type (BT/MT), commune, date range, and reclamation flag. Infinite-scroll list with detail modals showing reporter info, equipment, media attachments, and status toggle.
- 👥 Team Management — View all registered users with emails, toggle roles (field ↔ admin), create new accounts directly (without signing out the admin), and delete users with confirmation.
- ⚙️ Profile & Settings — Admin profile screen with sign-out functionality.
- 🛡️ Route Protection — Server-side role verification in the admin layout; non-admins are redirected immediately.
| Layer | Technology | Version |
|---|---|---|
| Framework | Expo | SDK 54 |
| Runtime | React Native | 0.81.5 (New Architecture) |
| UI Library | React | 19.1.0 |
| Navigation | Expo Router | v6 (File-based, Typed Routes) |
| Backend | Supabase | Auth, PostgreSQL, Storage, Edge Functions |
| Local DB | expo-sqlite | Offline-first persistence |
| Styling | NativeWind + StyleSheet.create |
v4 (Tailwind CSS for RN) |
| Validation | Zod | v4 — Runtime boundary validation |
| Animations | React Native Reanimated | v4 |
| Charts | React Native Gifted Charts | v1.4 |
| List Performance | @shopify/flash-list | v2 |
SRM follows a hybrid offline-first architecture to ensure reliability in the field:
graph TD
User["Field Agent 📱"] --> App["SRM App (Expo)"]
App --> SQLite["Local SQLite DB"]
SQLite --> Sync["Sync Engine (lib/sync.ts)"]
Sync <--> Supabase["Supabase Cloud ☁️"]
App --> ImageQueue["Pending Uploads Queue"]
ImageQueue --> Storage["Supabase Storage (S3)"]
Admin["Administrator 🛡️"] --> AdminUI["Admin Dashboard (Tabs)"]
AdminUI <--> Supabase
VoiceEdge["Edge Function (Gemini 3)"] <--> Supabase
SRM/
├── app/ # Expo Router screens
│ ├── (admin)/ # Admin dashboard (Tabs layout)
│ │ ├── _layout.tsx # Tab navigator + route guard
│ │ ├── dashboard.tsx # KPIs, charts, latest incidents
│ │ ├── incidents.tsx # Search, filter, manage all incidents
│ │ ├── users.tsx # Team management (CRUD)
│ │ └── profile.tsx # Admin settings & sign-out
│ ├── (auth)/ # Authentication screens
│ │ ├── login.tsx # Email/Password login
│ │ ├── signup.tsx # Registration with profile creation
│ │ └── magic-link.tsx # OTP passwordless login
│ └── (tabs)/ # Field agent core screens
│ ├── home.tsx # Incident feed
│ ├── create-incident-new.tsx # Multi-step incident wizard
│ └── profile.tsx # Agent profile + admin access
├── components/ # Reusable UI components
│ ├── AdminTabBar.tsx # Custom bottom tab bar (accent themed)
│ └── CustomBarChart.tsx # Styled bar chart wrapper
├── src/core/ # Business logic layer
│ ├── constants/
│ │ └── incidentTypes.ts # BT (16 types) & MT (12 types) definitions
│ ├── entities/
│ │ └── admin.ts # Zod schemas for admin domain
│ └── services/
│ ├── adminService.ts # Dashboard stats, charts, resolution KPIs
│ ├── incidentAdminService.ts # Filtered queries, status mutations
│ └── userAdminService.ts # User CRUD (non-session-swapping create)
├── db/ # SQLite schema & pending_uploads queue
├── lib/ # External service configurations
│ ├── supabase.ts # Supabase client (SecureStore adapter)
│ ├── sync.ts # Offline sync engine
│ ├── imageUtils.ts # Compress & upload to Supabase Storage
├── contexts/
│ └── AuthContext.tsx # Auth state + role-based access
├── supabase/
│ ├── migrations/ # SQL migrations for views, RPCs
│ └── functions/process-voice/ # Deno Edge Function (Gemini 3 Flash)
└── constants/ # Theme and color definitions
-
Clone the repository:
git clone https://github.com/kourdroid/SRM_IM.git cd SRM_IM -
Install dependencies:
pnpm install
-
Environment Setup: Copy
.env.exampleto.envand fill in your credentials:cp .env.example .env
EXPO_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co EXPO_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-public-key EXPO_PUBLIC_N8N_WEBHOOK_URL=https://n8n.example.com/webhook/your-id # Optional
-
Run database migrations: Execute the SQL files in
supabase/migrations/in your Supabase SQL Editor, in order:20260228013334_dashboard_stats.sql— Creates thedashboard_stats_viewandget_monthly_incidentsRPC.20260520000000_user_management.sql— Createsget_admin_usersanddelete_user_by_adminRPCs.
-
Set up Supabase Storage: Create a public bucket named
incident-mediavia the Supabase Dashboard or SQL Editor. -
Start the development server:
pnpm start
The local database uses expo-sqlite. Reference data (Communes) and field data (Incidents) are synchronized using lib/sync.ts.
| Data | Direction | Strategy |
|---|---|---|
| Communes | Server → Client | Pull on every sync |
| Incidents | Bidirectional | Pull latest 100 from server; push unsynced local records |
| Image Uploads | Client → Server | Queued in pending_uploads table; uploaded during sync when online |
- Clean up invalid local records (null
commune_id, missingtype). - Pull latest communes from Supabase.
- Pull latest 100 incidents from server.
- Push unsynced local incidents to server.
- Process
pending_uploadsqueue — compress and upload images to theincident-mediabucket.
All admin write operations (status toggle, role change, user creation/deletion) are strictly online-only. Network reachability is verified before every mutation to prevent sync conflicts.
- Authentication: Supabase Auth with
expo-secure-storefor token persistence. Auto-refresh on app foreground. - Route Guards: Admin layout verifies role from
user_profilestable before rendering. Non-admins are redirected. - RPC Security: Database functions use
SECURITY DEFINERwith explicit admin role checks; self-deletion is blocked. - User Creation: Uses a non-persisted Supabase client (
persistSession: false) to create new accounts without signing out the active admin session. - Validation: Zod schemas at API boundaries for all admin mutations (status updates, role changes).
Proprietary — Developed for ONEE Morocco.
Developed with ❤️ by The Architect & Kourdroid.