230 lines
7.0 KiB
Markdown
230 lines
7.0 KiB
Markdown
---
|
|
name: feature-guide
|
|
description: Panduan implementasi masing-masing fitur utama Tour Planner App (Trip Selection, Schedule Builder, Packing List, Budget Calculator). Gunakan skill ini saat memulai atau melanjutkan pengerjaan satu halaman/fitur tertentu.
|
|
---
|
|
|
|
# Skill: Feature Guide — Tour Planner App
|
|
|
|
## Fitur 1: Trip Selection (Home Page)
|
|
|
|
**File:** `src/pages/Home.jsx`
|
|
|
|
### Alur User
|
|
1. User membuka aplikasi → landing di halaman Home
|
|
2. Bisa search destinasi atau browse dari grid card
|
|
3. Filter berdasarkan kategori dan budget
|
|
4. Klik card → modal konfirmasi trip (isi tanggal, nama trip)
|
|
5. Confirm → redirect ke `/schedule`
|
|
|
|
### Data yang Diperlukan
|
|
```javascript
|
|
// src/data/destinations.js
|
|
export const destinations = [
|
|
{
|
|
id: 'bali-001',
|
|
name: 'Bali',
|
|
country: 'Indonesia',
|
|
category: 'pantai', // 'pantai' | 'gunung' | 'kota' | 'budaya'
|
|
description: 'Surga tropis...',
|
|
highlights: ['Tanah Lot', 'Ubud', 'Kuta Beach'],
|
|
estimatedBudget: { min: 2000000, max: 5000000 },
|
|
popularDuration: 4, // hari
|
|
rating: 4.8,
|
|
reviewCount: 12400,
|
|
image: '/assets/bali.jpg', // atau URL picsum
|
|
tags: ['romantis', 'budaya', 'alam'],
|
|
bestTime: 'April - Oktober',
|
|
},
|
|
// minimal 10 destinasi
|
|
];
|
|
```
|
|
|
|
### State yang Diupdate
|
|
```javascript
|
|
setTrip({
|
|
id: destination.id,
|
|
name: tripName, // input user
|
|
destination: destination.name,
|
|
startDate: startDate, // dari date picker
|
|
endDate: endDate,
|
|
totalDays: diffInDays(startDate, endDate) + 1,
|
|
coverImage: destination.image,
|
|
type: 'preset',
|
|
category: destination.category,
|
|
});
|
|
initSchedule(totalDays, startDate); // inisialisasi array schedule
|
|
```
|
|
|
|
### Komponen yang Dibutuhkan
|
|
- `TripCard.jsx` — card destinasi dengan image, info, hover effect
|
|
- `TripSelector.jsx` — modal form input tanggal & nama trip
|
|
- `SearchBar.jsx` — search dengan debounce
|
|
- `FilterBar.jsx` — filter kategori (pills/tabs)
|
|
|
|
---
|
|
|
|
## Fitur 2: Daily Schedule Builder (Schedule Page)
|
|
|
|
**File:** `src/pages/Schedule.jsx`
|
|
|
|
### Alur User
|
|
1. User masuk ke `/schedule`
|
|
2. Melihat tab per hari (Day 1, Day 2, ...)
|
|
3. Klik "+ Tambah Aktivitas" → modal form
|
|
4. Aktivitas ditampilkan di timeline vertikal
|
|
5. Bisa drag & drop untuk reorder
|
|
|
|
### Komponen yang Dibutuhkan
|
|
- `DayTabs.jsx` — tab navigasi per hari
|
|
- `ScheduleTimeline.jsx` — timeline visual
|
|
- `ActivityItem.jsx` — satu item aktivitas (draggable)
|
|
- `AddActivityModal.jsx` — form tambah/edit aktivitas
|
|
|
|
### Implementasi Drag & Drop
|
|
```jsx
|
|
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
|
import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
|
|
import { useSortable } from '@dnd-kit/sortable';
|
|
import { CSS } from '@dnd-kit/utilities';
|
|
|
|
// Di ActivityItem — tambahkan useSortable
|
|
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: activity.id });
|
|
const style = { transform: CSS.Transform.toString(transform), transition };
|
|
|
|
// Di ScheduleTimeline — wrap dengan DndContext
|
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
|
<SortableContext items={activityIds} strategy={verticalListSortingStrategy}>
|
|
{activities.map(a => <ActivityItem key={a.id} activity={a} />)}
|
|
</SortableContext>
|
|
</DndContext>
|
|
```
|
|
|
|
### Template Aktivitas Cepat
|
|
```javascript
|
|
// src/data/activities.js
|
|
export const activityTemplates = [
|
|
{ name: 'Sarapan', category: 'makan', duration: 60, defaultTime: '07:00' },
|
|
{ name: 'Check-in Hotel', category: 'check-in', duration: 30, defaultTime: '14:00' },
|
|
{ name: 'Makan Siang', category: 'makan', duration: 60, defaultTime: '12:00' },
|
|
{ name: 'Makan Malam', category: 'makan', duration: 90, defaultTime: '18:30' },
|
|
{ name: 'Kunjungan Wisata', category: 'wisata', duration: 120, defaultTime: '10:00' },
|
|
{ name: 'Belanja Oleh-oleh', category: 'lainnya', duration: 60, defaultTime: '16:00' },
|
|
{ name: 'Berangkat/Transportasi', category: 'transportasi', duration: 180, defaultTime: '06:00' },
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## Fitur 3: Packing List Manager (PackingList Page)
|
|
|
|
**File:** `src/pages/PackingList.jsx`
|
|
|
|
### Alur User
|
|
1. User masuk ke `/packing`
|
|
2. Melihat daftar per kategori dengan progress
|
|
3. Centang item yang sudah dipak
|
|
4. Tambah item baru di kategori tertentu
|
|
5. Load template sesuai tipe trip
|
|
|
|
### Template Packing
|
|
```javascript
|
|
// src/data/packingTemplates.js
|
|
export const packingTemplates = {
|
|
pantai: {
|
|
Pakaian: [
|
|
{ name: 'Baju pantai', qty: 3 },
|
|
{ name: 'Celana pendek', qty: 2 },
|
|
{ name: 'Kacamata hitam', qty: 1 },
|
|
],
|
|
// ...
|
|
},
|
|
gunung: { /* ... */ },
|
|
kota: { /* ... */ },
|
|
budaya: { /* ... */ },
|
|
};
|
|
```
|
|
|
|
### Komponen yang Dibutuhkan
|
|
- `PackingOverview.jsx` — statistik total item & progress keseluruhan
|
|
- `PackingCategory.jsx` — section per kategori dengan sub-list
|
|
- `PackingItem.jsx` — checkbox item + qty counter + delete
|
|
|
|
### Logic Progress
|
|
```javascript
|
|
const getTotalStats = (packingList) => {
|
|
let total = 0, checked = 0;
|
|
Object.values(packingList).forEach(items => {
|
|
items.forEach(item => {
|
|
total++;
|
|
if (item.checked) checked++;
|
|
});
|
|
});
|
|
return { total, checked, percent: total > 0 ? Math.round((checked/total)*100) : 0 };
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Fitur 4: Budget Calculator (Budget Page)
|
|
|
|
**File:** `src/pages/Budget.jsx`
|
|
|
|
### Alur User
|
|
1. User masuk ke `/budget`
|
|
2. Set total budget di bagian atas
|
|
3. Tambah pengeluaran satu per satu
|
|
4. Melihat ringkasan dan chart
|
|
5. Warning merah jika over budget
|
|
|
|
### Komponen yang Dibutuhkan
|
|
- `BudgetHeader.jsx` — input total budget + overview card (total, spent, remaining)
|
|
- `BudgetChart.jsx` — Recharts PieChart + BarChart
|
|
- `ExpenseList.jsx` — list semua pengeluaran
|
|
- `AddExpenseModal.jsx` — form tambah pengeluaran
|
|
|
|
### Recharts Integration
|
|
```jsx
|
|
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer, BarChart, Bar, XAxis, YAxis } from 'recharts';
|
|
|
|
// Data untuk pie chart
|
|
const pieData = Object.entries(byCategory).map(([name, value]) => ({ name, value }));
|
|
|
|
<ResponsiveContainer width="100%" height={240}>
|
|
<PieChart>
|
|
<Pie data={pieData} cx="50%" cy="50%" outerRadius={80} dataKey="value">
|
|
{pieData.map((entry, index) => (
|
|
<Cell key={index} fill={BUDGET_CATEGORY_COLORS[entry.name]} />
|
|
))}
|
|
</Pie>
|
|
<Tooltip formatter={(value) => formatIDR(value)} />
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
```
|
|
|
|
### Format Currency
|
|
```javascript
|
|
// src/utils/formatCurrency.js
|
|
export const formatIDR = (amount) =>
|
|
new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(amount);
|
|
|
|
export const formatUSD = (amount) =>
|
|
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
|
```
|
|
|
|
---
|
|
|
|
## Navigasi Antar Halaman
|
|
|
|
```
|
|
/ (Home) → pilih trip
|
|
/schedule → jadwal harian
|
|
/packing → packing list
|
|
/budget → kalkulasi budget
|
|
```
|
|
|
|
Navbar harus selalu tampil dan menampilkan:
|
|
- Logo / nama app
|
|
- Link ke semua 4 halaman
|
|
- Indikator halaman aktif
|
|
- Badge progress (% packing done, budget status)
|