← Back to Blog

Project 03 — SA Incident Tracker

Building a zero-cost community safety reporting tool for Durban / KZN — anonymous incident submission, admin moderation, live CCTV feeds, and a canvas-based photo blur tool. GAS + Sheets as the backend.

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.