diff --git a/app/[locale]/(protected)/admin/add-experts/create/page.tsx b/app/[locale]/(protected)/admin/add-experts/create/page.tsx index abc66bc2..783be176 100644 --- a/app/[locale]/(protected)/admin/add-experts/create/page.tsx +++ b/app/[locale]/(protected)/admin/add-experts/create/page.tsx @@ -34,6 +34,9 @@ const FormSchema = z.object({ email: z.string({ required_error: "Required", }), + position: z.string({ + required_error: "Required", + }), region: z.string({ required_error: "Required", }), @@ -147,7 +150,28 @@ export default function AddExpertForm() { )} /> + ( + + Posisi + + + + )} + /> [] = [ + { + accessorKey: "no", + header: "No", + cell: ({ row }) => {row.getValue("no")}, + }, + + { + accessorKey: "title", + header: "Nama Media Online", + cell: ({ row }) => {row.getValue("title")}, + }, + { + accessorKey: "link", + header: "Link", + cell: ({ row }) => {row.getValue("categoryName")}, + }, +]; + +export default columns; diff --git a/app/[locale]/(protected)/admin/media-tracking/media-online/component/table.tsx b/app/[locale]/(protected)/admin/media-tracking/media-online/component/table.tsx new file mode 100644 index 00000000..28c87044 --- /dev/null +++ b/app/[locale]/(protected)/admin/media-tracking/media-online/component/table.tsx @@ -0,0 +1,302 @@ +"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, + UserIcon, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + 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 "./column"; +import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { listDataMedia } from "@/service/broadcast/broadcast"; +import { listEnableCategory } from "@/service/content/content"; +import { Checkbox } from "@/components/ui/checkbox"; +import { close, loading } from "@/config/swal"; +import { Link } from "@/i18n/routing"; + +const MediaOnlineTable = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [search, setSearch] = React.useState(""); + const [showData, setShowData] = React.useState("10"); + const [categories, setCategories] = React.useState(); + const [dataTable, setDataTable] = React.useState([]); + const [totalData, setTotalData] = React.useState(1); + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: Number(showData), + }); + const [categoryFilter, setCategoryFilter] = React.useState([]); + const [statusFilter, setStatusFilter] = React.useState([]); + const [page, setPage] = React.useState(1); + const [totalPage, setTotalPage] = React.useState(1); + 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, + }, + }); + + let typingTimer: any; + const doneTypingInterval = 1500; + + // const handleKeyUp = () => { + // clearTimeout(typingTimer); + // typingTimer = setTimeout(doneTyping, doneTypingInterval); + // }; + + const handleKeyDown = () => { + clearTimeout(typingTimer); + }; + + // async function doneTyping() { + // fetchData(); + // } + + React.useEffect(() => { + const pageFromUrl = searchParams?.get("page"); + if (pageFromUrl) { + setPage(Number(pageFromUrl)); + } + }, [searchParams]); + + // React.useEffect(() => { + // fetchData(); + // setPagination({ + // pageIndex: 0, + // pageSize: Number(showData), + // }); + // }, [page, showData]); + + // async function fetchData() { + // try { + // loading(); + // const res = await listDataMedia( + // page - 1, + // showData, + // search, + // categoryFilter?.sort().join(","), + // statusFilter?.sort().join(",") + // ); + // const data = res?.data?.data; + // const contentData = data?.content; + // contentData.forEach((item: any, index: number) => { + // item.no = (page - 1) * Number(showData) + index + 1; + // }); + + // console.log("contentData : ", data); + + // setDataTable(contentData); + // setTotalData(data?.totalElements); + // setTotalPage(data?.totalPages); + // close(); + // } catch (error) { + // console.error("Error fetching tasks:", error); + // } + // } + + React.useEffect(() => { + getCategories(); + }, []); + + async function getCategories() { + const category = await listEnableCategory(""); + const resCategory = category?.data?.data?.content; + setCategories(resCategory); + } + + const handleChange = (type: string, id: number, checked: boolean) => { + if (type === "category") { + if (checked) { + const temp: number[] = [...categoryFilter]; + temp.push(id); + setCategoryFilter(temp); + } else { + const temp = categoryFilter.filter((a) => a !== id); + setCategoryFilter(temp); + } + } else { + if (checked) { + const temp: number[] = [...statusFilter]; + temp.push(id); + setStatusFilter(temp); + } else { + const temp = statusFilter.filter((a) => a !== id); + setStatusFilter(temp); + } + } + }; + + return ( +
+
+

