Semua layar dirancang mobile-first untuk petugas di lapangan. Bahasa default Indonesia, dengan tombol ganti ke Inggris untuk juri. Data diambil dari API, bukan langsung dari Firestore.
Rute publik tidak perlu login. Rute operator dilindungi Firebase Auth dan hanya terbuka untuk akun dengan peran operator.
| Rute | Halaman | Pengguna | Isi utama | Data dari |
|---|---|---|---|---|
/ | Peta publik | semua | Grid risiko hari ini, insiden 24 jam, daftar insiden, legenda | GET /meta, /risk, /incidents |
/insiden/:id | Detail insiden | semua | Level, alasan, tindakan, bukti citra, riwayat deteksi, peta mini | GET /incidents/{id} |
/laporan | Sitrep harian | semua | Ringkasan satu halaman, pilih tanggal, tabel insiden per level | GET /sitreps/{date} |
/tentang | Tentang & metode | semua, juri | Cara kerja, ToPeCAl, sumber data, batasan sistem | statis |
/masuk | Login operator | operator | Tombol "Masuk dengan Google" | Firebase Auth |
/operator | Antrean draf | operator | Draf dari agen, tab Draf / Disetujui / Ditolak, urut level | GET /operator/queue |
/operator/alert/:id | Tinjau draf | operator | Edit pesan, ubah level dengan alasan, setujui atau tolak | GET / PATCH / POST /operator/alerts/{id} |
Target: dari membuka situs sampai membaca alasan insiden dalam tiga tap.
Target: satu draf bisa disetujui dalam kurang dari satu menit.
Di layar selebar 1024 px ke atas, daftar insiden pindah ke panel kiri selebar 360 px dan peta mengisi sisanya. Data dalam sketsa adalah contoh fiktif.
//insiden/:id/laporan/operator/operator/alert/:idUrutan dari bawah ke atas sesuai tabel. Basemap raster OpenStreetMap atau basemap vektor gratis. Cantumkan atribusinya.
| ID layer | Sumber | Tipe | Gaya | Zoom | Interaksi |
|---|---|---|---|---|---|
risk-grid | URL dari GET /risk (GeoJSON di Cloud Storage) | fill | warna per kelas risiko, opacity 0,55 | semua | tap: skor dan komponennya |
peat-boundary | peat.geojson di bucket publik, dimuat sekali | line | garis putus-putus 1 px, warna moss | semua | tidak ada |
villages | villages.geojson di bucket publik | symbol | nama desa, 11 px | ≥ 10 | tidak ada |
detections | properti detections dari detail insiden | circle | abu-abu 3 px | ≥ 9 | tidak ada |
incidents | GET /incidents?since=24h | circle | warna per level, radius 6–14 px sesuai jumlah deteksi | semua | tap: kartu insiden |
map.addLayer({
id: 'incidents', type: 'circle', source: 'incidents',
paint: {
'circle-color': ['match', ['get', 'level'],
'AWAS', '#c2410c', 'SIAGA', '#e8743b', 'WASPADA', '#a86f00', /* PANTAU */ '#8a958e'],
'circle-radius': ['interpolate', ['linear'], ['get', 'n_detections'], 1, 6, 5, 14],
'circle-stroke-color': '#ffffff', 'circle-stroke-width': 2,
},
});
Jangan pernah membedakan level hanya dengan warna. Setiap penanda level wajib menampilkan labelnya.
| Level | Label ID | Label EN | Warna |
|---|---|---|---|
PANTAU | Pantau | Monitor | #8A958E |
WASPADA | Waspada | Advisory | #A86F00 |
SIAGA | Siaga | Watch | #E8743B |
AWAS | Awas | Warning | #C2410C |
| Kelas risiko | Skor | Warna |
|---|---|---|
| Rendah | < 30 | #E3ECD9 |
| Sedang | 30–54 | #F3E2A6 |
| Tinggi | 55–74 | #EFB57A |
| Sangat tinggi | ≥ 75 | #D9714A |
| Komponen | Props | Dipakai di | Catatan |
|---|---|---|---|
LevelBadge | level, lang | semua | warna + label, tidak pernah warna saja |
RiskLegend | lang | peta | empat kelas, bisa dilipat di HP |
IncidentCard | incident, compact | peta, daftar, sitrep | mode ringkas untuk bottom sheet |
EvidenceImage | thumbUrl, sensor, date, topecalClass | detail, tinjau | tampilkan "belum ada citra" bila kosong |
ReasonList | reasons[] | detail, tinjau | 2–5 butir |
ActionList | actions{agency, community, residents} | detail, tinjau | ikon per audiens |
ValidatorStatus | passed, checks[] | tinjau, antrean | hijau semua lolos, kuning bila perlu dicek |
ToolTrace | calls[] | tinjau | daftar tools yang dipanggil agen, bisa dilipat. Tampilkan di video demo |
DataBanner | asOf, lastIngestAt, replay | semua halaman publik | biru untuk replay, kuning bila data > 2 jam |
LangToggle | lang | header | simpan pilihan di localStorage |
| Keadaan | Kapan | Tampilan | Teks (ID) |
|---|---|---|---|
| Memuat | request berjalan | kerangka abu-abu, peta tetap tampil | tanpa teks |
| Kosong | tidak ada insiden di gambut | pesan tenang, legenda risiko tetap tampil | "Tidak ada insiden di lahan gambut dalam 24 jam terakhir." |
| Galat | API gagal | kotak pesan + tombol Coba lagi | "Data belum bisa dimuat. Periksa koneksi lalu coba lagi." |
| Data lama | lastIngestAt > 2 jam | banner kuning | "Data terakhir diperbarui {jam}. Pembaruan sedang terlambat." |
| Replay | replay = true | banner biru | "Mode replay: data per {tanggal jam}." |
Simpan di src/i18n/id.json dan en.json. Jangan menulis teks langsung di komponen.
| Kunci | ID | EN |
|---|---|---|
level.pantau | Pantau | Monitor |
level.waspada | Waspada | Advisory |
level.siaga | Siaga | Watch |
level.awas | Awas | Warning |
verify.none | Belum ada citra satelit | No satellite image yet |
verify.flaming | Citra menunjukkan api menyala | Imagery shows flaming fire |
verify.smouldering | Citra menunjukkan api membara | Imagery shows smouldering fire |
btn.approve | Setujui | Approve |
btn.save | Simpan | Save |
btn.reject | Tolak | Reject |
btn.regenerate | Buat ulang | Regenerate |
toast.approved | Alert disetujui dan tampil di peta publik. | Alert approved and now on the public map. |
banner.replay | Mode replay: data per {date}. | Replay mode: data as of {date}. |
/api/** diarahkan ke Cloud Runweb/ ├─ index.html ├─ src/ │ ├─ main.ts │ ├─ api.ts # fetch ke /api/v1, sisipkan token operator │ ├─ auth.ts # Firebase Auth, cek klaim role │ ├─ map/ │ │ ├─ map.ts # inisialisasi + 5 layer │ │ └─ styles.ts │ ├─ pages/ # Peta, Insiden, Laporan, Tentang, Masuk, Operator, Tinjau │ ├─ components/ # 10 komponen di bagian 06 │ └─ i18n/ id.json, en.json └─ firebase.json