This commit is contained in:
hanif salafi 2024-12-18 09:48:15 +07:00
commit 8e210bc307
58 changed files with 5183 additions and 934 deletions

View File

@ -12,24 +12,60 @@ import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { CalendarEvent, CalendarCategory } from "./data"; import { CalendarCategory } from "./data";
import { EventContentArg } from "@fullcalendar/core"; import { EventContentArg } from "@fullcalendar/core";
import EventModal from "./event-modal"; import EventModal from "./event-modal";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { getAgendaSettingsList } from "@/service/agenda-setting/agenda-setting";
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000)); const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
interface CalendarViewProps { interface CalendarViewProps {
events: CalendarEvent[]; events: CalendarEvent[];
categories: CalendarCategory[]; categories: CalendarCategory[];
} }
export interface CalendarEvent {
id: string;
title: string;
start: Date;
end: Date;
allDay: boolean;
extendedProps: {
calendar: string;
description?: string;
};
}
export interface AgendaSettingsAPIResponse {
id: number;
title: string;
description: string;
agendaType: string;
startDate: string; // API mengembalikan tanggal dalam bentuk string
endDate: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
createdById: number | null;
createdByName: string | null;
}
interface APIResponse {
error: boolean;
message: any;
data: AgendaSettingsAPIResponse[] | null; // `data` bisa berupa array atau null
}
const CalendarView = ({ events, categories }: CalendarViewProps) => { const CalendarView = ({ events, categories }: CalendarViewProps) => {
const [selectedCategory, setSelectedCategory] = useState<string[] | null>( const [selectedCategory, setSelectedCategory] = useState<string[] | null>(
null null
); );
const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null); const [selectedEventDate, setSelectedEventDate] = useState<Date | null>(null);
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>( // const [selectedEvent, setSelectedEvent] = useState<CalendarEvent | null>(
null // null
); // );
const [apiEvents, setApiEvents] = useState<CalendarEvent[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [draggableInitialized, setDraggableInitialized] = const [draggableInitialized, setDraggableInitialized] =
useState<boolean>(false); useState<boolean>(false);
const t = useTranslations("CalendarApp"); const t = useTranslations("CalendarApp");
@ -48,6 +84,73 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
setSelectedCategory(categories?.map((c) => c.value)); setSelectedCategory(categories?.map((c) => c.value));
}, [events, categories]); }, [events, categories]);
useEffect(() => {
console.log("Fetched events from API:", apiEvents);
}, [apiEvents]);
const filteredEvents = apiEvents?.filter((event) =>
selectedCategory?.includes(event.extendedProps.calendar)
);
const displayedEvents =
filteredEvents?.length > 0 ? filteredEvents : apiEvents;
useEffect(() => {
console.log("Filtered events based on category:", displayedEvents);
}, [filteredEvents, apiEvents]);
useEffect(() => {
setSelectedCategory(categories?.map((c) => c.value));
}, [categories]);
useEffect(() => {
console.log("Selected categories:", selectedCategory);
}, [selectedCategory]);
useEffect(() => {
const fetchAgendaEvents = async () => {
setLoading(true);
try {
const selectedMonth = new Date(); // Replace with your logic for selected month
const year = selectedMonth.getFullYear().toString();
const month = (selectedMonth.getMonth() + 1).toString();
const typeFilter = ""; // Replace with your type filter logic if needed
const response: APIResponse = await getAgendaSettingsList(
year,
month,
typeFilter
);
if (response.data && Array.isArray(response.data)) {
// Transform API data to match CalendarEvent type
const eventsFromAPI: CalendarEvent[] = response.data.map((item) => ({
id: item.id.toString(),
title: item.title,
start: new Date(item.startDate),
end: new Date(item.endDate),
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
extendedProps: {
calendar: item.agendaType,
description: item.description,
},
}));
setApiEvents(eventsFromAPI);
} else {
console.warn("No events found in API response.");
setApiEvents([]);
}
} catch (error) {
console.error("Failed to fetch agenda settings:", error);
setApiEvents([]);
} finally {
setLoading(false);
}
};
fetchAgendaEvents();
}, []);
useEffect(() => { useEffect(() => {
const draggableEl = document.getElementById("external-events"); const draggableEl = document.getElementById("external-events");
@ -80,23 +183,24 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
draggableEl?.removeEventListener("mousedown", initDraggable); draggableEl?.removeEventListener("mousedown", initDraggable);
}; };
}, [dragEvents]); }, [dragEvents]);
// event click // event click
const handleEventClick = (arg: any) => { const handleEventClick = (arg: any) => {
setSelectedEventDate(null); setSelectedEventDate(null);
setSheetOpen(true); setSheetOpen(true);
setSelectedEvent(arg); setApiEvents(arg);
wait().then(() => (document.body.style.pointerEvents = "auto")); wait().then(() => (document.body.style.pointerEvents = "auto"));
}; };
// handle close modal // handle close modal
const handleCloseModal = () => { const handleCloseModal = () => {
setSheetOpen(false); setSheetOpen(false);
setSelectedEvent(null); setApiEvents([]);
setSelectedEventDate(null); setSelectedEventDate(null);
}; };
const handleDateClick = (arg: any) => { const handleDateClick = (arg: any) => {
setSheetOpen(true); setSheetOpen(true);
setSelectedEventDate(arg); setSelectedEventDate(arg);
setSelectedEvent(null); setApiEvents([]);
wait().then(() => (document.body.style.pointerEvents = "auto")); wait().then(() => (document.body.style.pointerEvents = "auto"));
}; };
@ -109,7 +213,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
}; };
const handleClassName = (arg: EventContentArg) => { const handleClassName = (arg: EventContentArg) => {
if (arg.event.extendedProps.calendar === "national") { if (arg.event.extendedProps.calendar === "mabes") {
return "primary"; return "primary";
} else if (arg.event.extendedProps.calendar === "polda") { } else if (arg.event.extendedProps.calendar === "polda") {
return "success"; return "success";
@ -122,10 +226,6 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
} }
}; };
const filteredEvents = events?.filter((event) =>
selectedCategory?.includes(event.extendedProps.calendar)
);
return ( return (
<> <>
<div className="grid grid-cols-12 gap-6 divide-x divide-border"> <div className="grid grid-cols-12 gap-6 divide-x divide-border">
@ -205,7 +305,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
center: "title", center: "title",
right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek", right: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
}} }}
events={filteredEvents} events={displayedEvents} // Use apiEvents here
editable={true} editable={true}
rerenderDelay={10} rerenderDelay={10}
eventDurationEditable={false} eventDurationEditable={false}
@ -226,7 +326,7 @@ const CalendarView = ({ events, categories }: CalendarViewProps) => {
open={sheetOpen} open={sheetOpen}
onClose={handleCloseModal} onClose={handleCloseModal}
categories={categories} categories={categories}
event={selectedEvent} event={apiEvents}
selectedDate={selectedEventDate} selectedDate={selectedEventDate}
/> />
</> </>

View File

@ -55,6 +55,7 @@ const BlogTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -86,11 +87,11 @@ const BlogTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationBlog(limit, page - 1, ""); const res = await paginationBlog(limit, page - 1, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -107,6 +108,11 @@ const BlogTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -119,6 +125,8 @@ const BlogTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -0,0 +1,18 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTask from "@/components/form/task/task-form";
import FormImage from "@/components/form/content/image-form";
import FormBlog from "@/components/form/blog/blog-form";
const BlogCreatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormBlog />
</div>
</div>
);
};
export default BlogCreatePage;

View File

