2024-11-27 04:14:10 +00:00
|
|
|
"use client";
|
2025-01-12 15:46:28 +00:00
|
|
|
import React, { useState, useEffect, act } from "react";
|
2024-11-27 04:14:10 +00:00
|
|
|
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";
|
2025-01-12 13:54:18 +00:00
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
2025-02-04 07:08:45 +00:00
|
|
|
import { Book, CheckSquare2, CheckSquare2Icon, Plus } from "lucide-react";
|
2024-11-27 04:14:10 +00:00
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
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-12-30 13:43:39 +00:00
|
|
|
import { getCookiesDecrypt } from "@/lib/utils";
|
2025-01-01 08:33:42 +00:00
|
|
|
import { CalendarCategory } from "./data";
|
2025-01-12 13:54:18 +00:00
|
|
|
import { error } from "@/config/swal";
|
|
|
|
|
import {
|
|
|
|
|
Popover,
|
|
|
|
|
PopoverContent,
|
|
|
|
|
PopoverTrigger,
|
|
|
|
|
} from "@/components/ui/popover";
|
2025-02-04 02:57:43 +00:00
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
|
|
|
|
} from "@/components/ui/dialog";
|
2025-01-01 08:33:42 +00:00
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
|
|
|
|
interface CalendarViewProps {
|
2025-01-01 08:33:42 +00:00
|
|
|
// events: CalendarEvent[];
|
2024-11-27 04:14:10 +00:00
|
|
|
categories: CalendarCategory[];
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-12 13:54:18 +00:00
|
|
|
export type CalendarEvent = {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
start: Date;
|
|
|
|
|
end: Date;
|
|
|
|
|
createBy: string;
|
|
|
|
|
createdByName: string;
|
|
|
|
|
allDay: boolean;
|
|
|
|
|
extendedProps: {
|
|
|
|
|
calendar: string;
|
|
|
|
|
description: string;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
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 AgendaSettingsAPIResponse {
|
|
|
|
|
id: number;
|
|
|
|
|
title: string;
|
2025-01-07 10:30:51 +00:00
|
|
|
createdByName: string;
|
2024-12-16 01:25:47 +00:00
|
|
|
description: string;
|
|
|
|
|
agendaType: string;
|
2025-01-07 10:30:51 +00:00
|
|
|
startDate: string;
|
2024-12-16 01:25:47 +00:00
|
|
|
endDate: string;
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
createdById: number | null;
|
|
|
|
|
}
|
2025-01-15 19:02:28 +00:00
|
|
|
|
2025-01-12 13:54:18 +00:00
|
|
|
interface YearlyData {
|
|
|
|
|
january?: Event[];
|
|
|
|
|
february?: Event[];
|
|
|
|
|
march?: Event[];
|
|
|
|
|
april?: Event[];
|
|
|
|
|
may?: Event[];
|
|
|
|
|
june?: Event[];
|
|
|
|
|
july?: Event[];
|
|
|
|
|
august?: Event[];
|
|
|
|
|
september?: Event[];
|
|
|
|
|
october?: Event[];
|
|
|
|
|
november?: Event[];
|
|
|
|
|
december?: Event[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface MonthCardProps {
|
|
|
|
|
monthId: keyof YearlyData;
|
|
|
|
|
label: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ListItemProps {
|
|
|
|
|
item: any;
|
2025-01-15 19:02:28 +00:00
|
|
|
text: string;
|
2025-01-12 13:54:18 +00:00
|
|
|
createdBy: string;
|
|
|
|
|
bgColor: string;
|
|
|
|
|
}
|
2024-12-16 01:25:47 +00:00
|
|
|
|
|
|
|
|
interface APIResponse {
|
|
|
|
|
error: boolean;
|
|
|
|
|
message: any;
|
|
|
|
|
data: AgendaSettingsAPIResponse[] | null; // `data` bisa berupa array atau null
|
|
|
|
|
}
|
2024-12-13 11:39:38 +00:00
|
|
|
|
2025-01-01 08:33:42 +00:00
|
|
|
const CalendarView = ({ 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-30 13:43:39 +00:00
|
|
|
const roleId = Number(getCookiesDecrypt("urie")) || 0;
|
2024-12-16 01:25:47 +00:00
|
|
|
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
|
2025-02-04 02:57:43 +00:00
|
|
|
const [Isloading, 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());
|
2025-01-12 15:46:28 +00:00
|
|
|
const [activeView, setActiveView] = useState("listYear");
|
2025-01-12 13:54:18 +00:00
|
|
|
const [yearlyData, setYearlyData] = useState();
|
2024-11-27 04:14:10 +00:00
|
|
|
|
2025-01-12 15:46:28 +00:00
|
|
|
const TODAY = dayjs().format("yyyy-MM-dd");
|
|
|
|
|
const INITIAL_YEAR = dayjs().format("YYYY");
|
|
|
|
|
const INITIAL_MONTH = dayjs().format("M");
|
2025-02-04 02:57:43 +00:00
|
|
|
const [open, setOpen] = useState(false);
|
2025-01-12 15:46:28 +00:00
|
|
|
|
|
|
|
|
const [selectedYear, setSelectedYear] = useState(new Date());
|
|
|
|
|
const [selectedMonth, setSelectedMonth] = useState(
|
2025-01-15 19:02:28 +00:00
|
|
|
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
|
2025-01-12 15:46:28 +00:00
|
|
|
);
|
|
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
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" },
|
|
|
|
|
]);
|
|
|
|
|
|
2025-01-01 08:33:42 +00:00
|
|
|
const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]);
|
|
|
|
|
|
2024-12-13 11:39:38 +00:00
|
|
|
useEffect(() => {
|
2025-01-12 15:46:28 +00:00
|
|
|
if (activeView == "listYear") {
|
|
|
|
|
getYearlyEvents();
|
|
|
|
|
} else {
|
|
|
|
|
getCalendarEvents();
|
|
|
|
|
}
|
|
|
|
|
}, [activeView, selectedMonth]);
|
2025-01-01 08:33:42 +00:00
|
|
|
|
2024-12-13 11:39:38 +00:00
|
|
|
const getCalendarEvents = async () => {
|
2025-01-12 15:46:28 +00:00
|
|
|
console.log("View : ", activeView);
|
2025-01-15 19:02:28 +00:00
|
|
|
const res = await getAgendaSettingsList(
|
|
|
|
|
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
|
|
|
|
selectedMonth.format("M") || INITIAL_MONTH,
|
|
|
|
|
""
|
|
|
|
|
);
|
2025-01-12 15:46:28 +00:00
|
|
|
console.log("View : API Response:", res);
|
2024-12-13 11:39:38 +00:00
|
|
|
|
2025-01-04 17:11:14 +00:00
|
|
|
if (res?.error) {
|
2025-01-12 13:54:18 +00:00
|
|
|
error(res?.message);
|
2025-01-01 08:33:42 +00:00
|
|
|
return;
|
2024-12-13 11:39:38 +00:00
|
|
|
}
|
2025-01-01 08:33:42 +00:00
|
|
|
|
2025-01-12 15:46:28 +00:00
|
|
|
const data = res?.data?.data;
|
2025-01-01 08:33:42 +00:00
|
|
|
|
2025-01-12 15:46:28 +00:00
|
|
|
if (data) {
|
|
|
|
|
console.log("Dataaa : ", data);
|
|
|
|
|
// Map API data to the calendarEvents structure
|
|
|
|
|
const events = res?.data?.data?.map((event: any) => ({
|
|
|
|
|
id: event.id.toString(),
|
|
|
|
|
title: event.title,
|
|
|
|
|
createBy: event.createdById,
|
|
|
|
|
createdByName: event.createdByName,
|
|
|
|
|
start: new Date(event.startDate),
|
|
|
|
|
end: new Date(event.endDate),
|
|
|
|
|
allDay: true,
|
|
|
|
|
extendedProps: {
|
|
|
|
|
calendar: event.agendaType,
|
|
|
|
|
description: event.description,
|
|
|
|
|
createdByName: event.createdByName,
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
setCalendarEvents(events);
|
|
|
|
|
}
|
2024-12-13 11:39:38 +00:00
|
|
|
};
|
|
|
|
|
|
2025-01-12 13:54:18 +00:00
|
|
|
const getYearlyEvents = async () => {
|
2025-01-15 19:02:28 +00:00
|
|
|
const res = await getAgendaSettingsList(
|
|
|
|
|
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
|
|
|
|
"",
|
|
|
|
|
""
|
|
|
|
|
);
|
|
|
|
|
if (res?.error) {
|
|
|
|
|
error(res.message);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
setYearlyData(res?.data?.data);
|
|
|
|
|
};
|
2025-01-12 13:54:18 +00:00
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
setSelectedCategory(categories?.map((c) => c.value));
|
2025-01-01 08:33:42 +00:00
|
|
|
}, [categories]);
|
2024-12-16 01:25:47 +00:00
|
|
|
|
2025-01-01 08:33:42 +00:00
|
|
|
const filteredEvents = calendarEvents?.filter((event) =>
|
2024-12-16 01:25:47 +00:00
|
|
|
selectedCategory?.includes(event.extendedProps.calendar)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const displayedEvents =
|
2025-01-01 08:33:42 +00:00
|
|
|
filteredEvents?.length > 1 ? filteredEvents : apiEvents;
|
2024-12-16 01:25:47 +00:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setSelectedCategory(categories?.map((c) => c.value));
|
|
|
|
|
}, [categories]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("Selected categories:", selectedCategory);
|
|
|
|
|
}, [selectedCategory]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchAgendaEvents();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-01-12 15:46:28 +00:00
|
|
|
const fetchAgendaEvents = async (year?: string, month?: string) => {
|
|
|
|
|
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 = await getAgendaSettingsList(year, month, typeFilter);
|
|
|
|
|
|
|
|
|
|
if (response?.data && Array.isArray(response?.data)) {
|
|
|
|
|
// Transform API data to match CalendarEvent type
|
2025-01-15 19:02:28 +00:00
|
|
|
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title,
|
|
|
|
|
createBy: "Mabes Polri - Approver",
|
|
|
|
|
createdByName: item.createdByName,
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
}));
|
2025-01-12 15:46:28 +00:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-01 08:33:42 +00:00
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("Fetched events from API 1:", apiEvents);
|
|
|
|
|
}, [apiEvents]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
console.log("Filtered events based on category 1:", calendarEvents);
|
|
|
|
|
}, [filteredEvents, apiEvents]);
|
|
|
|
|
|
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) => {
|
2025-01-12 13:54:18 +00:00
|
|
|
console.log("Event Click ", arg);
|
2024-11-27 04:14:10 +00:00
|
|
|
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"));
|
|
|
|
|
};
|
2025-01-12 13:54:18 +00:00
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
// 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);
|
2025-01-12 13:54:18 +00:00
|
|
|
console.log("Date :", arg);
|
2024-11-27 04:14:10 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-07 10:30:51 +00:00
|
|
|
const renderEventContent = (eventInfo: any) => {
|
|
|
|
|
const { title } = eventInfo.event;
|
2025-02-04 07:08:45 +00:00
|
|
|
const { isPublish } = eventInfo.event.extendedProps;
|
2025-01-15 19:02:28 +00:00
|
|
|
const { createdByName } = eventInfo.event.extendedProps;
|
2025-01-07 10:30:51 +00:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2025-02-04 07:08:45 +00:00
|
|
|
<div className="flex flex-row">
|
|
|
|
|
{" "}
|
|
|
|
|
{isPublish && <CheckSquare2 />}
|
|
|
|
|
<p className="ml-1">{title}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-01-08 20:01:32 +00:00
|
|
|
<p className="ml-1 text-xs text-start mt-2">
|
2025-01-12 15:46:28 +00:00
|
|
|
Created By : {createdByName}
|
2025-01-08 20:01:32 +00:00
|
|
|
</p>
|
2025-01-07 10:30:51 +00:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
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") {
|
2025-01-08 20:01:32 +00:00
|
|
|
return "bg-yellow-500 border-none";
|
2024-12-12 13:04:27 +00:00
|
|
|
} else if (arg.event.extendedProps.calendar === "polda") {
|
2025-01-08 20:01:32 +00:00
|
|
|
return "bg-blue-400 border-none";
|
2024-12-12 13:04:27 +00:00
|
|
|
} else if (arg.event.extendedProps.calendar === "polres") {
|
2025-01-08 20:01:32 +00:00
|
|
|
return "bg-slate-400 border-none";
|
2025-01-15 19:02:28 +00:00
|
|
|
} else if (arg.event.extendedProps.calendar === "satker") {
|
|
|
|
|
return "bg-orange-500 border-none";
|
2024-11-29 12:41:23 +00:00
|
|
|
} else if (arg.event.extendedProps.calendar === "international") {
|
2025-01-08 20:01:32 +00:00
|
|
|
return "bg-green-400 border-none";
|
2024-11-29 12:41:23 +00:00
|
|
|
} else {
|
2024-11-27 04:14:10 +00:00
|
|
|
return "primary";
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-12 13:54:18 +00:00
|
|
|
const handleDateChange = (startDate: string, endDate: string) => {
|
2025-01-12 15:46:28 +00:00
|
|
|
const startDateOfDate = new Date(startDate);
|
|
|
|
|
const endDateOfDate = new Date(endDate);
|
|
|
|
|
|
|
|
|
|
console.log("Change Date : ", startDateOfDate);
|
|
|
|
|
|
|
|
|
|
setSelectedMonth(dayjs(startDateOfDate));
|
|
|
|
|
};
|
2025-01-12 13:54:18 +00:00
|
|
|
|
|
|
|
|
const handleViewChange = (viewType: string) => {
|
2025-01-12 15:46:28 +00:00
|
|
|
console.log("Change view : ", viewType);
|
2025-01-12 13:54:18 +00:00
|
|
|
setActiveView(viewType);
|
2025-01-15 19:02:28 +00:00
|
|
|
};
|
2025-01-12 13:54:18 +00:00
|
|
|
|
|
|
|
|
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
2025-01-15 19:02:28 +00:00
|
|
|
{ id: "january", label: "Januari" },
|
|
|
|
|
{ id: "february", label: "Februari" },
|
|
|
|
|
{ id: "march", label: "Maret" },
|
|
|
|
|
{ id: "april", label: "April" },
|
|
|
|
|
{ id: "may", label: "Mei" },
|
|
|
|
|
{ id: "june", label: "Juni" },
|
|
|
|
|
{ id: "july", label: "Juli" },
|
|
|
|
|
{ id: "august", label: "Agustus" },
|
|
|
|
|
{ id: "september", label: "September" },
|
|
|
|
|
{ id: "october", label: "Oktober" },
|
|
|
|
|
{ id: "november", label: "November" },
|
|
|
|
|
{ id: "december", label: "Desember" },
|
2025-01-12 13:54:18 +00:00
|
|
|
];
|
|
|
|
|
|
2025-01-15 19:02:28 +00:00
|
|
|
const getEventColor = (type: Event["type"]): string => {
|
|
|
|
|
const colors: Record<Event["type"], string> = {
|
|
|
|
|
mabes: "bg-yellow-500",
|
|
|
|
|
polda: "bg-blue-400",
|
|
|
|
|
polres: "bg-slate-400",
|
|
|
|
|
international: "bg-green-400",
|
2025-01-12 13:54:18 +00:00
|
|
|
};
|
|
|
|
|
return colors[type];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleClickListItem = (item: any) => {
|
|
|
|
|
const formattedEvent: CalendarEvent = {
|
|
|
|
|
id: item.id.toString(),
|
|
|
|
|
title: item.title,
|
|
|
|
|
start: new Date(item.startDate),
|
|
|
|
|
end: new Date(item.endDate),
|
|
|
|
|
createBy: "Mabes Polri - Approver", // Sesuaikan dengan data yang sebenarnya jika ada
|
|
|
|
|
createdByName: item.createdByName,
|
|
|
|
|
allDay: true,
|
|
|
|
|
extendedProps: {
|
|
|
|
|
calendar: item.agendaType,
|
2025-01-15 19:02:28 +00:00
|
|
|
description: item.description,
|
|
|
|
|
},
|
2025-01-12 13:54:18 +00:00
|
|
|
};
|
|
|
|
|
const finalEvent: any = {
|
2025-01-15 19:02:28 +00:00
|
|
|
event: formattedEvent,
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-12 13:54:18 +00:00
|
|
|
console.log("Event click custom : ", finalEvent);
|
|
|
|
|
|
|
|
|
|
setSelectedEventDate(null);
|
|
|
|
|
setSheetOpen(true);
|
|
|
|
|
setApiEvents(finalEvent);
|
|
|
|
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
2025-01-15 19:02:28 +00:00
|
|
|
};
|
2025-01-12 13:54:18 +00:00
|
|
|
|
2025-01-15 19:02:28 +00:00
|
|
|
const ListItem: React.FC<ListItemProps> = ({
|
|
|
|
|
item,
|
|
|
|
|
text,
|
|
|
|
|
createdBy,
|
|
|
|
|
bgColor,
|
|
|
|
|
}) => (
|
|
|
|
|
<div
|
|
|
|
|
className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`}
|
|
|
|
|
onClick={() => handleClickListItem(item)}
|
|
|
|
|
>
|
2025-02-04 07:08:45 +00:00
|
|
|
<div className="flex flex-row items-center">
|
|
|
|
|
<CheckSquare2Icon />
|
|
|
|
|
<p className="ml-1">{text}</p>
|
|
|
|
|
</div>
|
2025-01-15 19:02:28 +00:00
|
|
|
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
2025-01-12 13:54:18 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const MonthCard: React.FC<MonthCardProps> = (props: any) => {
|
2025-01-15 19:02:28 +00:00
|
|
|
const { monthId, label } = props;
|
2025-01-12 13:54:18 +00:00
|
|
|
const events: any = yearlyData?.[monthId];
|
|
|
|
|
const displayedEvents = events?.slice(0, 3);
|
|
|
|
|
const hasMoreEvents = events?.length > 3;
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-200 pb-3 mr-1">
|
|
|
|
|
<div className="py-3">
|
|
|
|
|
<h4 className="font-bold text-center">{label}</h4>
|
|
|
|
|
</div>
|
2025-01-15 19:02:28 +00:00
|
|
|
|
2025-01-12 13:54:18 +00:00
|
|
|
<div className="px-2">
|
|
|
|
|
{events?.length === 0 ? (
|
|
|
|
|
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
|
|
|
|
<p className="text-center">Belum ada data</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
{displayedEvents?.map((event: any, index: number) => (
|
2025-01-15 19:02:28 +00:00
|
|
|
<ListItem
|
2025-01-12 13:54:18 +00:00
|
|
|
key={index}
|
|
|
|
|
item={event}
|
|
|
|
|
text={event.title}
|
|
|
|
|
createdBy={event.createdByName}
|
|
|
|
|
bgColor={getEventColor(event.agendaType)}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
2025-01-15 19:02:28 +00:00
|
|
|
|
2025-01-12 13:54:18 +00:00
|
|
|
{hasMoreEvents && (
|
|
|
|
|
<Popover>
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
<Button className="w-full" size="sm">
|
|
|
|
|
Lihat lebih banyak
|
|
|
|
|
</Button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent
|
|
|
|
|
side="top"
|
|
|
|
|
className="p-0 overflow-hidden bg-transparent shadow-none border-none"
|
|
|
|
|
>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader className="bg-default p-2 rounded-t-lg">
|
2025-01-15 19:02:28 +00:00
|
|
|
<CardTitle className="text-default-foreground text-base py-0">
|
|
|
|
|
{label}
|
|
|
|
|
</CardTitle>
|
2025-01-12 13:54:18 +00:00
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="p-2 text-sm">
|
|
|
|
|
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
|
|
|
|
|
{events.map((event: any, index: number) => (
|
2025-01-15 19:02:28 +00:00
|
|
|
<ListItem
|
2025-01-12 13:54:18 +00:00
|
|
|
key={index}
|
|
|
|
|
item={event}
|
|
|
|
|
text={event.title}
|
|
|
|
|
createdBy={event.createdByName}
|
|
|
|
|
bgColor={getEventColor(event.agendaType)}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-04 02:57:43 +00:00
|
|
|
const getModalContent = (type: "terkirim" | "diterima") => (
|
|
|
|
|
<div className="overflow-x-auto overflow-y-auto ">
|
|
|
|
|
{Isloading ? (
|
|
|
|
|
<p>Loading...</p>
|
|
|
|
|
) : (
|
|
|
|
|
<table className="w-full border-collapse border border-gray-300">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr className="bg-gray-100 border-b">
|
|
|
|
|
<th className="px-4 py-2 text-left">No</th>
|
|
|
|
|
<th className="px-4 py-2 text-left">Ticket Number</th>
|
|
|
|
|
<th className="px-4 py-2 text-left">Date and Time</th>
|
|
|
|
|
<th className="px-4 py-2 text-left">Title</th>
|
|
|
|
|
<th className="px-4 py-2 text-left">Status</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr key={""} className="border-b">
|
|
|
|
|
<td className="px-4 py-2">{"1"}</td>
|
|
|
|
|
<td className="px-4 py-2">{"MIA - 001"}</td>
|
|
|
|
|
<td className="px-4 py-2">{"23/01/2025 13:00"}</td>
|
|
|
|
|
<td className="px-4 py-2">{"Daily Issue 25 Januari 2025 "}</td>
|
|
|
|
|
<td className="px-4 py-2">
|
|
|
|
|
{type === "terkirim" ? "Completed" : "Completed"}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
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">
|
2025-01-01 08:33:42 +00:00
|
|
|
<CardHeader className="border-none mb-2 pt-5">
|
2025-02-04 02:57:43 +00:00
|
|
|
{roleId == 11 || roleId == 2 || roleId == 12 ? (
|
2025-01-12 15:46:28 +00:00
|
|
|
<Button
|
|
|
|
|
onClick={handleDateClick}
|
|
|
|
|
className="dark:bg-background dark:text-foreground"
|
|
|
|
|
>
|
|
|
|
|
<Plus className="w-4 h-4 me-1" />
|
|
|
|
|
{"Tambahkan Agenda baru"}
|
2025-01-15 19:02:28 +00:00
|
|
|
</Button>
|
|
|
|
|
) : (
|
2025-01-12 15:46:28 +00:00
|
|
|
""
|
2025-01-15 19:02:28 +00:00
|
|
|
)}
|
2025-02-04 02:57:43 +00:00
|
|
|
<div>
|
|
|
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
|
{roleId == 2 ? (
|
|
|
|
|
<Button className="dark:bg-background dark:text-foreground ml-2">
|
|
|
|
|
<Book className="w-4 h-4 me-1" />
|
|
|
|
|
Hasil Pantauan BAG PA
|
|
|
|
|
</Button>
|
|
|
|
|
) : null}
|
|
|
|
|
</DialogTrigger>
|
|
|
|
|
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>Hasil Pantauan</DialogTitle>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
{getModalContent("terkirim")}
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</div>
|
2025-01-01 08:33:42 +00:00
|
|
|
</CardHeader>
|
|
|
|
|
|
2024-11-27 04:14:10 +00:00
|
|
|
<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",
|
2025-01-12 13:54:18 +00:00
|
|
|
right: "listYear,dayGridMonth,listMonth",
|
|
|
|
|
}}
|
|
|
|
|
views={{
|
|
|
|
|
listYear: {
|
|
|
|
|
type: "dayGridMonth",
|
|
|
|
|
buttonText: "Year",
|
|
|
|
|
},
|
2024-11-27 04:14:10 +00:00
|
|
|
}}
|
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}
|
2025-01-12 13:54:18 +00:00
|
|
|
initialView="listYear"
|
2025-01-07 10:30:51 +00:00
|
|
|
eventContent={renderEventContent}
|
2025-01-12 13:54:18 +00:00
|
|
|
viewDidMount={(info) => handleViewChange(info.view.type)}
|
|
|
|
|
datesSet={(info: any) => {
|
|
|
|
|
console.log(info);
|
|
|
|
|
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
|
|
|
|
handleViewChange(info.view.type);
|
|
|
|
|
}}
|
2025-01-15 19:02:28 +00:00
|
|
|
viewClassNames={
|
|
|
|
|
activeView === "listYear" ? "hide-calendar-grid" : ""
|
|
|
|
|
}
|
2024-11-27 04:14:10 +00:00
|
|
|
/>
|
2025-01-12 13:54:18 +00:00
|
|
|
|
|
|
|
|
{activeView === "listYear" && (
|
|
|
|
|
<div className="custom-ui">
|
|
|
|
|
<div className="flex gap-1 mt-1">
|
2025-01-15 19:02:28 +00:00
|
|
|
{months.slice(0, 3).map((month) => (
|
|
|
|
|
<MonthCard
|
|
|
|
|
key={month.id}
|
|
|
|
|
monthId={month.id}
|
|
|
|
|
label={month.label}
|
|
|
|
|
/>
|
2025-01-12 13:54:18 +00:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Second Row */}
|
|
|
|
|
<div className="flex gap-1 mt-1">
|
2025-01-15 19:02:28 +00:00
|
|
|
{months.slice(3, 6).map((month) => (
|
|
|
|
|
<MonthCard
|
|
|
|
|
key={month.id}
|
|
|
|
|
monthId={month.id}
|
|
|
|
|
label={month.label}
|
|
|
|
|
/>
|
2025-01-12 13:54:18 +00:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Third Row */}
|
|
|
|
|
<div className="flex gap-1 mt-1">
|
2025-01-15 19:02:28 +00:00
|
|
|
{months.slice(6, 9).map((month) => (
|
|
|
|
|
<MonthCard
|
|
|
|
|
key={month.id}
|
|
|
|
|
monthId={month.id}
|
|
|
|
|
label={month.label}
|
|
|
|
|
/>
|
2025-01-12 13:54:18 +00:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Fourth Row */}
|
|
|
|
|
<div className="flex gap-1 mt-1">
|
2025-01-15 19:02:28 +00:00
|
|
|
{months.slice(9, 12).map((month) => (
|
|
|
|
|
<MonthCard
|
|
|
|
|
key={month.id}
|
|
|
|
|
monthId={month.id}
|
|
|
|
|
label={month.label}
|
|
|
|
|
/>
|
2025-01-12 13:54:18 +00:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2024-11-27 04:14:10 +00:00
|
|
|
</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;
|