Files
lti-web-client/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx
T
2026-01-12 17:31:00 +07:00

917 lines
32 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { Plus, MoreVertical, Pencil, Trash2, Search } from 'lucide-react';
import { Card, CardContent } from '@/figma-make/components/base/card';
import { Button } from '@/figma-make/components/base/button';
import { Label } from '@/figma-make/components/base/label';
import { Input } from '@/figma-make/components/base/input';
import { Textarea } from '@/figma-make/components/base/textarea';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/figma-make/components/base/select';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/figma-make/components/base/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/figma-make/components/base/alert-dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/figma-make/components/base/dropdown-menu';
import { toast } from 'sonner';
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';
import { Phase } from '@/types/api/daily-checklist/phase';
// Static categories - tidak bisa CRUD
const CATEGORIES = [
{ value: 'pullet_open', label: 'Pullet Open' },
{ value: 'pullet_close', label: 'Pullet Close' },
{ value: 'produksi_open', label: 'Produksi Open' },
{ value: 'produksi_close', label: 'Produksi Close' },
];
const TIME_TYPES = [
{ value: 'Umum', label: 'Umum' },
{ value: 'Pagi', label: 'Pagi' },
{ value: 'Siang', label: 'Siang' },
{ value: 'Sore', label: 'Sore' },
{ value: 'Malam', label: 'Malam' },
];
export function MasterAktivitasContent() {
const [selectedCategory, setSelectedCategory] = useState<string>('');
const [selectedPhase, setSelectedPhase] = useState<Phase | null>(null);
const {
data: phases,
isLoading: isLoadingPhases,
mutate: refreshPhases,
} = useSWR<
BaseApiResponse<Phase[] | undefined>,
AxiosError<BaseApiResponse>,
SWRHttpKey
>(
selectedCategory
? `${PhaseApi.basePath}?page=1&limit=100&category=${selectedCategory}`
: '',
httpClientFetcher,
{
keepPreviousData: true,
}
);
const {
data: phaseActivities,
isLoading: isLoadingPhaseActivities,
mutate: refreshPhaseActivities,
} = useSWR<
BaseApiResponse<PhaseActivity[] | undefined>,
AxiosError<BaseApiResponse>,
SWRHttpKey
>(
selectedPhase?.id
? `${PhaseActivityApi.basePath}?page=1&limit=100&phase_ids=${selectedPhase.id}`
: '',
httpClientFetcher,
{
keepPreviousData: true,
}
);
const [showPhaseModal, setShowPhaseModal] = useState(false);
const [showActivityModal, setShowActivityModal] = useState(false);
const [showPhaseDeleteConfirm, setShowPhaseDeleteConfirm] = useState(false);
const [showActivityDeleteConfirm, setShowActivityDeleteConfirm] =
useState(false);
const [phaseToDelete, setPhaseToDelete] = useState<string | null>(null);
const [activityToDelete, setActivityToDelete] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [initialLoading, setInitialLoading] = useState(true);
const [phaseSearchQuery, setPhaseSearchQuery] = useState('');
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'
);
const [phaseForm, setPhaseForm] = useState({
id: '',
name: '',
});
const [activityForm, setActivityForm] = useState({
id: '',
name: '',
description: '',
time_type: 'umum',
});
useEffect(() => {
setInitialLoading(false);
}, []);
// Phase handlers
const handleAddPhase = () => {
if (!selectedCategory) {
toast.error('Pilih kategori terlebih dahulu');
return;
}
setPhaseModalMode('create');
setPhaseForm({ id: '', name: '' });
setShowPhaseModal(true);
};
const handleEditPhase = (phase: Phase) => {
setPhaseModalMode('edit');
setPhaseForm({
id: String(phase.id),
name: phase.name,
});
setShowPhaseModal(true);
};
const handleSavePhase = async () => {
if (!phaseForm.name.trim()) {
toast.error('Nama phase harus diisi');
return;
}
if (phaseForm.name.trim().length < 3) {
toast.error('Nama phase minimal 3 karakter!');
return;
}
if (!selectedCategory) {
toast.error('Pilih kategori terlebih dahulu');
return;
}
setLoading(true);
try {
if (phaseModalMode === 'create') {
const createPhaseResponse = await PhaseApi.create({
category: selectedCategory,
name: phaseForm.name.trim(),
});
if (isResponseError(createPhaseResponse)) {
console.error('Error creating phase:', createPhaseResponse.message);
toast.error('Gagal menambahkan phase');
return;
}
refreshPhases();
toast.success('Phase berhasil ditambahkan');
} else {
const updatePhaseResponse = await PhaseApi.update(
Number(phaseForm.id),
{
name: phaseForm.name.trim(),
}
);
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: '' });
} catch (error) {
console.error('Error saving phase:', error);
toast.error('Terjadi kesalahan saat menyimpan phase');
} finally {
setLoading(false);
}
};
const handleDeletePhaseClick = (phaseId: string) => {
setPhaseToDelete(phaseId);
setShowPhaseDeleteConfirm(true);
};
const handleConfirmDeletePhase = async () => {
if (!phaseToDelete || !selectedCategory) return;
setLoading(true);
try {
const deletePhaseResponse = await PhaseApi.delete(Number(phaseToDelete));
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);
// Clear selection if deleted phase was selected
if (selectedPhase?.id === Number(phaseToDelete)) {
setSelectedPhase(null);
}
} catch (error) {
console.error('Error deleting phase:', error);
toast.error('Terjadi kesalahan saat menghapus phase');
} finally {
setLoading(false);
}
};
// Activity handlers
const handleAddActivity = () => {
if (!selectedPhase) {
toast.error('Pilih phase terlebih dahulu');
return;
}
setActivityModalMode('create');
setActivityForm({ id: '', name: '', description: '', time_type: 'umum' });
setShowActivityModal(true);
};
const handleEditActivity = (activity: PhaseActivity) => {
setActivityModalMode('edit');
setActivityForm({
id: String(activity.id),
name: activity.name,
description: activity.description || '',
time_type: activity.time_type,
});
setShowActivityModal(true);
};
const handleSaveActivity = async () => {
if (!activityForm.name.trim()) {
toast.error('Nama aktivitas harus diisi');
return;
}
if (activityForm.name.trim().length < 3) {
toast.error('Nama aktivitas minimal 3 karakter!');
return;
}
if (!selectedPhase) {
toast.error('Pilih phase terlebih dahulu');
return;
}
setLoading(true);
try {
if (activityModalMode === 'create') {
const createActivityResponse = await PhaseActivityApi.create({
phase_id: Number(selectedPhase.id),
name: activityForm.name.trim(),
description: activityForm.description.trim() || '',
time_type: activityForm.time_type,
});
if (isResponseError(createActivityResponse)) {
console.error(
'Error creating activity:',
createActivityResponse.message
);
toast.error('Gagal menambahkan aktivitas');
return;
}
refreshPhases();
refreshPhaseActivities();
toast.success('Aktivitas berhasil ditambahkan');
} else {
const updateActivityResponse = await PhaseActivityApi.update(
Number(activityForm.id),
{
name: activityForm.name.trim(),
description: activityForm.description.trim() || '',
time_type: activityForm.time_type,
}
);
if (isResponseError(updateActivityResponse)) {
console.error(
'Error updating activity:',
updateActivityResponse.message
);
toast.error('Gagal mengubah aktivitas');
return;
}
refreshPhases();
refreshPhaseActivities();
toast.success('Aktivitas berhasil diubah');
}
setShowActivityModal(false);
setActivityForm({ id: '', name: '', description: '', time_type: 'umum' });
} catch (error) {
console.error('Error saving activity:', error);
toast.error('Terjadi kesalahan saat menyimpan aktivitas');
} finally {
setLoading(false);
}
};
const handleDeleteActivityClick = (activityId: string) => {
setActivityToDelete(activityId);
setShowActivityDeleteConfirm(true);
};
const handleConfirmDeleteActivity = async () => {
if (!activityToDelete || !selectedPhase || !selectedCategory) return;
setLoading(true);
try {
const deleteActivityResponse = await PhaseActivityApi.delete(
Number(activityToDelete)
);
if (isResponseError(deleteActivityResponse)) {
console.error(
'Error deleting activity:',
deleteActivityResponse.message
);
toast.error('Gagal menghapus aktivitas');
return;
}
refreshPhases();
refreshPhaseActivities();
toast.success('Aktivitas berhasil dihapus');
setShowActivityDeleteConfirm(false);
setActivityToDelete(null);
} catch (error) {
console.error('Error deleting activity:', error);
toast.error('Terjadi kesalahan saat menghapus aktivitas');
} finally {
setLoading(false);
}
};
const getTimeTypeLabel = (timeType: string) => {
return TIME_TYPES.find((t) => t.value === timeType)?.label || timeType;
};
const getTimeTypeBadgeClass = (timeType: string) => {
switch (timeType.toLowerCase()) {
case 'umum':
return 'bg-gray-50 text-gray-700 border-gray-300';
case 'pagi':
return 'bg-orange-50 text-orange-700 border-orange-300';
case 'siang':
return 'bg-amber-50 text-amber-700 border-amber-300';
case 'sore':
return 'bg-purple-50 text-purple-700 border-purple-300';
case 'malam':
return 'bg-indigo-50 text-indigo-700 border-indigo-300';
default:
return 'bg-gray-50 text-gray-700 border-gray-300';
}
};
if (initialLoading) {
return (
<div className='min-h-screen'>
<div className='p-6'>
<div className='mb-6'>
<h1 className='text-2xl font-semibold text-gray-900'>
Master Aktivitas
</h1>
<p className='text-sm text-gray-600 mt-1'>
Master Data <span className='text-[#0069e0]'>Aktivitas</span>
</p>
</div>
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white'>
<CardContent className='p-12 text-center text-gray-500'>
Memuat data...
</CardContent>
</Card>
</div>
</div>
);
}
return (
<div className='min-h-screen'>
<div className='p-6'>
{/* Page Title */}
<div className='mb-6'>
<h1 className='text-2xl font-semibold text-gray-900'>
Master Aktivitas
</h1>
<p className='text-sm text-gray-600 mt-1'>
Master Data <span className='text-[#0069e0]'>Aktivitas</span>
</p>
</div>
{/* Category Selector Card */}
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white mb-6'>
<CardContent className='p-6'>
<div className='max-w-md'>
<Label htmlFor='category'>
Kategori <span className='text-red-500'>*</span>
</Label>
<Select
value={selectedCategory}
onValueChange={setSelectedCategory}
>
<SelectTrigger id='category' className='mt-1.5 border-gray-200'>
<SelectValue placeholder='Pilih kategori aktivitas' />
</SelectTrigger>
<SelectContent>
{CATEGORIES.map((cat) => (
<SelectItem key={cat.value} value={cat.value}>
{cat.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className='text-xs text-gray-500 mt-2'>
Pilih kategori untuk melihat dan mengelola phase dan aktivitas
</p>
</div>
</CardContent>
</Card>
{selectedCategory ? (
<div className='grid grid-cols-1 lg:grid-cols-5 gap-6'>
{/* LEFT PANEL: Phase List */}
<div className='lg:col-span-2'>
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white'>
<CardContent className='p-0'>
{/* Phase Toolbar */}
<div className='flex items-center justify-between gap-4 p-6 border-b border-gray-200/60'>
<div className='relative flex-1'>
<Search className='absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4' />
<Input
type='text'
placeholder='Cari phase...'
value={phaseSearchQuery}
onChange={(e) => setPhaseSearchQuery(e.target.value)}
className='pl-10 border-gray-200'
/>
</div>
<Button
onClick={handleAddPhase}
size='sm'
className='bg-[#0069e0] hover:bg-[#0052b3] text-white whitespace-nowrap'
>
<Plus className='w-4 h-4 mr-1' />
Phase
</Button>
</div>
{/* Phase List */}
<div className='divide-y divide-gray-200/60'>
{!isResponseSuccess(phases) ||
(isResponseSuccess(phases) && phases.data?.length === 0) ? (
<div className='p-8 text-center text-gray-500'>
{phaseSearchQuery
? 'Tidak ada phase yang ditemukan'
: 'Belum ada data phase'}
</div>
) : (
filteredPhases.map((phase) => (
<div
key={phase.id}
className={`flex items-center justify-between p-4 cursor-pointer transition-colors ${
selectedPhase?.id === phase.id
? 'bg-blue-50 border-l-4 border-[#0069e0]'
: 'hover:bg-gray-50'
}`}
onClick={() => setSelectedPhase(phase)}
>
<div className='flex-1 min-w-0'>
<p className='text-sm font-medium text-gray-900 truncate'>
{phase.name}
</p>
<p className='text-xs text-gray-500 mt-1'>
{phase.activity_count || 0} aktivitas
</p>
</div>
<DropdownMenu>
<DropdownMenuTrigger
asChild
onClick={(e) => e.stopPropagation()}
>
<Button
variant='ghost'
size='sm'
className='h-8 w-8 p-0 hover:bg-gray-100'
>
<MoreVertical className='h-4 w-4 text-gray-600' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem
onClick={() => handleEditPhase(phase)}
>
<Pencil className='mr-2 h-4 w-4' />
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
handleDeletePhaseClick(String(phase.id))
}
className='text-red-600'
>
<Trash2 className='mr-2 h-4 w-4' />
Hapus
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
))
)}
</div>
</CardContent>
</Card>
</div>
{/* RIGHT PANEL: Activity Detail */}
<div className='lg:col-span-3'>
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white'>
<CardContent className='p-0'>
{selectedPhase ? (
<>
{/* Activity Header */}
<div className='flex items-center justify-between p-6 border-b border-gray-200/60'>
<div>
<h2 className='text-lg font-semibold text-gray-900'>
Aktivitas di Phase: {selectedPhase.name}
</h2>
{isResponseSuccess(phaseActivities) && (
<p className='text-sm text-gray-600 mt-0.5'>
{phaseActivities.data?.length} aktivitas
</p>
)}
</div>
<Button
onClick={handleAddActivity}
className='bg-[#0069e0] hover:bg-[#0052b3] text-white'
>
<Plus className='w-4 h-4 mr-2' />
Tambah Aktivitas
</Button>
</div>
{/* Activity Table */}
<div className='overflow-x-auto'>
<table className='w-full'>
<thead>
<tr className='border-b border-gray-200/60 bg-gray-50/50'>
<th className='text-left py-3.5 px-6 text-sm font-semibold text-gray-700'>
Nama Aktivitas
</th>
<th className='text-center py-3.5 px-6 text-sm font-semibold text-gray-700 w-[80px]'>
Aksi
</th>
</tr>
</thead>
<tbody className='divide-y divide-gray-200/60'>
{!isResponseSuccess(phaseActivities) ||
(isResponseSuccess(phaseActivities) &&
phaseActivities.data?.length === 0) ? (
<tr>
<td
colSpan={2}
className='text-center py-12 text-gray-500'
>
Belum ada aktivitas di phase ini
</td>
</tr>
) : (
phaseActivities.data?.map((activity) => (
<tr
key={activity.id}
className='hover:bg-blue-50/30 transition-colors'
>
<td className='py-3.5 px-6'>
<div className='flex items-center gap-2'>
<div className='flex-1'>
<p className='text-sm text-gray-900'>
{activity.name}
</p>
{activity.description && (
<p className='text-xs text-gray-500 mt-1'>
{activity.description}
</p>
)}
</div>
<span
className={`text-xs px-2 py-0.5 rounded-full ${getTimeTypeBadgeClass(activity.time_type)}`}
>
{getTimeTypeLabel(activity.time_type)}
</span>
</div>
</td>
<td className='py-3.5 px-6 text-center'>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant='ghost'
size='sm'
className='h-8 w-8 p-0 hover:bg-gray-100'
>
<MoreVertical className='h-4 w-4 text-gray-600' />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem
onClick={() =>
handleEditActivity(activity)
}
>
<Pencil className='mr-2 h-4 w-4' />
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
handleDeleteActivityClick(
String(activity.id)
)
}
className='text-red-600'
>
<Trash2 className='mr-2 h-4 w-4' />
Hapus
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</>
) : (
<div className='p-12 text-center text-gray-500'>
Pilih phase untuk melihat aktivitas
</div>
)}
</CardContent>
</Card>
</div>
</div>
) : (
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white'>
<CardContent className='p-12 text-center text-gray-500'>
Pilih kategori terlebih dahulu untuk mengelola phase dan aktivitas
</CardContent>
</Card>
)}
</div>
{/* Phase Add/Edit Modal */}
<Dialog open={showPhaseModal} onOpenChange={setShowPhaseModal}>
<DialogContent className='sm:max-w-md bg-white rounded-xl shadow-lg'>
<DialogHeader>
<DialogTitle>
{phaseModalMode === 'create' ? 'Tambah Phase' : 'Edit Phase'}
</DialogTitle>
<DialogDescription>
{phaseModalMode === 'create'
? 'Masukkan detail phase baru'
: 'Ubah detail phase'}
</DialogDescription>
</DialogHeader>
<div className='space-y-4 py-4'>
<div>
<Label htmlFor='nama-phase'>
Nama Phase <span className='text-red-500'>*</span>
</Label>
<Input
id='nama-phase'
value={phaseForm.name}
onChange={(e) =>
setPhaseForm({ ...phaseForm, name: e.target.value })
}
placeholder='Masukkan nama phase'
className='mt-1.5'
disabled={loading}
/>
</div>
</div>
<DialogFooter>
<Button
variant='outline'
onClick={() => setShowPhaseModal(false)}
disabled={loading}
>
Batal
</Button>
<Button
onClick={handleSavePhase}
disabled={loading}
className='bg-[#0069e0] hover:bg-[#0052b3] text-white'
>
{loading ? 'Menyimpan...' : 'Simpan'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Activity Add/Edit Modal */}
<Dialog open={showActivityModal} onOpenChange={setShowActivityModal}>
<DialogContent className='sm:max-w-md bg-white rounded-xl shadow-lg'>
<DialogHeader>
<DialogTitle>
{activityModalMode === 'create'
? 'Tambah Aktivitas'
: 'Edit Aktivitas'}
</DialogTitle>
<DialogDescription>
{activityModalMode === 'create'
? 'Masukkan detail aktivitas baru'
: 'Ubah detail aktivitas'}
</DialogDescription>
</DialogHeader>
<div className='space-y-4 py-4'>
<div>
<Label htmlFor='nama-aktivitas'>
Nama Aktivitas <span className='text-red-500'>*</span>
</Label>
<Input
id='nama-aktivitas'
value={activityForm.name}
onChange={(e) =>
setActivityForm({ ...activityForm, name: e.target.value })
}
placeholder='Masukkan nama aktivitas'
className='mt-1.5'
disabled={loading}
/>
</div>
<div>
<Label htmlFor='keterangan'>Keterangan (Opsional)</Label>
<Textarea
id='keterangan'
value={activityForm.description}
onChange={(e) =>
setActivityForm({
...activityForm,
description: e.target.value,
})
}
placeholder='Masukkan keterangan aktivitas'
className='mt-1.5'
rows={3}
disabled={loading}
/>
</div>
<div>
<Label htmlFor='tipe-aktivitas'>
Tipe Aktivitas <span className='text-red-500'>*</span>
</Label>
<Select
value={activityForm.time_type}
onValueChange={(value) =>
setActivityForm({ ...activityForm, time_type: value })
}
disabled={loading}
>
<SelectTrigger
id='tipe-aktivitas'
className='mt-1.5 border-gray-200'
>
<SelectValue placeholder='Pilih tipe aktivitas' />
</SelectTrigger>
<SelectContent>
{TIME_TYPES.map((type) => (
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button
variant='outline'
onClick={() => setShowActivityModal(false)}
disabled={loading}
>
Batal
</Button>
<Button
onClick={handleSaveActivity}
disabled={loading}
className='bg-[#0069e0] hover:bg-[#0052b3] text-white'
>
{loading ? 'Menyimpan...' : 'Simpan'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Phase Delete Confirmation */}
<AlertDialog
open={showPhaseDeleteConfirm}
onOpenChange={setShowPhaseDeleteConfirm}
>
<AlertDialogContent className='bg-white rounded-xl shadow-lg sm:max-w-md'>
<AlertDialogHeader>
<AlertDialogTitle>Hapus Phase?</AlertDialogTitle>
<AlertDialogDescription>
Menghapus phase akan menghapus semua aktivitas di dalamnya secara
permanen.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={loading}>Batal</AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmDeletePhase}
disabled={loading}
className='bg-red-600 hover:bg-red-700 text-white'
>
{loading ? 'Menghapus...' : 'Hapus'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Activity Delete Confirmation */}
<AlertDialog
open={showActivityDeleteConfirm}
onOpenChange={setShowActivityDeleteConfirm}
>
<AlertDialogContent className='bg-white rounded-xl shadow-lg sm:max-w-md'>
<AlertDialogHeader>
<AlertDialogTitle>Hapus Aktivitas?</AlertDialogTitle>
<AlertDialogDescription>
Hapus aktivitas ini secara permanen?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={loading}>Batal</AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmDeleteActivity}
disabled={loading}
className='bg-red-600 hover:bg-red-700 text-white'
>
{loading ? 'Menghapus...' : 'Hapus'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}