diff --git a/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx b/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx index 74926aa2..051ca6aa 100644 --- a/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx +++ b/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx @@ -39,7 +39,14 @@ import { DropdownMenuTrigger, } from '@/figma-make/components/base/dropdown-menu'; import { toast } from 'sonner'; -import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase'; +import useSWR from 'swr'; +import { PhaseApi } from '@/services/api/daily-checklist/phase'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { AxiosError } from 'axios'; +import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { PhaseActivityApi } from '@/services/api/daily-checklist/phase-activity'; +import { PhaseActivity } from '@/types/api/daily-checklist/phase-activity'; // Static categories - tidak bisa CRUD const CATEGORIES = [ @@ -50,11 +57,11 @@ const CATEGORIES = [ ]; const TIME_TYPES = [ - { value: 'umum', label: 'Umum' }, - { value: 'pagi', label: 'Pagi' }, - { value: 'siang', label: 'Siang' }, - { value: 'sore', label: 'Sore' }, - { value: 'malam', label: 'Malam' }, + { value: 'Umum', label: 'Umum' }, + { value: 'Pagi', label: 'Pagi' }, + { value: 'Siang', label: 'Siang' }, + { value: 'Sore', label: 'Sore' }, + { value: 'Malam', label: 'Malam' }, ]; interface Phase { @@ -64,20 +71,46 @@ interface Phase { activityCount?: number; } -interface Activity { - id: string; - phase_id: string; - name: string; - description?: string; - time_type: string; -} - export function MasterAktivitasContent() { const [selectedCategory, setSelectedCategory] = useState(''); - const [phases, setPhases] = useState([]); - const [activities, setActivities] = useState([]); const [selectedPhase, setSelectedPhase] = useState(null); + const { + data: phases, + isLoading: isLoadingPhases, + mutate: refreshPhases, + } = useSWR< + BaseApiResponse, + AxiosError, + SWRHttpKey + >( + selectedCategory + ? `${PhaseApi.basePath}?page=1&limit=100&category=${selectedCategory}` + : '', + httpClientFetcher, + { + keepPreviousData: true, + } + ); + + const { + data: phaseActivities, + isLoading: isLoadingPhaseActivities, + mutate: refreshPhaseActivities, + } = useSWR< + BaseApiResponse, + AxiosError, + SWRHttpKey + >( + selectedPhase?.id + ? `${PhaseActivityApi.basePath}?page=1&limit=100&phase_id=${selectedPhase.id}` + : '', + httpClientFetcher, + { + keepPreviousData: true, + } + ); + const [showPhaseModal, setShowPhaseModal] = useState(false); const [showActivityModal, setShowActivityModal] = useState(false); const [showPhaseDeleteConfirm, setShowPhaseDeleteConfirm] = useState(false); @@ -94,6 +127,16 @@ export function MasterAktivitasContent() { const [phaseModalMode, setPhaseModalMode] = useState<'create' | 'edit'>( 'create' ); + + const filteredPhases = + (isResponseSuccess(phases) && + phases?.data?.filter((phase) => { + return phase.name + .toLowerCase() + .includes(phaseSearchQuery.toLowerCase()); + })) || + []; + const [activityModalMode, setActivityModalMode] = useState<'create' | 'edit'>( 'create' ); @@ -114,115 +157,6 @@ export function MasterAktivitasContent() { setInitialLoading(false); }, []); - // Fetch phases when category changes - useEffect(() => { - if (selectedCategory) { - fetchPhases(selectedCategory); - } else { - setPhases([]); - setSelectedPhase(null); - setActivities([]); - } - }, [selectedCategory]); - - // Fetch activities when phase changes - useEffect(() => { - if (selectedPhase) { - fetchActivities(selectedPhase.id); - } else { - setActivities([]); - } - }, [selectedPhase]); - - const fetchPhases = async ( - category: string, - preserveSelectedPhaseId?: string - ) => { - if (!isSupabaseConfigured()) { - console.warn( - 'Supabase not configured. Please add environment variables.' - ); - return; - } - - try { - const { data, error } = await supabase - .from('phases') - .select('*') - .eq('category', category) - .order('id', { ascending: true }); // ✅ Urutan berdasarkan ID (yang paling awal diinput di atas) - - if (error) { - console.error('Error fetching phases:', error); - toast.error('Gagal memuat data phase'); - return; - } - - // Fetch activity counts for each phase - const phasesWithCounts = await Promise.all( - (data || []).map(async (phase) => { - const { count } = await supabase - .from('phase_activities') - .select('*', { count: 'exact', head: true }) - .eq('phase_id', phase.id); - - return { ...phase, activityCount: count || 0 }; - }) - ); - - setPhases(phasesWithCounts); - - // Preserve selected phase if ID is provided - if (preserveSelectedPhaseId) { - const phaseToSelect = phasesWithCounts.find( - (p) => p.id === preserveSelectedPhaseId - ); - if (phaseToSelect) { - setSelectedPhase(phaseToSelect); - } else if (phasesWithCounts.length > 0) { - setSelectedPhase(phasesWithCounts[0]); - } else { - setSelectedPhase(null); - } - } else { - // Select first phase by default if exists - if (phasesWithCounts.length > 0) { - setSelectedPhase(phasesWithCounts[0]); - } else { - setSelectedPhase(null); - } - } - } catch (error) { - console.error('Error fetching phases:', error); - toast.error('Gagal memuat data phase'); - } - }; - - const fetchActivities = async (phaseId: string) => { - if (!isSupabaseConfigured()) { - return; - } - - try { - const { data, error } = await supabase - .from('phase_activities') - .select('*') - .eq('phase_id', phaseId) - .order('id', { ascending: true }); // ✅ Urutan berdasarkan ID (yang paling awal diinput di atas) - - if (error) { - console.error('Error fetching activities:', error); - toast.error('Gagal memuat data aktivitas'); - return; - } - - setActivities(data || []); - } catch (error) { - console.error('Error fetching activities:', error); - toast.error('Gagal memuat data aktivitas'); - } - }; - // Phase handlers const handleAddPhase = () => { if (!selectedCategory) { @@ -249,15 +183,13 @@ export function MasterAktivitasContent() { return; } - if (!selectedCategory) { - toast.error('Pilih kategori terlebih dahulu'); + if (phaseForm.name.trim().length < 3) { + toast.error('Nama phase minimal 3 karakter!'); return; } - if (!isSupabaseConfigured()) { - toast.error( - 'Supabase belum dikonfigurasi. Tambahkan environment variables.' - ); + if (!selectedCategory) { + toast.error('Pilih kategori terlebih dahulu'); return; } @@ -265,40 +197,40 @@ export function MasterAktivitasContent() { try { if (phaseModalMode === 'create') { - const { error } = await supabase.from('phases').insert([ - { - name: phaseForm.name.trim(), - category: selectedCategory, - }, - ]); + const createPhaseResponse = await PhaseApi.create({ + category: selectedCategory, + name: phaseForm.name.trim(), + }); - if (error) { - console.error('Error creating phase:', error); + if (isResponseError(createPhaseResponse)) { + console.error('Error creating phase:', createPhaseResponse.message); toast.error('Gagal menambahkan phase'); return; } + refreshPhases(); toast.success('Phase berhasil ditambahkan'); } else { - const { error } = await supabase - .from('phases') - .update({ + const updatePhaseResponse = await PhaseApi.update( + Number(phaseForm.id), + { name: phaseForm.name.trim(), - }) - .eq('id', phaseForm.id); + } + ); - if (error) { - console.error('Error updating phase:', error); - toast.error('Gagal mengubah phase'); + if (isResponseError(updatePhaseResponse)) { + console.error('Error creating phase:', updatePhaseResponse.message); + toast.error('Gagal menambahkan phase'); return; } + refreshPhases(); + toast.success('Phase berhasil diubah'); } setShowPhaseModal(false); setPhaseForm({ id: '', name: '' }); - await fetchPhases(selectedCategory); } catch (error) { console.error('Error saving phase:', error); toast.error('Terjadi kesalahan saat menyimpan phase'); @@ -318,32 +250,16 @@ export function MasterAktivitasContent() { setLoading(true); try { - // First, delete all activities in this phase - const { error: activitiesError } = await supabase - .from('phase_activities') - .delete() - .eq('phase_id', phaseToDelete); + const deletePhaseResponse = await PhaseApi.delete(Number(phaseToDelete)); - if (activitiesError) { - console.error('Error deleting phase activities:', activitiesError); - toast.error('Gagal menghapus aktivitas phase'); - setLoading(false); - return; - } - - // Then delete the phase - const { error: phaseError } = await supabase - .from('phases') - .delete() - .eq('id', phaseToDelete); - - if (phaseError) { - console.error('Error deleting phase:', phaseError); + if (isResponseError(deletePhaseResponse)) { + console.error('Error deleting phase:', deletePhaseResponse.message); toast.error('Gagal menghapus phase'); setLoading(false); return; } + refreshPhases(); toast.success('Phase dan semua aktivitasnya berhasil dihapus'); setShowPhaseDeleteConfirm(false); setPhaseToDelete(null); @@ -351,10 +267,7 @@ export function MasterAktivitasContent() { // Clear selection if deleted phase was selected if (selectedPhase?.id === phaseToDelete) { setSelectedPhase(null); - setActivities([]); } - - await fetchPhases(selectedCategory); } catch (error) { console.error('Error deleting phase:', error); toast.error('Terjadi kesalahan saat menghapus phase'); @@ -374,10 +287,10 @@ export function MasterAktivitasContent() { setShowActivityModal(true); }; - const handleEditActivity = (activity: Activity) => { + const handleEditActivity = (activity: PhaseActivity) => { setActivityModalMode('edit'); setActivityForm({ - id: activity.id, + id: String(activity.id), name: activity.name, description: activity.description || '', time_type: activity.time_type, @@ -391,15 +304,13 @@ export function MasterAktivitasContent() { return; } - if (!selectedPhase) { - toast.error('Pilih phase terlebih dahulu'); + if (activityForm.name.trim().length < 3) { + toast.error('Nama aktivitas minimal 3 karakter!'); return; } - if (!isSupabaseConfigured()) { - toast.error( - 'Supabase belum dikonfigurasi. Tambahkan environment variables.' - ); + if (!selectedPhase) { + toast.error('Pilih phase terlebih dahulu'); return; } @@ -407,47 +318,49 @@ export function MasterAktivitasContent() { try { if (activityModalMode === 'create') { - const { error } = await supabase.from('phase_activities').insert([ - { - phase_id: selectedPhase.id, - name: activityForm.name.trim(), - description: activityForm.description.trim() || null, - time_type: activityForm.time_type, - }, - ]); + const createActivityResponse = await PhaseActivityApi.create({ + phase_id: Number(selectedPhase.id), + name: activityForm.name.trim(), + description: activityForm.description.trim() || '', + time_type: activityForm.time_type, + }); - if (error) { - console.error('Error creating activity:', error); + if (isResponseError(createActivityResponse)) { + console.error( + 'Error creating activity:', + createActivityResponse.message + ); toast.error('Gagal menambahkan aktivitas'); return; } + refreshPhaseActivities(); toast.success('Aktivitas berhasil ditambahkan'); } else { - const { error } = await supabase - .from('phase_activities') - .update({ + const updateActivityResponse = await PhaseActivityApi.update( + Number(activityForm.id), + { name: activityForm.name.trim(), - description: activityForm.description.trim() || null, + description: activityForm.description.trim() || '', time_type: activityForm.time_type, - }) - .eq('id', activityForm.id); + } + ); - if (error) { - console.error('Error updating activity:', error); + if (isResponseError(updateActivityResponse)) { + console.error( + 'Error updating activity:', + updateActivityResponse.message + ); toast.error('Gagal mengubah aktivitas'); return; } + refreshPhaseActivities(); toast.success('Aktivitas berhasil diubah'); } setShowActivityModal(false); setActivityForm({ id: '', name: '', description: '', time_type: 'umum' }); - await fetchActivities(selectedPhase.id); - if (selectedCategory) { - await fetchPhases(selectedCategory, selectedPhase.id); // ✅ Preserve selected phase - } } catch (error) { console.error('Error saving activity:', error); toast.error('Terjadi kesalahan saat menyimpan aktivitas'); @@ -467,22 +380,23 @@ export function MasterAktivitasContent() { setLoading(true); try { - const { error } = await supabase - .from('phase_activities') - .delete() - .eq('id', activityToDelete); + const deleteActivityResponse = await PhaseActivityApi.delete( + Number(activityToDelete) + ); - if (error) { - console.error('Error deleting activity:', error); + if (isResponseError(deleteActivityResponse)) { + console.error( + 'Error deleting activity:', + deleteActivityResponse.message + ); toast.error('Gagal menghapus aktivitas'); return; } + refreshPhaseActivities(); toast.success('Aktivitas berhasil dihapus'); setShowActivityDeleteConfirm(false); setActivityToDelete(null); - await fetchActivities(selectedPhase.id); - await fetchPhases(selectedCategory, selectedPhase.id); // ✅ Preserve selected phase } catch (error) { console.error('Error deleting activity:', error); toast.error('Terjadi kesalahan saat menghapus aktivitas'); @@ -491,16 +405,12 @@ export function MasterAktivitasContent() { } }; - const filteredPhases = phases.filter((phase) => - phase.name.toLowerCase().includes(phaseSearchQuery.toLowerCase()) - ); - const getTimeTypeLabel = (timeType: string) => { return TIME_TYPES.find((t) => t.value === timeType)?.label || timeType; }; const getTimeTypeBadgeClass = (timeType: string) => { - switch (timeType) { + switch (timeType.toLowerCase()) { case 'umum': return 'bg-gray-50 text-gray-700 border-gray-300'; case 'pagi': @@ -610,7 +520,8 @@ export function MasterAktivitasContent() { {/* Phase List */}
- {filteredPhases.length === 0 ? ( + {!isResponseSuccess(phases) || + (isResponseSuccess(phases) && phases.data?.length === 0) ? (
{phaseSearchQuery ? 'Tidak ada phase yang ditemukan' @@ -684,9 +595,11 @@ export function MasterAktivitasContent() {

Aktivitas di Phase: {selectedPhase.name}

-

- {activities.length} aktivitas -

+ {isResponseSuccess(phaseActivities) && ( +

+ {phaseActivities.data?.length} aktivitas +

+ )}