merge
|
|
@ -1,23 +1,19 @@
|
|||
"use client";
|
||||
import React, { useState, useEffect, act } from "react";
|
||||
import FullCalendar from "@fullcalendar/react"; // must go before plugins
|
||||
import React, { useState, useEffect } from "react";
|
||||
import FullCalendar from "@fullcalendar/react";
|
||||
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, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Book,
|
||||
CheckCheck,
|
||||
CheckSquare2,
|
||||
CheckSquare2Icon,
|
||||
Plus,
|
||||
Timer,
|
||||
TimerIcon,
|
||||
} from "lucide-react";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { EventContentArg } from "@fullcalendar/core";
|
||||
|
|
@ -42,8 +38,8 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
|
||||
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
interface CalendarViewProps {
|
||||
// events: CalendarEvent[];
|
||||
categories: CalendarCategory[];
|
||||
}
|
||||
|
||||
|
|
@ -59,12 +55,11 @@ export type CalendarEvent = {
|
|||
extendedProps: {
|
||||
calendar: string;
|
||||
description: string;
|
||||
isPublish?: boolean;
|
||||
createdByName?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
||||
const INITIAL_MONTH = dayjs().format("M");
|
||||
|
||||
export interface AgendaSettingsAPIResponse {
|
||||
id: number;
|
||||
title: string;
|
||||
|
|
@ -74,24 +69,25 @@ export interface AgendaSettingsAPIResponse {
|
|||
startDate: string;
|
||||
endDate: string;
|
||||
isActive: boolean;
|
||||
isPublish: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdById: number | null;
|
||||
}
|
||||
|
||||
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[];
|
||||
january?: AgendaSettingsAPIResponse[];
|
||||
february?: AgendaSettingsAPIResponse[];
|
||||
march?: AgendaSettingsAPIResponse[];
|
||||
april?: AgendaSettingsAPIResponse[];
|
||||
may?: AgendaSettingsAPIResponse[];
|
||||
june?: AgendaSettingsAPIResponse[];
|
||||
july?: AgendaSettingsAPIResponse[];
|
||||
august?: AgendaSettingsAPIResponse[];
|
||||
september?: AgendaSettingsAPIResponse[];
|
||||
october?: AgendaSettingsAPIResponse[];
|
||||
november?: AgendaSettingsAPIResponse[];
|
||||
december?: AgendaSettingsAPIResponse[];
|
||||
}
|
||||
|
||||
interface MonthCardProps {
|
||||
|
|
@ -100,411 +96,345 @@ interface MonthCardProps {
|
|||
}
|
||||
|
||||
interface ListItemProps {
|
||||
item: any;
|
||||
item: AgendaSettingsAPIResponse;
|
||||
text: string;
|
||||
createdBy: string;
|
||||
isPublish: boolean | null;
|
||||
bgColor: string;
|
||||
colorList: string[] | null;
|
||||
}
|
||||
|
||||
interface APIResponse {
|
||||
error: boolean;
|
||||
message: any;
|
||||
data: AgendaSettingsAPIResponse[] | null;
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: YearlyData | null;
|
||||
errorCode: string | null;
|
||||
}
|
||||
|
||||
type EventType = "0" | "1" | "2" | "3" | "4" | "5";
|
||||
|
||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
||||
const INITIAL_MONTH = dayjs().format("M");
|
||||
|
||||
const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string[] | null>(
|
||||
null
|
||||
);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string[]>([]);
|
||||
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
||||
const roleId = Number(getCookiesDecrypt("urie")) || 0;
|
||||
const userLevelId = Number(getCookiesDecrypt("ulie")) || 0;
|
||||
console.log("roleId", roleId);
|
||||
console.log("userlevel", userLevelId);
|
||||
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
|
||||
const [Isloading, setLoading] = useState<boolean>(false);
|
||||
const [draggableInitialized, setDraggableInitialized] =
|
||||
useState<boolean>(false);
|
||||
const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const t = useTranslations("CalendarApp");
|
||||
// event canvas state
|
||||
|
||||
// Modal states
|
||||
const [sheetOpen, setSheetOpen] = useState<boolean>(false);
|
||||
const [date, setDate] = React.useState<Date>(new Date());
|
||||
const [date, setDate] = useState<Date>(new Date());
|
||||
const [activeView, setActiveView] = useState("listYear");
|
||||
const [yearlyData, setYearlyData] = useState();
|
||||
|
||||
const TODAY = dayjs().format("yyyy-MM-dd");
|
||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
||||
const INITIAL_MONTH = dayjs().format("M");
|
||||
const [yearlyData, setYearlyData] = useState<YearlyData>({});
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedEventData, setSelectedEventData] = useState<any>(null);
|
||||
|
||||
const [selectedYear, setSelectedYear] = useState(new Date());
|
||||
const [selectedMonth, setSelectedMonth] = useState(
|
||||
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
|
||||
);
|
||||
|
||||
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" },
|
||||
]);
|
||||
|
||||
const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]);
|
||||
|
||||
// Load events when view or month changes
|
||||
useEffect(() => {
|
||||
if (activeView == "listYear") {
|
||||
if (activeView === "listYear") {
|
||||
getYearlyEvents();
|
||||
} else {
|
||||
getCalendarEvents();
|
||||
}
|
||||
}, [activeView, selectedMonth]);
|
||||
|
||||
const getCalendarEvents = async () => {
|
||||
console.log("View : ", activeView);
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
selectedMonth.format("M") || INITIAL_MONTH,
|
||||
""
|
||||
);
|
||||
console.log("View : API Response:", res);
|
||||
|
||||
if (res?.error) {
|
||||
error(res?.message);
|
||||
return;
|
||||
// Initialize selected categories
|
||||
useEffect(() => {
|
||||
if (categories?.length > 0) {
|
||||
setSelectedCategory(categories.map((c) => c.value));
|
||||
}
|
||||
}, [categories]);
|
||||
|
||||
const data = res?.data?.data;
|
||||
const getCalendarEvents = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
selectedMonth.format("M") || INITIAL_MONTH,
|
||||
""
|
||||
);
|
||||
|
||||
if (data) {
|
||||
console.log("Dataaa : ", data);
|
||||
if (res?.error) {
|
||||
error(res?.message || "Failed to fetch events");
|
||||
return;
|
||||
}
|
||||
|
||||
// For monthly view, we need to extract events from the specific month
|
||||
const monthData = res?.data?.data;
|
||||
if (monthData) {
|
||||
const allEvents: CalendarEvent[] = [];
|
||||
// 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: {
|
||||
isPublish: event.isPublish,
|
||||
calendar: event.agendaType,
|
||||
description: event.description,
|
||||
const events = monthData?.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: {
|
||||
isPublish: event.isPublish,
|
||||
calendar: event.agendaType,
|
||||
description: event.description,
|
||||
createdByName: event.createdByName,
|
||||
},
|
||||
}));
|
||||
|
||||
console.log("Dataaa event : ", events);
|
||||
|
||||
setCalendarEvents(events);
|
||||
console.log("Event Data : ", events);
|
||||
setCalendarEvents(events);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch calendar events:", err);
|
||||
error("Failed to fetch calendar events");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getYearlyEvents = async () => {
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
"",
|
||||
""
|
||||
);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await getAgendaSettingsList(
|
||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||
"",
|
||||
""
|
||||
);
|
||||
|
||||
if (res?.error) {
|
||||
error(res?.message || "Failed to fetch yearly events");
|
||||
return;
|
||||
}
|
||||
|
||||
// The API already returns data organized by months
|
||||
const yearlyData = res?.data?.data;
|
||||
if (yearlyData) {
|
||||
setYearlyData(yearlyData);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch yearly events:", err);
|
||||
error("Failed to fetch yearly events");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
setYearlyData(res?.data?.data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
}, [categories]);
|
||||
|
||||
const filteredEvents = calendarEvents?.filter((event) => {
|
||||
// Filter events based on selected categories
|
||||
const filteredEvents = calendarEvents.filter((event) => {
|
||||
if (!selectedCategory.length) return false;
|
||||
console.log("Event category : ", selectedCategory);
|
||||
|
||||
const eventCategories = event.extendedProps.calendar
|
||||
?.split(",")
|
||||
.map((val: string) => val.trim()); // agar "1, 2" tetap dianggap benar
|
||||
.map((val: string) => val.trim());
|
||||
|
||||
const allCategoryId = ["1", "2", "3", "4", "5"];
|
||||
// Cek apakah SEMUA validTypeIds ada di typeIdsInData
|
||||
const hasAllCategories = allCategoryId.every(categoryId => selectedCategory.includes(categoryId));
|
||||
|
||||
return eventCategories?.some((cat: string) =>
|
||||
selectedCategory?.includes(cat)
|
||||
selectedCategory.includes(cat) || (hasAllCategories && cat == "0")
|
||||
);
|
||||
});
|
||||
|
||||
const displayedEvents =
|
||||
filteredEvents?.length > 1 ? filteredEvents : apiEvents;
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategory(categories?.map((c: any) => c.value));
|
||||
}, [categories]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Selected categories:", selectedCategory);
|
||||
}, [selectedCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAgendaEvents();
|
||||
}, []);
|
||||
|
||||
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
|
||||
const eventsFromAPI: CalendarEvent[] = response?.data?.map((item) => ({
|
||||
id: item.id.toString(),
|
||||
title: item.title,
|
||||
createBy: "Mabes Polri - Approver",
|
||||
createdByName: item.createdByName,
|
||||
isPublish: item.isPublish,
|
||||
start: new Date(item.startDate),
|
||||
end: new Date(item.endDate),
|
||||
allDay: true,
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Fetched events from API 1:", apiEvents);
|
||||
}, [apiEvents]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Filtered events based on category 1:", calendarEvents);
|
||||
}, [filteredEvents, apiEvents]);
|
||||
|
||||
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]);
|
||||
|
||||
// event click
|
||||
// Event handlers
|
||||
const handleEventClick = (arg: any) => {
|
||||
console.log("Event Click ", arg);
|
||||
setSelectedEventDate(null);
|
||||
setSelectedEventData(arg);
|
||||
setSheetOpen(true);
|
||||
setApiEvents(arg);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
};
|
||||
|
||||
// handle close modal
|
||||
const handleCloseModal = () => {
|
||||
setSheetOpen(false);
|
||||
setApiEvents([]);
|
||||
setSelectedEventData(null);
|
||||
setSelectedEventDate(null);
|
||||
};
|
||||
|
||||
const handleDateClick = (arg: any) => {
|
||||
setSheetOpen(true);
|
||||
console.log("Date :", arg);
|
||||
setSelectedEventDate(arg);
|
||||
setApiEvents([]);
|
||||
setSelectedEventData(null);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
};
|
||||
|
||||
const handleCategorySelection = (category: string) => {
|
||||
if (selectedCategory && selectedCategory.includes(category)) {
|
||||
setSelectedCategory(selectedCategory.filter((c) => c !== category));
|
||||
} else {
|
||||
setSelectedCategory([...(selectedCategory || []), category]);
|
||||
}
|
||||
};
|
||||
|
||||
const renderEventContent = (eventInfo: any) => {
|
||||
const { title } = eventInfo.event;
|
||||
const { createdByName, isPublish } = eventInfo.event.extendedProps;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row">
|
||||
{" "}
|
||||
{isPublish === true ? <CheckCheck size={15} /> : <Timer size={15} />}
|
||||
<p className="ml-1">{title}</p>
|
||||
</div>
|
||||
|
||||
<p className="ml-1 text-xs text-start mt-2">
|
||||
Created By : {createdByName}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const handleClassName = (arg: EventContentArg) => {
|
||||
if (arg.event.extendedProps.calendar === "1") {
|
||||
return "bg-yellow-500 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "2") {
|
||||
return "bg-blue-400 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "3") {
|
||||
return "bg-slate-400 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "4") {
|
||||
return "bg-orange-500 border-none";
|
||||
} else if (arg.event.extendedProps.calendar === "5") {
|
||||
return "bg-green-400 border-none";
|
||||
} else {
|
||||
return "primary";
|
||||
}
|
||||
setSelectedCategory(prev => {
|
||||
if (prev.includes(category)) {
|
||||
return prev.filter((c) => c !== category);
|
||||
} else {
|
||||
return [...prev, category];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDateChange = (startDate: string, endDate: string) => {
|
||||
const startDateOfDate = new Date(startDate);
|
||||
const endDateOfDate = new Date(endDate);
|
||||
|
||||
console.log("Change Date : ", startDateOfDate);
|
||||
|
||||
setSelectedMonth(dayjs(startDateOfDate));
|
||||
};
|
||||
|
||||
const handleViewChange = (viewType: string) => {
|
||||
console.log("Change view : ", viewType);
|
||||
setActiveView(viewType);
|
||||
};
|
||||
|
||||
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
||||
{ 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" },
|
||||
];
|
||||
|
||||
const getEventColor = (type: Event["type"]): string => {
|
||||
const colors: Record<Event["type"], string> = {
|
||||
1: "bg-yellow-500",
|
||||
2: "bg-blue-400",
|
||||
3: "bg-slate-400",
|
||||
4: "bg-orange-500",
|
||||
5: "bg-green-400",
|
||||
};
|
||||
return colors[type];
|
||||
};
|
||||
|
||||
const handleClickListItem = (item: any) => {
|
||||
const handleClickListItem = (item: AgendaSettingsAPIResponse) => {
|
||||
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
|
||||
createBy: item.createdById?.toString() || "",
|
||||
createdByName: item.createdByName,
|
||||
isPublish: item.isPublish,
|
||||
allDay: true,
|
||||
extendedProps: {
|
||||
calendar: item.agendaType,
|
||||
description: item.description,
|
||||
isPublish: item.isPublish,
|
||||
createdByName: item.createdByName,
|
||||
},
|
||||
};
|
||||
const finalEvent: any = {
|
||||
event: formattedEvent,
|
||||
};
|
||||
|
||||
console.log("Event click custom : ", finalEvent);
|
||||
|
||||
const eventArg = { event: formattedEvent };
|
||||
setSelectedEventDate(null);
|
||||
setSelectedEventData(eventArg);
|
||||
setSheetOpen(true);
|
||||
setApiEvents(finalEvent);
|
||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
const getEventColor = (type: EventType): string => {
|
||||
const typeSplit = type.split(",");
|
||||
const firstType = typeSplit[0] as EventType;
|
||||
|
||||
const colors: Record<EventType, string> = {
|
||||
"0": "bg-black",
|
||||
"1": "bg-yellow-500",
|
||||
"2": "bg-blue-400",
|
||||
"3": "bg-slate-400",
|
||||
"4": "bg-orange-500",
|
||||
"5": "bg-green-400",
|
||||
};
|
||||
|
||||
return colors[firstType] || "bg-gray-400";
|
||||
};
|
||||
|
||||
const getEventColorList = (type: EventType): string[] | null => {
|
||||
const typeSplit = type.split(",");
|
||||
const typeList = typeSplit.slice(1);
|
||||
|
||||
if (typeList.length === 0) return null;
|
||||
|
||||
const colors: Record<EventType, string> = {
|
||||
"0": "bg-black",
|
||||
"1": "bg-yellow-500",
|
||||
"2": "bg-blue-400",
|
||||
"3": "bg-slate-400",
|
||||
"4": "bg-orange-500",
|
||||
"5": "bg-green-400",
|
||||
};
|
||||
|
||||
return typeList.map((item) => colors[item as EventType] || "bg-gray-200");
|
||||
};
|
||||
|
||||
const renderEventContent = (eventInfo: any) => {
|
||||
const { title } = eventInfo.event;
|
||||
const { createdByName, isPublish, calendar } = eventInfo.event.extendedProps;
|
||||
const bgColor = getEventColor(calendar);
|
||||
const colorList = getEventColorList(calendar);
|
||||
|
||||
return (
|
||||
<div className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch ${bgColor}`}>
|
||||
<div className="flex-1">
|
||||
<div className="flex flex-row items-center">
|
||||
{isPublish === true ? <CheckCheck size={15} /> : <Timer size={15} />}
|
||||
<p className="ml-1">{title}</p>
|
||||
</div>
|
||||
<p className="ml-1 text-xs text-start mt-2">
|
||||
Created By: {createdByName}
|
||||
</p>
|
||||
</div>
|
||||
{colorList && colorList.length > 0 && (
|
||||
<div className="w-3 flex flex-col h-full ml-2 overflow-hidden item-stretch gap-1">
|
||||
{colorList.map((color: string, index: number) => (
|
||||
<div key={index} className={`h-[10px] rounded-sm ${color}`}></div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const handleClassName = (arg: EventContentArg) => {
|
||||
const eventColor = getEventColor(arg.event.extendedProps.calendar);
|
||||
return eventColor || "primary";
|
||||
};
|
||||
|
||||
// Components
|
||||
const ListItem: React.FC<ListItemProps> = ({
|
||||
item,
|
||||
text,
|
||||
createdBy,
|
||||
isPublish,
|
||||
bgColor,
|
||||
colorList,
|
||||
}) => (
|
||||
<div
|
||||
className={`w-full p-1 mb-2 rounded-md text-white text-sm ${bgColor}`}
|
||||
<div
|
||||
className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch cursor-pointer hover:opacity-80 ${bgColor}`}
|
||||
onClick={() => handleClickListItem(item)}
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
{isPublish ? <CheckCheck size={15} /> : <Timer size={15} />}
|
||||
<p className="ml-1">{text}</p>
|
||||
<div className="flex-1">
|
||||
<div className="flex flex-row items-center">
|
||||
{isPublish ? <CheckCheck size={15} /> : <Timer size={15} />}
|
||||
<p className="ml-1">{text}</p>
|
||||
</div>
|
||||
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||
</div>
|
||||
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||
{colorList && colorList.length > 0 && (
|
||||
<div className="w-3 flex flex-col h-full ml-2 overflow-hidden item-stretch gap-1">
|
||||
{colorList.map((color: string, index: number) => (
|
||||
<div key={index} className={`h-[10px] rounded-sm ${color}`}></div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const MonthCard: React.FC<MonthCardProps> = (props: any) => {
|
||||
const { monthId, label } = props;
|
||||
const events: any = yearlyData?.[monthId];
|
||||
const displayedEvents = events?.slice(0, 3);
|
||||
const hasMoreEvents = events?.length > 3;
|
||||
const MonthCard: React.FC<MonthCardProps> = ({ monthId, label }) => {
|
||||
const events = 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>
|
||||
|
||||
<div className="px-2">
|
||||
{events?.length === 0 ? (
|
||||
{events.length === 0 ? (
|
||||
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
||||
<p className="text-center">{t("no-data-yet")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{displayedEvents?.map((event: any, index: number) => (
|
||||
{displayedEvents.map((event, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
key={`${event.id}-${index}`}
|
||||
item={event}
|
||||
text={event.title}
|
||||
createdBy={event.createdByName}
|
||||
isPublish={event.isPublish}
|
||||
bgColor={getEventColor(event.agendaType)}
|
||||
bgColor={getEventColor(event.agendaType as EventType)}
|
||||
colorList={getEventColorList(event.agendaType as EventType)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{hasMoreEvents && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
|
|
@ -524,14 +454,15 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
</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) => (
|
||||
{events.map((event, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
key={`${event.id}-full-${index}`}
|
||||
item={event}
|
||||
text={event.title}
|
||||
createdBy={event.createdByName}
|
||||
isPublish={event.isPublish}
|
||||
bgColor={getEventColor(event.agendaType)}
|
||||
bgColor={getEventColor(event.agendaType as EventType)}
|
||||
colorList={getEventColorList(event.agendaType as EventType)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -547,9 +478,24 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const getModalContent = (type: "terkirim" | "diterima") => (
|
||||
<div className="overflow-x-auto overflow-y-auto ">
|
||||
{Isloading ? (
|
||||
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
||||
{ 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" },
|
||||
];
|
||||
|
||||
const getModalContent = () => (
|
||||
<div className="overflow-x-auto overflow-y-auto">
|
||||
{isLoading ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<table className="w-full border-collapse border border-gray-300">
|
||||
|
|
@ -563,14 +509,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
</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 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">Completed</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -580,11 +524,11 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
|
||||
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">
|
||||
<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 ">
|
||||
{roleId == 3 || roleId == 11 || roleId == 2 || roleId == 12 ? (
|
||||
<CardHeader className="border-none mb-2 pt-5">
|
||||
{[3, 11, 2, 12].includes(roleId) && (
|
||||
<Button
|
||||
onClick={handleDateClick}
|
||||
className="dark:bg-background dark:text-foreground w-full"
|
||||
|
|
@ -592,72 +536,64 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
<Plus className="w-4 h-4 me-1" />
|
||||
{t("addEvent")}
|
||||
</Button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div>
|
||||
|
||||
{roleId === 3 && userLevelId === 216 && (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{roleId === 3 && userLevelId === 216 ? (
|
||||
<Button className="dark:bg-background dark:text-foreground w-full">
|
||||
<Book size={15} className="w-4 h-4 mr-3" />
|
||||
{t("bag-pa-monitoring-results")}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button className="dark:bg-background dark:text-foreground w-full">
|
||||
<Book size={15} className="w-4 h-4 mr-3" />
|
||||
{t("bag-pa-monitoring-results")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("monitoring-results")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{getModalContent("terkirim")}
|
||||
{getModalContent()}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)}
|
||||
</CardHeader>
|
||||
|
||||
<div className="px-3">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={(s) => {
|
||||
handleDateClick(s);
|
||||
onSelect={(selectedDate) => {
|
||||
if (selectedDate) {
|
||||
setDate(selectedDate);
|
||||
handleDateClick(selectedDate);
|
||||
}
|
||||
}}
|
||||
className="rounded-md border w-full p-0 border-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* <div id="external-events" className=" space-y-1.5 mt-6 px-4">
|
||||
<p className="text-sm font-medium text-default-700 mb-3">
|
||||
{t("shortDesc")}
|
||||
</p>
|
||||
{dragEvents.map((event) => (
|
||||
<ExternalDraggingevent key={event.id} event={event} />
|
||||
))}
|
||||
</div> */}
|
||||
<div className="py-4 text-default-800 font-semibold text-xs uppercase mt-4 mb-2 px-4">
|
||||
<div className="py-4 text-default-800 font-semibold text-xs uppercase mt-4 mb-2 px-4">
|
||||
{t("filter")}
|
||||
</div>
|
||||
<ul className="space-y-3 px-4">
|
||||
<li className=" flex gap-3">
|
||||
<li className="flex gap-3">
|
||||
<Checkbox
|
||||
checked={selectedCategory?.length === categories?.length}
|
||||
onClick={() => {
|
||||
if (selectedCategory?.length === categories?.length) {
|
||||
checked={selectedCategory.length === categories.length}
|
||||
onCheckedChange={() => {
|
||||
if (selectedCategory.length === categories.length) {
|
||||
setSelectedCategory([]);
|
||||
} else {
|
||||
setSelectedCategory(categories?.map((c) => c.value));
|
||||
setSelectedCategory(categories.map((c) => c.value));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label>All</Label>
|
||||
</li>
|
||||
{categories?.map((category) => (
|
||||
<li className="flex gap-3 " key={category.value}>
|
||||
{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)}
|
||||
checked={selectedCategory.includes(category.value)}
|
||||
onCheckedChange={() => handleCategorySelection(category.value)}
|
||||
/>
|
||||
<Label htmlFor={category.label}>{category.label}</Label>
|
||||
</li>
|
||||
|
|
@ -666,15 +602,10 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="col-span-12 lg:col-span-8 2xl:col-span-9 pt-5">
|
||||
<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,
|
||||
]}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin]}
|
||||
headerToolbar={{
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
|
|
@ -686,7 +617,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
buttonText: "Year",
|
||||
},
|
||||
}}
|
||||
events={displayedEvents}
|
||||
events={filteredEvents}
|
||||
editable={true}
|
||||
rerenderDelay={10}
|
||||
eventDurationEditable={false}
|
||||
|
|
@ -702,70 +633,40 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
|||
eventContent={renderEventContent}
|
||||
viewDidMount={(info) => handleViewChange(info.view.type)}
|
||||
datesSet={(info: any) => {
|
||||
console.log(info);
|
||||
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
||||
handleViewChange(info.view.type);
|
||||
}}
|
||||
viewClassNames={
|
||||
activeView === "listYear" ? "hide-calendar-grid" : ""
|
||||
}
|
||||
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
|
||||
/>
|
||||
|
||||
{activeView === "listYear" && (
|
||||
<div className="custom-ui">
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(0, 3).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(3, 6).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(6, 9).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1 mt-1">
|
||||
{months.slice(9, 12).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{[0, 3, 6, 9].map((startIndex) => (
|
||||
<div key={startIndex} className="flex gap-1 mt-1">
|
||||
{months.slice(startIndex, startIndex + 3).map((month) => (
|
||||
<MonthCard
|
||||
key={month.id}
|
||||
monthId={month.id}
|
||||
label={month.label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<EventModal
|
||||
open={sheetOpen}
|
||||
onClose={handleCloseModal}
|
||||
categories={categories}
|
||||
event={apiEvents}
|
||||
event={selectedEventData}
|
||||
selectedDate={selectedEventDate}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CalendarView;
|
||||
export default CalendarView;
|
||||
|
|
@ -21,7 +21,7 @@ const CalenderPage = () => {
|
|||
const categories = await getCategories();
|
||||
let valueShowed: string[] = [];
|
||||
if (userLevelNumber == 1) {
|
||||
valueShowed = ["1", "2", "3", "4", "5"];
|
||||
valueShowed = ["0", "1", "2", "3", "4", "5"];
|
||||
} else if (userLevelNumber == 2 && userLevelId != 761) {
|
||||
valueShowed = ["2", "3"];
|
||||
} else if (
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const AreaCoverageWorkUnits = () => {
|
|||
<p className="text-base font-bold">Polda Jajaran</p>
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md" className="max-h-[90vh] overflow-hidden flex flex-col ">
|
||||
<DialogContent size="md" className="max-h-[90vh] overflow-hidden flex flex-col" data-lenis-prevent>
|
||||
<DialogHeader className="flex flex-col justify-center">
|
||||
<DialogTitle>
|
||||
<p className="text-center">Polda Jajaran</p>
|
||||
|
|
@ -63,7 +63,7 @@ const AreaCoverageWorkUnits = () => {
|
|||
<p className="text-base font-bold">Satuan Kerja Polri</p>
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md">
|
||||
<DialogContent size="md" data-lenis-prevent>
|
||||
<DialogHeader className="flex flex-col justify-center">
|
||||
<DialogTitle>
|
||||
<p className="text-center">Satuan Kerja Polri</p>
|
||||
|
|
|
|||
|
|
@ -54,22 +54,22 @@ const regions = [
|
|||
{
|
||||
name: "Polda Kalimantan Barat",
|
||||
slug: "kalimantan-barat",
|
||||
logo: "/logo/polda/polda-kalbar.png",
|
||||
logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Selatan",
|
||||
slug: "kalimantan-selatan",
|
||||
logo: "/logo/polda/polda-kalsel.png",
|
||||
logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Tengah",
|
||||
slug: "kalimantan-tengah",
|
||||
logo: "/logo/polda/polda-kalteng.png",
|
||||
logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Timur",
|
||||
slug: "kalimantan-timur",
|
||||
logo: "/logo/polda/polda-kaltim.png",
|
||||
logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Utara",
|
||||
|
|
@ -117,6 +117,16 @@ const regions = [
|
|||
slug: "papua-barat",
|
||||
logo: "/logo/polda/polda-papua-barat.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Papua Barat Daya",
|
||||
slug: "papua-barat-daya",
|
||||
logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Papua Tengah",
|
||||
slug: "papua-tengah",
|
||||
logo: "/logo/polda/polda-papua-tengah.png",
|
||||
},
|
||||
{ name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
{
|
||||
name: "Polda Sulawesi Barat",
|
||||
|
|
@ -173,14 +183,18 @@ const regions = [
|
|||
const Coverage: React.FC = () => {
|
||||
const [seeAllValue, setSeeAllValue] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions);
|
||||
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(
|
||||
regions
|
||||
);
|
||||
const pathname = usePathname();
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
const handleSearch = () => {
|
||||
const value = searchTerm.toLowerCase();
|
||||
|
||||
const filtered = regions.filter((polda) => polda.name.toLowerCase().includes(value));
|
||||
const filtered = regions.filter((polda) =>
|
||||
polda.name.toLowerCase().includes(value)
|
||||
);
|
||||
setFilteredList(filtered);
|
||||
};
|
||||
|
||||
|
|
@ -198,16 +212,32 @@ const Coverage: React.FC = () => {
|
|||
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||
</svg>`;
|
||||
|
||||
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
|
||||
const toBase64 = (str: string) =>
|
||||
typeof window === "undefined"
|
||||
? Buffer.from(str).toString("base64")
|
||||
: window.btoa(str);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="max-h-[60vh] overflow-y-auto px-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 px-4 max-h-[60vh]">
|
||||
{filteredList?.map((region: any) => (
|
||||
<Link key={region.slug} href={`/polda/${region.slug}`} className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all">
|
||||
<Link
|
||||
key={region.slug}
|
||||
href={`/polda/${region.slug}`}
|
||||
className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all"
|
||||
>
|
||||
<div className="mb-1 flex items-center justify-center">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={region.logo} alt={region.name} className="w-14 h-14 object-contain" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1920}
|
||||
height={1080}
|
||||
src={region.logo}
|
||||
alt={region.name}
|
||||
className="w-14 h-14 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs font-semibold">{region.name}</p>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -9,48 +9,172 @@ import Image from "next/image";
|
|||
|
||||
const regions = [
|
||||
{ name: "ITWASUM POLRI", slug: "itwasum", logo: "/logo/satker/ITWASUM.png" },
|
||||
{ name: "BAINTELKAM POLRI", slug: "baintelkam", logo: "/logo/satker/BAINTELKAM.png" },
|
||||
{ name: "BAHARKAM POLRI", slug: "baharkam", logo: "/logo/satker/BAHARKAM.png" },
|
||||
{ name: "BARESKRIM POLRI", slug: "bareskrim", logo: "/logo/satker/BARESKRIM.png" },
|
||||
{ name: "LEMDIKLAT POLRI", slug: "lemdiklat", logo: "/logo/satker/LEMDIKLAT.png" },
|
||||
{
|
||||
name: "BAINTELKAM POLRI",
|
||||
slug: "baintelkam",
|
||||
logo: "/logo/satker/BAINTELKAM.png",
|
||||
},
|
||||
{
|
||||
name: "BAHARKAM POLRI",
|
||||
slug: "baharkam",
|
||||
logo: "/logo/satker/BAHARKAM.png",
|
||||
},
|
||||
{
|
||||
name: "BARESKRIM POLRI",
|
||||
slug: "bareskrim",
|
||||
logo: "/logo/satker/BARESKRIM.png",
|
||||
},
|
||||
{
|
||||
name: "LEMDIKLAT POLRI",
|
||||
slug: "lemdiklat",
|
||||
logo: "/logo/satker/LEMDIKLAT.png",
|
||||
},
|
||||
{ name: "KORBRIMOB POLRI", slug: "brimob", logo: "/logo/satker/BRIMOB.png" },
|
||||
{ name: "STAMAOPS POLRI", slug: "sops", logo: "/logo/satker/SOPS.png" },
|
||||
{ name: "STAMARENA POLRI", slug: "srena", logo: "/logo/satker/SRENA.png" },
|
||||
{ name: "SSDM POLRI", slug: "ssdm", logo: "/logo/satker/SSDM.png" },
|
||||
{ name: "SLOG POLRI", slug: "slog", logo: "/logo/satker/SLOG.png" },
|
||||
{ name: "SAHLI KAPOLRI", slug: "stafahli", logo: "/logo/satker/STAFAHLI.png" },
|
||||
{ name: "DIVPROPRAM POLRI", slug: "divpropram", logo: "/logo/satker/DIVPROPAM.png" },
|
||||
{ name: "DIVKUM", slug: "divkum", logo: "/assets/satker/divkum.png" },
|
||||
{ name: "DIVHUBINTER POLRI", slug: "divhubinter", logo: "/logo/satker/DIVHUBINTER.png" },
|
||||
{
|
||||
name: "SAHLI KAPOLRI",
|
||||
slug: "stafahli",
|
||||
logo: "/logo/satker/STAFAHLI.png",
|
||||
},
|
||||
{
|
||||
name: "DIVPROPRAM POLRI",
|
||||
slug: "divpropram",
|
||||
logo: "/logo/satker/DIVPROPAM.png",
|
||||
},
|
||||
{ name: "DIVKUM", slug: "divkum", logo: "/logo/satker/DIVKUM.png" },
|
||||
{
|
||||
name: "DIVHUBINTER POLRI",
|
||||
slug: "divhubinter",
|
||||
logo: "/logo/satker/DIVHUBINTER.png",
|
||||
},
|
||||
{ name: "DIVTIK POLRI", slug: "div-tik", logo: "/logo/satker/DIV-TIK.png" },
|
||||
{ name: "KORLANTAS POLRI", slug: "korlantas", logo: "/logo/satker/KORLANTAS.png" },
|
||||
{ name: "DENSUS 88 POLRI", slug: "densus-88", logo: "/logo/satker/DENSUS-88.png" },
|
||||
{ name: "PUSDOKKES POLRI", slug: "pusdokkes", logo: "/logo/satker/PUSDOKKES.png" },
|
||||
{ name: "PUSLITBANG POLRI", slug: "puslitbang", logo: "/logo/satker/PUSLITBANG.png" },
|
||||
{
|
||||
name: "KORLANTAS POLRI",
|
||||
slug: "korlantas",
|
||||
logo: "/logo/satker/KORLANTAS.png",
|
||||
},
|
||||
{
|
||||
name: "DENSUS 88 POLRI",
|
||||
slug: "densus-88",
|
||||
logo: "/logo/satker/DENSUS-88.png",
|
||||
},
|
||||
{
|
||||
name: "PUSDOKKES POLRI",
|
||||
slug: "pusdokkes",
|
||||
logo: "/logo/satker/PUSDOKKES.png",
|
||||
},
|
||||
{
|
||||
name: "PUSLITBANG POLRI",
|
||||
slug: "puslitbang",
|
||||
logo: "/logo/satker/PUSLITBANG.png",
|
||||
},
|
||||
{ name: "PUSKEU POLRI", slug: "puskeu", logo: "/logo/satker/PUSKEU.png" },
|
||||
{ name: "PUSJARAH POLRI", slug: "pusjarah", logo: "/logo/satker/PUSJARAH.png" },
|
||||
{
|
||||
name: "PUSJARAH POLRI",
|
||||
slug: "pusjarah",
|
||||
logo: "/logo/satker/PUSJARAH.png",
|
||||
},
|
||||
{ name: "SETUM POLRI", slug: "setum", logo: "/logo/satker/SETUM.png" },
|
||||
{ name: "YANMA POLRI", slug: "yanma", logo: "/logo/satker/YANMA.png" },
|
||||
{ name: "SPRIPIM POLRI", slug: "spripim", logo: "/logo/satker/SPRIPIM.png" },
|
||||
{ name: "KORPOLAIRUD BAHARKAM POLRI", slug: "polairud", logo: "/logo/satker/POLAIRUD.png" },
|
||||
{ name: "KORSABHARA BAHARKAM POLRI", slug: "korps-sabhara-baharkam", logo: "/logo/satker/KORPS-SABHARA-BAHARKAM.png" },
|
||||
{ name: "KORBINMAS BAHARKAM POLRI", slug: "binmas", logo: "/logo/satker/BINMAS.png" },
|
||||
{ name: "DITTIPIDUM BARESKRIM POLRI", slug: "dittipidum", logo: "/logo/satker/DITTIPIDUM.png" },
|
||||
{ name: "DITTIPIDEKSUS BARESKRIM POLRI", slug: "dittipideksus", logo: "/logo/satker/DITTIPIDEKSUS.png" },
|
||||
{ name: "DITTIPIDKOR BARESKRIM POLRI", slug: "dittipidkor", logo: "/logo/satker/DITTIPIDKOR.png" },
|
||||
{ name: "DITTIPIDNARKOBA BARESKRIM POLRI", slug: "dittipidnarkoba", logo: "/logo/satker/DITTIPIDNARKOBA.png" },
|
||||
{ name: "DITTIPIDTER BARESKRIM POLRI", slug: "dittipidter", logo: "/logo/satker/DITTIPIDTER.png" },
|
||||
{ name: "DITTIPIDSIBER BARESKRIM POLRI", slug: "dittipidsiber", logo: "/logo/satker/DITTIPIDSIBER.png" },
|
||||
{ name: "DIT PPA-PPO BARESKRIM POLRI", slug: "dit-ppa-ppo", logo: "/logo/satker/DITPPAPPO.png" },
|
||||
{ name: "PUSLABFOR BARESKRIM POLRI", slug: "puslabfor", logo: "/assets/satker/puslabfor.png" },
|
||||
{ name: "PUSIKNAS BARESKRIM POLRI", slug: "pusiknas", logo: "/logo/satker/PUSKINAS.png" },
|
||||
{ name: "STIK LEMDIKLAT POLRI", slug: "stik-ptik", logo: "/logo/satker/STIK-PTIK.png" },
|
||||
{ name: "AKPOL LEMDIKLAT POLRI", slug: "akpol", logo: "/logo/satker/AKPOL.png" },
|
||||
{ name: "SESPIM LEMDIKLAT POLRI", slug: "sespim-polri", logo: "/logo/satker/SESPIM-POLRI.png" },
|
||||
{ name: "SETUKPA LEMDIKLAT POLRI", slug: "setupa-polri", logo: "/logo/satker/SETUPA-POLRI.png" },
|
||||
{ name: "SEPOLWAN LEMDIKLAT POLRI", slug: "sepolwan-polri", logo: "/logo/satker/SEPOLWAN-POLRI.png" },
|
||||
{ name: "SEBASA LEMDIKLAT POLRI", slug: "sebasa-polri", logo: "/logo/satker/SEBASA-POLRI.png" },
|
||||
{ name: "RUMKIT BHAYANGKARA TK I", slug: "rumkit-bhayangkara", logo: "/logo/satker/RUMKIT-BHAYANGKARA.png" },
|
||||
{
|
||||
name: "KORPOLAIRUD BAHARKAM POLRI",
|
||||
slug: "polairud",
|
||||
logo: "/logo/satker/POLAIRUD.png",
|
||||
},
|
||||
{
|
||||
name: "KORSABHARA BAHARKAM POLRI",
|
||||
slug: "korps-sabhara-baharkam",
|
||||
logo: "/logo/satker/KORPS-SABHARA-BAHARKAM.png",
|
||||
},
|
||||
{
|
||||
name: "KORBINMAS BAHARKAM POLRI",
|
||||
slug: "binmas",
|
||||
logo: "/logo/satker/BINMAS.png",
|
||||
},
|
||||
{
|
||||
name: "DITTIPIDUM BARESKRIM POLRI",
|
||||
slug: "dittipidum",
|
||||
logo: "/logo/satker/DITTIPIDUM.png",
|
||||
},
|
||||
{
|
||||
name: "DITTIPIDEKSUS BARESKRIM POLRI",
|
||||
slug: "dittipideksus",
|
||||
logo: "/logo/satker/DITTIPIDEKSUS.png",
|
||||
},
|
||||
{
|
||||
name: "DITTIPIDKOR BARESKRIM POLRI",
|
||||
slug: "dittipidkor",
|
||||
logo: "/logo/satker/DITTIPIDKOR.png",
|
||||
},
|
||||
{
|
||||
name: "DITTIPIDNARKOBA BARESKRIM POLRI",
|
||||
slug: "dittipidnarkoba",
|
||||
logo: "/logo/satker/DITTIPIDNARKOBA.png",
|
||||
},
|
||||
{
|
||||
name: "DITTIPIDTER BARESKRIM POLRI",
|
||||
slug: "dittipidter",
|
||||
logo: "/logo/satker/DITTIPIDTER.png",
|
||||
},
|
||||
{
|
||||
name: "DITTIPIDSIBER BARESKRIM POLRI",
|
||||
slug: "dittipidsiber",
|
||||
logo: "/logo/satker/DITTIPIDSIBER.png",
|
||||
},
|
||||
{
|
||||
name: "DIT PPA-PPO BARESKRIM POLRI",
|
||||
slug: "dit-ppa-ppo",
|
||||
logo: "/logo/satker/DITPPAPPO.png",
|
||||
},
|
||||
{
|
||||
name: "PUSLABFOR BARESKRIM POLRI",
|
||||
slug: "puslabfor",
|
||||
logo: "/assets/satker/puslabfor.png",
|
||||
},
|
||||
{
|
||||
name: "PUSIKNAS BARESKRIM POLRI",
|
||||
slug: "pusiknas",
|
||||
logo: "/logo/satker/PUSKINAS.png",
|
||||
},
|
||||
{
|
||||
name: "STIK LEMDIKLAT POLRI",
|
||||
slug: "stik-ptik",
|
||||
logo: "/logo/satker/STIK-PTIK.png",
|
||||
},
|
||||
{
|
||||
name: "AKPOL LEMDIKLAT POLRI",
|
||||
slug: "akpol",
|
||||
logo: "/logo/satker/AKPOL.png",
|
||||
},
|
||||
{
|
||||
name: "SESPIM LEMDIKLAT POLRI",
|
||||
slug: "sespim-polri",
|
||||
logo: "/logo/satker/SESPIM-POLRI.png",
|
||||
},
|
||||
{
|
||||
name: "SETUKPA LEMDIKLAT POLRI",
|
||||
slug: "setupa-polri",
|
||||
logo: "/logo/satker/SETUPA-POLRI.png",
|
||||
},
|
||||
{
|
||||
name: "SEPOLWAN LEMDIKLAT POLRI",
|
||||
slug: "sepolwan-polri",
|
||||
logo: "/logo/satker/SEPOLWAN-POLRI.png",
|
||||
},
|
||||
{
|
||||
name: "SEBASA LEMDIKLAT POLRI",
|
||||
slug: "sebasa-polri",
|
||||
logo: "/logo/satker/SEBASA-POLRI.png",
|
||||
},
|
||||
{
|
||||
name: "RUMKIT BHAYANGKARA TK I",
|
||||
slug: "rumkit-bhayangkara",
|
||||
logo: "/logo/satker/RUMKIT-BHAYANGKARA.png",
|
||||
},
|
||||
{ name: "POLAIR", slug: "polair", logo: "/logo/satker/POLAIR.png" },
|
||||
{ name: "POLUDARA", slug: "poludara", logo: "/logo/satker/POLUDARA.png" },
|
||||
{ name: "PUSINAFIS", slug: "pusinafis", logo: "/logo/satker/PUSINAFIS.png" },
|
||||
|
|
@ -62,14 +186,18 @@ const regions = [
|
|||
const Division = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [seeAllValue, setSeeAllValue] = useState(false);
|
||||
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions);
|
||||
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(
|
||||
regions
|
||||
);
|
||||
const pathname = usePathname();
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
const handleSearch = () => {
|
||||
const value = searchTerm.toLowerCase();
|
||||
|
||||
const filtered = regions.filter((satker) => satker.name.toLowerCase().includes(value));
|
||||
const filtered = regions.filter((satker) =>
|
||||
satker.name.toLowerCase().includes(value)
|
||||
);
|
||||
setFilteredList(filtered);
|
||||
};
|
||||
|
||||
|
|
@ -87,7 +215,10 @@ const Division = () => {
|
|||
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||
</svg>`;
|
||||
|
||||
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
|
||||
const toBase64 = (str: string) =>
|
||||
typeof window === "undefined"
|
||||
? Buffer.from(str).toString("base64")
|
||||
: window.btoa(str);
|
||||
|
||||
return (
|
||||
<div className="mx-auto px-4 w-full">
|
||||
|
|
@ -118,9 +249,21 @@ const Division = () => {
|
|||
{/* Grid Wilayah */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 px-4 overflow-y-auto max-h-[60vh]">
|
||||
{filteredList?.map((region: any) => (
|
||||
<Link href={`/satker/${region.slug}`} className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all">
|
||||
<Link
|
||||
href={`/satker/${region.slug}`}
|
||||
className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all"
|
||||
>
|
||||
<div className="mb-1 flex items-center justify-center">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={region.logo} alt={region.name} className="w-14 h-14 object-contain" />
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1920}
|
||||
height={1080}
|
||||
src={region.logo}
|
||||
alt={region.name}
|
||||
className="w-14 h-14 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs font-semibold">{region.name}</p>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ const Footer = () => {
|
|||
<DialogContent
|
||||
className="flex flex-col overflow-y-scroll h-[80%]"
|
||||
size="md"
|
||||
data-lenis-prevent
|
||||
>
|
||||
<div className="flex flex-row items-center justify-center gap-4">
|
||||
<img src="/assets/icon-privacy.png" alt="Privacy" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
|
||||
import FormSurvey from "./survey";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
|
@ -83,6 +83,7 @@ const UserSurveyBox = () => {
|
|||
const response = await createSurveyData(data);
|
||||
console.log("API Response:", response);
|
||||
setShowSurvey(false);
|
||||
setOpenPolda(false);
|
||||
} catch (error) {
|
||||
console.error("Error submitting survey:", error);
|
||||
} finally {
|
||||
|
|
@ -114,7 +115,7 @@ const UserSurveyBox = () => {
|
|||
SURVEY SEKARANG
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md" className="max-h-[90vh] overflow-auto flex flex-col">
|
||||
<DialogContent size="md" className="max-h-[90vh] overflow-auto flex flex-col" data-lenis-prevent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-bold">SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI</DialogTitle>
|
||||
<DialogDescription className="text-sm">Kami menghargai pendapat Anda! Survei ini bertujuan untuk meningkatkan kualitas layanan MediaHub Polri.</DialogDescription>
|
||||
|
|
@ -148,7 +149,7 @@ const UserSurveyBox = () => {
|
|||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => setShowSurvey(false)}>
|
||||
<Button variant="outline" onClick={() => setOpenPolda(false)}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 118 KiB |