# BOD Rollback — Panduan Tampilan Frontend 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 > Ketika kamu rollback **"Testing"** ke **"Testing 2"**: > 1. **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. > 2. **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%), `approvalStatus` jadi **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**. > 3. **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. > 4. 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. > 5. 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'` **atau** `rollbackPaused === 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 `GET /api/v1/projects/:projectId/action-plans/:activityId` → `data` (potongan): ```json { "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: ```json { "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: ```json { "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): ```json { "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`.*