The Problem
The existing incident tracker served Durban and KZN’s neighbourhood watch communities, but needed evolution beyond a single PIN-protected dashboard. The v4 rebuild addressed this: proper multi-admin governance, geographic territory scoping, invite-token onboarding, and WhatsApp integration for real-time incident escalation.
The tool covers the full range of incidents a community safety network deals with — fire and smoke, water leaks, unattended animals, road hazards, suspicious activity, and civil disruptions — with moderation, public visibility, and admin control over incident type definitions.
Architecture
The zero-cost stack remains:
- Frontend — Vanilla JS, HTML, CSS on Cloudflare Pages
- Backend — Google Apps Script Web App as a REST API
- Database — Google Sheets with tabs for Pending/Approved/Rejected reports, admin roster, and editable incident types
- Photos — ImgBB for client-side uploads, canvas blur tool for redacting sensitive details
- Map — Leaflet.js with CartoDB tiles, Nominatim address search
- Notifications — WhatsApp deep-link messaging (wa.me) routed to territory-scoped admins
Multi-Admin RBAC with Territory Matching
The v4 system replaces a single PIN with proper role-based access control:
Super Admin — Full system view. Sees all incident reports, manages area admins, edits incident types, sets fallback contact for unassigned territories.
Area Admin — Territory-scoped access using haversine distance matching. An area admin only sees incident reports submitted within their defined radius (latitude, longitude, km). Reports outside every territory are flagged as “Unassigned” and visible only to Super Admin.
Each admin stores: role, territory centre (lat/lng), radius, WhatsApp phone number, and a 6-digit PIN for the admin dashboard.
Invite-Token PIN Claim Flow
New admins are invited via a one-time token link. Following that link, they claim their PIN (any 6 digits they choose), which is stored in the Admins sheet. This removes the need for PIN distribution outside the system and lets admins control their own credentials.
Editable Incident Types
Rather than hardcoding incident categories, they now live in a Google Sheets tab called IncidentTypes. Each type
has: value (internal ID), label (display name), icon (emoji), color (hex), category (group), and active (boolean).
Admins update this tab directly — no code redeploy needed. The public map dynamically loads type definitions from this sheet on every visit.
WhatsApp Notify and Escalation
After a report is submitted, the system routes a WhatsApp message to the matched area admin using a deep-link:
wa.me/{phoneNumber}?text=.... The message includes incident type, location, and a link to the admin dashboard.
If no area admin covers the location, the message goes to the Super Admin (fallback). WhatsApp links require a user tap to send — they’re not automatic push notifications, respecting the async, on-demand nature of the platform.
Sub-Category Filters and Map Persistence
The public map supports both category and sub-category filtering via compact dropdowns, letting users drill down by incident type. Map view state (pan, zoom, selected filters) persists to localStorage across sessions.
Canvas Blur and CORS
The admin photo editor includes a blur tool for redacting faces and licence plates. Drawing cross-origin images directly onto canvas taints it, so we fetch as a blob first:
const res = await fetch(src);
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
img.src = blobUrl; // same-origin — canvas stays clean
Redacted photos are re-uploaded to ImgBB and the new URL replaces the original in the report.
Comments, Status, and Public Updates
Each report has:
- Comments — admin notes stored as JSON, visible publicly in the map popup as “Updates”
- Status — Active, Under Investigation, Resolved, False Alarm, or Duplicate, shown as a colour-coded badge
- CCTV panel — linked media for incidents with camera footage
This gives the community visibility into whether incidents are being acted on.
What This Project Demonstrates
- RBAC and geographic scoping using haversine distance
- Google Sheets as a multi-role CMS (types, admins, reports, comments)
- GAS as a stateless REST API with request routing
- Canvas API with CORS workarounds for client-side image editing
- Leaflet.js with live geocoding and filtered queries
- WhatsApp deep-link integration for escalation
- localStorage for stateful UX without a backend session store
- PIN-less admin flow using one-time claim tokens
- Client-side filtering and full moderation workflow
Live: sa-incident-tracker.pages.dev
GitHub: SA-incident-tracker
Next: Project 04 — SA Fuel Price API. Moving off the GAS stack for the first time into classical Node.js + PostgreSQL backend architecture.