@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import BlogTable from "./components/blog-table"; import BlogTable from "./components/blog-table";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Link } from "@/components/navigation";
const BlogPage = async () => { const BlogPage = async () => {
return ( return (
@ -17,10 +18,12 @@ const BlogPage = async () => {
Table Indeks Table Indeks
</div> </div>
<div className="flex-none"> <div className="flex-none">
<Button fullWidth size="md"> <Link href={"/contributor/blog/create"}>
<Plus className="w-6 h-6 me-1.5" /> <Button fullWidth color="primary">
Add Index <Plus className="w-6 h-6 me-1.5" />
</Button> Add Index
</Button>
</Link>
</div> </div>
</div> </div>
</CardTitle> </CardTitle>

View File

@ -121,7 +121,7 @@ const TableAudio = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
@ -140,7 +140,7 @@ const TableAudio = () => {
filterBySource, filterBySource,
startDateString, startDateString,
endDateString, endDateString,
"" search
); );
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
@ -157,6 +157,10 @@ const TableAudio = () => {
console.error("Error fetching tasks:", error); console.error("Error fetching tasks:", error);
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
@ -170,6 +174,8 @@ const TableAudio = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -73,7 +73,7 @@ const TableImage = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState("");
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
@ -117,7 +117,7 @@ const TableImage = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
@ -136,7 +136,7 @@ const TableImage = () => {
filterBySource, filterBySource,
startDateString, startDateString,
endDateString, endDateString,
"" search
); );
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
@ -154,6 +154,11 @@ const TableImage = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -166,6 +171,8 @@ const TableImage = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -121,12 +121,12 @@ const TableTeks = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const isForSelf = Number(roleId) == 4; const isForSelf = Number(roleId) == 4;
const res = await listNulisAI(limit, page - 1, ""); const res = await listNulisAI(limit, page - 1, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -143,6 +143,11 @@ const TableTeks = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -155,6 +160,8 @@ const TableTeks = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -117,7 +117,7 @@ const TableTeks = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
@ -136,7 +136,7 @@ const TableTeks = () => {
filterBySource, filterBySource,
startDateString, startDateString,
endDateString, endDateString,
"" search
); );
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
@ -154,6 +154,11 @@ const TableTeks = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -166,6 +171,8 @@ const TableTeks = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -117,7 +117,7 @@ const TableImage = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
@ -136,7 +136,7 @@ const TableImage = () => {
filterBySource, filterBySource,
startDateString, startDateString,
endDateString, endDateString,
"" search
); );
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
@ -154,6 +154,11 @@ const TableImage = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -166,6 +171,8 @@ const TableImage = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -11,6 +11,8 @@ import {
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -42,9 +44,18 @@ const columns: ColumnDef<any>[] = [
{ {
accessorKey: "createdAt", accessorKey: "createdAt",
header: "Tanggal Unggah ", header: "Tanggal Unggah ",
cell: ({ row }) => ( cell: ({ row }) => {
<span className="whitespace-nowrap">{row.getValue("createdAt")}</span> const createdAt = row.getValue("createdAt") as
), | string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
}, },
{ {
accessorKey: "isActive", accessorKey: "isActive",
@ -81,15 +92,18 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<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/planning/mediahub/publish/${row.original.id}`}
View >
</DropdownMenuItem> <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"> <Eye className="w-4 h-4 me-1.5" />
<SquarePen className="w-4 h-4 me-1.5" /> Publish
Edit </DropdownMenuItem>
</DropdownMenuItem> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
// onClick={() => deletePlan(row.id)}
>
<Trash2 className="w-4 h-4 me-1.5" /> <Trash2 className="w-4 h-4 me-1.5" />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -52,6 +52,7 @@ import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination"; import TablePagination from "@/components/table/table-pagination";
import columns from "./columns"; import columns from "./columns";
import { getPlanningSentPagination } from "@/service/planning/planning"; import { getPlanningSentPagination } from "@/service/planning/planning";
import search from "@/app/[locale]/(protected)/app/chat/components/search";
const MediahubTable = () => { const MediahubTable = () => {
const router = useRouter(); const router = useRouter();
@ -73,6 +74,7 @@ const MediahubTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -104,11 +106,11 @@ const MediahubTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await getPlanningSentPagination(limit, page - 1, 1, ""); const res = await getPlanningSentPagination(limit, page - 1, 1, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -125,6 +127,11 @@ const MediahubTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -137,6 +144,8 @@ const MediahubTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -0,0 +1,17 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTask from "@/components/form/task/task-form";
import PublishMediahub from "@/components/form/planning/mediahub-publish";
const MediahubPublishPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<PublishMediahub />
</div>
</div>
);
};
export default MediahubPublishPage;

View File

@ -11,6 +11,8 @@ import {
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -42,9 +44,18 @@ const columns: ColumnDef<any>[] = [
{ {
accessorKey: "createdAt", accessorKey: "createdAt",
header: "Tanggal Unggah ", header: "Tanggal Unggah ",
cell: ({ row }) => ( cell: ({ row }) => {
<span className="whitespace-nowrap">{row.getValue("createdAt")}</span> const createdAt = row.getValue("createdAt") as
), | string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
}, },
{ {
accessorKey: "isActive", accessorKey: "isActive",
@ -81,14 +92,15 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<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/planning/medsos-mediahub/publish/${row.original.id}`}
View >
</DropdownMenuItem> <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"> <Eye className="w-4 h-4 me-1.5" />
<SquarePen className="w-4 h-4 me-1.5" /> Publish
Edit </DropdownMenuItem>
</DropdownMenuItem> </Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"> <DropdownMenuItem 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" /> <Trash2 className="w-4 h-4 me-1.5" />
Delete Delete

View File

@ -73,6 +73,7 @@ const MedsosTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -104,11 +105,11 @@ const MedsosTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await getPlanningSentPagination(limit, page - 1, 2, ""); const res = await getPlanningSentPagination(limit, page - 1, 2, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -125,6 +126,11 @@ const MedsosTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -137,6 +143,8 @@ const MedsosTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
onChange={handleSearch}
value={search}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -0,0 +1,16 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import PublishMedsos from "@/components/form/planning/medsos-publish";
const MedsosPublishPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<PublishMedsos />
</div>
</div>
);
};
export default MedsosPublishPage;

View File

@ -50,6 +50,7 @@ const EventTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -81,11 +82,11 @@ const EventTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationSchedule(limit, page - 1, 2, ""); const res = await paginationSchedule(limit, page - 1, 2, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -102,6 +103,11 @@ const EventTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -114,6 +120,8 @@ const EventTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -63,6 +63,7 @@ const PressConferenceTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -94,11 +95,11 @@ const PressConferenceTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationSchedule(limit, page - 1, 1, ""); const res = await paginationSchedule(limit, page - 1, 1, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -115,6 +116,11 @@ const PressConferenceTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -127,6 +133,8 @@ const PressConferenceTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -64,6 +64,7 @@ const PressReleaseTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -95,11 +96,11 @@ const PressReleaseTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationSchedule(limit, page - 1, 3, ""); const res = await paginationSchedule(limit, page - 1, 3, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -116,9 +117,14 @@ const PressReleaseTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5 mt-3">
<div> <div>
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
@ -128,6 +134,8 @@ const PressReleaseTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -12,6 +12,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -111,10 +112,12 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <Link href={`/contributor/task/detail/${row.original.id}`}>
<Eye className="w-4 h-4 me-1.5" /> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
View <Eye className="w-4 h-4 me-1.5" />
</DropdownMenuItem> View
</DropdownMenuItem>
</Link>
<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">
<SquarePen className="w-4 h-4 me-1.5" /> <SquarePen className="w-4 h-4 me-1.5" />
Edit Edit

View File

@ -74,6 +74,7 @@ const TaskTable = () => {
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [isSpecificAttention, setIsSpecificAttention] = React.useState(true); const [isSpecificAttention, setIsSpecificAttention] = React.useState(true);
const [search, setSearch] = React.useState<string>("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -105,11 +106,16 @@ const TaskTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit, isSpecificAttention]); }, [page, limit, isSpecificAttention, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await listTask("", page - 1, limit, isSpecificAttention ? "atensi-khusu" : "tugas-harian"); const res = await listTask(
search,
page - 1,
limit,
isSpecificAttention ? "atensi-khusus" : "tugas-harian"
);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -126,6 +132,11 @@ const TaskTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="mx-5 mb-3"> <div className="mx-5 mb-3">
@ -133,7 +144,11 @@ const TaskTable = () => {
<div className="row"> <div className="row">
<div className="flex justify-between mb-6"> <div className="flex justify-between mb-6">
<label className="inline-flex text-md cursor-pointer"> <label className="inline-flex text-md cursor-pointer">
<input type="checkbox" onChange={() => setIsSpecificAttention(!isSpecificAttention)} hidden /> <input
type="checkbox"
onChange={() => setIsSpecificAttention(!isSpecificAttention)}
hidden
/>
<span <span
className={` ${ className={` ${
isSpecificAttention isSpecificAttention
@ -147,7 +162,9 @@ const TaskTable = () => {
<span <span
className={` className={`
${ ${
!isSpecificAttention ? "bg-default-900 text-white" : " dark:text-default-700 border-2" !isSpecificAttention
? "bg-default-900 text-white"
: " dark:text-default-700 border-2"
} }
px-[18px] py-1 transition duration-100 rounded px-[18px] py-1 transition duration-100 rounded
`} `}
@ -169,6 +186,8 @@ const TaskTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -5,11 +5,12 @@ import { useTranslations } from "next-intl";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { UploadIcon } from "lucide-react"; import { UploadIcon } from "lucide-react";
import RecentActivity from "./routine-task/recent-activity";
import CompanyTable from "./routine-task/routine-task-table";
import TaskTable from "../contributor/task/components/task-table"; import TaskTable from "../contributor/task/components/task-table";
import PressConferenceTable from "../contributor/schedule/press-release/components/pressrilis-table"; import PressConferenceTable from "../contributor/schedule/press-release/components/pressrilis-table";
import BlogTable from "../contributor/blog/components/blog-table"; import BlogTable from "../contributor/blog/components/blog-table";
import ContentTable from "./routine-task/components/content-table";
import RecentActivity from "./routine-task/components/recent-activity";
import { Link } from "@/components/navigation";
const DashboardPage = () => { const DashboardPage = () => {
const t = useTranslations("AnalyticsDashboard"); const t = useTranslations("AnalyticsDashboard");
@ -94,7 +95,7 @@ const DashboardPage = () => {
<DashboardDropdown /> <DashboardDropdown />
</CardHeader> </CardHeader>
<CardContent className="p-0"> <CardContent className="p-0">
<CompanyTable /> <ContentTable />
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
@ -110,14 +111,16 @@ const DashboardPage = () => {
Table Penugasan Table Penugasan
</div> </div>
<div> <div>
<Button color="primary" className="text-white"> <Link href={"/contributor/task/create"}>
<UploadIcon /> <Button color="primary" className="text-white">
Buat Penugasan <UploadIcon />
</Button> Buat Penugasan
</Button>
</Link>
</div> </div>
</div> </div>
</Card> </Card>
<CardContent className="p-0"> <CardContent className="p-0 mt-3">
<TaskTable /> <TaskTable />
</CardContent> </CardContent>
</Card> </Card>
@ -128,7 +131,7 @@ const DashboardPage = () => {
<div className="grid grid-cols-12 gap-5"> <div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12"> <div className="lg:col-span-12 col-span-12">
<Card> <Card>
<CardContent className="p-0"> <CardContent className="p-0 ">
<PressConferenceTable /> <PressConferenceTable />
</CardContent> </CardContent>
</Card> </Card>
@ -145,14 +148,16 @@ const DashboardPage = () => {
Table Indeks Table Indeks
</div> </div>
<div> <div>
<Button color="primary" className="text-white"> <Link href={"/contributor/blog/create"}>
<UploadIcon /> <Button color="primary" className="text-white">
Tambah Indeks <UploadIcon />
</Button> Tambah Indeks
</Button>
</Link>
</div> </div>
</div> </div>
</Card> </Card>
<CardContent className="p-0"> <CardContent className="p-0 mt-3">
<BlogTable /> <BlogTable />
</CardContent> </CardContent>
</Card> </Card>

View File

@ -0,0 +1,174 @@
import * as React from "react";
import { ColumnDef } from "@tanstack/react-table";
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { format } from "date-fns";
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: "No",
cell: ({ row }) => (
<div className="flex items-center gap-5">
<div className="flex-1 text-start">
<h4 className="text-sm font-medium text-default-600 whitespace-nowrap mb-1">
{row.getValue("no")}
</h4>
</div>
</div>
),
},
{
accessorKey: "title",
header: "Title",
cell: ({ row }: { row: { getValue: (key: string) => string } }) => {
const title: string = row.getValue("title");
return (
<span className="">
{title.length > 50 ? `${title.slice(0, 10)}...` : title}
</span>
);
},
},
{
accessorKey: "createdAt",
header: "Upload Date",
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "fileTypeName",
header: "Type Content",
cell: ({ row }: { row: { getValue: (key: string) => string } }) => {
const title: string = row.getValue("fileTypeName");
return (
<span className="whitespace-nowrap">
{title.length > 50 ? `${title.slice(0, 30)}...` : title}
</span>
);
},
},
// {
// accessorKey: "creatorGroup",
// header: "Creator Group",
// cell: ({ row }) => (
// <span className="whitespace-nowrap">{row.getValue("creatorGroup")}</span>
// ),
// },
// {
// accessorKey: "creatorName",
// header: "Sumber",
// cell: ({ row }) => (
// <span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
// ),
// },
// {
// accessorKey: "publishedOn",
// header: "Published",
// cell: ({ row }) => {
// const isPublish = row.original.isPublish;
// const isPublishOnPolda = row.original.isPublishOnPolda;
// let displayText = "-";
// if (isPublish && !isPublishOnPolda) {
// displayText = "Mabes";
// } else if (isPublish && isPublishOnPolda) {
// displayText = "Mabes & Polda";
// } else if (!isPublish && isPublishOnPolda) {
// displayText = "Polda";
// }
// return (
// <div className="text-center whitespace-nowrap" title={displayText}>
// {displayText}
// </div>
// );
// },
// },
{
accessorKey: "statusName",
header: "Status",
cell: ({ row }) => {
// Mendapatkan nilai statusName
const statusName = row.getValue<string>("statusName");
// Mapping warna berdasarkan statusName
const colorMapping: Record<string, string> = {
"Menunggu Review": "text-orange-500 border-orange-500",
Diterima: "text-green-500 border-green-500",
"Minta Update": "text-blue-500 border-blue-500",
Ditolak: "text-red-500 border-red-500",
};
// Mendapatkan kelas warna dari mapping, default ke abu-abu jika tidak ditemukan
const buttonClass =
colorMapping[statusName] || "text-gray-500 border-gray-500";
return (
<div>
<Button
size="sm"
variant="outline"
className={`btn btn-sm pill-btn ml-1 ${buttonClass}`}
>
{statusName || "Tidak Diketahui"}
</Button>
</div>
);
},
},
{
id: "actions",
accessorKey: "action",
header: "Actions",
enableHiding: false,
cell: ({ row }) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
>
<span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<a href="/en/task/detail/[id]">
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</a>
<DropdownMenuItem 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>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
export default columns;

View File

@ -0,0 +1,278 @@
"use client";
import * as React from "react";
import {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronLeft,
ChevronRight,
Eye,
MoreVertical,
Search,
SquarePen,
Trash2,
TrendingDown,
TrendingUp,
} from "lucide-react";
import { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { paginationBlog } from "@/service/blog/blog";
import { ticketingPagination } from "@/service/ticketing/ticketing";
import { Badge } from "@/components/ui/badge";
import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination";
import columns from "./columns";
import { listDataAll, listDataImage } from "@/service/content/content";
import { Icon } from "@iconify/react/dist/iconify.js";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
type StatusFilter = string[];
const ContentTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10);
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const [categories, setCategories] = React.useState<string[]>();
const [categoryFilter, setCategoryFilter] = React.useState<string[]>([]);
const [statusFilter, setStatusFilter] = React.useState<StatusFilter>([]);
const [startDateString, setStartDateString] = React.useState<string>("");
const [endDateString, setEndDateString] = React.useState<string>("");
const [filterByCreator, setFilterByCreator] = React.useState<string>("");
const [fileTypeFilter, setFileTypeFilter] = React.useState<string[]>([]);
const [filterBySource, setFilterBySource] = React.useState<string>("");
const [search, setSearch] = React.useState("");
const roleId = getCookiesDecrypt("urie");
const table = useReactTable({
data: dataTable,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination,
},
});
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
React.useEffect(() => {
fetchData();
}, [page, limit, fileTypeFilter, statusFilter, search]);
async function fetchData() {
try {
const isForSelf = Number(roleId) === 4;
const res = await listDataAll(
isForSelf,
!isForSelf,
page - 1,
limit,
fileTypeFilter.sort().join(","),
categoryFilter.sort().join(","),
statusFilter.sort().join(","),
statusFilter.sort().join(",").includes("1") ? userLevelId : "",
filterByCreator,
filterBySource,
startDateString,
endDateString,
search
);
const data = res.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * limit + index + 1;
});
console.log("contentData : ", contentData);
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(data?.totalPages);
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return (
<div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className="h-4 w-4 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/>
</InputGroup>
</div>
<div className="flex flex-row items-center gap-3">
{/* <Select
onValueChange={(value) => {
setStatusFilter([value]);
}}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select a Filter Status" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Status</SelectLabel>
<SelectItem value="1">Menunggu Review</SelectItem>
<SelectItem value="2">Diterima</SelectItem>
<SelectItem value="3">Minta Update</SelectItem>
<SelectItem value="4">Ditolak</SelectItem>
</SelectGroup>
</SelectContent>
</Select> */}
<Select
onValueChange={(value) => {
setFileTypeFilter([value]);
}}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select a Filter" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Filter</SelectLabel>
<SelectItem value="1">Image</SelectItem>
<SelectItem value="2">Video</SelectItem>
<SelectItem value="3">Teks</SelectItem>
<SelectItem value="4">Audio</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<Table className="overflow-hidden mt-3">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="bg-default-200">
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
className="h-[75px]"
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<TablePagination
table={table}
totalData={totalData}
totalPage={totalPage}
/>
</div>
);
};
export default ContentTable;

View File

@ -0,0 +1,136 @@
"use client";
import { getCookiesDecrypt } from "@/lib/utils";
import { listDataAll } from "@/service/content/content";
import {
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
} from "@tanstack/react-table";
import { DockIcon, ImageIcon, MicIcon, YoutubeIcon } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import React from "react";
import search from "../../../app/chat/components/search";
type StatusFilter = string[];
interface Counts {
images: number;
audiovisual: number;
text: number;
audio: number;
}
const RecentActivity: React.FC = () => {
const router = useRouter();
const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>("");
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const [categories, setCategories] = React.useState<string[]>();
const [categoryFilter, setCategoryFilter] = React.useState<string[]>([]);
const [statusFilter, setStatusFilter] = React.useState<StatusFilter>([]);
const [startDateString, setStartDateString] = React.useState<string>("");
const [endDateString, setEndDateString] = React.useState<string>("");
const [filterByCreator, setFilterByCreator] = React.useState<string>("");
const [fileTypeFilter, setFileTypeFilter] = React.useState<string[]>([]);
const [filterBySource, setFilterBySource] = React.useState<string>("");
const [counts, setCounts] = React.useState<Counts>({
images: 0,
audiovisual: 0,
text: 0,
audio: 0,
});
const roleId = getCookiesDecrypt("urie");
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
React.useEffect(() => {
fetchData();
}, [page, limit]);
async function fetchData() {
try {
const isForSelf = Number(roleId) === 4;
const res = await listDataAll(
isForSelf,
!isForSelf,
page - 1,
limit,
search,
fileTypeFilter.sort().join(","),
categoryFilter.sort().join(","),
statusFilter.sort().join(","),
statusFilter.sort().join(",").includes("1") ? userLevelId : "",
filterByCreator,
filterBySource,
startDateString,
endDateString
);
const data = res.data?.data;
const { content } = data || [];
// Calculate counts for each typeId
const newCounts: Counts = {
images: content.filter((item: any) => item.typeId === 1).length,
audiovisual: content.filter((item: any) => item.typeId === 2).length,
text: content.filter((item: any) => item.typeId === 3).length,
audio: content.filter((item: any) => item.typeId === 4).length,
};
setDataTable(content);
setCounts(newCounts);
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
return (
<div className="flex flex-col gap-5">
<div className="flex flex-row items-center gap-3">
<ImageIcon size={40} className="text-blue-700" />
<p className="text-xl">{counts.images} FOTO</p>
</div>
<div className="flex flex-row items-center gap-3">
<YoutubeIcon size={40} className="text-blue-700" />
<p className="text-xl">{counts.audiovisual} AUDIO VISUAL</p>
</div>
<div className="flex flex-row items-center gap-3">
<DockIcon size={40} className="text-blue-700" />
<p className="text-xl">{counts.text} TEXT</p>
</div>
<div className="flex flex-row items-center gap-3">
<MicIcon size={40} className="text-blue-700" />
<p className="text-xl">{counts.audio} AUDIO</p>
</div>
</div>
);
};
export default RecentActivity;

View File

@ -1,36 +0,0 @@
"use client";
import {
DockIcon,
ImageIcon,
MicIcon,
PaperclipIcon,
TextIcon,
VideoIcon,
YoutubeIcon,
} from "lucide-react";
const RecentActivity = () => {
return (
<div className="flex flex-col gap-5">
<div className="flex flex-row items-center gap-3">
<ImageIcon size={40} className="text-blue-700" />
<p className="text-xl">0 FOTO</p>
</div>
<div className="flex flex-row items-center gap-3">
<YoutubeIcon size={40} className="text-blue-700" />
<p className="text-xl">0 AUDIO VISUAL</p>
</div>
<div className="flex flex-row items-center gap-3">
<DockIcon size={40} className="text-blue-700" />
<p className="text-xl">0 TEXT</p>
</div>
<div className="flex flex-row items-center gap-3">
<MicIcon size={40} className="text-blue-700" />
<p className="text-xl">0 AUDIO</p>
</div>
</div>
);
};
export default RecentActivity;

View File

@ -1,272 +0,0 @@
"use client";
import * as React from "react";
import {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { data } from "./data";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
Badge,
ChevronLeft,
ChevronRight,
Eye,
MoreVertical,
SquarePen,
Trash2,
TrendingDown,
TrendingUp,
} from "lucide-react";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export type CompanyData = {
company: string;
category: string;
revenue: string;
sales: number;
status: string;
up: boolean;
};
export const columns: ColumnDef<CompanyData>[] = [
{
accessorKey: "company",
header: "Judul",
cell: ({ row }) => (
<div className="flex items-center gap-5">
<div className="flex-none">
<div className="w-8 h-8">
<Avatar>
<AvatarImage src={row.getValue("company")}></AvatarImage>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</div>
</div>
<div className="flex-1 text-start">
<h4 className="text-sm font-medium text-default-600 whitespace-nowrap mb-1">
Biffco Enterprises Ltd.
</h4>
<div className="text-xs font-normal text-default-600 ">
Biffco@example.com
</div>
</div>
</div>
),
},
{
accessorKey: "category",
header: "Tanggal Unggah",
cell: ({ row }) => (
<span className="whitespace-nowrap">{row.getValue("category")}</span>
),
},
{
accessorKey: "sales",
header: "Tipe Konten",
cell: ({ row }) => (
<div className="flex items-center gap-4">
<span>{row.getValue("sales")}</span>
{row?.original.up ? (
<TrendingUp className="text-success w-4 h-4" />
) : (
<TrendingDown className="text-destructive w-4 h-4" />
)}
</div>
),
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const statusColors: Record<CompanyData["status"], string> = {
paid: "bg-success/20 text-success",
due: "bg-warning/20 text-warning",
canceled: "bg-destructive/20 text-destructive",
};
const status = row.getValue<CompanyData["status"]>("status");
return (
<Badge className={cn("rounded-full px-5", statusColors[status])}>
{status}
</Badge>
);
},
},
{
id: "actions",
accessorKey: "action",
header: "Actions",
enableHiding: false,
cell: ({ row }) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="icon"
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
>
<span className="sr-only">Open menu</span>
<MoreVertical className="h-4 w-4 text-default-800" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
<DropdownMenuItem 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>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
const CompanyTable = () => {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 6,
});
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination,
},
});
return (
<div className="w-full overflow-x-auto">
<Table className="overflow-hidden">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="bg-default-200">
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
className="h-[75px]"
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<div className="flex items-center justify-center py-4 gap-2 flex-none">
<Button
variant="outline"
size="icon"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
className="w-8 h-8"
>
<ChevronLeft className="w-4 h-4" />
</Button>
{table.getPageOptions().map((page, pageIndex) => (
<Button
key={`basic-data-table-${pageIndex}`}
onClick={() => table.setPageIndex(pageIndex)}
size="icon"
className="w-8 h-8"
variant={
table.getState().pagination.pageIndex === pageIndex
? "default"
: "outline"
}
>
{page + 1}
</Button>
))}
<Button
variant="outline"
size="icon"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
className="w-8 h-8"
>
<ChevronRight className="w-4 h-4" />
</Button>
</div>
</div>
);
};
export default CompanyTable;

View File

@ -126,14 +126,14 @@ const EscalationTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await getTicketingCollaborationPagination( const res = await getTicketingCollaborationPagination(
page - 1, page - 1,
limit, limit,
"" search
); );
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
@ -149,10 +149,15 @@ const EscalationTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
<div> <div className="mt-3">
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" /> <Search className=" h-4 w-4 dark:text-white" />
@ -161,6 +166,8 @@ const EscalationTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -125,11 +125,15 @@ const EscalationTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await getTicketingEscalationPagination(page - 1, limit, ""); const res = await getTicketingEscalationPagination(
page - 1,
limit,
search
);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -144,10 +148,15 @@ const EscalationTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
<div> <div className="mt-3">
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" /> <Search className=" h-4 w-4 dark:text-white" />
@ -156,6 +165,8 @@ const EscalationTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -122,11 +122,11 @@ const TableAudio = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await listTicketingInternal(page - 1, limit, ""); const res = await listTicketingInternal(page - 1, limit, search);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -143,10 +143,15 @@ const TableAudio = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto mt-3">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
<div> <div className="mt-3">
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" /> <Search className=" h-4 w-4 dark:text-white" />
@ -155,6 +160,8 @@ const TableAudio = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -36,22 +36,11 @@ import {
TrendingDown, TrendingDown,
TrendingUp, TrendingUp,
} from "lucide-react"; } from "lucide-react";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group"; import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { paginationBlog } from "@/service/blog/blog";
import { ticketingPagination } from "@/service/ticketing/ticketing";
import { Badge } from "@/components/ui/badge";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination"; import TablePagination from "@/components/table/table-pagination";
import columns from "./columns"; import columns from "./columns";
import { listTask } from "@/service/task";
import { listContest } from "@/service/contest/contest"; import { listContest } from "@/service/contest/contest";
const TaskTable = () => { const TaskTable = () => {
@ -74,7 +63,7 @@ const TaskTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const [activeTab, setActiveTab] = React.useState("atensi-khusus"); const [search, setSearch] = React.useState<string>("");
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -106,11 +95,11 @@ const TaskTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit]); }, [page, limit, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await listContest(limit, page - 1, ""); const res = await listContest(search, limit, page - 1);
const data = res.data?.data; const data = res.data?.data;
const contentData = data?.content; const contentData = data?.content;
contentData.forEach((item: any, index: number) => { contentData.forEach((item: any, index: number) => {
@ -127,6 +116,11 @@ const TaskTable = () => {
} }
} }
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex justify-between items-center px-5">
@ -139,6 +133,8 @@ const TaskTable = () => {
type="text" type="text"
placeholder="Search Judul..." placeholder="Search Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white" className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/> />
</InputGroup> </InputGroup>
</div> </div>

View File

@ -1,78 +0,0 @@
"use client";
import { Link } from "@/components/navigation";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getListContent } from "@/service/landing/landing";
import { formatDateToIndonesian, generateLocalizedPath } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useParams, usePathname, useRouter } from "next/navigation";
import React, { Component, useEffect, useState } from "react";
const VideoSliderPage = () => {
const [newContent, setNewContent] = useState<any>();
const [selectedTab, setSelectedTab] = useState("video");
const router = useRouter();
const pathname = usePathname();
const params = useParams();
const locale = params?.locale;
const type = "popular";
useEffect(() => {
initFetch();
});
const initFetch = async () => {
const request = {
sortBy: type == "popular" ? "clickCount" : "createdAt",
contentTypeId: "2",
};
const response = await getListContent(request);
console.log("category", response);
setNewContent(response?.data?.data?.content);
};
return (
<div className="mx-3 px-5">
<Carousel className="w-full max-w-7xl mx-auto ">
<CarouselContent>
{newContent?.map((video: any) => (
<CarouselItem
key={video?.id}
className="md:basis-1/2 lg:basis-1/3 "
>
<Link
href={generateLocalizedPath(
`/video/detail/${video?.id}`,
String(locale)
)}
className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"
>
<img
src={video?.thumbnailLink}
className="w-full h-32 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300 rounded-md"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-r from-black to-slate-500 text-white p-2">
<h1 className="text-sm font-semibold truncate">
{video?.title}
</h1>
<p className="flex flex-row items-center text-sm gap-2">
{formatDateToIndonesian(new Date(video?.createdAt))}{" "}
{video?.timezone ? video?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{video.clickCount}{" "}
</p>
</div>
</Link>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);
};
export default VideoSliderPage;

View File

@ -1,89 +0,0 @@
"use client";
import { Link } from "@/components/navigation";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
generateLocalizedPath,
textEllipsis,
} from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useParams, usePathname, useRouter } from "next/navigation";
import React, { Component, useEffect, useState } from "react";
const AudioSliderPage = () => {
const [newContent, setNewContent] = useState<any>();
const [selectedTab, setSelectedTab] = useState("video");
const router = useRouter();
const pathname = usePathname();
const params = useParams();
const locale = params?.locale;
const type = "popular";
useEffect(() => {
initFetch();
});
const initFetch = async () => {
const request = {
sortBy: type == "popular" ? "clickCount" : "createdAt",
contentTypeId: "4",
};
const response = await getListContent(request);
console.log("category", response);
setNewContent(response?.data?.data?.content);
};
return (
<div className="mx-3 px-5">
<Carousel>
<CarouselContent>
{newContent?.map((audio: any) => (
<CarouselItem key={audio?.id} className="md:basis-1/2 lg:basis-1/3">
<div className="flex flex-row gap-6">
<a
href="#"
className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
>
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16">
<svg
width="32"
height="34"
viewBox="0 0 32 34"
fill="null"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
fill="white"
/>
</svg>
</div>
<div className="flex flex-col flex-1">
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
{formatDateToIndonesian(new Date(audio?.createdAt))}{" "}
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{audio?.clickCount}{" "}
</div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
{textEllipsis(audio.title, 50)}
</div>
</div>
</a>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);
};
export default AudioSliderPage;

View File

@ -0,0 +1,128 @@
"use client";
import { Link } from "@/components/navigation";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
generateLocalizedPath,
textEllipsis,
} from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useParams, usePathname, useRouter } from "next/navigation";
import React, { Component, useEffect, useState } from "react";
const AudioSliderPage = () => {
const [audioData, setAudioData] = useState<any>();
const [displayAudio, setDisplayAudio] = useState<any[]>([]);
const [page, setPage] = useState(1);
useEffect(() => {
initFetch();
}, []);
useEffect(() => {
if (audioData?.length > 0) {
shuffleAndSetVideos();
const interval = setInterval(shuffleAndSetVideos, 5000);
return () => clearInterval(interval); // Cleanup interval on unmount
}
}, [audioData]);
const initFetch = async () => {
const response = await getListContent({
page: page - 1,
size: 12,
sortBy: "createdAt",
contentTypeId: "4",
});
console.log(response);
setAudioData(response?.data?.data?.content);
};
const shuffleAndSetVideos = () => {
const shuffled = shuffleArray([...audioData]);
setDisplayAudio(shuffled.slice(0, 3));
};
const shuffleArray = (array: any[]) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
return (
<div className="mx-3 px-5">
<div className=" grid grid-cols-1 gap-6 ">
{displayAudio?.map((audio: any) => (
<Link
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
key={audio?.id}
className="flex flex-col sm:flex-row items-center hover:scale-110 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
>
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-16">
<svg
width="32"
height="34"
viewBox="0 0 32 34"
fill="null"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
fill="white"
/>
</svg>
</div>
<div className="flex flex-col flex-1">
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
{formatDateToIndonesian(new Date(audio?.createdAt))}{" "}
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" /> 518
</div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
{audio?.title}
</div>
</div>
<div className="flex items-center justify-center gap-3">
<div className="mt-2">
<img src="/assets/wave.svg" className="w-80" />
</div>
<div className="flex flex-row items-center justify-center text-gray-500 dark:text-gray-400">
<img
src="/assets/audio-icon.png"
alt="#"
className="flex items-center justify-center"
/>
<div className="flex mx-2 items-center justify-center">
{audio?.duration}
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
>
<path
fill="#f00"
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
/>
</svg>
</div>
</div>
</Link>
))}
</div>
</div>
);
};
export default AudioSliderPage;

View File

@ -0,0 +1,549 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import Cookies from "js-cookie";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
categoryName: string;
};
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailAudio() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [selectedTarget, setSelectedTarget] = useState("");
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const addReply = (commentId: number) => {
if (replyText.trim()) {
const newCommentData = commentsData.map((comment: any) => {
if (comment.id === commentId) {
return {
...comment,
replies: [
...comment.replies,
{
text: replyText,
username: "You",
date: new Date().toLocaleString(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response.data?.data;
setDetail(details);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrls);
}
}
initState();
}, [id, refresh]);
return (
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</div>
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Tag</Label>
<p className="border rounded-md text-blue-600 px-2 py-2">
{detail?.tags}
</p>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
<div className="flex flex-row items-center gap-2 text-blue-500">
<InboxIcon />
<p className="text-blue-500">Kotak Saran (0)</p>
</div>
</div>
<Button>Content Rewrite</Button>
</div>
</div>
</div>
</CardContent>
<CardContent className="border rounded-md mt-5">
<div className="flex flex-row gap-5">
<div className="w-6/12 border px-3 mt-3 rounded-md">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<div className="w-full">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<audio
className="w-full"
src={data}
controls
title={`Audio ${data.id}`}
/>
</SwiperSlide>
))}
</Swiper>
<div className="mt-2">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-700">
Audio {data.id}
</span>
<audio
className="h-[24px] w-[80px]"
src={data}
controls
title={`Audio ${data.id}`}
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
</div>
<div className="w-6/12 border px-3 rounded-md mt-3">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
<div className="flex flex-row justify-between items-center">
<p>file</p>
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
className="flex items-center gap-3 mt-2"
>
{/* <img
className="object-cover w-20 h-20"
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
alt={`Article ${data.id}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</CardContent>
<CardContent>
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">Berikan Komentar</Label>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage
src="/images/avatar/avatar-1.png"
alt="@shadcn"
/>
</Avatar>
<textarea
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
placeholder="Tuliskan komentar Anda di sini.."
></textarea>
</div>
<div className="flex items-center mt-2 gap-3">
<button className="flex items-center text-gray-600 hover:text-gray-800">
<PaperclipIcon className="w-5 h-5" />
Lampirkan
</button>
<button className="flex items-center text-gray-600 hover:text-gray-800">
<SmileIcon className="w-5 h-5" />
Emoticon
</button>
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Kirim
</button>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Komentar</Label>
{commentsData.map((comment) => (
<div
key={comment.id}
className="flex items-start gap-3 mt-2"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt={`@${reply.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{reply.username}
</span>
<span className="text-gray-500 text-sm">
{reply.date}
</span>
<p className="text-gray-800 mt-1">
{reply.text}
</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
);
}

View File

@ -0,0 +1,518 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import Cookies from "js-cookie";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import JoditEditor from "jodit-react";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
categoryName: string;
};
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailDocument() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [selectedTarget, setSelectedTarget] = useState("");
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const addReply = (commentId: number) => {
if (replyText.trim()) {
const newCommentData = commentsData.map((comment: any) => {
if (comment.id === commentId) {
return {
...comment,
replies: [
...comment.replies,
{
text: replyText,
username: "You",
date: new Date().toLocaleString(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response.data?.data;
setDetail(details);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrls);
}
}
initState();
}, [id, refresh]);
return (
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</div>
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Tag</Label>
<p className="border rounded-md text-blue-600 px-2 py-2">
{detail?.tags}
</p>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
<div className="flex flex-row items-center gap-2 text-blue-500">
<InboxIcon />
<p className="text-blue-500">Kotak Saran (0)</p>
</div>
</div>
<Button>Content Rewrite</Button>
</div>
</div>
</div>
</CardContent>
<CardContent className="border rounded-md mt-5">
<div className="flex flex-row gap-5">
<div className="w-6/12 border px-3 mt-3 rounded-md">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<div className="w-full ">
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
</div>
</div>
</div>
</div>
<div className="w-6/12 border px-3 rounded-md mt-3">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
<div className="flex flex-row justify-between items-center">
<p>file</p>
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
className="flex items-center gap-3 mt-2"
>
{/* <img
className="object-cover w-20 h-20"
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
alt={` ${data.id}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</CardContent>
<CardContent>
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">Berikan Komentar</Label>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage
src="/images/avatar/avatar-1.png"
alt="@shadcn"
/>
</Avatar>
<textarea
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
placeholder="Tuliskan komentar Anda di sini.."
></textarea>
</div>
<div className="flex items-center mt-2 gap-3">
<button className="flex items-center text-gray-600 hover:text-gray-800">
<PaperclipIcon className="w-5 h-5" />
Lampirkan
</button>
<button className="flex items-center text-gray-600 hover:text-gray-800">
<SmileIcon className="w-5 h-5" />
Emoticon
</button>
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Kirim
</button>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Komentar</Label>
{commentsData.map((comment) => (
<div
key={comment.id}
className="flex items-start gap-3 mt-2"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt={`@${reply.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{reply.username}
</span>
<span className="text-gray-500 text-sm">
{reply.date}
</span>
<p className="text-gray-800 mt-1">
{reply.text}
</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
);
}

View File

@ -0,0 +1,115 @@
"use client";
import { Link } from "@/components/navigation";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
generateLocalizedPath,
textEllipsis,
} from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useParams, usePathname, useRouter } from "next/navigation";
import React, { Component, useEffect, useState } from "react";
const TeksSliderPage = () => {
const [documentData, setDocumentData] = useState<any>();
const [displayDocument, setDisplayDocument] = useState<any[]>([]);
const [page, setPage] = useState(1);
useEffect(() => {
initFetch();
}, []);
useEffect(() => {
if (documentData?.length > 0) {
shuffleAndSetVideos();
const interval = setInterval(shuffleAndSetVideos, 5000);
return () => clearInterval(interval); // Cleanup interval on unmount
}
}, [documentData]);
const initFetch = async () => {
const response = await getListContent({
page: page - 1,
size: 12,
sortBy: "createdAt",
contentTypeId: "3",
});
console.log(response);
setDocumentData(response?.data?.data?.content);
};
const shuffleAndSetVideos = () => {
const shuffled = shuffleArray([...documentData]);
setDisplayDocument(shuffled.slice(0, 2));
};
const shuffleArray = (array: any[]) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
return (
<div className="mx-3 px-5">
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
{displayDocument?.map((document: any) => (
<Link
href={`/shared/curated-content/giat-routine/document/detail/${document.id}`}
key={document?.id}
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
>
<div className="flex items-center justify-center rounded-lg w-16 h-16">
<svg
width="28"
height="34"
viewBox="0 0 28 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z"
fill="black"
/>
</svg>
</div>
<div className="flex flex-col flex-1">
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
{formatDateToIndonesian(new Date(document?.createdAt))}{" "}
{document?.timezone ? document?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" /> 518
</div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
{document?.title}
</div>
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 512 512"
>
<path
fill="#f00"
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
/>
</svg>
Download Dokumen
</div>
</div>
</Link>
))}
</div>
</div>
);
};
export default TeksSliderPage;

View File

@ -1,79 +0,0 @@
"use client";
import { Link } from "@/components/navigation";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
generateLocalizedPath,
textEllipsis,
} from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useParams, usePathname, useRouter } from "next/navigation";
import React, { Component, useEffect, useState } from "react";
const ImageSliderPage = () => {
const [newContent, setNewContent] = useState<any>();
const [selectedTab, setSelectedTab] = useState("video");
const router = useRouter();
const pathname = usePathname();
const params = useParams();
const locale = params?.locale;
const type = "popular";
useEffect(() => {
initFetch();
});
const initFetch = async () => {
const request = {
sortBy: type == "popular" ? "clickCount" : "createdAt",
contentTypeId: "3",
};
const response = await getListContent(request);
console.log("category", response);
setNewContent(response?.data?.data?.content);
};
return (
<div className="mx-3 px-5">
<Carousel>
<CarouselContent>
{newContent?.map((image: any) => (
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
<Link
href={generateLocalizedPath(
`/image/detail/${image?.id}`,
String(locale)
)}
className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"
>
<img
src={image?.thumbnailLink}
className="w-full h-32 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300 rounded-md"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-r from-black to-slate-500 text-white p-2">
<h1 className="text-sm font-semibold truncate">
{image?.title}
</h1>
<p className="flex flex-row items-center text-sm gap-2">
{formatDateToIndonesian(new Date(image?.createdAt))}{" "}
{image?.timezone ? image?.timezone : "WIB"}|{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{image?.clickCount}{" "}
</p>
</div>
</Link>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);
};
export default ImageSliderPage;

View File

@ -0,0 +1,543 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import Cookies from "js-cookie";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
categoryName: string;
};
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailImage() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [selectedTarget, setSelectedTarget] = useState("");
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const addReply = (commentId: number) => {
if (replyText.trim()) {
const newCommentData = commentsData.map((comment: any) => {
if (comment.id === commentId) {
return {
...comment,
replies: [
...comment.replies,
{
text: replyText,
username: "You",
date: new Date().toLocaleString(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response.data?.data;
setDetail(details);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrls);
}
}
initState();
}, [id, refresh]);
return (
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</div>
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Tag</Label>
<p className="border rounded-md text-blue-600 px-2 py-2">
{detail?.tags}
</p>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
<div className="flex flex-row items-center gap-2 text-blue-500">
<InboxIcon />
<p className="text-blue-500">Kotak Saran (0)</p>
</div>
</div>
<Button>Content Rewrite</Button>
</div>
</div>
</div>
</CardContent>
<CardContent className="border rounded-md mt-5">
<div className="flex flex-row gap-5">
<div className="w-6/12 border px-3 mt-3 rounded-md">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<div className="w-full ">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-fill h-full w-full"
src={data}
alt={` ${data.id}`}
/>
</SwiperSlide>
))}
</Swiper>
<div className=" mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
// className="mySwiper2"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-cover h-[60px] w-[80px]"
src={data}
alt={` ${data.id}`}
/>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
</div>
<div className="w-6/12 border px-3 rounded-md mt-3">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
<div className="flex flex-row justify-between items-center">
<p>file</p>
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
className="flex items-center gap-3 mt-2"
>
{/* <img
className="object-cover w-20 h-20"
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
alt={`Article ${data.id}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</CardContent>
<CardContent>
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">Berikan Komentar</Label>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage
src="/images/avatar/avatar-1.png"
alt="@shadcn"
/>
</Avatar>
<textarea
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
placeholder="Tuliskan komentar Anda di sini.."
></textarea>
</div>
<div className="flex items-center mt-2 gap-3">
<button className="flex items-center text-gray-600 hover:text-gray-800">
<PaperclipIcon className="w-5 h-5" />
Lampirkan
</button>
<button className="flex items-center text-gray-600 hover:text-gray-800">
<SmileIcon className="w-5 h-5" />
Emoticon
</button>
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Kirim
</button>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Komentar</Label>
{commentsData.map((comment) => (
<div
key={comment.id}
className="flex items-start gap-3 mt-2"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt={`@${reply.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{reply.username}
</span>
<span className="text-gray-500 text-sm">
{reply.date}
</span>
<p className="text-gray-800 mt-1">
{reply.text}
</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
);
}

View File

@ -0,0 +1,122 @@
"use client";
import { Link } from "@/components/navigation";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
generateLocalizedPath,
textEllipsis,
} from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import {
SortingState,
ColumnFiltersState,
VisibilityState,
PaginationState,
} from "@tanstack/react-table";
import {
useParams,
usePathname,
useRouter,
useSearchParams,
} from "next/navigation";
import React, { Component, useEffect, useState } from "react";
const ImageSliderPage = () => {
const router = useRouter();
const searchParams = useSearchParams();
const [imageData, setImageData] = useState<any>();
const [displayImage, setDisplayImage] = useState<any[]>([]);
const [page, setPage] = useState(1);
useEffect(() => {
fetchData();
}, [page]);
useEffect(() => {
if (imageData?.length > 0) {
shuffleAndSetVideos();
const interval = setInterval(shuffleAndSetVideos, 5000);
return () => clearInterval(interval); // Cleanup interval on unmount
}
}, [imageData]);
const fetchData = async () => {
const response = await getListContent({
page: page - 1,
size: 6,
sortBy: "createdAt",
contentTypeId: "1",
});
console.log(response);
const data = response.data?.data;
const contentData = data?.content;
setImageData(contentData);
};
const shuffleAndSetVideos = () => {
const shuffled = shuffleArray([...imageData]);
setDisplayImage(shuffled.slice(0, 3));
};
const shuffleArray = (array: any[]) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
return (
<div className="mx-3 px-5">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{displayImage?.map((image: any) => (
<Card
key={image?.id}
className="hover:sc ale-110 transition-transform duration-300"
>
<Link
href={`/shared/curated-content//giat-routine/image/detail/${image.id}`}
>
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
<img
src={image?.thumbnailLink}
className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg"
/>
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
{formatDateToIndonesian(new Date(image?.createdAt))}{" "}
{image?.timezone ? image?.timezone : "WIB"}|{" "}
<Icon icon="formkit:eye" width="15" height="15" />
{image?.clickCount}{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 20 20"
>
<path
fill="#f00"
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
/>
</svg>{" "}
</div>
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
{image?.title}
</div>
</CardContent>
</Link>
</Card>
))}
</div>
</div>
);
};
export default ImageSliderPage;

View File

@ -1,103 +0,0 @@
"use client";
import { Link } from "@/components/navigation";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
generateLocalizedPath,
textEllipsis,
} from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import { useParams, usePathname, useRouter } from "next/navigation";
import React, { Component, useEffect, useState } from "react";
const TeksSliderPage = () => {
const [newContent, setNewContent] = useState<any>();
const [selectedTab, setSelectedTab] = useState("video");
const router = useRouter();
const pathname = usePathname();
const params = useParams();
const locale = params?.locale;
const type = "popular";
useEffect(() => {
initFetch();
});
const initFetch = async () => {
const request = {
sortBy: type == "popular" ? "clickCount" : "createdAt",
contentTypeId: "1",
};
const response = await getListContent(request);
console.log("category", response);
setNewContent(response?.data?.data?.content);
};
return (
<div className="mx-3 px-5">
<Carousel>
<CarouselContent>
{newContent?.map((text: any) => (
<CarouselItem key={text?.id} className="md:basis-1/2 lg:basis-1/3">
<div className="md:basis-1/2 lg:basis-1/3">
<a
href="#"
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full h-[100px]"
>
<div className="flex items-center justify-center rounded-lg w-16 h-2 lg:h-16">
<svg
width="28"
height="34"
viewBox="0 0 28 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z"
fill="black"
/>
</svg>
</div>
<div className="flex flex-col flex-1">
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
{formatDateToIndonesian(new Date(text?.createdAt))}{" "}
{text?.timezone ? text?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{text?.clickCount}{" "}
</div>
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
{textEllipsis(text?.title, 50)}
</div>
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 512 512"
>
<path
fill="#f00"
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
/>
</svg>
Download Dokumen
</div>
</div>
</a>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);
};
export default TeksSliderPage;

View File

@ -0,0 +1,95 @@
"use client";
import { Link } from "@/components/navigation";
import { Card, CardContent } from "@/components/ui/card";
import { getListContent } from "@/service/landing/landing";
import { formatDateToIndonesian } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import image from "next/image";
import React, { useEffect, useState } from "react";
const VideoSliderPage = () => {
const [allVideoData, setAllVideoData] = useState<any[]>([]);
const [displayVideos, setDisplayVideos] = useState<any[]>([]);
const [page, setPage] = useState(1);
useEffect(() => {
initFetch();
}, []);
useEffect(() => {
if (allVideoData?.length > 0) {
shuffleAndSetVideos();
const interval = setInterval(shuffleAndSetVideos, 5000);
return () => clearInterval(interval); // Cleanup interval on unmount
}
}, [allVideoData]);
const initFetch = async () => {
const response = await getListContent({
page: page - 1,
size: 12,
sortBy: "createdAt",
contentTypeId: "2",
});
setAllVideoData(response?.data?.data?.content || []);
};
const shuffleAndSetVideos = () => {
const shuffled = shuffleArray([...allVideoData]);
setDisplayVideos(shuffled.slice(0, 3));
};
const shuffleArray = (array: any[]) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
return (
<div className="mx-3 px-5">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{displayVideos.map((video: any) => (
<Card
key={video?.id}
className="hover:scale-110 transition-transform duration-300"
>
<Link
href={`/shared/curated-content/giat-routine/video/detail/${video.id}`}
>
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
<img
src={video?.thumbnailLink}
className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg"
/>
<div className="flex flex-row items-center gap-2 text-[10px] mx-2">
{formatDateToIndonesian(new Date(video?.createdAt))}{" "}
{video?.timezone || "WIB"} |
<Icon icon="formkit:eye" width="15" height="15" />
{video?.clickCount}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 20 20"
>
<path
fill="#f00"
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
/>
</svg>
</div>
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
{video?.title}
</div>
</CardContent>
</Link>
</Card>
))}
</div>
</div>
);
};
export default VideoSliderPage;

View File

@ -0,0 +1,551 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import Cookies from "js-cookie";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
categoryName: string;
};
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailImage() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [selectedTarget, setSelectedTarget] = useState("");
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailVideo, setDetailVideo] = useState<any>([]);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const addReply = (commentId: number) => {
if (replyText.trim()) {
const newCommentData = commentsData.map((comment: any) => {
if (comment.id === commentId) {
return {
...comment,
replies: [
...comment.replies,
{
text: replyText,
username: "You",
date: new Date().toLocaleString(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response.data?.data;
setDetail(details);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { url: string }) =>
file.url ? file.url : "default-image.jpg"
);
setDetailVideo(fileUrls);
const filesDataThumbnail = details.files || [];
const fileUrlsThumbnail = filesDataThumbnail.map(
(file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrlsThumbnail);
}
}
initState();
}, [id, refresh]);
return (
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</div>
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Tag</Label>
<p className="border rounded-md text-blue-600 px-2 py-2">
{detail?.tags}
</p>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
<div className="flex flex-row items-center gap-2 text-blue-500">
<InboxIcon />
<p className="text-blue-500">Kotak Saran (0)</p>
</div>
</div>
<Button>Content Rewrite</Button>
</div>
</div>
</div>
</CardContent>
<CardContent className="border rounded-md mt-5">
<div className="flex flex-row gap-5">
<div className="w-6/12 border px-3 mt-3 rounded-md">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<div className="w-full">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<video
className="object-fill h-full w-full"
src={data}
controls
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
</SwiperSlide>
))}
</Swiper>
<div className="mt-2">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<video
className="object-cover h-[60px] w-[80px]"
src={data}
muted
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
</div>
<div className="w-6/12 border px-3 rounded-md mt-3">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
<div className="flex flex-row justify-between items-center">
<p>file</p>
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
className="flex items-center gap-3 mt-2"
>
{/* <img
className="object-cover w-20 h-20"
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
alt={` ${data.id}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</CardContent>
<CardContent>
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">Berikan Komentar</Label>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage
src="/images/avatar/avatar-1.png"
alt="@shadcn"
/>
</Avatar>
<textarea
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
placeholder="Tuliskan komentar Anda di sini.."
></textarea>
</div>
<div className="flex items-center mt-2 gap-3">
<button className="flex items-center text-gray-600 hover:text-gray-800">
<PaperclipIcon className="w-5 h-5" />
Lampirkan
</button>
<button className="flex items-center text-gray-600 hover:text-gray-800">
<SmileIcon className="w-5 h-5" />
Emoticon
</button>
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Kirim
</button>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Komentar</Label>
{commentsData.map((comment) => (
<div
key={comment.id}
className="flex items-start gap-3 mt-2"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt={`@${reply.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{reply.username}
</span>
<span className="text-gray-500 text-sm">
{reply.date}
</span>
<p className="text-gray-800 mt-1">
{reply.text}
</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
);
}

View File

@ -19,11 +19,11 @@ import newContent from "@/components/landing-page/new-content";
import { locale } from "dayjs"; import { locale } from "dayjs";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getListContent } from "@/service/landing/landing"; import { getListContent } from "@/service/landing/landing";
import GiatRoutine from "./giat-routine/audio-visual"; import GiatRoutine from "./giat-routine/video/audio-visual";
import VideoSliderPage from "./giat-routine/audio-visual"; import VideoSliderPage from "./giat-routine/video/audio-visual";
import AudioSliderPage from "./giat-routine/audio"; import AudioSliderPage from "./giat-routine/audio/audio";
import ImageSliderPage from "./giat-routine/image"; import ImageSliderPage from "./giat-routine/image/image";
import TeksSliderPage from "./giat-routine/teks"; import TeksSliderPage from "./giat-routine/document/teks";
import ContestTable from "../contest/components/contest-table"; import ContestTable from "../contest/components/contest-table";
const CuratedContentPage = () => { const CuratedContentPage = () => {

View File

@ -0,0 +1,422 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import { createTask } from "@/config/api";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
} from "@/service/content/content";
import { postBlog } from "@/service/blog/blog";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
slug: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
name: string;
};
const initialCategories: Category[] = [
{
id: "1",
name: "Giat Polri",
},
{
id: "2",
name: "Giat Pimpinan",
},
{
id: "3",
name: "Liputan Kegiatan",
},
{
id: "4",
name: "Seputar Prestasi",
},
];
export default function FormBlog() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [categories] = useState<Category[]>(initialCategories); // State untuk kategori
const [selectedTarget, setSelectedTarget] = useState("");
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [isDraft, setIsDraft] = useState(false);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
let fileTypeId = "1";
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
});
// const handleKeyDown = (e: any) => {
// const newTag = e.target.value.trim(); // Ambil nilai input
// if (e.key === "Enter" && newTag) {
// e.preventDefault(); // Hentikan submit form
// if (!tags.includes(newTag)) {
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
// setValue("tags", ""); // Kosongkan input
// }
// }
// };
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const files = Array.from(event.target.files);
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
console.log("DATAFILE::", selectedFiles);
}
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
// useEffect(() => {
// async function initState() {
// getCategories();
// // setVideoActive(fileTypeId == '2');
// // getRoles();
// }
// initState();
// }, []);
// const getCategories = async () => {
// try {
// const category = await listEnableCategory(fileTypeId);
// const resCategory: Category[] = category.data.data.content;
// setCategories(resCategory);
// console.log("data category", resCategory);
// if (scheduleId && scheduleType === "3") {
// const findCategory = resCategory.find((o) =>
// o.name.toLowerCase().includes("pers rilis")
// );
// if (findCategory) {
// // setValue("categoryId", findCategory.id);
// setSelectedCategory(findCategory.id); // Set the selected category
// const response = await getTagsBySubCategoryId(findCategory.id);
// setTags(response?.data.data);
// }
// }
// } catch (error) {
// console.error("Failed to fetch categories:", error);
// }
// };
const save = async (data: TaskSchema) => {
const requestData = {
...data,
title: data.title,
description: data.description,
categoryId: selectedTarget,
slug: data.slug,
metadata: data.meta,
// tags: data.tags,
isDraft,
};
const response = await postBlog(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/blog");
});
};
const onSubmit = (data: TaskSchema) => {
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 handlePublish = () => {
setIsDraft(false);
};
const handleSave = () => {
setIsDraft(true);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Indeks</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="py-3">
<Label>Deskripsi</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Slug</Label>
<Controller
control={control}
name="slug"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Slug"
/>
)}
/>
{errors.slug?.message && (
<p className="text-red-400 text-sm">
{errors.slug.message}
</p>
)}
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Meta</Label>
<Controller
control={control}
name="meta"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Meta"
/>
)}
/>
{errors.meta?.message && (
<p className="text-red-400 text-sm">
{errors.meta.message}
</p>
)}
</div>
</div>
</div>
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[500px]">
<div className="px-3 py-3">
<Label htmlFor="fileInput">Gambar Utama</Label>
<Input
id="fileInput"
type="file"
// onChange={(e) => {
// const file = e.target.files[0];
// if (file) {
// console.log("Selected File:", file);
// // Tambahkan logika jika diperlukan, misalnya upload file ke server
// }
// }}
className=""
/>
</div>
<div className="px-3 py-3">
<label
htmlFor="kategori"
className="block text-sm font-medium text-gray-700"
>
Kategori
</label>
<Select
value={selectedTarget}
onValueChange={(value) => {
console.log("Selected Category ID:", value);
setSelectedTarget(value);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category.id} value={category.id}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="px-3 py-3">
<Label>Tags</Label>
{/* <Controller
control={control}
name="tags"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
onKeyDown={handleKeyDown}
/>
)}
/> */}
<div className="text-sm text-red-500">
{tags.length === 0 && "Please add at least one tag."}
</div>
<div className="flex flex-wrap gap-2 border border-gray-300 mt-2 rounded-md p-2 items-center">
{tags.map((tag, index) => (
<div
key={index}
className="flex items-center gap-1 bg-blue-100 text-blue-800 rounded-full px-3 py-1 text-sm font-medium"
>
<span>{tag}</span>
<button
className="text-blue-600 hover:text-blue-800 focus:outline-none"
onClick={() => handleRemoveTag(index)}
>
×
</button>
</div>
))}
</div>
</div>
</Card>
<div className="flex flex-row justify-end gap-3">
<div className="mt-4">
<Button
type="submit"
color="success"
onClick={() => handlePublish()}
>
Publish
</Button>
</div>
<div className="mt-4">
<Button
type="submit"
color="primary"
onClick={() => handleSave()}
>
Submit
</Button>
</div>
<div className="mt-4">
<Button type="submit" color="primary" variant="outline">
Cancel
</Button>
</div>
</div>
</div>
</div>
</form>
);
}

View File

@ -0,0 +1,352 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { createTask } from "@/service/task";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { id } from "date-fns/locale";
import { getPlanningById } from "@/service/planning/planning";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
});
export type mediahubDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignmentType: {
id: number;
name: string;
};
date: string;
description: string;
is_active: string;
};
export default function PublishMediahub() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
const [taskOutput, setTaskOutput] = useState({
all: false,
video: false,
audio: false,
image: false,
text: false,
});
const [mainType, setMainType] = useState<number>(1);
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all");
const [startDate, setStartDate] = useState<Date>(new Date());
const [detail, setDetail] = useState<mediahubDetail>();
const [refresh] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
const {
control,
handleSubmit,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
});
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = Number(event.target.value);
setMainType(selectedValue);
setPlatformTypeVisible(selectedValue === 2);
};
useEffect(() => {
async function initState() {
if (id) {
const response = await getPlanningById(id);
const details = response.data?.data;
setDetail(details);
if (details?.date) {
setStartDate(new Date(details.date)); // Konversi string tanggal ke objek Date
}
}
}
initState();
}, [id, refresh]);
useEffect(() => {
if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
setTaskOutput({
all: outputSet.has(0),
video: outputSet.has(2),
audio: outputSet.has(4),
image: outputSet.has(1),
text: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
useEffect(() => {
if (detail?.assignedToTopLevel) {
const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number)
); // Membagi string ke dalam array dan mengonversi ke nomor
setUnitSelection({
allUnit: outputSet.has(0),
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "3",
image: "4",
text: "5",
};
const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
.join(",");
const requestData = {
...data,
// assignmentType,
// assignmentCategory,
target: selectedTarget,
unitSelection,
assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentPurpose: "1",
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
id: null,
narration: data.naration,
platformType: "",
title: data.title,
};
const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/task");
});
};
const onSubmit = (data: TaskSchema) => {
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);
}
});
};
return (
<Card>
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p>
{detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="mt-5">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3 mt-1">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="flex flex-row items-center">
<div className="mt-5">
<Label>Pelaksana Tugas</Label>
<div className="flex flex-wrap mt-1 gap-2">
{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) =>
setUnitSelection({ ...unitSelection, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
</div>
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="publication">Publikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="amplification">Amplifikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="contra">Kontra</Label>
</div>
</RadioGroup>
</div>
<div className="mt-5">
<div className="flex flex-col">
<Label>Date</Label>
<div>
<Button
value={detail.date}
variant="outline"
size="md"
className={cn(
" justify-between text-left font-normal border-default-200 text-default-600 md:px-4 w-3/12",
!startDate && "text-muted-foreground"
)}
>
{startDate ? (
format(startDate, "PP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
<div className="mt-5">
<Label>Narasi Penugasan</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div>
</div>
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
</form>
) : (
""
)}
</div>
</Card>
);
}

View File

@ -0,0 +1,352 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { createTask } from "@/service/task";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { id } from "date-fns/locale";
import { getPlanningById } from "@/service/planning/planning";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
});
export type medsosDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignmentType: {
id: number;
name: string;
};
date: string;
description: string;
is_active: string;
};
export default function PublishMedsos() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
const [taskOutput, setTaskOutput] = useState({
all: false,
video: false,
audio: false,
image: false,
text: false,
});
const [mainType, setMainType] = useState<number>(1);
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all");
const [startDate, setStartDate] = useState<Date>(new Date());
const [detail, setDetail] = useState<medsosDetail>();
const [refresh] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
const {
control,
handleSubmit,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
});
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = Number(event.target.value);
setMainType(selectedValue);
setPlatformTypeVisible(selectedValue === 2);
};
useEffect(() => {
async function initState() {
if (id) {
const response = await getPlanningById(id);
const details = response.data?.data;
setDetail(details);
if (details?.date) {
setStartDate(new Date(details.date)); // Konversi string tanggal ke objek Date
}
}
}
initState();
}, [id, refresh]);
useEffect(() => {
if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
setTaskOutput({
all: outputSet.has(0),
video: outputSet.has(2),
audio: outputSet.has(4),
image: outputSet.has(1),
text: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
useEffect(() => {
if (detail?.assignedToTopLevel) {
const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number)
); // Membagi string ke dalam array dan mengonversi ke nomor
setUnitSelection({
allUnit: outputSet.has(0),
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "3",
image: "4",
text: "5",
};
const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
.join(",");
const requestData = {
...data,
// assignmentType,
// assignmentCategory,
target: selectedTarget,
unitSelection,
assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentPurpose: "1",
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
id: null,
narration: data.naration,
platformType: "",
title: data.title,
};
const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/task");
});
};
const onSubmit = (data: TaskSchema) => {
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);
}
});
};
return (
<Card>
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p>
{detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="mt-5">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3 mt-1">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="flex flex-row items-center">
<div className="mt-5">
<Label>Pelaksana Tugas</Label>
<div className="flex flex-wrap mt-1 gap-2">
{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) =>
setUnitSelection({ ...unitSelection, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
</div>
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="publication">Publikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="amplification">Amplifikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="contra">Kontra</Label>
</div>
</RadioGroup>
</div>
<div className="mt-5">
<div className="flex flex-col">
<Label>Date</Label>
<div>
<Button
value={detail.date}
variant="outline"
size="md"
className={cn(
" justify-between text-left font-normal border-default-200 text-default-600 md:px-4 w-3/12",
!startDate && "text-muted-foreground"
)}
>
{startDate ? (
format(startDate, "PP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
<div className="mt-5">
<Label>Narasi Penugasan</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div>
</div>
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
</form>
) : (
""
)}
</div>
</Card>
);
}

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import React, { useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod"; import * as z from "zod";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import { import {
Select, Select,
SelectContent, SelectContent,
@ -20,7 +20,7 @@ import {
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react"; import JoditEditor from "jodit-react";
import { createTask } from "@/service/task"; import { createTask, getTask } from "@/service/task";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -29,11 +29,32 @@ const taskSchema = z.object({
}), }),
}); });
export type taskDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignmentType: {
id: number;
name: string;
};
assignmentMainType: {
id: number;
name: string;
};
taskType: string;
broadcastType: string;
narration: string;
is_active: string;
};
export default function FormTask() { export default function FormTask() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const editor = useRef(null); const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>; type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
// State for various form fields // State for various form fields
const [taskOutput, setTaskOutput] = useState({ const [taskOutput, setTaskOutput] = useState({
@ -46,9 +67,13 @@ export default function FormTask() {
// const [assignmentType, setAssignmentType] = useState("mediahub"); // const [assignmentType, setAssignmentType] = useState("mediahub");
// const [assignmentCategory, setAssignmentCategory] = useState("publication"); // const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<number>(1); // untuk Tipe Penugasan const [mainType, setMainType] = useState<string>("1");
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1"); const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all"); const [selectedTarget, setSelectedTarget] = useState("all");
const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
@ -66,12 +91,57 @@ export default function FormTask() {
resolver: zodResolver(taskSchema), resolver: zodResolver(taskSchema),
}); });
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => { // const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = Number(event.target.value); // const selectedValue = Number(event.target.value);
setMainType(selectedValue); // setMainType(selectedValue);
setPlatformTypeVisible(selectedValue === 2); // setPlatformTypeVisible(selectedValue === 2);
}; // };
useEffect(() => {
async function initState() {
if (id) {
const response = await getTask(id);
const details = response.data?.data;
setDetail(details);
}
}
initState();
}, [id, refresh]);
useEffect(() => {
if (detail?.broadcastType) {
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
}
}, [detail?.broadcastType]);
useEffect(() => {
if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
setTaskOutput({
all: outputSet.has(0),
video: outputSet.has(2),
audio: outputSet.has(4),
image: outputSet.has(1),
text: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
useEffect(() => {
if (detail?.assignedToTopLevel) {
const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number)
);
setUnitSelection({
allUnit: outputSet.has(0),
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
const fileTypeMapping = { const fileTypeMapping = {
@ -94,6 +164,8 @@ export default function FormTask() {
target: selectedTarget, target: selectedTarget,
unitSelection, unitSelection,
assignedToRole: "3", assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType, assignmentMainTypeId: mainType,
assignmentPurpose: "1", assignmentPurpose: "1",
assignmentTypeId: type, assignmentTypeId: type,
@ -106,11 +178,6 @@ export default function FormTask() {
const response = await createTask(requestData); const response = await createTask(requestData);
// if (response.error) {
// error(response.message);
// return false;
// }
console.log("Form Data Submitted:", requestData); console.log("Form Data Submitted:", requestData);
console.log("response", response); console.log("response", response);
@ -121,7 +188,7 @@ export default function FormTask() {
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then(() => {
router.push("/contributor/task"); router.push("/en/contributor/task");
}); });
}; };
@ -145,144 +212,184 @@ export default function FormTask() {
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Penugasan</p> <p className="text-lg font-semibold mb-3">Form Penugasan</p>
<form onSubmit={handleSubmit(onSubmit)}> {detail !== undefined ? (
<div className="gap-5 mb-5"> <form onSubmit={handleSubmit(onSubmit)}>
{/* Input Title */} <div className="gap-5 mb-5">
<div className="space-y-2"> {/* Input Title */}
<Label>Judul</Label> <div className="space-y-2">
<Controller <Label>Judul</Label>
control={control} <Controller
name="title" control={control}
render={({ field }) => ( name="title"
<Input render={({ field }) => (
size="md" <Input
type="text" size="md"
value={field.value} type="text"
onChange={field.onChange} value={detail?.title}
placeholder="Enter Title" onChange={field.onChange}
/> placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)} )}
/> </div>
{errors.title?.message && ( <div className="flex flex-row items-center">
<p className="text-red-400 text-sm">{errors.title.message}</p> <div className="mt-5">
)} <Label>Tujuan Pemilihan Tugas</Label>
</div> <Select onValueChange={setSelectedTarget}>
<div className="flex flex-row items-center"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Semua Pengguna</SelectItem>
<SelectItem value="contributor">Kontributor</SelectItem>
<SelectItem value="approver">Approver</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap gap-3 mt-5 pt-5 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) =>
setUnitSelection({ ...unitSelection, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="mt-5"> <div className="mt-5">
<Label>Tujuan Pemilihan Tugas</Label> <Label>Tipe Penugasan</Label>
<Select onValueChange={setSelectedTarget}> <RadioGroup
<SelectTrigger size="md"> value={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
<SelectValue placeholder="Pilih" /> onValueChange={(value) => setMainType(value)}
</SelectTrigger> // value={String(mainType)}
<SelectContent> // onValueChange={(value) => setMainType(Number(value))}
<SelectItem value="all">Semua Pengguna</SelectItem> className="flex flex-wrap gap-3"
<SelectItem value="contributor">Kontributor</SelectItem> >
<SelectItem value="approver">Approver</SelectItem> <RadioGroupItem value="1" id="mediahub" />
</SelectContent> <Label htmlFor="mediahub">Mediahub</Label>
</Select> <RadioGroupItem value="2" id="medsos-mediahub" />
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup>
</div> </div>
<div className="flex flex-wrap gap-3 mt-5 pt-5 ml-3"> <div className="mt-5">
{Object.keys(unitSelection).map((key) => ( <Label>Jenis Tugas </Label>
<div className="flex items-center gap-2" key={key}> <RadioGroup
<Checkbox value={detail.taskType.toString()}
id={key} onValueChange={(value) => setTaskType(String(value))}
checked={unitSelection[key as keyof typeof unitSelection]} className="flex flex-wrap gap-3"
onCheckedChange={(value) => >
setUnitSelection({ ...unitSelection, [key]: value }) <RadioGroupItem value="atensi-khusus" id="khusus" />
} <Label htmlFor="atensi-khusus">Atensi Khusus</Label>
/> <RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor={key}> <Label htmlFor="tugas-harian">Tugas Harian</Label>
{key.charAt(0).toUpperCase() + key.slice(1)} </RadioGroup>
</Label> </div>
{/* RadioGroup Assignment Category */}
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="publication">Publikasi</Label>
</div> </div>
))} <div className="flex items-center gap-2">
</div> <RadioGroupItem value="2" id="amplification" />
</div> <Label htmlFor="amplification">Amplifikasi</Label>
<div className="mt-5">
<Label>Tipe Penugasan</Label>
<RadioGroup
value={String(mainType)}
onValueChange={(value) => setMainType(Number(value))}
className="flex flex-wrap gap-6"
>
<RadioGroupItem value="1" id="mediahub" />
<Label htmlFor="mediahub">Mediahub</Label>
<RadioGroupItem value="2" id="medsos-mediahub" />
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup>
</div>
{/* RadioGroup Assignment Category */}
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={type} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-6"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="publication">Publikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="amplification">Amplifikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="contra">Kontra</Label>
</div>
</RadioGroup>
</div>
<div className="mt-5">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div> </div>
))} <div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="contra">Kontra</Label>
</div>
</RadioGroup>
</div> </div>
</div> <div className="mt-5">
<div className="mt-5"> <Label>Output Tugas</Label>
<Label>Narasi Penugasan</Label> <div className="flex flex-wrap gap-3">
<Controller {Object.keys(taskOutput).map((key) => (
control={control} <div className="flex items-center gap-2" key={key}>
name="naration" <Checkbox
render={({ field: { onChange, value } }) => ( id={key}
<JoditEditor checked={taskOutput[key as keyof typeof taskOutput]}
ref={editor} onCheckedChange={(value) =>
value={value} setTaskOutput({ ...taskOutput, [key]: value })
onChange={onChange} }
className="dark:text-black" />
/> <Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="mt-5">
<Label>Broadcast </Label>
<RadioGroup
value={broadcastType} // Nilai terpilih diambil dari state broadcastType
onValueChange={(value) => setBroadcastType(value)} // Mengatur nilai saat radio berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="all" id="all" />
<Label htmlFor="all">Semua</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="email" id="email" />
<Label htmlFor="email">Email Blast</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="whatsapp" id="whatsapp" />
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
</div>
</RadioGroup>
</div>
<div className="mt-5">
<Label>Narasi Penugasan</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.narration}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)} )}
/> </div>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div> </div>
</div>
{/* Submit Button */} {/* Submit Button */}
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit Submit
</Button> </Button>
</div> </div>
</form> </form>
) : (
""
)}
</div> </div>
</Card> </Card>
); );

122
package-lock.json generated
View File

@ -151,6 +151,7 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -1216,6 +1217,7 @@
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": { "dependencies": {
"string-width": "^5.1.2", "string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0", "string-width-cjs": "npm:string-width@^4.2.0",
@ -1232,6 +1234,7 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -1243,6 +1246,7 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
}, },
@ -1773,6 +1777,7 @@
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "2.0.5", "@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9" "run-parallel": "^1.1.9"
@ -1785,6 +1790,7 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
@ -1793,6 +1799,7 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.scandir": "2.1.5", "@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0" "fastq": "^1.6.0"
@ -1822,6 +1829,7 @@
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true, "optional": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@ -3731,7 +3739,7 @@
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
"devOptional": true, "dev": true,
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"
} }
@ -4025,6 +4033,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -4038,6 +4047,7 @@
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": { "dependencies": {
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
}, },
@ -4051,12 +4061,14 @@
"node_modules/any-promise": { "node_modules/any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true
}, },
"node_modules/anymatch": { "node_modules/anymatch": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
@ -4101,7 +4113,8 @@
"node_modules/arg": { "node_modules/arg": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
}, },
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
@ -4392,12 +4405,14 @@
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
}, },
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@ -4419,6 +4434,7 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.1.1" "fill-range": "^7.1.1"
}, },
@ -4478,6 +4494,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -4577,6 +4594,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
"braces": "~3.0.2", "braces": "~3.0.2",
@ -4600,6 +4618,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@ -4784,6 +4803,7 @@
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@ -4807,6 +4827,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": { "bin": {
"cssesc": "bin/cssesc" "cssesc": "bin/cssesc"
}, },
@ -5482,7 +5503,8 @@
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
}, },
"node_modules/diff": { "node_modules/diff": {
"version": "5.2.0", "version": "5.2.0",
@ -5507,7 +5529,8 @@
"node_modules/dlv": { "node_modules/dlv": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
}, },
"node_modules/doctrine": { "node_modules/doctrine": {
"version": "3.0.0", "version": "3.0.0",
@ -5608,7 +5631,8 @@
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
}, },
"node_modules/elkjs": { "node_modules/elkjs": {
"version": "0.9.3", "version": "0.9.3",
@ -5656,7 +5680,8 @@
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.17.1", "version": "5.17.1",
@ -6496,6 +6521,7 @@
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3", "@nodelib/fs.walk": "^1.2.3",
@ -6511,6 +6537,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@ -6534,6 +6561,7 @@
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true,
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
@ -6577,6 +6605,7 @@
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -6667,6 +6696,7 @@
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"dev": true,
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.0", "cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1" "signal-exit": "^4.0.1"
@ -6735,6 +6765,7 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@ -6890,6 +6921,7 @@
"version": "10.3.10", "version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"dev": true,
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^2.3.5", "jackspeak": "^2.3.5",
@ -6911,6 +6943,7 @@
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
}, },
@ -6922,6 +6955,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@ -6930,6 +6964,7 @@
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
@ -7897,6 +7932,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
}, },
@ -8028,6 +8064,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -8051,6 +8088,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -8074,6 +8112,7 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
}, },
@ -8118,6 +8157,7 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@ -8366,6 +8406,7 @@
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"dev": true,
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
}, },
@ -8383,6 +8424,7 @@
"version": "1.21.6", "version": "1.21.6",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"dev": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@ -8646,6 +8688,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -8754,7 +8797,8 @@
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "0.390.0", "version": "0.390.0",
@ -9468,6 +9512,7 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
@ -10255,6 +10300,7 @@
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": { "dependencies": {
"braces": "^3.0.3", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
@ -10315,6 +10361,7 @@
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
@ -10363,6 +10410,7 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"dependencies": { "dependencies": {
"any-promise": "^1.0.0", "any-promise": "^1.0.0",
"object-assign": "^4.0.1", "object-assign": "^4.0.1",
@ -10665,6 +10713,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -10719,6 +10768,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -11017,6 +11067,7 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -11030,6 +11081,7 @@
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@ -11068,6 +11120,7 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@ -11079,6 +11132,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -11087,6 +11141,7 @@
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -11104,6 +11159,7 @@
"version": "8.4.49", "version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -11131,6 +11187,7 @@
"version": "15.1.0", "version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"dependencies": { "dependencies": {
"postcss-value-parser": "^4.0.0", "postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0", "read-cache": "^1.0.0",
@ -11147,6 +11204,7 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"dependencies": { "dependencies": {
"camelcase-css": "^2.0.1" "camelcase-css": "^2.0.1"
}, },
@ -11165,6 +11223,7 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -11199,6 +11258,7 @@
"version": "2.6.1", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
"dev": true,
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
}, },
@ -11210,6 +11270,7 @@
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -11234,6 +11295,7 @@
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
@ -11245,7 +11307,8 @@
"node_modules/postcss-value-parser": { "node_modules/postcss-value-parser": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
}, },
"node_modules/preact": { "node_modules/preact": {
"version": "10.12.1", "version": "10.12.1",
@ -11350,6 +11413,7 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -11886,6 +11950,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"dependencies": { "dependencies": {
"pify": "^2.3.0" "pify": "^2.3.0"
} }
@ -11894,6 +11959,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
}, },
@ -12316,6 +12382,7 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true,
"engines": { "engines": {
"iojs": ">=1.0.0", "iojs": ">=1.0.0",
"node": ">=0.10.0" "node": ">=0.10.0"
@ -12388,6 +12455,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -12579,6 +12647,7 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": { "dependencies": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
}, },
@ -12590,6 +12659,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -12626,6 +12696,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -12755,6 +12826,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": { "dependencies": {
"eastasianwidth": "^0.2.0", "eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2", "emoji-regex": "^9.2.2",
@ -12772,6 +12844,7 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@ -12784,12 +12857,14 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": { "node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
}, },
"node_modules/string-width/node_modules/ansi-regex": { "node_modules/string-width/node_modules/ansi-regex": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -12801,6 +12876,7 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
}, },
@ -12936,6 +13012,7 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
}, },
@ -12948,6 +13025,7 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
}, },
@ -13052,6 +13130,7 @@
"version": "3.35.0", "version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0", "commander": "^4.0.0",
@ -13073,6 +13152,7 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -13246,6 +13326,7 @@
"version": "3.4.16", "version": "3.4.16",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz",
"integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==",
"dev": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@ -13305,6 +13386,7 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"dependencies": { "dependencies": {
"any-promise": "^1.0.0" "any-promise": "^1.0.0"
} }
@ -13313,6 +13395,7 @@
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"dependencies": { "dependencies": {
"thenify": ">= 3.1.0 < 4" "thenify": ">= 3.1.0 < 4"
}, },
@ -13420,6 +13503,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },
@ -13477,7 +13561,8 @@
"node_modules/ts-interface-checker": { "node_modules/ts-interface-checker": {
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
}, },
"node_modules/tsconfig-paths": { "node_modules/tsconfig-paths": {
"version": "3.15.0", "version": "3.15.0",
@ -13955,7 +14040,8 @@
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "9.0.1", "version": "9.0.1",
@ -14194,6 +14280,7 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
}, },
@ -14300,6 +14387,7 @@
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^6.1.0", "ansi-styles": "^6.1.0",
"string-width": "^5.0.1", "string-width": "^5.0.1",
@ -14317,6 +14405,7 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
"string-width": "^4.1.0", "string-width": "^4.1.0",
@ -14332,12 +14421,14 @@
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
}, },
"node_modules/wrap-ansi-cjs/node_modules/string-width": { "node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@ -14351,6 +14442,7 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -14362,6 +14454,7 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -14373,6 +14466,7 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
}, },

