think
16px
820px

UX Redesign — Pemilik Manfaat Flow (Perubahan PP)

Expert UX design brief. Covers all three stages of the BO lifecycle
in Perubahan PP: Unggah Dokumen → Tinjauan → back to Unggah.


1. Current Flow — Pain Points

PpPerubahanFormPage (step 2 "Lengkapi Data")
  └─ PpPemilikManfaatSection
       ├─ [Tambah Pemilik Manfaat]  TambahPemilikManfaatDialog
           step 1: kewarganegaraan  (WNI / WNA)
           step 2: upload ID doc   (+ NPWP for WNI)
           step 3: pick kriteria
            POST /pemilik-manfaat/from-document    always APPENDS 
       
       └─ per-BO card
            [Perbarui dengan Dokumen]  same dialog in "update" mode
            kriteria picker  (active)
            identity fields  (read-only if matched or seeded)

                     user clicks Lanjutkan

PpPerubahanReviewPageV2 (Tinjauan)
  └─ PpPemilikManfaatCards
       shows only NEW BOs (matchedDocumentId != null)    
       kriteria picker + Konfirmasi button per card

                     user clicks  Kembali

PpPerubahanFormPage again
  └─ [Tambah Pemilik Manfaat]  uploads APPENDED to existing BOs    triggers