Media Online

+
+ +
+ + + + setSearch(e.target.value)} + className="max-w-[300px]" + /> +
+ + + + + + + + 1 - 10 Data + + + 1 - 20 Data + + + 1 - 25 Data + + + 1 - 50 Data + + + + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+ +
+ ); +}; + +export default MediaOnlineTable; diff --git a/app/[locale]/(protected)/admin/media-tracking/media-online/page.tsx b/app/[locale]/(protected)/admin/media-tracking/media-online/page.tsx new file mode 100644 index 00000000..bd811c30 --- /dev/null +++ b/app/[locale]/(protected)/admin/media-tracking/media-online/page.tsx @@ -0,0 +1,11 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import MediaOnlineTable from "./component/table"; + +export default function AdminMediaOnline() { + return ( +
+ + +
+ ); +} diff --git a/app/[locale]/(protected)/admin/media-tracking/tb-news/component/column.tsx b/app/[locale]/(protected)/admin/media-tracking/tb-news/component/column.tsx new file mode 100644 index 00000000..e963dea3 --- /dev/null +++ b/app/[locale]/(protected)/admin/media-tracking/tb-news/component/column.tsx @@ -0,0 +1,58 @@ +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 { Badge } from "@/components/ui/badge"; +import { + formatDateToIndonesian, + getOnlyDate, + htmlToString, +} from "@/utils/globals"; +import { Link, useRouter } from "@/i18n/routing"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible"; + +const columns: ColumnDef[] = [ + { + accessorKey: "no", + header: "No", + cell: ({ row }) => {row.getValue("no")}, + }, + { + accessorKey: "date", + header: "Tanggal", + cell: ({ row }) => {row.getValue("categoryName")}, + }, + { + accessorKey: "title", + header: "Media Online", + cell: ({ row }) => {row.getValue("title")}, + }, + { + accessorKey: "link", + header: "Link Berita", + cell: ({ row }) => {row.getValue("categoryName")}, + }, +]; + +export default columns; diff --git a/app/[locale]/(protected)/admin/media-tracking/tb-news/component/table.tsx b/app/[locale]/(protected)/admin/media-tracking/tb-news/component/table.tsx new file mode 100644 index 00000000..c2407cd7 --- /dev/null +++ b/app/[locale]/(protected)/admin/media-tracking/tb-news/component/table.tsx @@ -0,0 +1,305 @@ +"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, + UserIcon, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + 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 "./column"; +import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { listDataMedia } from "@/service/broadcast/broadcast"; +import { listEnableCategory } from "@/service/content/content"; +import { Checkbox } from "@/components/ui/checkbox"; +import { close, loading } from "@/config/swal"; +import { Link } from "@/i18n/routing"; +import { Label } from "@/components/ui/label"; + +const NewsTable = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [search, setSearch] = React.useState(""); + const [showData, setShowData] = React.useState("10"); + const [categories, setCategories] = React.useState(); + const [dataTable, setDataTable] = React.useState([]); + const [totalData, setTotalData] = React.useState(1); + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); + const [showTable, setShowTable] = React.useState(false); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: Number(showData), + }); + const [categoryFilter, setCategoryFilter] = React.useState([]); + const [statusFilter, setStatusFilter] = React.useState([]); + const [page, setPage] = React.useState(1); + const [totalPage, setTotalPage] = React.useState(1); + 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, + }, + }); + + let typingTimer: any; + const doneTypingInterval = 1500; + + // const handleKeyUp = () => { + // clearTimeout(typingTimer); + // typingTimer = setTimeout(doneTyping, doneTypingInterval); + // }; + + const handleKeyDown = () => { + clearTimeout(typingTimer); + }; + + // async function doneTyping() { + // fetchData(); + // } + + React.useEffect(() => { + const pageFromUrl = searchParams?.get("page"); + if (pageFromUrl) { + setPage(Number(pageFromUrl)); + } + }, [searchParams]); + + // React.useEffect(() => { + // fetchData(); + // setPagination({ + // pageIndex: 0, + // pageSize: Number(showData), + // }); + // }, [page, showData]); + + // async function fetchData() { + // try { + // loading(); + // const res = await listDataMedia( + // page - 1, + // showData, + // search, + // categoryFilter?.sort().join(","), + // statusFilter?.sort().join(",") + // ); + // const data = res?.data?.data; + // const contentData = data?.content; + // contentData.forEach((item: any, index: number) => { + // item.no = (page - 1) * Number(showData) + index + 1; + // }); + + // console.log("contentData : ", data); + + // setDataTable(contentData); + // setTotalData(data?.totalElements); + // setTotalPage(data?.totalPages); + // close(); + // } catch (error) { + // console.error("Error fetching tasks:", error); + // } + // } + + React.useEffect(() => { + getCategories(); + }, []); + + async function getCategories() { + const category = await listEnableCategory(""); + const resCategory = category?.data?.data?.content; + setCategories(resCategory); + } + + const handleChange = (type: string, id: number, checked: boolean) => { + if (type === "category") { + if (checked) { + const temp: number[] = [...categoryFilter]; + temp.push(id); + setCategoryFilter(temp); + } else { + const temp = categoryFilter.filter((a) => a !== id); + setCategoryFilter(temp); + } + } else { + if (checked) { + const temp: number[] = [...statusFilter]; + temp.push(id); + setStatusFilter(temp); + } else { + const temp = statusFilter.filter((a) => a !== id); + setStatusFilter(temp); + } + } + }; + + return ( +
+
+

