QUDO-159
This commit is contained in:
commit
4ab11ea377
|
|
@ -145,6 +145,7 @@ export default function EditUserForm() {
|
||||||
password: data.password,
|
password: data.password,
|
||||||
passwordConf: data.confirmPassword,
|
passwordConf: data.confirmPassword,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
|
isAdmin: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
loading();
|
loading();
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,7 @@ export default function CreateUserForm() {
|
||||||
password: data.password,
|
password: data.password,
|
||||||
passwordConf: data.confirmPassword,
|
passwordConf: data.confirmPassword,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
|
isAdmin: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.role == "OPT-ID") {
|
if (data.role == "OPT-ID") {
|
||||||
|
|
|
||||||
|
|
@ -302,7 +302,7 @@ export default function EditUserForm() {
|
||||||
|
|
||||||
async function save(data: z.infer<typeof FormSchema>) {
|
async function save(data: z.infer<typeof FormSchema>) {
|
||||||
let req: any = {
|
let req: any = {
|
||||||
id: id,
|
id: Number(id),
|
||||||
firstName: data.fullname,
|
firstName: data.fullname,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
roleId: data.role,
|
roleId: data.role,
|
||||||
|
|
@ -313,6 +313,7 @@ export default function EditUserForm() {
|
||||||
password: data.password,
|
password: data.password,
|
||||||
passwordConf: data.confirmPassword,
|
passwordConf: data.confirmPassword,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
|
isAdmin: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.role == "OPT-ID") {
|
if (data.role == "OPT-ID") {
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,12 @@ import {
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
import { setBanner } from "@/service/settings/settings";
|
import { deleteAdvertisements, setBanner } from "@/service/settings/settings";
|
||||||
import { error } from "@/config/swal";
|
import { error } from "@/config/swal";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import { deleteMedia } from "@/service/content/content";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -74,12 +77,49 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { toast } = useToast();
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
const handleBanner = async (id: number) => {
|
async function doDelete(id: any) {
|
||||||
const response = setBanner(id, true);
|
// loading();
|
||||||
toast({
|
const data = {
|
||||||
title: "Success",
|
id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await deleteAdvertisements(id);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteAdvertisements = (id: any) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus Data",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
@ -93,17 +133,24 @@ const columns: ColumnDef<any>[] = [
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
|
<Link href={`/admin/settings/iklan/detail/${row.original.id}`}>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Link
|
<Eye className="w-4 h-4 me-1.5" />
|
||||||
href={`/contributor/content/image/detail/${row.original.id}`}
|
View
|
||||||
>
|
|
||||||
Detail
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<Link href={`/admin/settings/iklan/update/${row.original.id}`}>
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<a onClick={() => handleBanner(row.original.id)}>
|
<SquarePen className="w-4 h-4 me-1.5" />
|
||||||
Jadikan Banner
|
Edit
|
||||||
</a>
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => handleDeleteAdvertisements(row.original.id)}
|
||||||
|
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { TambahIklanDetail } from "@/components/form/setting/form-add-iklan-detail";
|
||||||
|
|
||||||
|
const AdvertisementsDetailPage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<TambahIklanDetail />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdvertisementsDetailPage;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import { TambahIklanUpdate } from "@/components/form/setting/form-add-iklan-update";
|
||||||
|
|
||||||
|
const AdvertisementsUpdatePage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<TambahIklanUpdate />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdvertisementsUpdatePage;
|
||||||
|
|
@ -1,23 +1,19 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { useState, useEffect, act } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import FullCalendar from "@fullcalendar/react"; // must go before plugins
|
import FullCalendar from "@fullcalendar/react";
|
||||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
import interactionPlugin, { Draggable } from "@fullcalendar/interaction";
|
import interactionPlugin, { Draggable } from "@fullcalendar/interaction";
|
||||||
import listPlugin from "@fullcalendar/list";
|
import listPlugin from "@fullcalendar/list";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import ExternalDraggingevent from "./dragging-events";
|
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
Book,
|
Book,
|
||||||
CheckCheck,
|
CheckCheck,
|
||||||
CheckSquare2,
|
|
||||||
CheckSquare2Icon,
|
|
||||||
Plus,
|
Plus,
|
||||||
Timer,
|
Timer,
|
||||||
TimerIcon,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { EventContentArg } from "@fullcalendar/core";
|
import { EventContentArg } from "@fullcalendar/core";
|
||||||
|
|
@ -42,8 +38,8 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
|
||||||
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
interface CalendarViewProps {
|
interface CalendarViewProps {
|
||||||
// events: CalendarEvent[];
|
|
||||||
categories: CalendarCategory[];
|
categories: CalendarCategory[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,12 +55,11 @@ export type CalendarEvent = {
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
calendar: string;
|
calendar: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
isPublish?: boolean;
|
||||||
|
createdByName?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
|
||||||
const INITIAL_MONTH = dayjs().format("M");
|
|
||||||
|
|
||||||
export interface AgendaSettingsAPIResponse {
|
export interface AgendaSettingsAPIResponse {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -74,24 +69,25 @@ export interface AgendaSettingsAPIResponse {
|
||||||
startDate: string;
|
startDate: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
isPublish: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdById: number | null;
|
createdById: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface YearlyData {
|
interface YearlyData {
|
||||||
january?: Event[];
|
january?: AgendaSettingsAPIResponse[];
|
||||||
february?: Event[];
|
february?: AgendaSettingsAPIResponse[];
|
||||||
march?: Event[];
|
march?: AgendaSettingsAPIResponse[];
|
||||||
april?: Event[];
|
april?: AgendaSettingsAPIResponse[];
|
||||||
may?: Event[];
|
may?: AgendaSettingsAPIResponse[];
|
||||||
june?: Event[];
|
june?: AgendaSettingsAPIResponse[];
|
||||||
july?: Event[];
|
july?: AgendaSettingsAPIResponse[];
|
||||||
august?: Event[];
|
august?: AgendaSettingsAPIResponse[];
|
||||||
september?: Event[];
|
september?: AgendaSettingsAPIResponse[];
|
||||||
october?: Event[];
|
october?: AgendaSettingsAPIResponse[];
|
||||||
november?: Event[];
|
november?: AgendaSettingsAPIResponse[];
|
||||||
december?: Event[];
|
december?: AgendaSettingsAPIResponse[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MonthCardProps {
|
interface MonthCardProps {
|
||||||
|
|
@ -100,7 +96,7 @@ interface MonthCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ListItemProps {
|
interface ListItemProps {
|
||||||
item: any;
|
item: AgendaSettingsAPIResponse;
|
||||||
text: string;
|
text: string;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
isPublish: boolean | null;
|
isPublish: boolean | null;
|
||||||
|
|
@ -109,78 +105,74 @@ interface ListItemProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface APIResponse {
|
interface APIResponse {
|
||||||
error: boolean;
|
success: boolean;
|
||||||
message: any;
|
message: string;
|
||||||
data: AgendaSettingsAPIResponse[] | null;
|
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 CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string[] | null>(
|
const [selectedCategory, setSelectedCategory] = useState<string[]>([]);
|
||||||
null
|
|
||||||
);
|
|
||||||
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
|
||||||
const roleId = Number(getCookiesDecrypt("urie")) || 0;
|
const roleId = Number(getCookiesDecrypt("urie")) || 0;
|
||||||
const userLevelId = Number(getCookiesDecrypt("ulie")) || 0;
|
const userLevelId = Number(getCookiesDecrypt("ulie")) || 0;
|
||||||
console.log("roleId", roleId);
|
const [calendarEvents, setCalendarEvents] = useState<CalendarEvent[]>([]);
|
||||||
console.log("userlevel", userLevelId);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
|
|
||||||
const [Isloading, setLoading] = useState<boolean>(false);
|
|
||||||
const [draggableInitialized, setDraggableInitialized] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
const t = useTranslations("CalendarApp");
|
const t = useTranslations("CalendarApp");
|
||||||
// event canvas state
|
|
||||||
|
// Modal states
|
||||||
const [sheetOpen, setSheetOpen] = useState<boolean>(false);
|
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 [activeView, setActiveView] = useState("listYear");
|
||||||
const [yearlyData, setYearlyData] = useState();
|
const [yearlyData, setYearlyData] = useState<YearlyData>({});
|
||||||
|
|
||||||
const TODAY = dayjs().format("yyyy-MM-dd");
|
|
||||||
const INITIAL_YEAR = dayjs().format("YYYY");
|
|
||||||
const INITIAL_MONTH = dayjs().format("M");
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const [selectedEventData, setSelectedEventData] = useState<any>(null);
|
||||||
|
|
||||||
const [selectedYear, setSelectedYear] = useState(new Date());
|
|
||||||
const [selectedMonth, setSelectedMonth] = useState(
|
const [selectedMonth, setSelectedMonth] = useState(
|
||||||
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
|
dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1))
|
||||||
);
|
);
|
||||||
|
|
||||||
const [dragEvents] = useState([
|
// Load events when view or month changes
|
||||||
{ 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[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeView == "listYear") {
|
if (activeView === "listYear") {
|
||||||
getYearlyEvents();
|
getYearlyEvents();
|
||||||
} else {
|
} else {
|
||||||
getCalendarEvents();
|
getCalendarEvents();
|
||||||
}
|
}
|
||||||
}, [activeView, selectedMonth]);
|
}, [activeView, selectedMonth]);
|
||||||
|
|
||||||
|
// Initialize selected categories
|
||||||
|
useEffect(() => {
|
||||||
|
if (categories?.length > 0) {
|
||||||
|
setSelectedCategory(categories.map((c) => c.value));
|
||||||
|
}
|
||||||
|
}, [categories]);
|
||||||
|
|
||||||
const getCalendarEvents = async () => {
|
const getCalendarEvents = async () => {
|
||||||
console.log("View : ", activeView);
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
const res = await getAgendaSettingsList(
|
const res = await getAgendaSettingsList(
|
||||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||||
selectedMonth.format("M") || INITIAL_MONTH,
|
selectedMonth.format("M") || INITIAL_MONTH,
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
console.log("View : API Response:", res);
|
|
||||||
|
|
||||||
if (res?.error) {
|
if (res?.error) {
|
||||||
error(res?.message);
|
error(res?.message || "Failed to fetch events");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = res?.data?.data;
|
// For monthly view, we need to extract events from the specific month
|
||||||
|
const monthData = res?.data?.data;
|
||||||
if (data) {
|
if (monthData) {
|
||||||
console.log("Dataaa : ", data);
|
const allEvents: CalendarEvent[] = [];
|
||||||
// Map API data to the calendarEvents structure
|
// Map API data to the calendarEvents structure
|
||||||
const events = res?.data?.data?.map((event: any) => ({
|
const events = monthData?.map((event: any) => ({
|
||||||
id: event.id.toString(),
|
id: event.id.toString(),
|
||||||
title: event.title,
|
title: event.title,
|
||||||
createBy: event.createdById,
|
createBy: event.createdById,
|
||||||
|
|
@ -196,300 +188,196 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log("Dataaa event : ", events);
|
console.log("Event Data : ", events);
|
||||||
|
|
||||||
setCalendarEvents(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 getYearlyEvents = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
const res = await getAgendaSettingsList(
|
const res = await getAgendaSettingsList(
|
||||||
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
selectedMonth?.format("YYYY") || INITIAL_YEAR,
|
||||||
"",
|
"",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res?.error) {
|
if (res?.error) {
|
||||||
error(res.message);
|
error(res?.message || "Failed to fetch yearly events");
|
||||||
return false;
|
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(() => {
|
// Filter events based on selected categories
|
||||||
setSelectedCategory(categories?.map((c) => c.value));
|
const filteredEvents = calendarEvents.filter((event) => {
|
||||||
}, [categories]);
|
if (!selectedCategory.length) return false;
|
||||||
|
console.log("Event category : ", selectedCategory);
|
||||||
|
|
||||||
const filteredEvents = calendarEvents?.filter((event) => {
|
|
||||||
const eventCategories = event.extendedProps.calendar
|
const eventCategories = event.extendedProps.calendar
|
||||||
?.split(",")
|
?.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) =>
|
return eventCategories?.some((cat: string) =>
|
||||||
selectedCategory?.includes(cat)
|
selectedCategory.includes(cat) || (hasAllCategories && cat == "0")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const displayedEvents =
|
// Event handlers
|
||||||
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
|
|
||||||
const handleEventClick = (arg: any) => {
|
const handleEventClick = (arg: any) => {
|
||||||
console.log("Event Click ", arg);
|
|
||||||
setSelectedEventDate(null);
|
setSelectedEventDate(null);
|
||||||
|
setSelectedEventData(arg);
|
||||||
setSheetOpen(true);
|
setSheetOpen(true);
|
||||||
setApiEvents(arg);
|
|
||||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle close modal
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseModal = () => {
|
||||||
setSheetOpen(false);
|
setSheetOpen(false);
|
||||||
setApiEvents([]);
|
setSelectedEventData(null);
|
||||||
setSelectedEventDate(null);
|
setSelectedEventDate(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateClick = (arg: any) => {
|
const handleDateClick = (arg: any) => {
|
||||||
setSheetOpen(true);
|
setSheetOpen(true);
|
||||||
console.log("Date :", arg);
|
|
||||||
setSelectedEventDate(arg);
|
setSelectedEventDate(arg);
|
||||||
setApiEvents([]);
|
setSelectedEventData(null);
|
||||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCategorySelection = (category: string) => {
|
const handleCategorySelection = (category: string) => {
|
||||||
if (selectedCategory && selectedCategory.includes(category)) {
|
setSelectedCategory(prev => {
|
||||||
setSelectedCategory(selectedCategory.filter((c) => c !== category));
|
if (prev.includes(category)) {
|
||||||
|
return prev.filter((c) => c !== category);
|
||||||
} else {
|
} else {
|
||||||
setSelectedCategory([...(selectedCategory || []), category]);
|
return [...prev, category];
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderEventContent = (eventInfo: any) => {
|
|
||||||
const { title } = eventInfo.event;
|
|
||||||
const { createdByName, isPublish } = eventInfo.event.extendedProps;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full p-2 mb-2 rounded-md text-white text-sm bg-yellow-500 flex justify-between items-stretch">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<div className="w-3 flex flex-col h-full ml-2 rounded-r-md">
|
|
||||||
<div className="flex-1 bg-red-500"></div>
|
|
||||||
<div className="flex-1 bg-yellow-400"></div>
|
|
||||||
<div className="flex-1 bg-green-500"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChange = (startDate: string, endDate: string) => {
|
const handleDateChange = (startDate: string, endDate: string) => {
|
||||||
const startDateOfDate = new Date(startDate);
|
const startDateOfDate = new Date(startDate);
|
||||||
const endDateOfDate = new Date(endDate);
|
|
||||||
|
|
||||||
console.log("Change Date : ", startDateOfDate);
|
|
||||||
|
|
||||||
setSelectedMonth(dayjs(startDateOfDate));
|
setSelectedMonth(dayjs(startDateOfDate));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewChange = (viewType: string) => {
|
const handleViewChange = (viewType: string) => {
|
||||||
console.log("Change view : ", viewType);
|
|
||||||
setActiveView(viewType);
|
setActiveView(viewType);
|
||||||
};
|
};
|
||||||
|
|
||||||
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
const handleClickListItem = (item: AgendaSettingsAPIResponse) => {
|
||||||
{ 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 typeSplit = type.split(",");
|
|
||||||
const firstType = typeSplit && typeSplit[0];
|
|
||||||
const colors: Record<Event["type"], string> = {
|
|
||||||
"0": "bg-black",
|
|
||||||
"1": "bg-yellow-500",
|
|
||||||
"2": "bg-blue-400",
|
|
||||||
"3": "bg-slate-400",
|
|
||||||
"4": "bg-orange-500",
|
|
||||||
"5": "bg-green-400",
|
|
||||||
};
|
|
||||||
console.log("Type : ", colors[firstType]);
|
|
||||||
return colors[firstType];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEventColorList = (type: Event["type"]): string[] | null => {
|
|
||||||
const typeSplit = type.split(",");
|
|
||||||
const typeList = typeSplit?.slice(1);
|
|
||||||
|
|
||||||
if (typeList.length === 0) return null;
|
|
||||||
|
|
||||||
const colors: Record<Event["type"], string> = {
|
|
||||||
"0": "bg-black",
|
|
||||||
"1": "bg-yellow-500",
|
|
||||||
"2": "bg-blue-400",
|
|
||||||
"3": "bg-slate-400",
|
|
||||||
"4": "bg-orange-500",
|
|
||||||
"5": "bg-green-400",
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeListColor = typeList.map((item) => colors[item] || "bg-gray-200");
|
|
||||||
|
|
||||||
console.log("Type List Color : ", typeListColor);
|
|
||||||
return typeListColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickListItem = (item: any) => {
|
|
||||||
const formattedEvent: CalendarEvent = {
|
const formattedEvent: CalendarEvent = {
|
||||||
id: item.id.toString(),
|
id: item.id.toString(),
|
||||||
title: item.title,
|
title: item.title,
|
||||||
start: new Date(item.startDate),
|
start: new Date(item.startDate),
|
||||||
end: new Date(item.endDate),
|
end: new Date(item.endDate),
|
||||||
createBy: "Mabes Polri - Approver", // Sesuaikan dengan data yang sebenarnya jika ada
|
createBy: item.createdById?.toString() || "",
|
||||||
createdByName: item.createdByName,
|
createdByName: item.createdByName,
|
||||||
isPublish: item.isPublish,
|
isPublish: item.isPublish,
|
||||||
allDay: true,
|
allDay: true,
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
calendar: item.agendaType,
|
calendar: item.agendaType,
|
||||||
description: item.description,
|
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);
|
setSelectedEventDate(null);
|
||||||
|
setSelectedEventData(eventArg);
|
||||||
setSheetOpen(true);
|
setSheetOpen(true);
|
||||||
setApiEvents(finalEvent);
|
|
||||||
wait().then(() => (document.body.style.pointerEvents = "auto"));
|
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> = ({
|
const ListItem: React.FC<ListItemProps> = ({
|
||||||
item,
|
item,
|
||||||
text,
|
text,
|
||||||
|
|
@ -498,7 +386,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
bgColor,
|
bgColor,
|
||||||
colorList,
|
colorList,
|
||||||
}) => (
|
}) => (
|
||||||
<div className={`w-full p-2 mb-2 rounded-md text-white text-sm flex justify-between items-stretch ${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)}
|
onClick={() => handleClickListItem(item)}
|
||||||
>
|
>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|
@ -508,45 +397,44 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
</div>
|
</div>
|
||||||
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{colorList && colorList.length > 0 && (
|
||||||
<div className="w-3 flex flex-col h-full ml-2 overflow-hidden item-stretch gap-1">
|
<div className="w-3 flex flex-col h-full ml-2 overflow-hidden item-stretch gap-1">
|
||||||
{colorList?.map((color: string) => (
|
{colorList.map((color: string, index: number) => (
|
||||||
<div className={`h-[10px] rounded-sm ${color}`}></div>
|
<div key={index} className={`h-[10px] rounded-sm ${color}`}></div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const MonthCard: React.FC<MonthCardProps> = (props: any) => {
|
const MonthCard: React.FC<MonthCardProps> = ({ monthId, label }) => {
|
||||||
const { monthId, label } = props;
|
const events = yearlyData[monthId] || [];
|
||||||
const events: any = yearlyData?.[monthId];
|
const displayedEvents = events.slice(0, 3);
|
||||||
const displayedEvents = events?.slice(0, 3);
|
const hasMoreEvents = events.length > 3;
|
||||||
const hasMoreEvents = events?.length > 3;
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-200 pb-3 mr-1">
|
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-200 pb-3 mr-1">
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
<h4 className="font-bold text-center">{label}</h4>
|
<h4 className="font-bold text-center">{label}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
{events?.length === 0 ? (
|
{events.length === 0 ? (
|
||||||
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
||||||
<p className="text-center">{t("no-data-yet")}</p>
|
<p className="text-center">{t("no-data-yet")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{displayedEvents?.map((event: any, index: number) => (
|
{displayedEvents.map((event, index) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={index}
|
key={`${event.id}-${index}`}
|
||||||
item={event}
|
item={event}
|
||||||
text={event.title}
|
text={event.title}
|
||||||
createdBy={event.createdByName}
|
createdBy={event.createdByName}
|
||||||
isPublish={event.isPublish}
|
isPublish={event.isPublish}
|
||||||
bgColor={getEventColor(event.agendaType)}
|
bgColor={getEventColor(event.agendaType as EventType)}
|
||||||
colorList={getEventColorList(event.agendaType)}
|
colorList={getEventColorList(event.agendaType as EventType)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{hasMoreEvents && (
|
{hasMoreEvents && (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
|
|
@ -566,15 +454,15 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-2 text-sm">
|
<CardContent className="p-2 text-sm">
|
||||||
<div className="max-h-[300px] overflow-y-auto border rounded-md border-gray-300 p-2">
|
<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
|
<ListItem
|
||||||
key={index}
|
key={`${event.id}-full-${index}`}
|
||||||
item={event}
|
item={event}
|
||||||
text={event.title}
|
text={event.title}
|
||||||
createdBy={event.createdByName}
|
createdBy={event.createdByName}
|
||||||
isPublish={event.isPublish}
|
isPublish={event.isPublish}
|
||||||
bgColor={getEventColor(event.agendaType)}
|
bgColor={getEventColor(event.agendaType as EventType)}
|
||||||
colorList={getEventColorList(event.agendaType)}
|
colorList={getEventColorList(event.agendaType as EventType)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -590,9 +478,24 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getModalContent = (type: "terkirim" | "diterima") => (
|
const months: Array<{ id: keyof YearlyData; label: string }> = [
|
||||||
<div className="overflow-x-auto overflow-y-auto ">
|
{ id: "january", label: "Januari" },
|
||||||
{Isloading ? (
|
{ 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>
|
<p>Loading...</p>
|
||||||
) : (
|
) : (
|
||||||
<table className="w-full border-collapse border border-gray-300">
|
<table className="w-full border-collapse border border-gray-300">
|
||||||
|
|
@ -606,14 +509,12 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr key={""} className="border-b">
|
<tr className="border-b">
|
||||||
<td className="px-4 py-2">{"1"}</td>
|
<td className="px-4 py-2">1</td>
|
||||||
<td className="px-4 py-2">{"MIA - 001"}</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">23/01/2025 13:00</td>
|
||||||
<td className="px-4 py-2">{"Daily Issue 25 Januari 2025 "}</td>
|
<td className="px-4 py-2">Daily Issue 25 Januari 2025</td>
|
||||||
<td className="px-4 py-2">
|
<td className="px-4 py-2">Completed</td>
|
||||||
{type === "terkirim" ? "Completed" : "Completed"}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -626,8 +527,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
<div className="grid grid-cols-12 gap-6 divide-x divide-border">
|
<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">
|
<Card className="col-span-12 lg:col-span-4 2xl:col-span-3 pb-5">
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
<CardHeader className="border-none mb-2 pt-5 ">
|
<CardHeader className="border-none mb-2 pt-5">
|
||||||
{roleId == 3 || roleId == 11 || roleId == 2 || roleId == 12 ? (
|
{[3, 11, 2, 12].includes(roleId) && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDateClick}
|
onClick={handleDateClick}
|
||||||
className="dark:bg-background dark:text-foreground w-full"
|
className="dark:bg-background dark:text-foreground w-full"
|
||||||
|
|
@ -635,72 +536,64 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
<Plus className="w-4 h-4 me-1" />
|
<Plus className="w-4 h-4 me-1" />
|
||||||
{t("addEvent")}
|
{t("addEvent")}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
<div>
|
|
||||||
|
{roleId === 3 && userLevelId === 216 && (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{roleId === 3 && userLevelId === 216 ? (
|
|
||||||
<Button className="dark:bg-background dark:text-foreground w-full">
|
<Button className="dark:bg-background dark:text-foreground w-full">
|
||||||
<Book size={15} className="w-4 h-4 mr-3" />
|
<Book size={15} className="w-4 h-4 mr-3" />
|
||||||
{t("bag-pa-monitoring-results")}
|
{t("bag-pa-monitoring-results")}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
|
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("monitoring-results")}</DialogTitle>
|
<DialogTitle>{t("monitoring-results")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{getModalContent("terkirim")}
|
{getModalContent()}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<div className="px-3">
|
<div className="px-3">
|
||||||
<Calendar
|
<Calendar
|
||||||
mode="single"
|
mode="single"
|
||||||
selected={date}
|
selected={date}
|
||||||
onSelect={(s) => {
|
onSelect={(selectedDate) => {
|
||||||
handleDateClick(s);
|
if (selectedDate) {
|
||||||
|
setDate(selectedDate);
|
||||||
|
handleDateClick(selectedDate);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="rounded-md border w-full p-0 border-none"
|
className="rounded-md border w-full p-0 border-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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")}
|
{t("filter")}
|
||||||
</div>
|
</div>
|
||||||
<ul className="space-y-3 px-4">
|
<ul className="space-y-3 px-4">
|
||||||
<li className=" flex gap-3">
|
<li className="flex gap-3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedCategory?.length === categories?.length}
|
checked={selectedCategory.length === categories.length}
|
||||||
onClick={() => {
|
onCheckedChange={() => {
|
||||||
if (selectedCategory?.length === categories?.length) {
|
if (selectedCategory.length === categories.length) {
|
||||||
setSelectedCategory([]);
|
setSelectedCategory([]);
|
||||||
} else {
|
} else {
|
||||||
setSelectedCategory(categories?.map((c) => c.value));
|
setSelectedCategory(categories.map((c) => c.value));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Label>All</Label>
|
<Label>All</Label>
|
||||||
</li>
|
</li>
|
||||||
{categories?.map((category) => (
|
{categories.map((category) => (
|
||||||
<li className="flex gap-3 " key={category.value}>
|
<li className="flex gap-3" key={category.value}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className={category.className}
|
className={category.className}
|
||||||
id={category.label}
|
id={category.label}
|
||||||
checked={selectedCategory?.includes(category.value)}
|
checked={selectedCategory.includes(category.value)}
|
||||||
onClick={() => handleCategorySelection(category.value)}
|
onCheckedChange={() => handleCategorySelection(category.value)}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={category.label}>{category.label}</Label>
|
<Label htmlFor={category.label}>{category.label}</Label>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -712,12 +605,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
<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">
|
<CardContent className="dashcode-app-calendar">
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
plugins={[
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin]}
|
||||||
dayGridPlugin,
|
|
||||||
timeGridPlugin,
|
|
||||||
interactionPlugin,
|
|
||||||
listPlugin,
|
|
||||||
]}
|
|
||||||
headerToolbar={{
|
headerToolbar={{
|
||||||
left: "prev,next today",
|
left: "prev,next today",
|
||||||
center: "title",
|
center: "title",
|
||||||
|
|
@ -729,7 +617,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
buttonText: "Year",
|
buttonText: "Year",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
events={displayedEvents}
|
events={filteredEvents}
|
||||||
editable={true}
|
editable={true}
|
||||||
rerenderDelay={10}
|
rerenderDelay={10}
|
||||||
eventDurationEditable={false}
|
eventDurationEditable={false}
|
||||||
|
|
@ -745,19 +633,17 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
eventContent={renderEventContent}
|
eventContent={renderEventContent}
|
||||||
viewDidMount={(info) => handleViewChange(info.view.type)}
|
viewDidMount={(info) => handleViewChange(info.view.type)}
|
||||||
datesSet={(info: any) => {
|
datesSet={(info: any) => {
|
||||||
console.log(info);
|
|
||||||
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
handleDateChange(info.view.currentStart, info.view.currentEnd);
|
||||||
handleViewChange(info.view.type);
|
handleViewChange(info.view.type);
|
||||||
}}
|
}}
|
||||||
viewClassNames={
|
viewClassNames={activeView === "listYear" ? "hide-calendar-grid" : ""}
|
||||||
activeView === "listYear" ? "hide-calendar-grid" : ""
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{activeView === "listYear" && (
|
{activeView === "listYear" && (
|
||||||
<div className="custom-ui">
|
<div className="custom-ui">
|
||||||
<div className="flex gap-1 mt-1">
|
{[0, 3, 6, 9].map((startIndex) => (
|
||||||
{months.slice(0, 3).map((month) => (
|
<div key={startIndex} className="flex gap-1 mt-1">
|
||||||
|
{months.slice(startIndex, startIndex + 3).map((month) => (
|
||||||
<MonthCard
|
<MonthCard
|
||||||
key={month.id}
|
key={month.id}
|
||||||
monthId={month.id}
|
monthId={month.id}
|
||||||
|
|
@ -765,46 +651,18 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EventModal
|
<EventModal
|
||||||
open={sheetOpen}
|
open={sheetOpen}
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
categories={categories}
|
categories={categories}
|
||||||
event={apiEvents}
|
event={selectedEventData}
|
||||||
selectedDate={selectedEventDate}
|
selectedDate={selectedEventDate}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const CalenderPage = () => {
|
||||||
const categories = await getCategories();
|
const categories = await getCategories();
|
||||||
let valueShowed: string[] = [];
|
let valueShowed: string[] = [];
|
||||||
if (userLevelNumber == 1) {
|
if (userLevelNumber == 1) {
|
||||||
valueShowed = ["1", "2", "3", "4", "5"];
|
valueShowed = ["0", "1", "2", "3", "4", "5"];
|
||||||
} else if (userLevelNumber == 2 && userLevelId != 761) {
|
} else if (userLevelNumber == 2 && userLevelId != 761) {
|
||||||
valueShowed = ["2", "3"];
|
valueShowed = ["2", "3"];
|
||||||
} else if (
|
} else if (
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@ export default function ExecutiveDataDashboard() {
|
||||||
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
|
? "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
|
||||||
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
|
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?"
|
||||||
: safeLevelName.includes("POLDA")
|
: safeLevelName.includes("POLDA")
|
||||||
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?polda-selected=${state}&`
|
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=${state}&`
|
||||||
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda?polda-selected=${state}&`;
|
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-per-polda-new?polda-selected=${state}&`;
|
||||||
|
|
||||||
const view5 =
|
const view5 =
|
||||||
levelName == "MABES POLRI"
|
levelName == "MABES POLRI"
|
||||||
|
|
@ -120,8 +120,8 @@ export default function ExecutiveDataDashboard() {
|
||||||
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-kategori-top10?"
|
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-kategori-top10?"
|
||||||
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-top10?"
|
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-top10?"
|
||||||
: safeLevelName.includes("POLDA")
|
: safeLevelName.includes("POLDA")
|
||||||
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-top10-polda?polda-selected=${state}&`
|
? `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda-new?polda-selected=${state}&`
|
||||||
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-kategori-top10-polda?polda-selected=${state}&`;
|
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-content-interaction-polda-new?polda-selected=${state}&`;
|
||||||
|
|
||||||
const view9 =
|
const view9 =
|
||||||
levelName == "MABES POLRI"
|
levelName == "MABES POLRI"
|
||||||
|
|
@ -369,61 +369,6 @@ export default function ExecutiveDataDashboard() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg">
|
|
||||||
<b>
|
|
||||||
{isInternational[3]
|
|
||||||
? "INTERACTIONS OF CATEGORY AND ACCESS TIME"
|
|
||||||
: "INTERAKSI KATEGORI DAN WAKTU AKSES"}
|
|
||||||
</b>
|
|
||||||
</p>
|
|
||||||
{levelName === "MABES POLRI" ? (
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<p>{t("choose_category")}</p>
|
|
||||||
<div className="flex flex-row gap-1 border-2 w-fit">
|
|
||||||
<Button
|
|
||||||
onClick={() => handleInternational(3, false)}
|
|
||||||
className={` hover:text-white rounded-none
|
|
||||||
${
|
|
||||||
isInternational[2]
|
|
||||||
? "bg-white text-black "
|
|
||||||
: "bg-black text-white "
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Indonesia
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => handleInternational(3, true)}
|
|
||||||
className={`hover:text-white rounded-none ${
|
|
||||||
isInternational[1]
|
|
||||||
? "bg-black text-white "
|
|
||||||
: "bg-white text-black "
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{t("international")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
<div className="my-5">
|
|
||||||
{ticket4 == "" ? (
|
|
||||||
<iframe
|
|
||||||
src={`${baseUrl + view9 + param}`}
|
|
||||||
width="100%"
|
|
||||||
height="750"
|
|
||||||
frameBorder="0"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<iframe
|
|
||||||
src={`${`${url + ticket4}/${view9}${param}`}`}
|
|
||||||
width="100%"
|
|
||||||
height="750"
|
|
||||||
frameBorder="0"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="popular-content">
|
<TabsContent value="popular-content">
|
||||||
|
|
|
||||||
|
|
@ -149,9 +149,7 @@ export default function ExecutiveDashboard() {
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<p className="text-base font-semibold">
|
<p className="text-base font-semibold">Publish Konten Polda</p>
|
||||||
Upload konten hari ini Polda
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="my-5">
|
<div className="my-5">
|
||||||
{ticket1 == "" ? (
|
{ticket1 == "" ? (
|
||||||
|
|
@ -173,39 +171,6 @@ export default function ExecutiveDashboard() {
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Satker */}
|
|
||||||
{(levelNumber === "1" || levelNumber === "3") && (
|
|
||||||
<Card
|
|
||||||
className={`rounded-sm p-3 ${
|
|
||||||
levelNumber === "3" ? "w-full" : "w-4/12"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row justify-between">
|
|
||||||
<p className="text-base font-semibold">
|
|
||||||
Upload konten hari ini Satker
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="my-5">
|
|
||||||
{ticket2 == "" ? (
|
|
||||||
<iframe
|
|
||||||
src={`${baseUrl + view6 + param}`}
|
|
||||||
width="100%"
|
|
||||||
height="750"
|
|
||||||
frameBorder="0"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<iframe
|
|
||||||
src={`${url + ticket2}/${view6}${param}`}
|
|
||||||
width="100%"
|
|
||||||
height="750"
|
|
||||||
frameBorder="0"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Polres */}
|
|
||||||
{(levelNumber === "1" || levelNumber === "2") && (
|
{(levelNumber === "1" || levelNumber === "2") && (
|
||||||
<Card
|
<Card
|
||||||
className={`rounded-sm p-3 ${
|
className={`rounded-sm p-3 ${
|
||||||
|
|
@ -213,9 +178,7 @@ export default function ExecutiveDashboard() {
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<p className="text-base font-semibold">
|
<p className="text-base font-semibold">Publish Konten Polres</p>
|
||||||
Upload konten hari ini Polres
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="my-5">
|
<div className="my-5">
|
||||||
{ticket3 == "" ? (
|
{ticket3 == "" ? (
|
||||||
|
|
@ -236,6 +199,36 @@ export default function ExecutiveDashboard() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Satker */}
|
||||||
|
{(levelNumber === "1" || levelNumber === "3") && (
|
||||||
|
<Card
|
||||||
|
className={`rounded-sm p-3 ${
|
||||||
|
levelNumber === "3" ? "w-full" : "w-4/12"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<p className="text-base font-semibold">Publish Konten Satker</p>
|
||||||
|
</div>
|
||||||
|
<div className="my-5">
|
||||||
|
{ticket2 == "" ? (
|
||||||
|
<iframe
|
||||||
|
src={`${baseUrl + view6 + param}`}
|
||||||
|
width="100%"
|
||||||
|
height="750"
|
||||||
|
frameBorder="0"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<iframe
|
||||||
|
src={`${url + ticket2}/${view6}${param}`}
|
||||||
|
width="100%"
|
||||||
|
height="750"
|
||||||
|
frameBorder="0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full mt-3">
|
<div className="w-full mt-3">
|
||||||
<Card className="rounded-sm p-3 h-[750px]">
|
<Card className="rounded-sm p-3 h-[750px]">
|
||||||
|
|
|
||||||
|
|
@ -531,7 +531,7 @@ const FilterPage = () => {
|
||||||
height={1440}
|
height={1440}
|
||||||
alt="filter-image"
|
alt="filter-image"
|
||||||
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
|
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
|
||||||
src={image?.thumbnailLink}
|
src={image?.smallThumbnailLink || image?.thumbnailLink}
|
||||||
className="h-full lg:h-[300px] w-full object-cover rounded-lg transition-transform duration-300"
|
className="h-full lg:h-[300px] w-full object-cover rounded-lg transition-transform duration-300"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -545,7 +545,7 @@ const FilterPage = () => {
|
||||||
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
||||||
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
|
||||||
<ImageBlurry
|
<ImageBlurry
|
||||||
src={image?.thumbnailLink}
|
src={image?.smallThumbnailLink || image?.thumbnailLink}
|
||||||
alt={image?.title}
|
alt={image?.title}
|
||||||
style={{
|
style={{
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
|
|
|
||||||
|
|
@ -13,96 +13,560 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Plus } from "lucide-react";
|
import { ChevronDown, ChevronUp, Plus } from "lucide-react";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import { format, parseISO } from "date-fns";
|
||||||
|
import { getUserLevelForAssignments } from "@/service/task";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { DateRange } from "react-day-picker";
|
||||||
|
import { postCalendar } from "@/service/schedule/schedule";
|
||||||
|
import { id } from "date-fns/locale";
|
||||||
|
import router from "next/router";
|
||||||
|
import {
|
||||||
|
detailAdvertisements,
|
||||||
|
postAdvertisements,
|
||||||
|
} from "@/service/settings/settings";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import FileUploader from "../shared/file-uploader";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export function TambahIklanModalDetail() {
|
const calendarSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
description: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FileWithPreview extends File {
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileUploaded {
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Detail {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TambahIklanDetail() {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const t = useTranslations("Schedule");
|
||||||
|
const { id } = useParams() as { id: string };
|
||||||
|
type CalendarSchema = z.infer<typeof calendarSchema>;
|
||||||
|
const [eventDate, setEventDate] = React.useState<Date | null>(new Date());
|
||||||
|
const [listDest, setListDest] = React.useState([]);
|
||||||
|
const [checkedLevels, setCheckedLevels] = React.useState(new Set());
|
||||||
|
const [expandedPolda, setExpandedPolda] = React.useState([{}]);
|
||||||
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const [isImageUploadFinish, setIsImageUploadFinish] = React.useState(false);
|
||||||
|
const [files, setFiles] = React.useState<FileWithPreview[]>([]);
|
||||||
|
const [selectedPlacement, setSelectedPlacement] = React.useState<string>("");
|
||||||
|
const [imageUploadedFiles, setImageUploadedFiles] = React.useState<
|
||||||
|
FileUploaded[]
|
||||||
|
>([]);
|
||||||
|
const [detail, setDetail] = React.useState<Detail>();
|
||||||
|
const [refresh, setRefresh] = React.useState(false);
|
||||||
|
const [imageFiles, setImageFiles] = React.useState<FileWithPreview[]>([]);
|
||||||
|
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||||
|
from: new Date(2025, 0, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [unitSelection, setUnitSelection] = React.useState({
|
||||||
|
semua: false,
|
||||||
|
mabes: false,
|
||||||
|
polda: false,
|
||||||
|
satker: false,
|
||||||
|
internasional: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<CalendarSchema>({
|
||||||
|
resolver: zodResolver(calendarSchema),
|
||||||
|
defaultValues: {
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePlacementSelect = (value: string) => {
|
||||||
|
setSelectedPlacement(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
async function initState() {
|
||||||
|
if (id) {
|
||||||
|
const response = await detailAdvertisements(id);
|
||||||
|
const details = response?.data?.data;
|
||||||
|
|
||||||
|
setDetail(details);
|
||||||
|
if (details?.assignedToLevel) {
|
||||||
|
const levelIds = details.assignedToLevel
|
||||||
|
.split(",")
|
||||||
|
.map((id: string) => parseInt(id));
|
||||||
|
setCheckedLevels(new Set(levelIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.placements) {
|
||||||
|
setSelectedPlacement(details.placements); // "left-bottom", etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initState();
|
||||||
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
async function fetchPoldaPolres() {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getUserLevelForAssignments();
|
||||||
|
setListDest(response?.data?.data.list);
|
||||||
|
console.log("polda", response?.data?.data?.list);
|
||||||
|
const initialExpandedState = response?.data?.data.list.reduce(
|
||||||
|
(acc: any, polda: any) => {
|
||||||
|
acc[polda.id] = false;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
setExpandedPolda(initialExpandedState);
|
||||||
|
console.log("polres", initialExpandedState);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Polda/Polres data:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchPoldaPolres();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (levelId: number) => {
|
||||||
|
setCheckedLevels((prev) => {
|
||||||
|
const updatedLevels = new Set(prev);
|
||||||
|
if (updatedLevels.has(levelId)) {
|
||||||
|
updatedLevels.delete(levelId);
|
||||||
|
} else {
|
||||||
|
updatedLevels.add(levelId);
|
||||||
|
}
|
||||||
|
return updatedLevels;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePoldaPolresChange = () => {
|
||||||
|
return Array.from(checkedLevels).join(",");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnitChange = (
|
||||||
|
key: keyof typeof unitSelection,
|
||||||
|
value: boolean
|
||||||
|
) => {
|
||||||
|
if (key === "semua") {
|
||||||
|
const newState = {
|
||||||
|
semua: value,
|
||||||
|
mabes: value,
|
||||||
|
polda: value,
|
||||||
|
satker: value,
|
||||||
|
internasional: value,
|
||||||
|
};
|
||||||
|
setUnitSelection(newState);
|
||||||
|
} else {
|
||||||
|
const updatedSelection = {
|
||||||
|
...unitSelection,
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
const allChecked = ["mabes", "polda", "satker", "internasional"].every(
|
||||||
|
(k) => updatedSelection[k as keyof typeof unitSelection]
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedSelection.semua = allChecked;
|
||||||
|
|
||||||
|
setUnitSelection(updatedSelection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleExpand = (poldaId: any) => {
|
||||||
|
setExpandedPolda((prev: any) => ({
|
||||||
|
...prev,
|
||||||
|
[poldaId]: !prev[poldaId],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (data: CalendarSchema) => {
|
||||||
|
const unitMapping = {
|
||||||
|
allUnit: "0",
|
||||||
|
mabes: "1",
|
||||||
|
polda: "2",
|
||||||
|
satker: "4",
|
||||||
|
internasional: "5",
|
||||||
|
};
|
||||||
|
const assignmentToString = Object.keys(unitSelection)
|
||||||
|
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||||
|
.map((key) => unitMapping[key as keyof typeof unitMapping])
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
const formMedia = new FormData();
|
||||||
|
formMedia.append("title", data.title);
|
||||||
|
formMedia.append("placements", selectedPlacement);
|
||||||
|
formMedia.append("description", data.description);
|
||||||
|
formMedia.append("redirectLink", "https://new.netidhub.com");
|
||||||
|
formMedia.append("assignedToLevel", handlePoldaPolresChange());
|
||||||
|
formMedia.append("file", imageFiles[0]);
|
||||||
|
|
||||||
|
console.log("Form Data Submitted:", formMedia);
|
||||||
|
|
||||||
|
loading();
|
||||||
|
const response = await postAdvertisements(formMedia);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
|
||||||
|
Cookies.set("scheduleId", response?.data?.data.id, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function uploadResumableFile(
|
||||||
|
idx: number,
|
||||||
|
id: string,
|
||||||
|
file: any,
|
||||||
|
fileTypeId: string,
|
||||||
|
duration: string
|
||||||
|
) {
|
||||||
|
console.log(idx, id, file, fileTypeId, duration);
|
||||||
|
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
|
const headers = {
|
||||||
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = new Upload(file, {
|
||||||
|
endpoint: `${process.env.NEXT_PUBLIC_API}/advertisements/file/upload`,
|
||||||
|
headers: headers,
|
||||||
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
|
chunkSize: 20_000,
|
||||||
|
metadata: {
|
||||||
|
advertisementsId: id,
|
||||||
|
filename: file.name,
|
||||||
|
contentType: file.type,
|
||||||
|
fileTypeId: fileTypeId,
|
||||||
|
duration,
|
||||||
|
},
|
||||||
|
onBeforeRequest: function (req) {
|
||||||
|
var xhr = req.getUnderlyingObject();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
},
|
||||||
|
onError: async (e: any) => {
|
||||||
|
console.log("Error upload :", e);
|
||||||
|
error(e);
|
||||||
|
},
|
||||||
|
onChunkComplete: (
|
||||||
|
chunkSize: any,
|
||||||
|
bytesAccepted: any,
|
||||||
|
bytesTotal: any
|
||||||
|
) => {
|
||||||
|
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||||
|
// progressInfo[idx].percentage = uploadPersen;
|
||||||
|
// counterUpdateProgress++;
|
||||||
|
// console.log(counterUpdateProgress);
|
||||||
|
// setProgressList(progressInfo);
|
||||||
|
// setCounterProgress(counterUpdateProgress);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
// uploadPersen = 100;
|
||||||
|
// progressInfo[idx].percentage = 100;
|
||||||
|
// counterUpdateProgress++;
|
||||||
|
// setCounterProgress(counterUpdateProgress);
|
||||||
|
successTodo();
|
||||||
|
if (fileTypeId == "1") {
|
||||||
|
setIsImageUploadFinish(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
upload.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
successTodo();
|
||||||
|
}, [isImageUploadFinish]);
|
||||||
|
|
||||||
|
function successTodo() {
|
||||||
|
if (isImageUploadFinish) {
|
||||||
|
successSubmit("/in/admin/settings/iklan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const successSubmit = (redirect: string) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push(redirect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (data: CalendarSchema) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data",
|
||||||
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFilePreview = (url: string) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Image
|
||||||
<DialogTrigger asChild>
|
width={48}
|
||||||
<Button onClick={() => setOpen(true)} color="primary" size="md">
|
height={48}
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
alt={"file preview"}
|
||||||
Tambah Iklan
|
src={url}
|
||||||
</Button>
|
className=" rounded border p-0.5"
|
||||||
</DialogTrigger>
|
/>
|
||||||
<DialogContent size="md">
|
);
|
||||||
<DialogHeader>
|
};
|
||||||
<DialogTitle>Tambah Iklan</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
|
const handleRemoveFile = (id: number) => {};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card className="px-3 py-3">
|
||||||
|
{detail !== undefined ? (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Target Area</p>
|
<p className="font-medium">Target Area</p>
|
||||||
<div className="flex flex-wrap gap-4 mt-2">
|
<div className="flex flex-wrap gap-4 mt-2">
|
||||||
{["Kiri - 1", "Kiri - 2", "Kanan - 1", "Kanan - 2"].map(
|
{[
|
||||||
(label) => (
|
{ label: "Kiri - 1", value: "left-top" },
|
||||||
<label key={label} className="flex items-center gap-2">
|
{ label: "Kiri - 2", value: "left-bottom" },
|
||||||
<Checkbox id={label} />
|
{ label: "Kanan - 1", value: "right-top" },
|
||||||
<span>{label}</span>
|
{ label: "Kanan - 2", value: "right-bottom" },
|
||||||
</label>
|
].map(({ label, value }) => (
|
||||||
)
|
<label key={value} className="flex items-center gap-2">
|
||||||
)}
|
<Checkbox
|
||||||
</div>
|
id={value}
|
||||||
</div>
|
checked={selectedPlacement === value}
|
||||||
|
onCheckedChange={() => handlePlacementSelect(value)}
|
||||||
<div>
|
|
||||||
<p className="font-medium">Publish Area</p>
|
|
||||||
<div className="flex flex-wrap gap-4 mt-2">
|
|
||||||
{["Semua", "Nasional", "Polda", "Satker", "International"].map(
|
|
||||||
(label) => (
|
|
||||||
<label key={label} className="flex items-center gap-2">
|
|
||||||
<Checkbox id={label} />
|
|
||||||
<span>{label}</span>
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">Nama Iklan</p>
|
|
||||||
<Input placeholder="Masukkan nama iklan" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-2 border-dashed rounded-md p-4 flex flex-col items-center justify-center text-center text-sm text-muted-foreground">
|
|
||||||
<svg
|
|
||||||
className="w-6 h-6 mb-2 text-blue-500"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1M12 12v9m0-9l3 3m-3-3l-3 3m6-11a4 4 0 00-8 0v1H4a2 2 0 00-2 2v4h20v-4a2 2 0 00-2-2h-4v-1z"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
<span>{label}</span>
|
||||||
<div>
|
</label>
|
||||||
Drag your file(s) or{" "}
|
))}
|
||||||
<span className="text-blue-500 underline cursor-pointer">
|
|
||||||
browse
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs mt-1">Max 10 MB files are allowed</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{/* <div>
|
||||||
<p className="font-medium">Deskripsi</p>
|
<p className="font-medium">Publish Area</p>
|
||||||
<Textarea placeholder="Masukkan deskripsi iklan" rows={4} />
|
<div className="flex flex-row">
|
||||||
|
<div className="flex flex-wrap gap-3 lg:ml-3 ">
|
||||||
|
{Object.keys(unitSelection).map((key) => (
|
||||||
|
<div className="flex items-center gap-2" key={key}>
|
||||||
|
<Checkbox
|
||||||
|
id={key}
|
||||||
|
checked={
|
||||||
|
unitSelection[key as keyof typeof unitSelection]
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
handleUnitChange(
|
||||||
|
key as keyof typeof unitSelection,
|
||||||
|
value as boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={key}>
|
||||||
|
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
<div className="text-right">
|
</div>
|
||||||
<Button
|
<div className=" lg:pl-3">
|
||||||
type="submit"
|
<Dialog>
|
||||||
className="bg-blue-600 text-white hover:bg-blue-700"
|
<DialogTrigger asChild>
|
||||||
>
|
<Button variant="soft" size="sm" color="primary">
|
||||||
Tambah Iklan
|
[{t("custom")}]
|
||||||
</Button>
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
Daftar Wilayah Polda dan Polres
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||||
|
{listDest.map((polda: any) => (
|
||||||
|
<div key={polda.id} className="border p-2">
|
||||||
|
<Label className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={checkedLevels.has(polda.id)}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(polda.id)
|
||||||
|
}
|
||||||
|
className="mr-3"
|
||||||
|
/>
|
||||||
|
{polda.name}
|
||||||
|
<button
|
||||||
|
onClick={() => toggleExpand(polda.id)}
|
||||||
|
className="ml-2 focus:outline-none"
|
||||||
|
>
|
||||||
|
{expandedPolda[polda.id] ? (
|
||||||
|
<ChevronUp size={16} />
|
||||||
|
) : (
|
||||||
|
<ChevronDown size={16} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</Label>
|
||||||
|
{expandedPolda[polda.id] && (
|
||||||
|
<div className="ml-6 mt-2">
|
||||||
|
<Label className="block">
|
||||||
|
<Checkbox
|
||||||
|
checked={polda?.subDestination?.every(
|
||||||
|
(polres: any) =>
|
||||||
|
checkedLevels.has(polres.id)
|
||||||
|
)}
|
||||||
|
onCheckedChange={(isChecked) => {
|
||||||
|
const updatedLevels = new Set(
|
||||||
|
checkedLevels
|
||||||
|
);
|
||||||
|
polda?.subDestination?.forEach(
|
||||||
|
(polres: any) => {
|
||||||
|
if (isChecked) {
|
||||||
|
updatedLevels.add(polres.id);
|
||||||
|
} else {
|
||||||
|
updatedLevels.delete(polres.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setCheckedLevels(updatedLevels);
|
||||||
|
}}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
Pilih Semua Polres
|
||||||
|
</Label>
|
||||||
|
{polda?.subDestination?.map((polres: any) => (
|
||||||
|
<Label
|
||||||
|
key={polres.id}
|
||||||
|
className="block mt-1"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={checkedLevels.has(polres.id)}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(polres.id)
|
||||||
|
}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
{polres.name}
|
||||||
|
</Label>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Nama Iklan</p>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input
|
||||||
|
size={"md"}
|
||||||
|
type="text"
|
||||||
|
value={detail?.title}
|
||||||
|
onChange={field.onChange}
|
||||||
|
placeholder="Masukan Nama Iklan"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.title?.message && (
|
||||||
|
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>Foto</Label>
|
||||||
|
<p className="text-xs text-red-500">
|
||||||
|
(Warning: Foto yang di upload adalah Foto Potrait)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Image
|
||||||
|
src={`https://netidhub.com/api/advertisements/viewer/${id}`}
|
||||||
|
alt="Thumbnail Gambar Utama"
|
||||||
|
className=" rounded-md my-3"
|
||||||
|
width={300}
|
||||||
|
height={500}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Deskripsi</p>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Textarea
|
||||||
|
rows={3}
|
||||||
|
value={detail?.description}
|
||||||
|
onChange={field.onChange}
|
||||||
|
placeholder="Masukan Description"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-right">
|
||||||
|
<Link href={"admin/settings/iklan"}>
|
||||||
|
<Button type="button" variant={"outline"} color="primary">
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,96 +13,554 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Plus } from "lucide-react";
|
import { ChevronDown, ChevronUp, Plus } from "lucide-react";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import { format, parseISO } from "date-fns";
|
||||||
|
import { getUserLevelForAssignments } from "@/service/task";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { DateRange } from "react-day-picker";
|
||||||
|
import { postCalendar } from "@/service/schedule/schedule";
|
||||||
|
import { id } from "date-fns/locale";
|
||||||
|
import router from "next/router";
|
||||||
|
import {
|
||||||
|
detailAdvertisements,
|
||||||
|
postAdvertisements,
|
||||||
|
} from "@/service/settings/settings";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import FileUploader from "../shared/file-uploader";
|
||||||
|
import { Icon } from "@/components/ui/icon";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export function TambahIklanModalUpdate() {
|
const calendarSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
description: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FileWithPreview extends File {
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileUploaded {
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Detail {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TambahIklanUpdate() {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const t = useTranslations("Schedule");
|
||||||
|
const { id } = useParams() as { id: string };
|
||||||
|
type CalendarSchema = z.infer<typeof calendarSchema>;
|
||||||
|
const [eventDate, setEventDate] = React.useState<Date | null>(new Date());
|
||||||
|
const [listDest, setListDest] = React.useState([]);
|
||||||
|
const [checkedLevels, setCheckedLevels] = React.useState(new Set());
|
||||||
|
const [expandedPolda, setExpandedPolda] = React.useState([{}]);
|
||||||
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const [isImageUploadFinish, setIsImageUploadFinish] = React.useState(false);
|
||||||
|
const [files, setFiles] = React.useState<FileWithPreview[]>([]);
|
||||||
|
const [selectedPlacement, setSelectedPlacement] = React.useState<string>("");
|
||||||
|
const [imageUploadedFiles, setImageUploadedFiles] = React.useState<
|
||||||
|
FileUploaded[]
|
||||||
|
>([]);
|
||||||
|
const [detail, setDetail] = React.useState<Detail>();
|
||||||
|
const [refresh, setRefresh] = React.useState(false);
|
||||||
|
const [imageFiles, setImageFiles] = React.useState<FileWithPreview[]>([]);
|
||||||
|
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||||
|
from: new Date(2025, 0, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [unitSelection, setUnitSelection] = React.useState({
|
||||||
|
semua: false,
|
||||||
|
mabes: false,
|
||||||
|
polda: false,
|
||||||
|
satker: false,
|
||||||
|
internasional: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<CalendarSchema>({
|
||||||
|
resolver: zodResolver(calendarSchema),
|
||||||
|
defaultValues: {
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePlacementSelect = (value: string) => {
|
||||||
|
setSelectedPlacement(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
async function initState() {
|
||||||
|
if (id) {
|
||||||
|
const response = await detailAdvertisements(id);
|
||||||
|
const details = response?.data?.data;
|
||||||
|
|
||||||
|
setDetail(details);
|
||||||
|
if (details?.assignedToLevel) {
|
||||||
|
const levelIds = details.assignedToLevel
|
||||||
|
.split(",")
|
||||||
|
.map((id: string) => parseInt(id));
|
||||||
|
setCheckedLevels(new Set(levelIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details?.placements) {
|
||||||
|
setSelectedPlacement(details.placements); // "left-bottom", etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initState();
|
||||||
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
async function fetchPoldaPolres() {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getUserLevelForAssignments();
|
||||||
|
setListDest(response?.data?.data.list);
|
||||||
|
console.log("polda", response?.data?.data?.list);
|
||||||
|
const initialExpandedState = response?.data?.data.list.reduce(
|
||||||
|
(acc: any, polda: any) => {
|
||||||
|
acc[polda.id] = false;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
setExpandedPolda(initialExpandedState);
|
||||||
|
console.log("polres", initialExpandedState);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Polda/Polres data:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchPoldaPolres();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (levelId: number) => {
|
||||||
|
setCheckedLevels((prev) => {
|
||||||
|
const updatedLevels = new Set(prev);
|
||||||
|
if (updatedLevels.has(levelId)) {
|
||||||
|
updatedLevels.delete(levelId);
|
||||||
|
} else {
|
||||||
|
updatedLevels.add(levelId);
|
||||||
|
}
|
||||||
|
return updatedLevels;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePoldaPolresChange = () => {
|
||||||
|
return Array.from(checkedLevels).join(",");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnitChange = (
|
||||||
|
key: keyof typeof unitSelection,
|
||||||
|
value: boolean
|
||||||
|
) => {
|
||||||
|
if (key === "semua") {
|
||||||
|
const newState = {
|
||||||
|
semua: value,
|
||||||
|
mabes: value,
|
||||||
|
polda: value,
|
||||||
|
satker: value,
|
||||||
|
internasional: value,
|
||||||
|
};
|
||||||
|
setUnitSelection(newState);
|
||||||
|
} else {
|
||||||
|
const updatedSelection = {
|
||||||
|
...unitSelection,
|
||||||
|
[key]: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
const allChecked = ["mabes", "polda", "satker", "internasional"].every(
|
||||||
|
(k) => updatedSelection[k as keyof typeof unitSelection]
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedSelection.semua = allChecked;
|
||||||
|
|
||||||
|
setUnitSelection(updatedSelection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleExpand = (poldaId: any) => {
|
||||||
|
setExpandedPolda((prev: any) => ({
|
||||||
|
...prev,
|
||||||
|
[poldaId]: !prev[poldaId],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (data: CalendarSchema) => {
|
||||||
|
const unitMapping = {
|
||||||
|
allUnit: "0",
|
||||||
|
mabes: "1",
|
||||||
|
polda: "2",
|
||||||
|
satker: "4",
|
||||||
|
internasional: "5",
|
||||||
|
};
|
||||||
|
const assignmentToString = Object.keys(unitSelection)
|
||||||
|
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||||
|
.map((key) => unitMapping[key as keyof typeof unitMapping])
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
const formMedia = new FormData();
|
||||||
|
if (detail?.id) {
|
||||||
|
formMedia.append("id", detail.id.toString()); // Kirim ID untuk update
|
||||||
|
}
|
||||||
|
formMedia.append("title", data.title);
|
||||||
|
formMedia.append("placements", selectedPlacement);
|
||||||
|
formMedia.append("description", data.description);
|
||||||
|
formMedia.append("redirectLink", "https://new.netidhub.com");
|
||||||
|
formMedia.append("assignedToLevel", handlePoldaPolresChange());
|
||||||
|
formMedia.append("file", imageFiles[0]);
|
||||||
|
|
||||||
|
console.log("Form Data Submitted:", formMedia);
|
||||||
|
|
||||||
|
loading();
|
||||||
|
const response = await postAdvertisements(formMedia);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
|
||||||
|
Cookies.set("scheduleId", response?.data?.data.id, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
successTodo();
|
||||||
|
}, [isImageUploadFinish]);
|
||||||
|
|
||||||
|
function successTodo() {
|
||||||
|
if (isImageUploadFinish) {
|
||||||
|
successSubmit("/in/admin/settings/iklan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const successSubmit = (redirect: string) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push(redirect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (data: CalendarSchema) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data",
|
||||||
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFilePreview = (url: string) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Image
|
||||||
<DialogTrigger asChild>
|
width={48}
|
||||||
<Button onClick={() => setOpen(true)} color="primary" size="md">
|
height={48}
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
alt={"file preview"}
|
||||||
Tambah Iklan
|
src={url}
|
||||||
</Button>
|
className=" rounded border p-0.5"
|
||||||
</DialogTrigger>
|
/>
|
||||||
<DialogContent size="md">
|
);
|
||||||
<DialogHeader>
|
};
|
||||||
<DialogTitle>Tambah Iklan</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
|
const handleRemoveFile = (id: number) => {};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card className="px-3 py-3">
|
||||||
|
{detail !== undefined ? (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Target Area</p>
|
<p className="font-medium">Target Area</p>
|
||||||
<div className="flex flex-wrap gap-4 mt-2">
|
<div className="flex flex-wrap gap-4 mt-2">
|
||||||
{["Kiri - 1", "Kiri - 2", "Kanan - 1", "Kanan - 2"].map(
|
{[
|
||||||
(label) => (
|
{ label: "Kiri - 1", value: "left-top" },
|
||||||
<label key={label} className="flex items-center gap-2">
|
{ label: "Kiri - 2", value: "left-bottom" },
|
||||||
<Checkbox id={label} />
|
{ label: "Kanan - 1", value: "right-top" },
|
||||||
|
{ label: "Kanan - 2", value: "right-bottom" },
|
||||||
|
].map(({ label, value }) => (
|
||||||
|
<label key={value} className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
id={value}
|
||||||
|
checked={selectedPlacement === value}
|
||||||
|
onCheckedChange={() => handlePlacementSelect(value)}
|
||||||
|
/>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</label>
|
</label>
|
||||||
)
|
))}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{/* <div>
|
||||||
<p className="font-medium">Publish Area</p>
|
<p className="font-medium">Publish Area</p>
|
||||||
<div className="flex flex-wrap gap-4 mt-2">
|
<div className="flex flex-row">
|
||||||
{["Semua", "Nasional", "Polda", "Satker", "International"].map(
|
<div className="flex flex-wrap gap-3 lg:ml-3 ">
|
||||||
(label) => (
|
{Object.keys(unitSelection).map((key) => (
|
||||||
<label key={label} className="flex items-center gap-2">
|
<div className="flex items-center gap-2" key={key}>
|
||||||
<Checkbox id={label} />
|
<Checkbox
|
||||||
<span>{label}</span>
|
id={key}
|
||||||
</label>
|
checked={
|
||||||
|
unitSelection[key as keyof typeof unitSelection]
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
handleUnitChange(
|
||||||
|
key as keyof typeof unitSelection,
|
||||||
|
value as boolean
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={key}>
|
||||||
|
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className=" lg:pl-3">
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="soft" size="sm" color="primary">
|
||||||
|
[{t("custom")}]
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
Daftar Wilayah Polda dan Polres
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||||
|
{listDest.map((polda: any) => (
|
||||||
|
<div key={polda.id} className="border p-2">
|
||||||
|
<Label className="flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={checkedLevels.has(polda.id)}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(polda.id)
|
||||||
|
}
|
||||||
|
className="mr-3"
|
||||||
|
/>
|
||||||
|
{polda.name}
|
||||||
|
<button
|
||||||
|
onClick={() => toggleExpand(polda.id)}
|
||||||
|
className="ml-2 focus:outline-none"
|
||||||
|
>
|
||||||
|
{expandedPolda[polda.id] ? (
|
||||||
|
<ChevronUp size={16} />
|
||||||
|
) : (
|
||||||
|
<ChevronDown size={16} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</Label>
|
||||||
|
{expandedPolda[polda.id] && (
|
||||||
|
<div className="ml-6 mt-2">
|
||||||
|
<Label className="block">
|
||||||
|
<Checkbox
|
||||||
|
checked={polda?.subDestination?.every(
|
||||||
|
(polres: any) =>
|
||||||
|
checkedLevels.has(polres.id)
|
||||||
|
)}
|
||||||
|
onCheckedChange={(isChecked) => {
|
||||||
|
const updatedLevels = new Set(
|
||||||
|
checkedLevels
|
||||||
|
);
|
||||||
|
polda?.subDestination?.forEach(
|
||||||
|
(polres: any) => {
|
||||||
|
if (isChecked) {
|
||||||
|
updatedLevels.add(polres.id);
|
||||||
|
} else {
|
||||||
|
updatedLevels.delete(polres.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setCheckedLevels(updatedLevels);
|
||||||
|
}}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
Pilih Semua Polres
|
||||||
|
</Label>
|
||||||
|
{polda?.subDestination?.map((polres: any) => (
|
||||||
|
<Label
|
||||||
|
key={polres.id}
|
||||||
|
className="block mt-1"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={checkedLevels.has(polres.id)}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(polres.id)
|
||||||
|
}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
{polres.name}
|
||||||
|
</Label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Nama Iklan</p>
|
<p className="font-medium">Nama Iklan</p>
|
||||||
<Input placeholder="Masukkan nama iklan" />
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input
|
||||||
|
size={"md"}
|
||||||
|
type="text"
|
||||||
|
defaultValue={detail?.title}
|
||||||
|
onChange={field.onChange}
|
||||||
|
placeholder="Masukan Nama Iklan"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.title?.message && (
|
||||||
|
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-2 border-dashed rounded-md p-4 flex flex-col items-center justify-center text-center text-sm text-muted-foreground">
|
|
||||||
<svg
|
|
||||||
className="w-6 h-6 mb-2 text-blue-500"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1M12 12v9m0-9l3 3m-3-3l-3 3m6-11a4 4 0 00-8 0v1H4a2 2 0 00-2 2v4h20v-4a2 2 0 00-2-2h-4v-1z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div>
|
<div>
|
||||||
Drag your file(s) or{" "}
|
<Label>Foto</Label>
|
||||||
<span className="text-blue-500 underline cursor-pointer">
|
<p className="text-xs text-red-500">
|
||||||
browse
|
(Warning: Foto yang di upload adalah Foto Potrait)
|
||||||
</span>
|
</p>
|
||||||
|
<FileUploader
|
||||||
|
accept={{
|
||||||
|
"image/*": [],
|
||||||
|
}}
|
||||||
|
maxSize={100}
|
||||||
|
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||||
|
onDrop={(files) => setImageFiles(files)}
|
||||||
|
/>
|
||||||
|
{imageUploadedFiles?.map((file: any, index: number) => (
|
||||||
|
<div>
|
||||||
|
<Card className="mt-2">
|
||||||
|
<img
|
||||||
|
src={file.url}
|
||||||
|
alt="Thumbnail Gambar Utama"
|
||||||
|
className="w-full h-auto rounded-md"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex gap-3 items-center">
|
||||||
|
<div className="file-preview">
|
||||||
|
{renderFilePreview(file.url)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs mt-1">Max 10 MB files are allowed</div>
|
<div>
|
||||||
|
<div className=" text-sm text-card-foreground">
|
||||||
|
{file.fileName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
color="destructive"
|
||||||
|
variant="outline"
|
||||||
|
className=" border-none rounded-full"
|
||||||
|
onClick={() => handleRemoveFile(file)}
|
||||||
|
>
|
||||||
|
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Image
|
||||||
|
src={`https://netidhub.com/api/advertisements/viewer/${id}`}
|
||||||
|
alt="Thumbnail Gambar Utama"
|
||||||
|
className=" rounded-md my-3"
|
||||||
|
width={300}
|
||||||
|
height={500}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Deskripsi</p>
|
<p className="font-medium">Deskripsi</p>
|
||||||
<Textarea placeholder="Masukkan deskripsi iklan" rows={4} />
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Textarea
|
||||||
|
rows={3}
|
||||||
|
defaultValue={detail?.description}
|
||||||
|
onChange={field.onChange}
|
||||||
|
placeholder="Masukan Description"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
|
<Link href={"admin/settings/iklan"}>
|
||||||
|
<Button type="button" variant={"outline"} color="primary">
|
||||||
|
Kembali
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="bg-blue-600 text-white hover:bg-blue-700"
|
variant={"default"}
|
||||||
|
color="primary"
|
||||||
|
className="mx-3"
|
||||||
>
|
>
|
||||||
Tambah Iklan
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</form>
|
||||||
</Dialog>
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ const EventCalender = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Event List */}
|
{/* Event List */}
|
||||||
<div className="space-y-3 max-h-[230px] overflow-y-auto pr-5">
|
<div className="space-y-3 max-h-[230px] overflow-y-auto pr-5" data-lenis-prevent>
|
||||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3">
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3">
|
||||||
Daftar Acara
|
Daftar Acara
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,10 @@ const NewContent = (props: { group: string; type: string }) => {
|
||||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||||
<TabsList className="flex gap-2 bg-transparent p-0">
|
<TabsList className="flex gap-2 bg-transparent p-0">
|
||||||
{[
|
{[
|
||||||
{ label: "Audio Visual", value: "video" },
|
|
||||||
{ label: "Audio", value: "audio" },
|
|
||||||
{ label: "Foto", value: "image" },
|
{ label: "Foto", value: "image" },
|
||||||
|
{ label: "Audio Visual", value: "video" },
|
||||||
{ label: "Teks", value: "text" },
|
{ label: "Teks", value: "text" },
|
||||||
|
{ label: "Audio", value: "audio" },
|
||||||
].map((tab) => (
|
].map((tab) => (
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
key={tab.value}
|
key={tab.value}
|
||||||
|
|
|
||||||
|
|
@ -228,15 +228,17 @@ const LoginForm = () => {
|
||||||
Number(profile?.data?.data?.roleId) == 18 ||
|
Number(profile?.data?.data?.roleId) == 18 ||
|
||||||
Number(profile?.data?.data?.roleId) == 19
|
Number(profile?.data?.data?.roleId) == 19
|
||||||
) {
|
) {
|
||||||
if (
|
if (profile?.data?.data?.roleId === 18) {
|
||||||
profile?.data?.data?.roleId === 18 ||
|
window.location.href = "/in/dashboard/executive-data";
|
||||||
profile?.data?.data?.roleId === 2
|
|
||||||
) {
|
|
||||||
window.location.href = "/in/dashboard/executive";
|
|
||||||
// router.push('/admin/dashboard');
|
// router.push('/admin/dashboard');
|
||||||
Cookies.set("status", "login", {
|
Cookies.set("status", "login", {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
});
|
||||||
|
} else if (profile?.data?.data?.roleId === 2) {
|
||||||
|
window.location.href = "/in/dashboard/executive";
|
||||||
|
Cookies.set("status", "login", {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
} else if (
|
} else if (
|
||||||
profile?.data?.data?.userLevel?.id == 794 ||
|
profile?.data?.data?.userLevel?.id == 794 ||
|
||||||
profile?.data?.data?.userLevel?.parentLevelId == 761
|
profile?.data?.data?.userLevel?.parentLevelId == 761
|
||||||
|
|
@ -532,10 +534,10 @@ const LoginForm = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="button"
|
||||||
fullWidth
|
fullWidth
|
||||||
// onClick={handleEmailValidation}
|
onClick={handleEmailValidation}
|
||||||
// disabled={isPending}
|
disabled={isPending}
|
||||||
>
|
>
|
||||||
Selanjutnya
|
Selanjutnya
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -3017,9 +3017,9 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
icon: "material-symbols:dashboard",
|
icon: "material-symbols:dashboard",
|
||||||
submenus: [
|
submenus: [
|
||||||
{
|
{
|
||||||
href: "/dashboard/executive-data",
|
href: "/dashboard/executive",
|
||||||
label: "Executive",
|
label: "Executive",
|
||||||
active: pathname === "/dashboard/executive-data",
|
active: pathname === "/dashboard/executive",
|
||||||
icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
@ -3348,9 +3348,9 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
icon: "material-symbols:dashboard",
|
icon: "material-symbols:dashboard",
|
||||||
submenus: [
|
submenus: [
|
||||||
{
|
{
|
||||||
href: "/dashboard/executive-data",
|
href: "/dashboard/executive",
|
||||||
label: "Executive",
|
label: "Executive",
|
||||||
active: pathname === "/dashboard/executive-data",
|
active: pathname === "/dashboard/executive",
|
||||||
icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -137,3 +137,13 @@ export async function postAdvertisements(data: any) {
|
||||||
};
|
};
|
||||||
return httpPostInterceptor(url, data, headers);
|
return httpPostInterceptor(url, data, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function detailAdvertisements(id: any) {
|
||||||
|
const url = `advertisements?id=${id}`;
|
||||||
|
return httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAdvertisements(id: string | number) {
|
||||||
|
const url = `advertisements?id=${id}`;
|
||||||
|
return httpDeleteInterceptor(url);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue