kontenhumas-fe/app/[locale]/(admin)/admin/schedule/page.tsx

330 lines
10 KiB
TypeScript
Raw Normal View History

2025-10-31 16:21:05 +00:00
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { id as localeId } from "date-fns/locale";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import {
Loader2,
Plus,
Trash2,
Pencil,
CalendarDays,
Search,
ChevronLeft,
ChevronRight,
} from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { CalendarIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { deleteSchedule, getAllSchedules } from "@/service/landing/landing";
const MySwal = withReactContent(Swal);
function safeFormatDate(dateString?: string, formatString = "dd MMM yyyy HH:mm") {
if (!dateString) return "-";
const date = new Date(dateString);
if (isNaN(date.getTime())) return "-";
return format(date, formatString, { locale: localeId });
}
export default function ScheduleListPage() {
const router = useRouter();
const [schedules, setSchedules] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState("");
const [isLive, setIsLive] = useState("semua");
const [page, setPage] = useState(1);
const [limit] = useState(6);
const [startDate, setStartDate] = useState<Date | undefined>();
const [endDate, setEndDate] = useState<Date | undefined>();
const [totalPage, setTotalPage] = useState(1);
const fetchSchedules = async () => {
setLoading(true);
try {
const params: any = {
page,
limit,
sortBy: "startDate",
sort: "asc",
title: search,
};
if (isLive !== "semua") {
params.isLiveStreaming = isLive === "true";
}
if (startDate) params.startDate = startDate.toISOString();
if (endDate) params.endDate = endDate.toISOString();
const res = await getAllSchedules(params);
if (!res.error) {
setSchedules(res.data?.data || res.data?.items || []);
setTotalPage(res.data?.meta?.totalPage || 1);
}
} catch (error) {
console.error("Error fetching schedules:", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchSchedules();
}, [page, isLive]);
const handleSearch = () => {
setPage(1);
fetchSchedules();
};
const handleDelete = async (id: number) => {
MySwal.fire({
title: "Hapus Jadwal?",
text: "Tindakan ini tidak dapat dibatalkan.",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Ya, hapus",
cancelButtonText: "Batal",
confirmButtonColor: "#d33",
}).then(async (result) => {
if (result.isConfirmed) {
const res = await deleteSchedule(id);
if (!res?.error) {
MySwal.fire({
icon: "success",
title: "Dihapus!",
text: "Jadwal berhasil dihapus.",
timer: 1500,
showConfirmButton: false,
});
fetchSchedules();
} else {
MySwal.fire({
icon: "error",
title: "Gagal!",
text: res.message || "Gagal menghapus jadwal.",
});
}
}
});
};
return (
<div className="max-w-7xl mx-auto p-6">
{/* Header */}
<div className="flex flex-wrap items-center justify-between mb-8 gap-4">
<h1 className="text-2xl font-semibold flex items-center gap-2">
<CalendarDays className="w-6 h-6 text-blue-600" />
Daftar Jadwal
</h1>
<Button onClick={() => router.push("/admin/schedules/create")}>
<Plus className="mr-2 h-4 w-4" /> Tambah Jadwal
</Button>
</div>
{/* Filter Bar */}
<div className="flex flex-wrap items-end gap-4 mb-6">
<div className="flex items-center gap-2">
<Input
placeholder="Cari judul..."
className="w-64"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<Button variant="outline" onClick={handleSearch}>
<Search className="w-4 h-4 mr-1" /> Cari
</Button>
</div>
<div className="flex flex-col">
<label className="text-sm font-medium mb-1">Tanggal Mulai</label>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-[180px] justify-start text-left">
<CalendarIcon className="mr-2 h-4 w-4" />
{startDate ? format(startDate, "dd MMM yyyy") : "Pilih"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={startDate}
onSelect={setStartDate}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
<div className="flex flex-col">
<label className="text-sm font-medium mb-1">Tanggal Selesai</label>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-[180px] justify-start text-left">
<CalendarIcon className="mr-2 h-4 w-4" />
{endDate ? format(endDate, "dd MMM yyyy") : "Pilih"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={endDate}
onSelect={setEndDate}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
<div className="flex flex-col">
<label className="text-sm font-medium mb-1">Tipe</label>
<Select value={isLive} onValueChange={setIsLive}>
<SelectTrigger className="w-[160px]">
<SelectValue placeholder="Semua" />
</SelectTrigger>
<SelectContent>
<SelectItem value="semua">Semua</SelectItem>
<SelectItem value="true">Live</SelectItem>
<SelectItem value="false">Offline</SelectItem>
</SelectContent>
</Select>
</div>
<Button
variant="default"
onClick={() => {
setSearch("");
setStartDate(undefined);
setEndDate(undefined);
setIsLive("semua");
setPage(1);
fetchSchedules();
}}
>
Reset
</Button>
</div>
{/* Content */}
{loading ? (
<div className="flex justify-center items-center py-16 text-muted-foreground">
<Loader2 className="animate-spin mr-2 h-5 w-5" />
Memuat data...
</div>
) : schedules.length === 0 ? (
<div className="text-center py-16 text-muted-foreground">
<p>Tidak ada jadwal ditemukan.</p>
</div>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
{schedules.map((item) => (
<Card
key={item.id}
className="shadow-md border border-gray-200 dark:border-gray-800 hover:shadow-lg transition-all"
>
<CardHeader className="pb-3">
<CardTitle className="text-lg font-semibold flex justify-between items-center">
<span>{item.title}</span>
<Badge
className={
item.isLiveStreaming
? "bg-green-600 text-white"
: "bg-gray-400 text-white"
}
>
{item.isLiveStreaming ? "Live" : "Offline"}
</Badge>
</CardTitle>
</CardHeader>
<CardContent className="text-sm space-y-3">
<div className="flex justify-between text-gray-600">
<span>{safeFormatDate(item.startDate, "dd MMM yyyy")}</span>
<span>
{safeFormatDate(item.startDate, "HH:mm")} -{" "}
{safeFormatDate(item.endDate, "HH:mm")}
</span>
</div>
<p className="text-gray-800 font-medium line-clamp-2">
{item.description || "-"}
</p>
<p className="text-gray-500 text-sm italic">
Lokasi: {item.location || "-"}
</p>
<div className="flex justify-between pt-3 border-t border-gray-200 mt-3">
<Button
variant="outline"
size="sm"
onClick={() => router.push(`/admin/schedule/edit/${item.id}`)}
>
<Pencil className="mr-2 h-4 w-4" /> Edit
</Button>
<Button
variant="default"
size="sm"
onClick={() => handleDelete(item.id)}
>
<Trash2 className="mr-2 h-4 w-4" /> Hapus
</Button>
</div>
</CardContent>
</Card>
))}
</div>
{/* Pagination */}
<div className="flex justify-center items-center mt-8 gap-3">
<Button
variant="outline"
size="sm"
disabled={page <= 1}
onClick={() => setPage((p) => Math.max(p - 1, 1))}
>
<ChevronLeft className="h-4 w-4" /> Prev
</Button>
<span className="text-sm font-medium">
Halaman {page} dari {totalPage}
</span>
<Button
variant="outline"
size="sm"
disabled={page >= totalPage}
onClick={() => setPage((p) => p + 1)}
>
Next <ChevronRight className="h-4 w-4" />
</Button>
</div>
</>
)}
</div>
);
}