Files
Ready-to-Planner-Tour/.agents/skills/state-management/SKILL.md
T
2026-08-27 19:45:15 +07:00

5.9 KiB

name, description
name description
state-management 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

{
  // === 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

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

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

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

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

// Di utils/helpers.js
export const generateId = () => crypto.randomUUID();

Contoh Penggunaan di Komponen

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

// ❌ 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
  )
}));