2024-11-27 04:14:10 +00:00
|
|
|
"use client";
|
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
|
import FullCalendar from "@fullcalendar/react"; // must go before plugins
|
|
|
|
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
|
|
|
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
|
|
|
|
import interactionPlugin, { Draggable } from "@fullcalendar/interaction";
|
|
|
|
|
import listPlugin from "@fullcalendar/list";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
|
import ExternalDraggingevent from "./dragging-events";
|
|
|
|
|
import { Calendar } from "@/components/ui/calendar";
|
|
|
|
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
|
|
|
|
import { Plus } from "lucide-react";
|
|
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
2024-12-16 01:25:47 +00:00
|
|
|
import { CalendarCategory } from "./data";
|
2024-11-29 12:41:23 +00:00
|
|
|
import { EventContentArg } from "@fullcalendar/core";
|
2024-11-27 04:14:10 +00:00
|
|
|
import EventModal from "./event-modal";
|
|
|
|
|
import { useTranslations } from "next-intl";
|
2024-12-13 11:39:38 +00:00
|
|
|
import { getAgendaSettingsList } from "@/service/agenda-setting/agenda-setting";
|
|
|
|
|
import dayjs from "dayjs";
|
2024-11-27 04:14:10 +00:00
|
|
|
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
|
|
|
|
interface CalendarViewProps {
|
|
|
|
|
events: CalendarEvent[];
|
|
|
|
|
categories: CalendarCategory[];
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-13 11:39:38 +00:00
|
|
|
const INITIAL_YEAR = dayjs().format("YYYY");
|
|
|
|
|
const INITIAL_MONTH = dayjs().format("M");
|
2024-12-16 01:25:47 +00:00
|
|
|
export interface CalendarEvent {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
start: Date;
|
|
|
|
|
end: Date;
|
|
|
|
|
allDay: boolean;
|
|
|
|
|
extendedProps: {
|
|
|
|
|
calendar: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface AgendaSettingsAPIResponse {
|
|
|
|
|
id: number;
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
agendaType: string;
|
|
|
|
|
startDate: string; // API mengembalikan tanggal dalam bentuk string
|
|
|
|
|
endDate: string;
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
createdById: number | null;
|
|
|
|
|
createdByName: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface APIResponse {
|
|
|
|
|
error: boolean;
|
|
|
|
|
message: any;
|
|
|
|
|
data: AgendaSettingsAPIResponse[] | null; // `data` bisa berupa array atau null
|
|
|
|
|
}
|
2024-12-13 11:39:38 +00:00
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
const CalendarView = ({ events, categories }: CalendarViewProps) => {
|
2024-11-29 12:41:23 +00:00
|
|
|
const [selectedCategory, setSelectedCategory] = useState<string[] | null>(
|
|
|
|
|
null
|
|
|
|
|
);
|
2024-11-27 04:14:10 +00:00
|
|
|
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
2024-12-16 01:25:47 +00:00
|
|
|
// const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(
|
|
|
|
|
// null
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
2024-11-29 12:41:23 +00:00
|
|
|
const [draggableInitialized, setDraggableInitialized] =
|
|
|
|
|
useState<boolean>(false);
|
|
|
|
|
const t = useTranslations("CalendarApp");
|
2024-11-27 04:14:10 +00:00
|
|
|
// event canvas state
|
|
|
|
|
const [sheetOpen, setSheetOpen] = useState<boolean>(false);
|
|
|
|
|
const [date, setDate] = React.useState<Date>(new Date());
|
|
|
|
|
|
|
|
|
|
const [dragEvents] = useState([
|
|
|
|
|
{ title: "New Event Planning", id: "101", tag: "business" },
|
|
|
|
|
{ title: "Meeting", id: "102", tag: "meeting" },
|
|
|
|
|
{ title: "Generating Reports", id: "103", tag: "holiday" },
|
|
|
|
|
{ title: "Create New theme", id: "104", tag: "etc" },
|
|
|
|
|
]);
|
|
|
|
|
|
2024-12-13 11:39:38 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
getCalendarEvents();
|
|
|
|
|
});
|
|
|
|
|
const getCalendarEvents = async () => {
|
|
|
|
|
const res = await getAgendaSettingsList(INITIAL_YEAR, INITIAL_MONTH, "");
|
|
|
|
|
console.log("ress", res);
|
|
|
|
|
|
|
|
|
|
if (res.error) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
setSelectedCategory(categories?.map((c) => c.value));
|
|
|
|
|
}, [events, categories]);
|
|
|
|
|
|
2024-12-16 01:25:47 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("Fetched events from API:", apiEvents);
|
|
|
|
|
}, [apiEvents]);
|
|
|
|
|
|
|
|
|
|
const filteredEvents = apiEvents?.filter((event) =>
|
|
|
|
|
selectedCategory?.includes(event.extendedProps.calendar)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const displayedEvents =
|
|
|
|
|
filteredEvents?.length > 0 ? filteredEvents : apiEvents;
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("Filtered events based on category:", displayedEvents);
|
|
|
|
|
}, [filteredEvents, apiEvents]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setSelectedCategory(categories?.map((c) => c.value));
|
|
|
|
|
}, [categories]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("Selected categories:", selectedCategory);
|
|
|
|
|
}, [selectedCategory]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchAgendaEvents = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const selectedMonth = new Date(); // Replace with your logic for selected month
|
|
|
|
|
const year = selectedMonth.getFullYear().toString();
|
|
|
|
|
const month = (selectedMonth.getMonth() + 1).toString();
|
|
|
|
|
const typeFilter = ""; // Replace with your type filter logic if needed
|
|
|
|
|
|
|
|
|
|
const response: APIResponse = await getAgendaSettingsList(
|
|
|
|
|
year,
|
|
|
|
|
month,
|
|
|
|
|
typeFilter
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.data && Array.isArray(response.data)) {
|
|
|
|
|
// Transform API data to match CalendarEvent type
|
|
|
|
|
const eventsFromAPI: CalendarEvent[] = response.data.map((item) => ({
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title,
|
|
|
|
|
start: new Date(item.startDate),
|
|
|
|
|
end: new Date(item.endDate),
|
|
|
|
|
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
|
|
|
|
extendedProps: {
|
|
|
|
|
calendar: item.agendaType,
|
|
|
|
|
description: item.description,
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
setApiEvents(eventsFromAPI);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn("No events found in API response.");
|
|
|
|
|
setApiEvents([]);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Failed to fetch agenda settings:", error);
|
|
|
|
|
setApiEvents([]);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetchAgendaEvents();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
const draggableEl = document.getElementById("external-events");
|
|
|
|
|
|
|
|
|
|
const initDraggable = () => {
|
|
|
|
|
if (draggableEl) {
|
|
|
|
|
new Draggable(draggableEl, {
|
|
|
|
|
itemSelector: ".fc-event",
|
|
|
|
|
eventData: function (eventEl) {
|
|
|
|
|
let title = eventEl.getAttribute("title");
|
|
|
|
|
let id = eventEl.getAttribute("data");
|
|
|
|
|
let event = dragEvents.find((e) => e.id === id);
|
|
|
|
|
let tag = event ? event.tag : "";
|
|
|
|
|
return {
|
|
|
|
|
title: title,
|
|
|
|
|
id: id,
|
|
|
|
|
extendedProps: {
|
|
|
|
|
calendar: tag,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (dragEvents.length > 0) {
|
|
|
|
|
initDraggable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
draggableEl?.removeEventListener("mousedown", initDraggable);
|
|
|
|
|
};
|
|
|
|
|
}, [dragEvents]);
|
2024-12-16 01:25:47 +00:00
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
// event click
|
|
|
|
|
const handleEventClick = (arg: any) => {
|
|
|
|
|
setSelectedEventDate(null);
|
|
|
|
|
setSheetOpen(true);
|
2024-12-16 01:25:47 +00:00
|
|
|
setApiEvents(arg);
|
2024-11-27 04:14:10 +00:00
|
|
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
|
|
|
|
};
|
|
|
|
|
// handle close modal
|
|
|
|
|
const handleCloseModal = () => {
|
|
|
|
|
setSheetOpen(false);
|
2024-12-16 01:25:47 +00:00
|
|
|
setApiEvents([]);
|
2024-11-27 04:14:10 +00:00
|
|
|
setSelectedEventDate(null);
|
|
|
|
|
};
|
|
|
|
|
const handleDateClick = (arg: any) => {
|
|
|
|
|
setSheetOpen(true);
|
|
|
|
|
setSelectedEventDate(arg);
|
2024-12-16 01:25:47 +00:00
|
|
|
setApiEvents([]);
|
2024-11-27 04:14:10 +00:00
|
|
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCategorySelection = (category: string) => {
|
|
|
|
|
if (selectedCategory && selectedCategory.includes(category)) {
|
|
|
|
|
setSelectedCategory(selectedCategory.filter((c) => c !== category));
|
|
|
|
|
} else {
|
2024-11-29 12:41:23 +00:00
|
|
|
setSelectedCategory([...(selectedCategory || []), category]);
|
2024-11-27 04:14:10 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleClassName = (arg: EventContentArg) => {
|
2024-12-16 01:25:47 +00:00
|
|
|
if (arg.event.extendedProps.calendar === "mabes") {
|
2024-11-27 04:14:10 +00:00
|
|
|
return "primary";
|
2024-12-12 13:04:27 +00:00
|
|
|
} else if (arg.event.extendedProps.calendar === "polda") {
|
2024-11-27 04:14:10 +00:00
|
|
|
return "success";
|
2024-12-12 13:04:27 +00:00
|
|
|
} else if (arg.event.extendedProps.calendar === "polres") {
|
|
|
|
|
return "destructive";
|
2024-11-29 12:41:23 +00:00
|
|
|
} else if (arg.event.extendedProps.calendar === "international") {
|
2024-11-27 04:14:10 +00:00
|
|
|
return "info";
|
2024-11-29 12:41:23 +00:00
|
|
|
} else {
|
2024-11-27 04:14:10 +00:00
|
|
|
return "primary";
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div className="grid grid-cols-12 gap-6 divide-x divide-border">
|
|
|
|
|
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
|
|
|
|
|
<CardContent className="p-0">
|
|
|
|
|
<CardHeader className="border-none mb-2 pt-5">
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleDateClick}
|
|
|
|
|
className="dark:bg-background dark:text-foreground"
|
|
|
|
|
>
|
|
|
|
|
<Plus className="w-4 h-4 me-1" />
|
2024-11-29 12:41:23 +00:00
|
|
|
{"Tambahkan Agenda baru"}
|
2024-11-27 04:14:10 +00:00
|
|
|
</Button>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<div className="px-3">
|
|
|
|
|
<Calendar
|
|
|
|
|
mode="single"
|
|
|
|
|
selected={date}
|
|
|
|
|
onSelect={(s) => {
|
|
|
|
|
handleDateClick(s);
|
|
|
|
|
}}
|
|
|
|
|
className="rounded-md border w-full p-0 border-none"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
2024-12-13 11:39:38 +00:00
|
|
|
{/* <div id="external-events" className=" space-y-1.5 mt-6 px-4">
|
2024-11-27 04:14:10 +00:00
|
|
|
<p className="text-sm font-medium text-default-700 mb-3">
|
2024-11-29 12:41:23 +00:00
|
|
|
{t("shortDesc")}
|
2024-11-27 04:14:10 +00:00
|
|
|
</p>
|
|
|
|
|
{dragEvents.map((event) => (
|
|
|
|
|
<ExternalDraggingevent key={event.id} event={event} />
|
|
|
|
|
))}
|
2024-12-13 11:39:38 +00:00
|
|
|
</div> */}
|
2024-11-27 04:14:10 +00:00
|
|
|
<div className="py-4 text-default-800 font-semibold text-xs uppercase mt-4 mb-2 px-4">
|
2024-11-29 12:41:23 +00:00
|
|
|
{t("filter")}
|
2024-11-27 04:14:10 +00:00
|
|
|
</div>
|
|
|
|
|
<ul className="space-y-3 px-4">
|
|
|
|
|
<li className=" flex gap-3">
|
|
|
|
|
<Checkbox
|
|
|
|
|
checked={selectedCategory?.length === categories?.length}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (selectedCategory?.length === categories?.length) {
|
|
|
|
|
setSelectedCategory([]);
|
|
|
|
|
} else {
|
|
|
|
|
setSelectedCategory(categories.map((c) => c.value));
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<Label>All</Label>
|
|
|
|
|
</li>
|
|
|
|
|
{categories?.map((category) => (
|
|
|
|
|
<li className="flex gap-3 " key={category.value}>
|
|
|
|
|
<Checkbox
|
|
|
|
|
className={category.className}
|
|
|
|
|
id={category.label}
|
|
|
|
|
checked={selectedCategory?.includes(category.value)}
|
|
|
|
|
onClick={() => handleCategorySelection(category.value)}
|
|
|
|
|
/>
|
|
|
|
|
<Label htmlFor={category.label}>{category.label}</Label>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
|
|
|
|
|
<CardContent className="dashcode-app-calendar">
|
|
|
|
|
<FullCalendar
|
|
|
|
|
plugins={[
|
|
|
|
|
dayGridPlugin,
|
|
|
|
|
timeGridPlugin,
|
|
|
|
|
interactionPlugin,
|
|
|
|
|
listPlugin,
|
|
|
|
|
]}
|
|
|
|
|
headerToolbar={{
|
|
|
|
|
left: "prev,next today",
|
|
|
|
|
center: "title",
|
|
|
|
|
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
|
|
|
|
|
}}
|
2024-12-29 09:09:10 +00:00
|
|
|
events={displayedEvents}
|
2024-11-27 04:14:10 +00:00
|
|
|
editable={true}
|
|
|
|
|
rerenderDelay={10}
|
|
|
|
|
eventDurationEditable={false}
|
|
|
|
|
selectable={true}
|
|
|
|
|
selectMirror={true}
|
|
|
|
|
droppable={true}
|
|
|
|
|
dayMaxEvents={2}
|
|
|
|
|
weekends={true}
|
|
|
|
|
eventClassNames={handleClassName}
|
|
|
|
|
dateClick={handleDateClick}
|
|
|
|
|
eventClick={handleEventClick}
|
|
|
|
|
initialView="dayGridMonth"
|
|
|
|
|
/>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
<EventModal
|
|
|
|
|
open={sheetOpen}
|
|
|
|
|
onClose={handleCloseModal}
|
|
|
|
|
categories={categories}
|
2024-12-16 01:25:47 +00:00
|
|
|
event={apiEvents}
|
2024-11-27 04:14:10 +00:00
|
|
|
selectedDate={selectedEventDate}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default CalendarView;
|