+ Tracking Berita hari ini! +

+
+
+ + +

Sisa kuota harian: 30 Artikel

+
+ {!showTable && ( +
+ + +
+ )} + {showTable && ( + <> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+ +
+ +
+ + )} +
+ ); +}; + +export default NewsTable; diff --git a/app/[locale]/(protected)/admin/media-tracking/tb-news/page.tsx b/app/[locale]/(protected)/admin/media-tracking/tb-news/page.tsx new file mode 100644 index 00000000..a8baea3f --- /dev/null +++ b/app/[locale]/(protected)/admin/media-tracking/tb-news/page.tsx @@ -0,0 +1,11 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import NewsTable from "./component/table"; + +export default function AdminNews() { + return ( +
+ + +
+ ); +} diff --git a/app/[locale]/(protected)/contributor/agenda-setting/calender-view.tsx b/app/[locale]/(protected)/contributor/agenda-setting/calender-view.tsx index fdc17169..833a2129 100644 --- a/app/[locale]/(protected)/contributor/agenda-setting/calender-view.tsx +++ b/app/[locale]/(protected)/contributor/agenda-setting/calender-view.tsx @@ -10,7 +10,15 @@ import { Label } from "@/components/ui/label"; import ExternalDraggingevent from "./dragging-events"; import { Calendar } from "@/components/ui/calendar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Book, CheckSquare2, CheckSquare2Icon, Plus } from "lucide-react"; +import { + Book, + CheckCheck, + CheckSquare2, + CheckSquare2Icon, + Plus, + Timer, + TimerIcon, +} from "lucide-react"; import { Checkbox } from "@/components/ui/checkbox"; import { EventContentArg } from "@fullcalendar/core"; import EventModal from "./event-modal"; @@ -46,6 +54,7 @@ export type CalendarEvent = { end: Date; createBy: string; createdByName: string; + isPublish: boolean | null; allDay: boolean; extendedProps: { calendar: string; @@ -94,7 +103,7 @@ interface ListItemProps { item: any; text: string; createdBy: string; - isPublish: boolean; + isPublish: boolean | null; bgColor: string; } @@ -176,12 +185,15 @@ const CalendarView = ({ categories }: CalendarViewProps) => { end: new Date(event.endDate), allDay: true, extendedProps: { + isPublish: event.isPublish, calendar: event.agendaType, description: event.description, createdByName: event.createdByName, }, })); + console.log("Dataaa event : ", events); + setCalendarEvents(events); } }; @@ -239,6 +251,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => { title: item.title, createBy: "Mabes Polri - Approver", createdByName: item.createdByName, + isPublish: item.isPublish, start: new Date(item.startDate), end: new Date(item.endDate), allDay: true, // Sesuaikan jika memang ada event sepanjang hari @@ -335,14 +348,13 @@ const CalendarView = ({ categories }: CalendarViewProps) => { const renderEventContent = (eventInfo: any) => { const { title } = eventInfo.event; - const { isPublish } = eventInfo.event.extendedProps; - const { createdByName } = eventInfo.event.extendedProps; + const { createdByName, isPublish } = eventInfo.event.extendedProps; return ( <>
{" "} - {isPublish == true && } + {isPublish === true ? : }