Pain point ❶ — Append-not-replace (Issue #10)
Every call to /pemilik-manfaat/from-document creates a new row.
There is no way to signal "I want to replace my existing set."
Going back from Tinjauan and uploading again silently doubles the list.
The button label "Tambah" reinforces "add one more" when the user's
mental model is "set the BO roster."

Pain point ❷ — No before/after in Tinjauan (Issue #9)
PpPemilikManfaatCards renders only BOs where matchedDocumentId != null
(newly uploaded this submission). The existing BO roster (from oldData)
is invisible. The reviewer can't answer "compared to what?" and can't tell
if they're adding, replacing, or leaving unchanged.

Pain point ❸ — Kriteria friction at upload time
The dialog forces the user to know and choose kriteria at the same moment
they're uploading identity docs. These are cognitively unrelated tasks.
Users who don't know the criteria yet are blocked from uploading.

Pain point ❹ — No extraction status feedback
After clicking "Tambah Pemilik Manfaat," the dialog closes and the BO card
appears with extracted fields (or empty fields). There's no visible signal
that OCR is in progress / done / failed. Silent failures look like "no data."

Pain point ❺ — Single-file dialog inside a flow that isn't single-file
The dialog handles one BO at a time. A company with 4 BOs requires 4 dialog
open/close cycles. There is no batch-upload concept even though the mental
model is "these are all the BOs for my company."


2. Design Goals

# Goal Principle
G1 Make replace-vs-add intent explicit before any destructive action Error Prevention (Nielsen #5)
G2 Show the complete BO roster in Tinjauan (before + after) Recognition over Recall (Nielsen #6)
G3 Separate upload from kriteria assignment (reduce task coupling) Progressive Disclosure
G4 Give real-time extraction status per BO card Visibility of System Status (Nielsen #1)
G5 Group related things spatially (before / after, new / unchanged / removed) Gestalt: Proximity, Common Fate
G6 Use consistent visual language across form and review stages Gestalt: Similarity
G7 The primary action path must always be visually dominant Visual Hierarchy

3. Redesigned Flow — Stage by Stage


Stage 1 · Form Page — Upload Section

3.1 Empty state (no BOs yet)

┌─────────────────────────────────────────────────────────────┐
│  Pemilik Manfaat                                            │
│  Unggah dokumen identitas setiap pemilik manfaat baru.     │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  ⊕  Tambah Pemilik Manfaat                         │   │
│  │     Unggah KTP/Paspor + NPWP untuk data otomatis   │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

No change from current — clean starting point.


3.2 After first upload (1+ BOs exist)

┌─────────────────────────────────────────────────────────────┐
  Pemilik Manfaat                      2 akan ditambahkan   
                                                             
  ╔═══════════════════════════════════════════════════════╗  
   Ahmad Fauzi                              [Baru]       
   KTP · 317401xxxxx · WNI                             
   ─────────────────────────────────────────────────    
   Kriteria: [Grup A ▾]  required before Konfirmasi    
                                                [🗑]     
  ╚═══════════════════════════════════════════════════════╝  
                                                             
  ╔═══════════════════════════════════════════════════════╗  
   Siti Rahayu                              [Baru]       
   🔄 Mengekstrak...  (status chip, animated)            
  ╚═══════════════════════════════════════════════════════╝  
                                                             
  [+ Tambah Satu Lagi]       [↺ Ganti Semua ▾]            
└─────────────────────────────────────────────────────────────┘

Key changes from current:
- "Tambah" button is now "Tambah Satu Lagi" (signals additive, not replace)
- New "Ganti Semua" button with a destructive-action affordance (chevron or secondary style)
- Extraction status chip per card (see §3.4)
- Kriteria removed from upload dialog → shown on card as required-before-confirm (see §3.3)


3.3 Upload dialog — simplified (kriteria removed)

╔══════════════════════════════════════════════════════════╗
   Tambah Pemilik Manfaat                               
  Unggah dokumen identitas  data diisi otomatis.        
╠══════════════════════════════════════════════════════════╣
                                                          
  1  Kewarganegaraan                                      
     [  WNI  ]  [  WNA  ]                                
                                                          
  2  Dokumen Identitas (KTP / SIM / Paspor)              
     ┌─────────────────────────────────────────────────┐ 
       📄  Pilih atau seret file (.pdf .jpg .png)     
     └─────────────────────────────────────────────────┘ 
                                                          
     NPWP  (WNI saja)                                    
     ┌─────────────────────────────────────────────────┐ 
       📄  Pilih atau seret file (.pdf .jpg .png)     
     └─────────────────────────────────────────────────┘ 
                                                          
    Kriteria pemilik manfaat akan dipilih setelah      
     data berhasil diekstrak dari dokumen.               
                                                          
╠══════════════════════════════════════════════════════════╣
  [Batal]                      [Tambah Pemilik Manfaat]  
╚══════════════════════════════════════════════════════════╝

Why remove kriteria from the dialog:
- Upload and kriteria assignment are two different cognitive tasks
- Kriteria require knowing legal definitions; upload just requires having the file
- Current dialog blocks users who don't know kriteria from uploading
- Card-level kriteria picker already exists and is well-placed (right before Konfirmasi)

The info callout at the bottom closes the loop: "you will pick kriteria after extraction."


3.4 Extraction status chips (per-card)

After dialog submits and card appears, the card shows a status region:

  Phase 1 (OCR in progress)  →  🔄 Mengekstrak...
  Phase 2 (OCR done)         →  ✅ Data berhasil diekstrak
  Phase 2 (OCR failed/empty) →  ⚠️  Perlu verifikasi manual

Implementation note: The card can derive status from the BO's status field
(EDITABLE with no data = extraction may still be running, EDITABLE with data = done).
A short poll after upload (1500 ms interval while status is "empty data") would surface
the extraction result. This mirrors the existing KTP extraction polling elsewhere.


3.5 "Ganti Semua" — confirm-then-replace flow

╔══════════════════════════════════════════════════════════╗
    Ganti Semua Pemilik Manfaat?                        
                                                          
  Tindakan ini akan menghapus 2 pemilik manfaat yang      
  sudah diunggah dan menggantinya dengan unggahan baru.  
                                                          
  Pemilik manfaat yang akan dihapus:                     
   Ahmad Fauzi (KTP · 317401xxxxx)                      
   Siti Rahayu (Paspor · B1234567)                      
                                                          
  [Batal]                      [Hapus & Mulai Ulang]     
╚══════════════════════════════════════════════════════════╝

After confirmation:
1. Backend DELETE all non-founder BOs (new endpoint or sequential DELETE per boId)
2. Open upload dialog in regular "add" mode
3. User starts fresh

Gestalt: Common Fate — The list of "BOs that will be deleted" groups the affected
items together, making it clear they share a fate. This prevents the user from being
surprised by what disappears.


3.6 Seeded (existing) BOs in form view

BOs that came from the old registration (via carrier: sourceDocumentId === existingDocumentId)
are currently shown with "Terdaftar (semula)" badge. Strengthen the visual separation:

┌─────────────────────────────────────────────────────────────┐
  PEMILIK MANFAAT SAAT INI (dari data terdaftar)            
  ──────────────────────────────────────────────────────    
                                                             
                             
   Budi Santoso               [Semula]  terkonfirmasi    
   KTP · 3171xxxxx · WNI                                
    [Perbarui dengan Dokumen ↺]                          
                             
                                                             
  PEMILIK MANFAAT BARU / DIUBAH                             
  ──────────────────────────────────────────────────────    
                                                             
  ╔═══════════════════════════════════════════════════════╗  
   Ahmad Fauzi                              [Baru]       
   KTP · 317401xxxxx · WNI                             
  ╚═══════════════════════════════════════════════════════╝  
└─────────────────────────────────────────────────────────────┘

Gestalt: Figure/Ground — Seeded BOs are visually recessive (dashed border, muted text,
lower contrast). New BOs are visually prominent (solid border, normal weight). The user
immediately understands which cards they need to act on.

Gestalt: Proximity — Two labeled sections ("Saat Ini" / "Baru") group items by their
role in this submission, not by arbitrary order.


Stage 2 · Tinjauan — BO Section

3.7 Full roster view with before/after

Current: shows only matchedDocumentId != null BOs (the new ones).

Proposed: show the complete picture — old roster + new additions side by side.

┌─────────────────────────────────────────────────────────────┐
  Data Pemilik Manfaat                                       
                                                             
  SEBELUM  ─────────────────────────────────────────────    
                                                             
                             
   Budi Santoso              [Tidak berubah]  [konteks]   
   KTP · 3171xxxxx · WNI                                 
                             
                                                             
  SESUDAH  ─────────────────────────────────────────────    
                                                             
  ╔═══════════════════════════════════════════════════════╗  
    Ahmad Fauzi                           [Baru +]      
    KTP · 317401xxxxx · WNI                            
    ── Kriteria ────────────────────────────────────   
     Wajib dipilih sebelum konfirmasi                
    [Grup A ]  [Grup B ]                            
                                [ Konfirmasi]         
  ╚═══════════════════════════════════════════════════════╝  
                                                             
  ╔═══════════════════════════════════════════════════════╗  
    Siti Rahayu                           [Baru +]      
    Paspor · B1234567 · WNA                            
    ── Kriteria ────────────────────────────────────   
    [Grup B ]                                         
                                [ Konfirmasi]         
  ╚═══════════════════════════════════════════════════════╝  
└─────────────────────────────────────────────────────────────┘

"Sebelum" section:
- Visually recessive: dashed/muted border, secondary text weight, no action buttons
- Label: [Tidak berubah] or [Semula]
- Data from oldData.pemilikManfaat[] (requires backend to surface this in ReviewData)
- Collapsed by default with an expand toggle if the list is long

"Sesudah" section:
- Active cards, same design as now (solid border, confirm button, kriteria picker)
- Label: [Baru +] for newly added, [Diperbarui ↺] for replaced identity

Gestalt: Continuity — The before/after division reads top to bottom like reading a
changelog. The eye follows the natural flow from "what was" to "what will be."

Visual Hierarchy:
- "Sesudah" cards are the primary action area — they dominate visually
- "Sebelum" cards are context only — they inform but don't demand attention
- The section labels act as anchors at the top of each group


3.8 BO card status labels

Consistent badge vocabulary across both stages:

State Badge Color
Seeded, unchanged Semula muted/secondary
Newly added this submission Baru + blue/indigo
Identity replaced this submission Diperbarui ↺ amber
Marked for removal Dihapus ✕ red/destructive
Confirmed ✓ Dikonfirmasi emerald (existing)
Pending confirmation ○ Perlu dikonfirmasi zinc

Gestalt: Similarity — Same badge shapes and color tokens across Form and Tinjauan.
A user who understood "Baru +" in the form view immediately recognises it in Tinjauan.


Stage 3 · Back-navigation from Tinjauan to Form

3.9 Preventing re-upload confusion

When user clicks ← Kembali from Tinjauan back to the Form:

Current: Form page loads with existing BOs already listed. User may not realise that
clicking "Tambah Pemilik Manfaat" will APPEND. Confusion leads to duplicates.

Proposed:

A. The "Tambah" button is renamed "Tambah Satu Lagi" — explicitly additive.

B. Show a status summary row above the action buttons when BOs already exist:

  ╔══════════════════════════════════════════════════════════╗
  ║  ✅  2 pemilik manfaat siap ditinjau                    ║
  ║  Lanjutkan ke Tinjau, atau tambah / ganti pemilik manfaat ║
  ╚══════════════════════════════════════════════════════════╝

  [+ Tambah Satu Lagi]               [↺ Ganti Semua]

C. If BOs are already confirmed in Tinjauan and user navigates back: show a
re-confirm warning banner (same as the existing field-edit warning pattern):

  ⚠ Anda kembali ke tahap unggah. Jika Anda mengubah daftar pemilik
    manfaat, konfirmasi sebelumnya akan dibatalkan.

Nielsen #1: Visibility of System Status — The user always knows:
- How many BOs are currently in their list
- Whether those BOs are confirmed or pending
- What will happen if they add/replace


4. State Transition Diagram

  ┌─────────────────────────────────────────────────────────────────┐
                                                                   
    Form Page                                                      
    ┌────────┐   upload doc   ┌────────────┐   all confirmed    
     Empty   ────────────▶   BOs listed  ─────────────────▶ 
    └────────┘                └──────┬─────┘                    
                                                                
                            ┌────────▼─────────┐                
                              Intent picker                    
                              (add one more                    
                               vs ganti semua)                 
                            └────────┬──────────┘                
                            add ─────┴───── ganti semua          
                                                               
                      upload dialog         confirm delete        
                                                               
                         BO appended         BOs cleared         
                                             upload dialog        
                                                                   
    Tinjauan Page                                                  
    ┌────────────────────────────────────────────────────────┐   
     Before section (read-only, oldData BOs)                   
     After section (new BOs, kriteria + confirm actions)       
    └────────────────────────────────────────────────────────┘   
                    Kembali (back-nav)                           
    Form Page (banner: re-confirm warning if BOs were confirmed)  
  └─────────────────────────────────────────────────────────────────┘

5. Implementation Plan

Priority 1 — Bug fix (Issue #10): Replace-vs-Append intent

Backend:

DELETE /api/pp/perubahan/:id/pemilik-manfaat

New endpoint: deletes all non-founder BO entries for a submission.
Returns 200 { deleted: N }.

Frontend — Form page:
- Rename "Tambah Pemilik Manfaat" → "Tambah Satu Lagi" (when BOs exist)
- Add "Ganti Semua" button (destructive secondary style)
- Confirm dialog listing BOs to be deleted → calls new bulk DELETE → opens upload dialog

Files to change:
- pp-pemilik-manfaat-section.tsx — button labels, Ganti Semua trigger
- pp-perubahan-form-section.tsx — wire new delete-all mutation
- backend/src/routes/pp-perubahan.ts — new DELETE /pemilik-manfaat endpoint


Priority 2 — Extraction status feedback (Issue #4 analog)

Frontend — BO card (form view):
- Add a status chip derived from bo.nama == null && bo.nomorIdentitas == null (extraction pending or failed)
- Short 1500 ms poll on the form page while any BO has no extracted data

Files to change:
- pp-pemilik-manfaat-section.tsx — status chip per BO card
- PpPerubahanFormPage.tsx — add refetchInterval while BOs are "empty"


Priority 3 — Decouple kriteria from upload dialog

Frontend — dialog:
- Remove step 3 (kriteria) from TambahPemilikManfaatDialog
- Add info callout: "Pilih kriteria setelah dokumen diekstrak"
- Card-level kriteria picker is already implemented — just ensure it's prominent enough

Files to change:
- tambah-pemilik-manfaat-dialog.tsx — remove kriteria step, add callout
- pp-pemilik-manfaat-section.tsx — ensure kriteria section is visually prominent on new cards


Priority 4 — Before/after view in Tinjauan (Issue #9)

Backend:
Surface oldData.pemilikManfaat[] (if it exists in the PpRegistrySnapshot) in the
pp-perubahan-review.ts projector. Add a new registryEntities.bo_sebelum group
or a new diff.pemilik_manfaat section that carries both old + new rows.

Frontend — PpPemilikManfaatCards:
- Accept and render a "sebelum" section (read-only, muted) above the active cards
- Add [Baru +] / [Diperbarui ↺] / [Semula] badges

Files to change:
- pp-perubahan-review.ts (projector) — add sebelum rows to envelope
- pp-pemilik-manfaat-cards.tsx — before/after sections
- Contract review.ts — if roster needs a new shape


Priority 5 — Status summary + back-nav warning

Frontend — Form page:
- Status summary row above action buttons (when BOs exist)
- "Re-confirm" warning banner when navigating back if BOs were previously confirmed

Files to change:
- pp-perubahan-form-section.tsx — status summary component
- PpPerubahanFormPage.tsx or form section — back-nav banner


6. Visual Language Reference

State          Border      Background    Badge text    Icon
──────────────────────────────────────────────────────────────
Semula         dashed      muted/20%     "Semula"      —
Baru           solid       white         "Baru +"      —
Diperbarui     solid       amber/5%      "Diperbarui"  ↺
Dihapus        solid       red/5%        "Dihapus"     ✕
Confirmed      emerald     white         "Dikonfirmasi" ✓
Pending        zinc        white         —             ○ (Circle)
Extracting     zinc        white         "Mengekstrak…" 🔄
Warn (empty)   amber       amber/5%      "Perlu verif" ⚠

Token consistency: reuse CARD_DONE_GLOW (emerald) for confirmed, border-amber-300
for warnings — same tokens used everywhere else in the review engine.


7. Principles Summary

Section Principle Applied
Rename "Tambah" → "Tambah Satu Lagi" Error Prevention — label matches behaviour
"Ganti Semua" confirmation with list Error Prevention + Common Fate — explicit, undo-safe
Seeded BOs in dashed/muted style Figure/Ground — context vs. action
Before/After sections in Tinjauan Continuity + Recognition over Recall
Extraction status chips Visibility of System Status
Kriteria moved to card, not dialog Progressive Disclosure
Consistent badge vocabulary Gestalt: Similarity — same language in both stages
Status summary above action buttons Feedback — always know where you stand
Back-nav warning banner Visibility of System Status