View File

@ -1,5 +1,6 @@
import { title } from "process"; import { title } from "process";
import { httpGetInterceptor } from "../http-config/http-interceptor-service"; import { httpGetInterceptor } from "../http-config/http-interceptor-service";
import { postAPIInterceptor } from "@/config/api";
export async function paginationBlog( export async function paginationBlog(
size: number, size: number,
@ -10,3 +11,8 @@ export async function paginationBlog(
`blog/pagination?enablePage=1&page=${page}&size=${size}&title=${title}` `blog/pagination?enablePage=1&page=${page}&size=${size}&title=${title}`
); );
} }
export async function postBlog(data: any) {
const url = "blog";
return postAPIInterceptor(url, data);
}

View File

@ -1,6 +1,47 @@
import { getAPIInterceptor, postAPIInterceptor } from "@/config/api"; import { getAPIInterceptor, postAPIInterceptor } from "@/config/api";
import { httpGetInterceptor } from "../http-config/http-interceptor-service"; import { httpGetInterceptor } from "../http-config/http-interceptor-service";
// export async function listDataAll(
// isForSelf,
// isApproval,
// page,
// limit,
// search,
// fileTypeFilter,
// statusFilter,
// startDate,
// endDate,
// needApprovalFromLevel
// ) {
// const name = search || "";
// const url = `media/list?title=${name}&enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&isForSelf=${isForSelf}&isApproval=${isApproval}&typeId=${fileTypeFilter}&statusId=${statusFilter}&startDate=${
// startDate == undefined ? "" : startDate
// }&endDate=${
// endDate == undefined ? "" : endDate
// }&needApprovalFromLevel=${needApprovalFromLevel}`;
// return getAPIInterceptor({ url });
// }
export async function listDataAll(
isForSelf: any,
isApproval: any,
page: any,
limit: any,
search: any,
fileTypeFilter: any,
statusFilter: any,
needApprovalFromLevel: any,
creator: any,
source: any,
startDate: any,
endDate: any,
title: string = ""
) {
return await getAPIInterceptor(
`media/list?enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&isForSelf=${isForSelf}&isApproval=${isApproval}&typeId=${fileTypeFilter}&statusId=${statusFilter}&needApprovalFromLevel=${needApprovalFromLevel}&title=${title}`
);
}
export async function listDataImage( export async function listDataImage(
limit: any, limit: any,
page: any, page: any,

View File

@ -4,9 +4,9 @@ import {
} from "../http-config/http-interceptor-service"; } from "../http-config/http-interceptor-service";
export async function listContest( export async function listContest(
title: string = "",
size: number, size: number,
page: number, page: number
title: string = ""
) { ) {
return await httpGetInterceptor( return await httpGetInterceptor(
`contest/pagination?enablePage=1&size=${size}&page=${page}&title=${title}` `contest/pagination?enablePage=1&size=${size}&page=${page}&title=${title}`

View File

@ -0,0 +1,6 @@
import { getAPIInterceptor } from "@/config/api";
export async function detailMedia(id: any) {
const url = `media?id=${id}`;
return getAPIInterceptor(url);
}

View File

@ -1,3 +1,4 @@
import { getAPIInterceptor } from "@/config/api";
import { httpGetInterceptor } from "../http-config/http-interceptor-service"; import { httpGetInterceptor } from "../http-config/http-interceptor-service";
export async function getPlanningSentPagination( export async function getPlanningSentPagination(
@ -10,3 +11,8 @@ export async function getPlanningSentPagination(
`planning/pagination/sent?enablePage=1&size=${size}&page=${page}&typeId=${typeId}&title=${title}` `planning/pagination/sent?enablePage=1&size=${size}&page=${page}&typeId=${typeId}&title=${title}`
); );
} }
export async function getPlanningById(id: any) {
const url = `planning?id=${id}`;
return getAPIInterceptor(url);
}

View File

@ -38,7 +38,7 @@ export async function createTask(data: any) {
export async function getTask(id: any) { export async function getTask(id: any) {
const url = `/assignment?id=${id}`; const url = `/assignment?id=${id}`;
return getAPIInterceptor({ url }); return getAPIInterceptor(url);
} }
export async function forwardTask(data: any) { export async function forwardTask(data: any) {