{title}

@@ -417,6 +429,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => { end: new Date(item.endDate), createBy: "Mabes Polri - Approver", // Sesuaikan dengan data yang sebenarnya jika ada createdByName: item.createdByName, + isPublish: item.isPublish, allDay: true, extendedProps: { calendar: item.agendaType, @@ -447,7 +460,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => { onClick={() => handleClickListItem(item)} >
- {isPublish == true && } + {isPublish ? : }

{text}

Created By: {createdBy}

diff --git a/app/[locale]/(protected)/contributor/content/audio/update-seo/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/audio/update-seo/[id]/page.tsx new file mode 100644 index 00000000..7fe0e43c --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/audio/update-seo/[id]/page.tsx @@ -0,0 +1,18 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormImageUpdate from "@/components/form/content/image-update-form"; +import FormAudioUpdate from "@/components/form/content/audio-update-form"; +import FormAudioSeo from "@/components/form/content/audio-update-seo"; + +const AudioUpdatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default AudioUpdatePage; diff --git a/app/[locale]/(protected)/contributor/content/image/update-seo/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/image/update-seo/[id]/page.tsx new file mode 100644 index 00000000..152ee87e --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/image/update-seo/[id]/page.tsx @@ -0,0 +1,17 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormImageUpdate from "@/components/form/content/image-update-form"; +import FormImageSeo from "@/components/form/content/image-update-seo"; + +const ImageUpdateSeoPage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default ImageUpdateSeoPage; diff --git a/app/[locale]/(protected)/contributor/content/teks/update-seo/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/teks/update-seo/[id]/page.tsx new file mode 100644 index 00000000..f3182778 --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/teks/update-seo/[id]/page.tsx @@ -0,0 +1,16 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormTeksUpdate from "@/components/form/content/teks-update-form"; +import FormTeksSeo from "@/components/form/content/teks-update-seo"; + +const TeksUpdatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default TeksUpdatePage; diff --git a/app/[locale]/(protected)/contributor/content/video/update-seo/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/video/update-seo/[id]/page.tsx new file mode 100644 index 00000000..260e06f2 --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/video/update-seo/[id]/page.tsx @@ -0,0 +1,18 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormImageUpdate from "@/components/form/content/image-update-form"; +import FormVideoUpdate from "@/components/form/content/video-update-form"; +import FormVideoSeo from "@/components/form/content/video-update-seo"; + +const VideoUpdatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default VideoUpdatePage; diff --git a/app/[locale]/(protected)/contributor/task/components/task-table.tsx b/app/[locale]/(protected)/contributor/task/components/task-table.tsx index 64d80ec2..82617efe 100644 --- a/app/[locale]/(protected)/contributor/task/components/task-table.tsx +++ b/app/[locale]/(protected)/contributor/task/components/task-table.tsx @@ -26,6 +26,7 @@ import { } from "@/components/ui/table"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { + ChevronDown, ChevronLeft, ChevronRight, Eye, @@ -52,6 +53,8 @@ import { useRouter, useSearchParams } from "next/navigation"; import TablePagination from "@/components/table/table-pagination"; import columns from "./columns"; import { listTask } from "@/service/task"; +import { Label } from "@/components/ui/label"; +import { format } from "date-fns"; const TaskTable = () => { const router = useRouter(); @@ -70,6 +73,10 @@ const TaskTable = () => { pageIndex: 0, pageSize: 10, }); + const [statusFilter, setStatusFilter] = React.useState([]); + const [dateFilter, setDateFilter] = React.useState(""); + const [endDate, setEndDate] = React.useState(""); + const [filterByCode, setFilterByCode] = React.useState(""); const [page, setPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1); const [limit, setLimit] = React.useState(10); @@ -106,18 +113,43 @@ const TaskTable = () => { React.useEffect(() => { fetchData(); - }, [page, limit, isSpecificAttention, search]); + }, [ + page, + limit, + isSpecificAttention, + search, + dateFilter, + filterByCode, + statusFilter, + ]); async function fetchData() { + const formattedStartDate = dateFilter + ? format(new Date(dateFilter), "yyyy-MM-dd") + : ""; try { const res = await listTask( - search, page - 1, + search, limit, - isSpecificAttention ? "atensi-khusus" : "tugas-harian" + filterByCode, + formattedStartDate, + isSpecificAttention ? "atensi-khusus" : "tugas-harian", + statusFilter ); + const data = res?.data?.data; const contentData = data?.content; + + // let contentDataFilter = res?.data?.data?.content || []; + + // Filter berdasarkan status + // contentDataFilter = contentDataFilter.filter((item: any) => { + // const isSelesai = statusFilter.includes(1) ? item.isDone : true; + // const isAktif = statusFilter.includes(2) ? item.isActive : true; + // return isSelesai && isAktif; + // }); + contentData.forEach((item: any, index: number) => { item.no = (page - 1) * limit + index + 1; }); @@ -133,10 +165,26 @@ const TaskTable = () => { } const handleSearch = (e: React.ChangeEvent) => { - setSearch(e.target.value); // Perbarui state search - table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel + setFilterByCode(e.target.value); + setSearch(e.target.value); + table.getColumn("judul")?.setFilterValue(e.target.value); }; + function handleStatusCheckboxChange(value: number) { + setStatusFilter((prev) => + prev.includes(value) + ? prev.filter((status) => status !== value) + : [...prev, value] + ); + } + + // const handleSearchFilterByCode = (e: React.ChangeEvent) => { + // const value = e.target.value; + // console.log("code :", value); + // setFilterByCode(value); + // fetchData(); + // }; + return (
@@ -184,24 +232,88 @@ const TaskTable = () => {
-
- ) => - table.getColumn("status")?.setFilterValue(event.target.value) - } - className="max-w-sm " - /> +
+
+
+ + + + + +
+

Filter

+
+
+ + setDateFilter(e.target.value)} + className="max-w-sm" + /> +
+ {/*
+ + +
*/} + +
+ handleStatusCheckboxChange(1)} + /> + +
+
+ handleStatusCheckboxChange(2)} + /> + +
+
+
+
+
+ {/*
+ ) => + table.getColumn("status")?.setFilterValue(event.target.value) + } + className="max-w-sm " + /> +
*/}
diff --git a/components/form/content/audio-form.tsx b/components/form/content/audio-form.tsx index 18b76e5b..d605f65b 100644 --- a/components/form/content/audio-form.tsx +++ b/components/form/content/audio-form.tsx @@ -53,6 +53,7 @@ import { error, loading } from "@/config/swal"; import { Item } from "@radix-ui/react-dropdown-menu"; import dynamic from "next/dynamic"; import { getCsrfToken } from "@/service/auth"; +import { Link } from "@/i18n/routing"; interface FileWithPreview extends File { preview: string; @@ -921,7 +922,7 @@ export default function FormAudio() { }`} onClick={() => handleArticleIdClick(id)} > - {id} + {"Narasi " + (index + 1)}

))} @@ -930,18 +931,8 @@ export default function FormAudio() {
{selectedArticleId && ( - - + )}
diff --git a/components/form/content/audio-update-seo.tsx b/components/form/content/audio-update-seo.tsx new file mode 100644 index 00000000..6c808570 --- /dev/null +++ b/components/form/content/audio-update-seo.tsx @@ -0,0 +1,757 @@ +"use client"; +import React, { + ChangeEvent, + Fragment, + 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, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} 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 Cookies from "js-cookie"; +import { + createMedia, + deleteFile, + deleteMedia, + getTagsBySubCategoryId, + listEnableCategory, + uploadThumbnail, +} from "@/service/content/content"; +import { detailMedia } from "@/service/curated-content/curated-content"; +import { Badge } from "@/components/ui/badge"; +import { CloudUpload, MailIcon, PieChart, XIcon } from "lucide-react"; +import dynamic from "next/dynamic"; +import { useDropzone } from "react-dropzone"; +import { Icon } from "@iconify/react/dist/iconify.js"; +import Image from "next/image"; +import { error, loading } from "@/lib/swal"; +import { getCsrfToken } from "@/service/auth"; +import { Upload } from "tus-js-client"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; +import ViewEditor from "@/components/editor/view-editor"; +import { getDetailArticle, getSeoScore } from "@/service/content/ai"; +import { Gauge } from "@mui/x-charts/Gauge"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Pie } from "react-chartjs-2"; +import { + Chart as ChartJS, + ArcElement, + Tooltip, + Legend, + ChartOptions, +} from "chart.js"; + +ChartJS.register(ArcElement, Tooltip, Legend); +const imageSchema = z.object({ + title: z.string().min(1, { message: "Judul diperlukan" }), + description: z + .string() + .min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }), + creatorName: z.string().min(1, { message: "Creator diperlukan" }), + // tags: z.string().min(1, { message: "Judul diperlukan" }), +}); + +type Category = { + id: string; + name: string; +}; + +type Detail = { + id: string; + title: string; + description: string; + slug: string; + category: { + id: string; + name: string; + }; + publishedFor: string; + + publishedForObject: { + id: number; + name: string; + }; + htmlDescription: string; + creatorName: string; + categoryName: string; + thumbnailLink: string; + tags: string; +}; + +type Option = { + id: string; + name: string; +}; + +const CustomEditor = dynamic( + () => { + return import("@/components/editor/custom-editor"); + }, + { ssr: false } +); + +interface FileWithPreview extends File { + preview: string; +} + +export default function FormAudioSeo() { + const MySwal = withReactContent(Swal); + const router = useRouter(); + + const { id } = useParams() as { id: string }; + console.log(id); + const editor = useRef(null); + type ImageSchema = z.infer; + + let progressInfo: any = []; + let counterUpdateProgress = 0; + const [progressList, setProgressList] = useState([]); + let uploadPersen = 0; + const [isStartUpload, setIsStartUpload] = useState(false); + const [counterProgress, setCounterProgress] = useState(0); + + const [selectedFiles, setSelectedFiles] = useState([]); + const taskId = Cookies.get("taskId"); + const scheduleId = Cookies.get("scheduleId"); + const scheduleType = Cookies.get("scheduleType"); + + const [categories, setCategories] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(); + const [tags, setTags] = useState([]); + const [detail, setDetail] = useState(); + const [refresh, setRefresh] = useState(false); + const [selectedPublishers, setSelectedPublishers] = useState([]); + const [articleBody, setArticleBody] = useState(""); + const [files, setFiles] = useState([]); + const [filesTemp, setFilesTemp] = useState([]); + const [publishedFor, setPublishedFor] = useState([]); + const inputRef = useRef(null); + const [selectedOptions, setSelectedOptions] = useState<{ + [fileId: number]: string; + }>({}); + const [articleData, setArticleData] = useState({ + title: "", + mainKeyword: "", + additionalKeywords: "", + metaTitle: "", + metaDescription: "", + }); + const [totalScoreSEO, setTotalScoreSEO] = useState(0); + const [errorSEO, setErrorSEO] = useState([]); + const [warningSEO, setWarningSEO] = useState([]); + const [optimizedSEO, setOptimizedSEO] = useState([]); + // const [errorData, setErrorData] = useState([]); + const [warningData, setWarningData] = useState([]); + const [optimizedData, setOptimizedData] = useState([]); + const [errorsData, setErrorData] = useState([]); + + const [selectedTarget, setSelectedTarget] = useState( + detail?.category.id + ); + const [unitSelection, setUnitSelection] = useState({ + allUnit: false, + mabes: false, + polda: false, + polres: false, + }); + + let fileTypeId = "1"; + + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFiles(acceptedFiles.map((file) => Object.assign(file))); + }, + }); + + const { + control, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ + resolver: zodResolver(imageSchema), + }); + + // 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 handleImageChange = (event: ChangeEvent) => { + 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)); + }; + + // const handleCheckboxChange = (id: number) => { + // setSelectedPublishers((prev) => + // prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id] + // ); + // }; + + useEffect(() => { + async function initState() { + getCategories(); + } + + 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); + } + }; + + useEffect(() => { + const fetchArticleData = async () => { + try { + const response = await getDetailArticle(id); + const data = response?.data?.data; + const cleanArticleBody = data.articleBody.replace(/]*>/g, ""); + + setArticleData({ + title: data.title || "", + mainKeyword: data.mainKeyword || "", + additionalKeywords: data.additionalKeywords || "", + metaTitle: data.metaTitle || "", + metaDescription: data.metaDescription || "", + }); + setArticleBody(cleanArticleBody || ""); + + // reset({ + // title: data.title, + // mainKeyword: data.mainKeyword, + // additionalKeywords: data.additionalKeywords, + // metaTitle: data.metaTitle, + // metaDescription: data.metaDescription, + // }); + setArticleBody(cleanArticleBody || ""); + } catch (error) { + console.error("Failed to fetch article data:", error); + } + }; + + if (id) { + fetchArticleData(); + } + }, [id]); + + useEffect(() => { + const fetchSeoScore = async () => { + const res = await getSeoScore(id); + if (res.error) { + error(res.message); + return false; + } + setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0); + let errorList = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.error, + ]; + setErrorSEO(errorList); + let warningList = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.warning, + ]; + setWarningSEO(warningList); + let optimizedList = [ + ...res.data.data?.seo_analysis?.analysis?.keyword_optimization + ?.optimized, + ...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized, + ]; + setOptimizedSEO(optimizedList); + setErrorData(errorList); + setWarningData(warningList); + setOptimizedData(optimizedList); + }; + fetchSeoScore(); + }); + + const data = { + labels: ["SEO Score (" + totalScoreSEO + "%)"], + datasets: [ + { + data: [totalScoreSEO], + backgroundColor: ["#4CAF50"], + hoverBackgroundColor: ["#388E3C"], + }, + ], + }; + + const options: ChartOptions<"pie"> = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: "bottom", // TypeScript now correctly recognizes this as a valid option + }, + }, + }; + + const save = async (data: ImageSchema) => { + loading(); + const finalTags = tags.join(", "); + const requestData = { + ...data, + id: detail?.id, + title: data.title, + description: data.description, + htmlDescription: data.description, + fileTypeId, + categoryId: selectedTarget, + subCategoryId: selectedTarget, + uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", + statusId: "1", + publishedFor: publishedFor.join(","), + creatorName: data.creatorName, + tags: finalTags, + isYoutube: false, + isInternationalMedia: false, + }; + + const response = await createMedia(requestData); + console.log("Form Data Submitted:", requestData); + + const formMedia = new FormData(); + const thumbnail = files[0]; + formMedia.append("file", thumbnail); + const responseThumbnail = await uploadThumbnail(id, formMedia); + if (responseThumbnail?.error == true) { + error(responseThumbnail?.message); + return false; + } + + const progressInfoArr = []; + for (const item of files) { + progressInfoArr.push({ percentage: 0, fileName: item.name }); + } + progressInfo = progressInfoArr; + setIsStartUpload(true); + setProgressList(progressInfoArr); + + close(); + // showProgress(); + files.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" + ); + }); + + MySwal.fire({ + title: "Sukses", + text: "Data berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push("/en/contributor/content/image"); + }); + }; + + async function uploadResumableFile( + idx: number, + id: string, + file: any, + duration: string + ) { + console.log(idx, id, file, duration); + + // const placements = getPlacement(file.placements); + // console.log("Placementttt: : ", placements); + + const resCsrf = await getCsrfToken(); + const csrfToken = resCsrf?.data?.token; + console.log("CSRF TOKEN : ", csrfToken); + const headers = { + "X-XSRF-TOKEN": csrfToken, + }; + + const upload = new Upload(file, { + endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`, + headers: headers, + retryDelays: [0, 3000, 6000, 12_000, 24_000], + chunkSize: 20_000, + metadata: { + mediaid: id, + filename: file.name, + filetype: file.type, + duration, + isWatermark: "true", // hardcode + }, + onBeforeRequest: function (req) { + var xhr = req.getUnderlyingObject(); + xhr.withCredentials = true; + }, + onError: async (e: any) => { + console.log("Error upload :", e); + error(e); + }, + onChunkComplete: ( + chunkSize: any, + bytesAccepted: any, + bytesTotal: any + ) => { + const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100); + progressInfo[idx].percentage = uploadPersen; + counterUpdateProgress++; + console.log(counterUpdateProgress); + setProgressList(progressInfo); + setCounterProgress(counterUpdateProgress); + }, + onSuccess: async () => { + uploadPersen = 100; + progressInfo[idx].percentage = 100; + counterUpdateProgress++; + setCounterProgress(counterUpdateProgress); + successTodo(); + }, + }); + + upload.start(); + } + + const onSubmit = (data: ImageSchema) => { + 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 successSubmit = (redirect: string) => { + MySwal.fire({ + title: "Sukses", + text: "Data berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push(redirect); + }); + }; + + function successTodo() { + let counter = 0; + for (const element of progressInfo) { + if (element.percentage == 100) { + counter++; + } + } + if (counter == progressInfo.length) { + setIsStartUpload(false); + // hideProgress(); + Cookies.remove("idCreate"); + successSubmit("/in/contributor/content/image/"); + } + } + + const handleRemoveFile = (file: FileWithPreview) => { + const uploadedFiles = files; + const filtered = uploadedFiles.filter((i) => i.name !== file.name); + setFiles([...filtered]); + }; + + function success() { + MySwal.fire({ + title: "Sukses", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then((result) => { + if (result.isConfirmed) { + // window.location.reload(); + } + }); + } + + const handleDeleteFile = (id: number) => { + MySwal.fire({ + title: "Hapus file", + text: "Apakah Anda yakin ingin menghapus file ini?", + icon: "warning", + showCancelButton: true, + cancelButtonColor: "#3085d6", + confirmButtonColor: "#d33", + confirmButtonText: "Hapus", + }).then((result) => { + if (result.isConfirmed) { + doDelete(id); + } + }); + }; + + async function doDelete(id: number) { + const data = { id }; + + try { + const response = await deleteFile(data); + if (response?.error) { + error(response.message); + return; + } + + // Jika berhasil, hapus file dari state lokal + setFiles((prevFiles: any) => + prevFiles.filter((file: any) => file.id !== id) + ); + success(); + } catch (err) { + error("Terjadi kesalahan saat menghapus file"); + } + } + + return ( +
+
+ + + + + Konten + + + Checker + + + + {articleData !== undefined ? ( + +
+ + +
+
+
+ +