213 lines
5.9 KiB
Markdown
213 lines
5.9 KiB
Markdown
---
|
|
name: state-management
|
|
description: Panduan lengkap penggunaan Zustand store untuk Tour Planner App. Gunakan skill ini ketika membaca, menulis, atau menambahkan state baru ke global store, atau saat membuat custom hooks.
|
|
---
|
|
|
|
# Skill: State Management — Zustand Store
|
|
|
|
## Overview
|
|
|
|
Aplikasi ini menggunakan Zustand dengan middleware `persist` untuk menyimpan state ke localStorage.
|
|
File utama: `src/store/tourStore.js`
|
|
localStorage key: `tour-planner-storage`
|
|
|
|
## Full Store Shape
|
|
|
|
```javascript
|
|
{
|
|
// === TRIP INFO ===
|
|
trip: {
|
|
id: null, // string | null
|
|
name: '', // nama trip user
|
|
destination: '', // nama destinasi
|
|
startDate: null, // ISO string
|
|
endDate: null, // ISO string
|
|
totalDays: 0, // number
|
|
coverImage: '', // URL atau path lokal
|
|
type: 'custom', // 'preset' | 'custom'
|
|
category: '', // 'pantai' | 'gunung' | 'kota' | 'budaya'
|
|
},
|
|
|
|
// === SCHEDULE ===
|
|
// Array of days, index = hari ke-N (0-based)
|
|
schedule: [
|
|
{
|
|
dayNumber: 1, // display number
|
|
date: '', // ISO string
|
|
activities: [
|
|
{
|
|
id: '', // uuid
|
|
time: '09:00', // HH:mm
|
|
name: '',
|
|
location: '',
|
|
duration: 60, // menit
|
|
category: '', // 'wisata' | 'makan' | 'transportasi' | 'check-in' | 'lainnya'
|
|
notes: '',
|
|
color: '', // CSS color untuk timeline
|
|
}
|
|
]
|
|
}
|
|
],
|
|
|
|
// === PACKING LIST ===
|
|
packingList: {
|
|
Pakaian: [
|
|
{ id: '', name: '', qty: 1, checked: false }
|
|
],
|
|
Dokumen: [],
|
|
Elektronik: [],
|
|
'Obat-obatan': [],
|
|
Toiletries: [],
|
|
Lainnya: [],
|
|
},
|
|
|
|
// === BUDGET ===
|
|
budget: {
|
|
total: 0, // number (IDR)
|
|
currency: 'IDR', // 'IDR' | 'USD' | 'EUR'
|
|
expenses: [
|
|
{
|
|
id: '', // uuid
|
|
name: '',
|
|
amount: 0,
|
|
category: '', // 'transportasi' | 'akomodasi' | 'makan' | 'aktivitas' | 'oleh-oleh' | 'lainnya'
|
|
day: null, // number (hari ke berapa) | null (umum)
|
|
date: '', // ISO string opsional
|
|
notes: '',
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Actions API
|
|
|
|
```javascript
|
|
const {
|
|
// Trip actions
|
|
setTrip, // (tripData: Partial<trip>) => void
|
|
resetTrip, // () => void
|
|
|
|
// Schedule actions
|
|
initSchedule, // (totalDays: number, startDate: string) => void
|
|
addActivity, // (dayIndex: number, activity: Activity) => void
|
|
updateActivity, // (dayIndex: number, activityId: string, data: Partial<Activity>) => void
|
|
removeActivity, // (dayIndex: number, activityId: string) => void
|
|
reorderActivities,// (dayIndex: number, oldIndex: number, newIndex: number) => void
|
|
moveActivity, // (fromDay: number, toDay: number, activityId: string) => void
|
|
|
|
// Packing actions
|
|
addPackingItem, // (category: string, item: PackingItem) => void
|
|
updatePackingItem,// (category: string, id: string, data: Partial<PackingItem>) => void
|
|
togglePackingItem,// (category: string, id: string) => void
|
|
removePackingItem,// (category: string, id: string) => void
|
|
loadPackingTemplate, // (tripType: string) => void
|
|
|
|
// Budget actions
|
|
setTotalBudget, // (amount: number) => void
|
|
addExpense, // (expense: Expense) => void
|
|
updateExpense, // (id: string, data: Partial<Expense>) => void
|
|
removeExpense, // (id: string) => void
|
|
|
|
// Global
|
|
resetAll, // () => void — reset semua state
|
|
|
|
} = useTourStore();
|
|
```
|
|
|
|
## Custom Hooks
|
|
|
|
Gunakan custom hooks untuk encapsulate logic:
|
|
|
|
### useTripStore.js
|
|
```javascript
|
|
import { useTourStore } from '../store/tourStore';
|
|
|
|
export const useTripStore = () => {
|
|
const { trip, setTrip, resetTrip } = useTourStore();
|
|
|
|
const updateTrip = (data) => setTrip({ ...trip, ...data });
|
|
const isConfigured = trip.id !== null;
|
|
|
|
return { trip, updateTrip, resetTrip, isConfigured };
|
|
};
|
|
```
|
|
|
|
### useSchedule.js
|
|
```javascript
|
|
import { useTourStore } from '../store/tourStore';
|
|
|
|
export const useSchedule = (dayIndex) => {
|
|
const { schedule, addActivity, removeActivity, reorderActivities } = useTourStore();
|
|
|
|
const day = schedule[dayIndex];
|
|
const activities = day?.activities ?? [];
|
|
const sortedActivities = [...activities].sort((a, b) => a.time.localeCompare(b.time));
|
|
|
|
return { day, activities: sortedActivities, addActivity: (a) => addActivity(dayIndex, a), removeActivity: (id) => removeActivity(dayIndex, id) };
|
|
};
|
|
```
|
|
|
|
### useBudget.js
|
|
```javascript
|
|
import { useTourStore } from '../store/tourStore';
|
|
|
|
export const useBudget = () => {
|
|
const { budget, setTotalBudget, addExpense, removeExpense } = useTourStore();
|
|
|
|
const totalSpent = budget.expenses.reduce((sum, e) => sum + e.amount, 0);
|
|
const remaining = budget.total - totalSpent;
|
|
const isOverBudget = remaining < 0;
|
|
const percentUsed = budget.total > 0 ? (totalSpent / budget.total) * 100 : 0;
|
|
|
|
const byCategory = budget.expenses.reduce((acc, e) => {
|
|
acc[e.category] = (acc[e.category] || 0) + e.amount;
|
|
return acc;
|
|
}, {});
|
|
|
|
return { budget, totalSpent, remaining, isOverBudget, percentUsed, byCategory, setTotalBudget, addExpense, removeExpense };
|
|
};
|
|
```
|
|
|
|
## Cara Membuat UUID
|
|
|
|
```javascript
|
|
// Di utils/helpers.js
|
|
export const generateId = () => crypto.randomUUID();
|
|
```
|
|
|
|
## Contoh Penggunaan di Komponen
|
|
|
|
```jsx
|
|
import { useTourStore } from '../store/tourStore';
|
|
|
|
const SchedulePage = () => {
|
|
const { schedule, addActivity } = useTourStore();
|
|
|
|
const handleAddActivity = (dayIndex, activityData) => {
|
|
addActivity(dayIndex, {
|
|
id: crypto.randomUUID(),
|
|
...activityData
|
|
});
|
|
};
|
|
|
|
return ( /* ... */ );
|
|
};
|
|
```
|
|
|
|
## Penting: Jangan Mutasi State Langsung
|
|
|
|
```javascript
|
|
// ❌ SALAH
|
|
state.schedule[0].activities.push(newActivity);
|
|
|
|
// ✅ BENAR (dalam store action)
|
|
set(state => ({
|
|
schedule: state.schedule.map((day, i) =>
|
|
i === dayIndex
|
|
? { ...day, activities: [...day.activities, newActivity] }
|
|
: day
|
|
)
|
|
}));
|
|
```
|