BOD Rollback — Panduan Tampilan Frontend
Dokumen ini untuk tim frontend. Menjawab: "mas ketika 'Testing' di-rollback ke 'Testing 2', apa yang seharusnya terjadi di layar?"
Semua behavior di bawah sudah terimplementasi di backend. Frontend tinggal merender field yang sudah ada di payload item (approvalStatus, bodResult, bodDisplayStatus, rollbackPaused, bodApprovals[], rollbackOptions[], dan status progress per baris). Contoh payload ada di §5.
1. Jawaban singkat (bisa langsung dikirim ke frontend)
Ketika kamu rollback "Testing" ke "Testing 2":
- Testing 2 beserta semua anaknya dibuka ulang untuk approval. Progress terakhir tiap item paling bawah (leaf) berubah jadi PENDING / menunggu approval. Item yang masih menunggu prerequisite-nya tidak ikut dibuka — tampil NOT_STARTED abu-abu, dan tidak bisa disentuh sampai prereqnya selesai.
- Testing (item yang di-rollback) tidak di-flip saat itu juga: seluruh subtree Testing tampil NOT_STARTED (
rollbackPaused = true), data & progress tetap utuh (masih 100%),approvalStatusjadi Waiting Approval, tombol aksi disabled. Setelah "Testing 2" di-approve & ditutup lagi, barulah subtree Testing dibuka ulang bertahap — semua node (akar leaf, non-leaf, leaf rantai-awal) jadi Waiting Approval tanpa flip progress (Completed & Waiting, notif ke VP); yang masih punya prereq / blocker tetap NOT_STARTED.- Item lain yang prerequisite-nya = "Testing 2" (konsumen downstream) ikut dikunci: tampil NOT_STARTED, data tidak diubah, semua tombol edit/approve disable sampai "Testing 2" di-approve & ditutup lagi.
- Setelah "Testing 2" selesai di-approve ulang (VP + BOD bila wajib), konsumennya & subtree Testing melepas satu-satu secara berurutan: si A jadi Waiting Approval, lalu anak/rantai berikutnya setelah A ditutup, dst.
- Semua ini tercatat di log aktivitas per item (siapa vote apa, item mana yang dibuka/dikunci/dilepas).
2. Apa yang berubah di UI sesaat setelah rollback resolve
Item yang di-rollback — "Testing" (item B) — sesaat setelah resolve
| Elemen UI | Nilai / tampilan |
|---|---|
| Chip status item | NOT_STARTED (bodResult = REJECTED_ROLLBACK atau rollbackPaused = true) |
approvalStatus (badge approval) |
Waiting Approval |
bodResult |
REJECTED_ROLLBACK |
bodReason |
alasan dari vote rollback pertama |
| Detail BOD approvals | daftar nama BOD + decision (APPROVE/REVISE/ROLLBACK) + reason + rollbackTargetName = "Testing 2" + respondedAt |
rollbackOptions |
terkunci ke 1 pilihan: "Testing 2" |
| Progress milik Testing (leaf) | TIDAK diubah — tetap APPROVED 100% (nilai/approvedBy/approvedAt utuh). Tidak pernah di-flip di sisi B; flip progress hanya terjadi bila VP REJECT (atau bila item ini yang dipilih jadi target rollback X) |
| Tombol aksi | disabled (on hold) sampai "Testing 2" selesai lagi |
Item yang di-rollback — "Testing" (item B) — setelah "Testing 2" di-approve & closed
| Bentuk B | Nilai / tampilan |
|---|---|
| B leaf (akar) | approvalStatus = WAITING_APPROVAL + rollbackPaused = false; progress tidak di-flip (Completed & Waiting) → tombol approve VP aktif; bila VP REJECT barulah progress terakhir jadi PENDING |
| B non-leaf | root: WAITING_APPROVAL + unpause; leaf rantai-awal (tanpa prereq / prereq rantai awal) & non-leaf: WAITING_APPROVAL + unpause tanpa flip (Completed & Waiting; notif ke VP); leaf yang masih menunggu prereq: tetap NOT_STARTED abu-abu; blocker subtree: tetap pause |
Target rollback — "Testing 2" (item X)
| Elemen UI | Nilai / tampilan |
|---|---|
| Chip status item | Need Approval (karena progress terakhirnya PENDING) — atau NOT_STARTED jika "Testing 2" sendiri masih menunggu prerequisite-nya |
approvalStatus |
Waiting Approval |
bodResult |
null (bukan item yang di-rollback) |
| Progress terakhir (leaf) | APPROVED → PENDING |
| Progress perantara | tidak berubah |
Anak-anak "Testing 2" (X) — semua level, live (restart penuh saat resolve)
| Kondisi anak | Tampilan |
|---|---|
| Leaf (tidak punya anak) & tidak memblokir | chip Need Approval, progress terakhir PENDING, approvalStatus Waiting Approval |
| Non-leaf & tidak memblokir | chip Need Approval (atau normal sesuai derived), approvalStatus Waiting Approval, progress tetap |
| Dipakai sebagai prerequisite oleh sibling live lain (blocker) | seluruh subtree tampil NOT_STARTED + rollbackPaused = true; tombol write disable; baru terbuka saat prereq pemicunya selesai |
Anak-anak "Testing" (B) — saat resolve
Seluruh subtree B: NOT_STARTED abu-abu (rollbackPaused = true), approvalStatus = WAITING_APPROVAL, progress tetap 100%, tombol write disabled. Buka ulang baru terjadi saat "Testing 2" ditutup lagi — lalu mengikuti aturan di atas (semua node → Waiting Approval tanpa flip; punya prereq → NOT_STARTED lanjut).
Aturan display yang dipakai backend (bisa dipakai juga untuk highlight):
bodResult === 'REJECTED_ROLLBACK'ataurollbackPaused === true→ item tampil NOT_STARTED (override).bodResult === 'REJECTED_REVISE'→ tampil "Rejected by BOD" (bodDisplayStatus = REJECTED_BY_BOD).approvalStatus === 'BOD_APPROVAL'→ tampil "BOD Approval".
3. Konsumen downstream — item lain yang prereq-nya = "Testing 2"
Ini bagian yang paling sering mengejutkan: rollback tidak hanya menyentuh Testing & Testing 2. Semua item same-level yang prerequisite langsungnya "Testing 2" ikut terkunci.
| Kondisi konsumen | Tampilan saat rollback | Saat "Testing 2" selesai lagi |
|---|---|---|
| Sudah CLOSED (approved) | tampil NOT_STARTED (rollbackPaused = true); data & progress tetap utuh; semua aksi disabled |
approvalStatus → Waiting Approval (progress tidak di-flip; VP tinggal approve ulang) |
| Belum CLOSED | tampil NOT_STARTED; dikunci | mengikuti alur prereq-nya; lanjut dari status terakhir |
| Anak-anak konsumen (subtree) | ikut NOT_STARTED | release berurutan: anak baru aktif setelah konsumennya ditutup lagi |
Item B (yang di-rollback) tidak masuk kategori konsumen — B di-hold dengan rollbackPaused = true + bodResult = REJECTED_ROLLBACK saat resolve, lalu subtree-nya dibuka ulang bertahap (tanpa flip) ketika "Testing 2" ditutup lagi.
Alur release (rantai Testing 2 → A → P → Q + subtree B):
"Testing 2" closed lagi
├─► A → Waiting Approval ← mulainya di sini (subtree B ikut restart tanpa flip)
├─► B leaf → Waiting Approval (Completed & Waiting; tidak di-flip; flip bila VP REJECT)
└─► B non-leaf → anak leaf rantai-awal → Waiting Approval (Completed & Waiting; tidak di-flip)
A di-approve & closed
└─► P → Waiting Approval
P di-approve & closed
└─► Q → Waiting Approval (dan seterusnya)
Timing-nya bertahap, bukan serentak. Notifikasi saat release mengikuti routing baku: item yang jadi Completed & Waiting Approval → notif ke VP-nya — seluruh node B-side masuk kategori ini (tanpa flip, progress tetap 100%). Flip progress hanya terjadi di sisi target X saat resolve; item yang di-flip (jadi Need Approval) → notif ke Owner/Admin.
Read-only / disabled saat rollbackPaused = true
Semua action berikut mengembalikan 400 ... is on hold: waiting for its prerequisite to be approved and closed — frontend sebaiknya men-disable tombol ini di item/subtree yang di-hold:
- edit data item (PATCH)
- approval respond, re-request, BOD respond
- submit / respond progress
- simpan plans (PUT)
- tambah/hapus member
4. Urutan tampilan di layar dari waktu ke waktu
| Waktu | Yang tampil |
|---|---|
| T0 — BOD vote masuk | List detail "Testing": badge BOD Approval; tiap BOD yang sudah vote tampil di bodApprovals (beri indikator sudah/blm vote) |
| T1 — resolve rollback | "Testing 2" + subtree X: chip NOT_STARTED / Need Approval; progress leaf X jadi PENDING. "Testing" + subtree B: NOT_STARTED abu-abu (rollbackPaused = true), progress tetap 100% APPROVED (tidak di-flip), semua aksi disabled. Konsumen jadi NOT_STARTED abu-abu; notifikasi Rolled Back by BOD ke VP; log aktivitas terisi per item |
| T2 — "Testing 2" di-approve lagi | "Testing 2" → CLOSED; konsumen A → Waiting Approval; subtree B mulai di-restart — semua node B → Waiting Approval tanpa flip (Completed & Waiting, notif Ready for Approval ke VP) |
| T3 — A di-approve | A → CLOSED; P → Waiting Approval; dst |
| T4 — "Testing" di-approve ulang | "Testing" → CLOSED (handover normal); selesai |
Frontend boleh menampilkan status ini apa adanya; urutan cascade dijamin backend (data + log tersedia per item).
5. Contoh payload detail item (English, sesuai API — untuk referensi field render)
GET /api/v1/projects/:projectId/action-plans/:activityId → data (potongan):
{
"id": "activity-testing-b",
"name": "Testing",
"approvalStatus": "WAITING_APPROVAL",
"approvalReason": null,
"bodRoundId": "round-1",
"bodResult": "REJECTED_ROLLBACK",
"bodReason": "hasil pengujian tidak sesuai",
"bodDisplayStatus": "NOT_STARTED",
"rollbackPaused": true,
"approverId": "user-vp-1",
"approverName": "Fajar",
"bodApprovals": [
{
"userId": "bod-1",
"name": "Hendra",
"decision": "ROLLBACK",
"reason": "hasil pengujian tidak sesuai",
"rollbackItemId": "activity-testing2-x",
"rollbackTargetName": "Testing 2",
"respondedAt": "2026-09-10T03:00:00.000Z"
}
],
"rollbackOptions": [{ "id": "activity-testing2-x", "name": "Testing 2" }]
}
Item konsumen (A) yang sedang di-hold:
{
"id": "activity-a",
"name": "A",
"approvalStatus": "CLOSED",
"bodResult": null,
"bodDisplayStatus": "NOT_STARTED",
"rollbackPaused": true
}
Baris progress (leaf "Testing" — item B — setelah rollback resolve): tidak pernah di-flip; hanya berubah bila VP REJECT:
{
"id": "progress-1",
"progress": 100,
"status": "APPROVED",
"approvedBy": "user-vp-1",
"approvedAt": "2026-09-09T09:00:00.000Z",
"description": "…"
}
Baris progress (leaf "Testing 2" — item X — setelah rollback resolve; langsung di-flip):
{
"id": "progress-2",
"progress": 100,
"status": "PENDING",
"approvedBy": null,
"approvedAt": null,
"description": "…"
}
6. Mapping singkat field → tampilan
| Field | Tampilan frontend |
|---|---|
status (derived) |
chip utama item |
bodDisplayStatus = NOT_STARTED |
tampil NOT_STARTED walau approvalStatus lain |
bodDisplayStatus = REJECTED_BY_BOD |
chip/notice "Rejected by BOD" |
bodDisplayStatus = BOD_APPROVAL |
badge "BOD Approval" (menunggu vote) |
approvalStatus = WAITING_APPROVAL |
aksi approve VP aktif |
rollbackPaused = true |
abu-abu + disable semua aksi + helper "on hold" |
progress row status = PENDING |
badge PENDING di list progress |
bodApprovals[].rollbackTargetName |
label target rollback di detail |
Referensi lanjutan (backend internals, boleh diabaikan frontend): .markdown/FLOW_BOD_REJECT_ROLLBACK.md.