# Setrip — Trust & Trip Detail Roadmap Status implementasi yang menaikkan kepercayaan calon peserta — trip detail experience yang meyakinkan + sistem reputasi organizer yang transparan. > **Prinsip:** trust = fungsi dari (a) kelengkapan informasi trip dan (b) reputasi organizer yang transparan. Setiap fitur dievaluasi: apakah memberi calon peserta alasan obyektif untuk percaya? --- ## Audit state sekarang (baseline) **Trip detail (~80% sudah ada):** - ✅ Itinerary, include/exclude, meeting point — schema lengkap di `Trip`, ditampilkan via `TripProgramBlock`. - ✅ Participant preview (kartu confirmed peserta dengan avatar, kota, interests). - ✅ Slot tersisa (X / Y) dengan progress bar berwarna. - ⚠️ Urgency message ada tapi tidak mencolok saat slot menipis. - ⚠️ Itinerary text bebas — organizer butuh hint format supaya konsisten isi lengkap. **Review/trust (~40% sudah ada):** - ✅ Model `TripReview` (rating + comment per trip+user, unique). - ✅ Trust panel di trip detail (verified badge, trips created, avg rating, review count). - ❌ Profil organizer publik `/u/[id]` belum tampilkan rating/review aggregate. - ❌ Belum ada total participants served, completion rate, rating breakdown. - ❌ Belum ada list ulasan terkumpul per organizer. - ❌ `TripStatus.COMPLETED` enum-nya ada tapi tidak pernah di-set. File baseline: [app/trips/[id]/page.tsx](app/trips/%5Bid%5D/page.tsx), [server/services/trust.service.ts](server/services/trust.service.ts), [server/services/review.service.ts](server/services/review.service.ts), [app/u/[id]/page.tsx](app/u/%5Bid%5D/page.tsx). --- ## PR-1 — Trip Detail Polish (UI only) ⏳ Cosmetic. Tidak ada migration, tidak ada perubahan service/repo. | # | Item | Status | File | |---|---|---|---| | 1.1 | Urgency badge mencolok saat `spotsLeft <= 3` ("⚡ Tinggal X spot!") di header progress bar | ⏳ | [app/trips/[id]/page.tsx](app/trips/%5Bid%5D/page.tsx) | | 1.2 | Participant preview ringkas di blok progress ("👥 Sudah join: Andi, Rina, Budi +4") — first impression tanpa scroll | ⏳ | [app/trips/[id]/page.tsx](app/trips/%5Bid%5D/page.tsx) | | 1.3 | Hint deskriptif + placeholder lebih konkret di field itinerary form create-trip | ⏳ | [features/trip/components/create-trip-form.tsx](features/trip/components/create-trip-form.tsx) | **Tindakan manual:** tidak ada. --- ## PR-2 — Organizer Trust Aggregates (service + UI, tanpa migration) ✅ Selesai. `tsc --noEmit` lulus. Tanpa schema baru, tanpa migration. **Keputusan asumsi yang dipakai:** - `tripsCompleted` ≠ `Trip.status = COMPLETED` (status itu tidak pernah di-set). Pakai `endDate < now()` (fallback `date < now()`) AND `status != CLOSED`. - `tripsCancelled` = `Trip.status = CLOSED` (organizer batalkan trip eksplisit). - `completionRate` butuh sample ≥ 3 (`COMPLETION_RATE_MIN_SAMPLE` di [lib/trust.ts](lib/trust.ts)) supaya tidak menyesatkan organizer baru. - Rating breakdown di-render sebagai bar chart kecil (visual cue lebih kuat dari angka mentah). - `OrganizerStatsPanel` di profil publik tidak di-render untuk user yang murni peserta — query Prisma juga di-skip kalau `organizedTrips.length === 0 && !isVerifiedOrganizer`. - Trip detail: stat box "Trip dibuat" diganti jadi **"Trip selesai"** (lebih meaningful) + tambah **"Peserta dilayani"**. Total 3 stat box, masih kompak. | # | Item | Status | File | |---|---|---|---| | 2.1 | Extend `OrganizerTrust` type: `tripsCompleted`, `tripsCancelled`, `totalParticipantsServed`, `completionRate`, `ratingBreakdown` | ✅ | [server/services/trust.service.ts](server/services/trust.service.ts) | | 2.2 | `tripsCompleted` di-derive dari `endDate < now()` (fallback `date < now()`) AND `status != CLOSED` | ✅ | [server/services/trust.service.ts](server/services/trust.service.ts) | | 2.3 | `totalParticipantsServed` = count `TripParticipant CONFIRMED` di trip yang sudah lewat & tidak dibatalkan | ✅ | [server/services/trust.service.ts](server/services/trust.service.ts) | | 2.4 | `completionRate` = `tripsCompleted / (tripsCompleted + tripsCancelled)`. Null bila sample < 3 | ✅ | [server/services/trust.service.ts](server/services/trust.service.ts), [lib/trust.ts](lib/trust.ts) | | 2.5 | `ratingBreakdown` via `prisma.tripReview.groupBy({ by: ['rating'] })` | ✅ | [server/services/trust.service.ts](server/services/trust.service.ts) | | 2.6 | Komponen `OrganizerStatsPanel` (badges + 4 stat box + bar chart breakdown) | ✅ | [features/profile/components/organizer-stats-panel.tsx](features/profile/components/organizer-stats-panel.tsx) | | 2.7 | Update `OrganizerTrustPanel` di trip detail — Trip selesai (+ subtitle "berjalan"), Peserta dilayani, Rating | ✅ | [features/trip/components/organizer-trust-panel.tsx](features/trip/components/organizer-trust-panel.tsx) | | 2.8 | Render `OrganizerStatsPanel` di `/u/[id]` (skip query untuk non-organizer) | ✅ | [app/u/[id]/page.tsx](app/u/%5Bid%5D/page.tsx) | **Tindakan manual:** tidak ada. --- ## PR-3 — Organizer Reviews Aggregator (service + UI, tanpa migration) ✅ Selesai. `tsc --noEmit` lulus. Tanpa schema baru, tanpa migration. **Keputusan asumsi yang dipakai:** - Default limit 20 ulasan terbaru — cukup untuk MVP, tidak perlu pagination dulu. - Komponen RSC (server component, no `"use client"`) — pure render, tidak ada interaktivitas. - Tipe `OrganizerReviewItem` di-extract dari `Awaited>[number]` supaya schema repo = sumber kebenaran tanpa duplikasi tipe. - Fetch trust + reviews via single `Promise.all` di `/u/[id]` (paralel, hemat 1 round-trip). - Komponen di-skip kalau `reviews.length === 0` — biar tidak makin "kosong" di profil organizer baru. Stats panel sudah punya pesan "Belum ada ulasan". - Rating ditampilkan sebagai bintang penuh (`★★★★☆`) bukan angka — visual cue lebih kuat untuk testimoni. - Header section: "X terbaru dari Y ulasan" kalau di-limit, atau cuma "Y ulasan" kalau seluruh list ditampilkan. | # | Item | Status | File | |---|---|---|---| | 3.1 | `reviewService.getReviewsByOrganizer(organizerId, limit?)` + tipe `OrganizerReviewItem` | ✅ | [server/services/review.service.ts](server/services/review.service.ts) | | 3.2 | Repo helper `findByOrganizer` (default limit 20, urut newest, include user + trip) | ✅ | [server/repositories/review.repo.ts](server/repositories/review.repo.ts) | | 3.3 | Komponen `OrganizerReviewsList` (avatar + name + bintang + trip link + tanggal + comment) | ✅ | [features/review/components/organizer-reviews-list.tsx](features/review/components/organizer-reviews-list.tsx) | | 3.4 | Render di `/u/[id]` di bawah `OrganizerStatsPanel`, fetch via `Promise.all` paralel | ✅ | [app/u/[id]/page.tsx](app/u/%5Bid%5D/page.tsx) | **Tindakan manual:** tidak ada. --- ## PR-4 — Trip Completion Mechanism (opsional, butuh diskusi) ⏳ Saat ini `Trip.status = COMPLETED` tidak pernah di-set oleh kode mana pun. PR ini hanya perlu kalau ingin pakai `status` sebagai sumber kebenaran formal (bukan computed-from-`endDate`). Opsi: - **A. Manual** — organizer klik "Tandai trip selesai" pasca-pulang. Pro: kontrol di organizer. Con: gampang lupa, `status` tidak accurate kalau organizer pasif. - **B. Cron job** — daily job set `status = COMPLETED` untuk trip dengan `endDate < today() AND status IN ('OPEN','FULL')`. Pro: otomatis akurat. Con: butuh infra cron (belum ada di project). - **C. Skip — biarkan computed-from-`endDate`** di service layer. Pro: paling sederhana, sejalan dengan PR-2 yang juga compute on-the-fly. Con: field `status` jadi sebagian "live" (OPEN/FULL/CLOSED murni, COMPLETED computed). **Rekomendasi:** **C** dulu sampai ada kebutuhan riil untuk transisi formal (mis. trigger payout organizer pasca-trip atau notif post-trip continuity di Phase C SOCIAL_ROADMAP). | # | Item | Status | |---|---|---| | 4.1 | Pilih opsi A/B/C | ⏳ | | 4.2 | Implementasi sesuai pilihan (atau dokumentasikan keputusan kalau C) | ⏳ | --- ## ❌ Anti-list (yang harus DITOLAK kalau muncul) - **Model `OrganizerReview` terpisah** — `TripReview` sudah cukup, 1 trip = 1 organizer. Bikin model baru = duplikasi data + sumber kebenaran ambigu. - **Denormalisasi cache** (mis. `User.cachedAvgRating`) sebelum aggregate query terbukti lambat. Premature optimization → drift jadi tech debt cepat. - **Auto-hapus review buruk** atau organizer "respon" review (untuk MVP). Bisa nanti — fokus dulu menampilkan data jujur. - **"Trust score" gabungan satu angka** — kasih breakdown agar calon peserta evaluasi sendiri. Single number gampang dimanipulasi & menyesatkan. - **Review user/peserta** (no-show, kooperatif?) — itu C6 di [SOCIAL_ROADMAP.md](SOCIAL_ROADMAP.md). Scope berbeda, jangan campur. - **Rating dengan setengah bintang / kustom 1-10** — tetap 1-5 integer (sudah di schema). Granularitas lebih halus tidak meningkatkan trust, hanya menambah noise. --- ## Saran phasing PR berurutan. Setiap PR mandiri (siap di-deploy): 1. **PR-1** — Trip detail polish. Cepat, low-risk, no migration. **Mulai dari sini.** 2. **PR-2** — Trust aggregates di service + UI. Read-only, no migration. 3. **PR-3** — Reviews list per organizer di service + UI. Read-only, no migration. 4. **PR-4** — Diskusi opsi completion mechanism (atau skip kalau opsi C dipilih). **Pertanyaan terbuka sebelum PR-2:** 1. Apakah `tripsCompleted` count termasuk trip dengan `status = CLOSED` yang `endDate < now()`? Saran: tidak — CLOSED = dibatalkan, dipisah ke `tripsCancelled`. 2. Threshold minimum supaya `completionRate` ditampilkan? Saran: min 3 trip selesai supaya angka tidak menyesatkan (1 trip dibatalkan dari 1 trip = 0% looks bad untuk organizer baru). 3. Tampilkan rating breakdown sebagai bar chart atau hanya angka? Saran: bar chart kecil — visual cue lebih kuat untuk credibility.