diff --git a/app/[locale]/(protected)/admin/settings/banner/component/banner-column.tsx b/app/[locale]/(protected)/admin/settings/banner/component/banner-column.tsx new file mode 100644 index 00000000..d17bdc2f --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/banner/component/banner-column.tsx @@ -0,0 +1,110 @@ +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"; +import StatusToogle from "./status-toogle"; +import StaticToogle from "./static-toogle"; + +const columns: ColumnDef[] = [ + // { + // accessorKey: "no", + // header: "No", + // cell: ({ row }) => {row.getValue("no")}, + // }, + + { + accessorKey: "title", + header: "Judul", + cell: ({ row }) => {row.getValue("title")}, + }, + { + accessorKey: "categoryName", + header: "Kategori", + cell: ({ row }) => {row.getValue("categoryName")}, + }, + { + accessorKey: "createdAt", + header: "Tanggal Unggah", + cell: ({ row }) => ( + {formatDateToIndonesian(row.getValue("createdAt"))} + ), + }, + { + accessorKey: "static", + header: "Static Banner", + cell: ({ row }) => ( + + ), + }, + { + accessorKey: "statusName", + header: "Status Banner", + cell: ({ row }) => ( + + ), + }, + + { + id: "actions", + accessorKey: "action", + header: "Actions", + enableHiding: false, + cell: ({ row }) => { + return ( + + + + + + + + Detail + + + + + ); + }, + }, +]; + +export default columns; diff --git a/app/[locale]/(protected)/admin/settings/banner/component/banner-table.tsx b/app/[locale]/(protected)/admin/settings/banner/component/banner-table.tsx new file mode 100644 index 00000000..b0fedb12 --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/banner/component/banner-table.tsx @@ -0,0 +1,166 @@ +"use client"; + +import * as React from "react"; +import { + ColumnDef, + ColumnFiltersState, + PaginationState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +import { useSearchParams } from "next/navigation"; + +import { close, loading } from "@/config/swal"; +import { Link, useRouter } from "@/i18n/routing"; +import columns from "./banner-column"; +import { listBanner, listStaticBanner } from "@/service/settings/settings"; + +const BannerListTable = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const [showData, setShowData] = React.useState("10"); + + 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 [getData, setGetData] = React.useState([]); + const dataChange = searchParams?.get("dataChange"); + + const [page, setPage] = React.useState(1); + const [totalPage, setTotalPage] = React.useState(1); + const table = useReactTable({ + data: getData, + 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(() => { + if (dataChange) { + router.push("/admin/settings/banner"); + } + getListBanner(); + }, [dataChange]); + + React.useEffect(() => { + getListBanner(); + // getListStaticBanner(); + }, [page, showData]); + + async function getListBanner() { + loading(); + let temp: any; + + const response = await listBanner(); + temp = response?.data?.data; + const response2 = await listStaticBanner(); + console.log("sadadddd", response2?.data?.data.length); + for (let i = 0; i < response2?.data?.data.length; i++) { + for (let j = 0; j < temp.length; j++) { + console.log("temp", j, temp[j].id); + if (response2?.data?.data[i].mediaUploadId === temp[j].id) { + temp[j].staticPage = true; + } else { + temp[j].staticPage = false; + } + } + } + + console.log("tesmasdasdasdasd", temp); + setGetData(temp); + + close(); + } + + return ( + <> + + + {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 BannerListTable; diff --git a/app/[locale]/(protected)/admin/settings/banner/component/column.tsx b/app/[locale]/(protected)/admin/settings/banner/component/column.tsx new file mode 100644 index 00000000..6ffd551f --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/banner/component/column.tsx @@ -0,0 +1,113 @@ +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"; +import { setBanner } from "@/service/settings/settings"; +import { error } from "@/config/swal"; +import { useToast } from "@/components/ui/use-toast"; + +const columns: ColumnDef[] = [ + { + accessorKey: "no", + header: "No", + cell: ({ row }) => {row.getValue("no")}, + }, + + { + accessorKey: "title", + header: "Judul", + cell: ({ row }) => {row.getValue("title")}, + }, + { + accessorKey: "categoryName", + header: "Kategori", + cell: ({ row }) => {row.getValue("categoryName")}, + }, + { + accessorKey: "createdAt", + header: "Tanggal Unggah", + cell: ({ row }) => ( + {formatDateToIndonesian(row.getValue("createdAt"))} + ), + }, + + { + accessorKey: "statusName", + header: "Status", + cell: ({ row }) => {row.getValue("statusName")}, + }, + + { + id: "actions", + accessorKey: "action", + header: "Actions", + enableHiding: false, + cell: ({ row }) => { + const { toast } = useToast(); + + const handleBanner = async (id: number) => { + const response = setBanner(id, true); + toast({ + title: "Success", + }); + }; + return ( + + + + + + + + Detail + + + + handleBanner(row.original.id)}> + Jadikan Banner + + + + + ); + }, + }, +]; + +export default columns; diff --git a/app/[locale]/(protected)/admin/settings/banner/component/static-toogle.tsx b/app/[locale]/(protected)/admin/settings/banner/component/static-toogle.tsx new file mode 100644 index 00000000..66392c4e --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/banner/component/static-toogle.tsx @@ -0,0 +1,40 @@ +import { Switch } from "@/components/ui/switch"; +import { useToast } from "@/components/ui/use-toast"; +import { useRouter } from "@/i18n/routing"; +import { setStaticBanner } from "@/service/settings/settings"; + +export default function StaticToogle(props: { + id: number; + initChecked: boolean; +}) { + const { id, initChecked } = props; + const { toast } = useToast(); + const router = useRouter(); + + const changeStaticBanner = async (id: number) => { + const response = await setStaticBanner(id); + + if (response?.error) { + toast({ + variant: "destructive", + title: response?.message, + }); + return false; + } + + toast({ + title: "Success ", + }); + router.push("/admin/settings/banner?dataChange=true"); + }; + + return ( + { + changeStaticBanner(id); + }} + /> + ); +} diff --git a/app/[locale]/(protected)/admin/settings/banner/component/status-toogle.tsx b/app/[locale]/(protected)/admin/settings/banner/component/status-toogle.tsx new file mode 100644 index 00000000..22f11b79 --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/banner/component/status-toogle.tsx @@ -0,0 +1,37 @@ +import { Switch } from "@/components/ui/switch"; +import { useToast } from "@/components/ui/use-toast"; +import { useRouter } from "@/i18n/routing"; +import { setBanner } from "@/service/settings/settings"; + +export default function StatusToogle(props: { + id: number; + initChecked: boolean; +}) { + const { id, initChecked } = props; + const { toast } = useToast(); + const router = useRouter(); + + const disableBanner = async () => { + const response = await setBanner(id, false); + + if (response?.error) { + toast({ + variant: "destructive", + title: response?.message, + }); + return false; + } + + toast({ + title: "Success ", + }); + router.push("/admin/settings/banner?dataChange=true"); + }; + return ( + disableBanner()} + /> + ); +} diff --git a/app/[locale]/(protected)/admin/settings/banner/component/table.tsx b/app/[locale]/(protected)/admin/settings/banner/component/table.tsx new file mode 100644 index 00000000..03175fb1 --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/banner/component/table.tsx @@ -0,0 +1,393 @@ +"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 ContentListTable = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + 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); + typingTimer = setTimeout(doneTyping, doneTypingInterval); + }; + + 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, + "", + 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 ( + <> +
+ +
+ + + + + + + + 1 - 10 Data + + + 1 - 20 Data + + + 1 - 25 Data + + + 1 - 50 Data + + + + + + + + + +
+ +
+

Kategory

+ {categories?.map((category: any) => ( +
+ + handleChange("category", category.id, Boolean(e)) + } + /> + +
+ ))} +

Status

+
+ + handleChange("status", 1, Boolean(e)) + } + /> + +
+
+ + handleChange("status", 2, Boolean(e)) + } + /> + +
+
+ + handleChange("status", 3, Boolean(e)) + } + /> + +
+
+ + handleChange("status", 4, Boolean(e)) + } + /> + +
+
+
+
+
+
+
+ + + {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 ContentListTable; diff --git a/app/[locale]/(protected)/admin/settings/banner/page.tsx b/app/[locale]/(protected)/admin/settings/banner/page.tsx new file mode 100644 index 00000000..ac014b4f --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/banner/page.tsx @@ -0,0 +1,44 @@ +"use client"; +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import ContentListTable from "./component/table"; +import { useState } from "react"; + +import BannerListTable from "./component/banner-table"; + +export default function AdminBanner() { + const [selectedTab, setSelectedTab] = useState("content"); + + return ( + + ); +} diff --git a/app/[locale]/(protected)/admin/settings/category/component/column.tsx b/app/[locale]/(protected)/admin/settings/category/component/column.tsx index 02fe1320..86a71741 100644 --- a/app/[locale]/(protected)/admin/settings/category/component/column.tsx +++ b/app/[locale]/(protected)/admin/settings/category/component/column.tsx @@ -92,42 +92,6 @@ const columns: ColumnDef[] = [ router.push("/admin/settings/category?dataChange=true"); }; return ( - // - // - // - // - // - // - // - // - - // - // - // - // - // - // - // - // - // - // categoryDelete(row.original.id)}>Delete - // - // - // - diff --git a/app/[locale]/(protected)/admin/settings/privacy/page.tsx b/app/[locale]/(protected)/admin/settings/privacy/page.tsx new file mode 100644 index 00000000..63f86234 --- /dev/null +++ b/app/[locale]/(protected)/admin/settings/privacy/page.tsx @@ -0,0 +1,123 @@ +"use client"; +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { close, error, loading } from "@/config/swal"; +import { Input } from "@/components/ui/input"; +import JoditEditor from "jodit-react"; +import { useEffect, useRef } from "react"; +import { getPrivacy, savePrivacy } from "@/service/settings/settings"; +import { useToast } from "@/components/ui/use-toast"; +import { Button } from "@/components/ui/button"; + +const FormSchema = z.object({ + title: z.string({ + required_error: "Required", + }), + description: z.string({ + required_error: "Required", + }), +}); + +export default function AdminPrivacyPolicy() { + const form = useForm>({ + resolver: zodResolver(FormSchema), + }); + const editor = useRef(null); + const { toast } = useToast(); + + useEffect(() => { + getPrivacyData(); + }, []); + + async function getPrivacyData() { + const response = await getPrivacy("1"); + console.log(response?.data?.data); + form.setValue("title", response?.data?.data?.title); + form.setValue("description", response?.data?.data?.htmlContent); + } + + const onSubmit = async (data: z.infer) => { + const req = { + id: 1, + title: data.title, + htmlContent: data.description, + isActive: true, + }; + + const response = await savePrivacy(req); + if (response?.error) { + error(response?.message); + return false; + } + toast({ + title: "Berhasil Simpan", + }); + }; + return ( + <> + +
+ + ( + + Judul + + + + + )} + /> + + ( + + Konten + + + + + + + )} + /> +
+ +
+ + + + ); +} diff --git a/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx b/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx index 0010f772..513c743f 100644 --- a/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx +++ b/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx @@ -1,5 +1,10 @@ // "use client"; -import React, { useState, useEffect } from "react"; +import React, { + useState, + useEffect, + useRef, + Fragment, +} from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -32,7 +37,7 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; -import { error, loading } from "@/lib/swal"; +import { error, loading, success } from "@/lib/swal"; import Cookies from "js-cookie"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; @@ -42,12 +47,18 @@ import { saveAgendaSettings } from "@/service/agenda-setting/agenda-setting"; import { Checkbox } from "@/components/ui/checkbox"; import { getUserLevelForAssignments } from "@/service/task"; import { AudioRecorder } from "react-audio-voice-recorder"; +import FileUploader from "@/components/form/shared/file-uploader"; +import { Upload } from "tus-js-client"; const schema = z.object({ title: z.string().min(3, { message: "Required" }), description: z.string().min(3, { message: "Required" }), }); +interface FileWithPreview extends File { + preview: string; +} + const EventModal = ({ open, onClose, @@ -64,7 +75,7 @@ const EventModal = ({ const [startDate, setStartDate] = useState(new Date()); const [endDate, setEndDate] = useState(new Date()); const [isPending, startTransition] = React.useTransition(); - const [calendarProps, setCalendarProps] = React.useState( + const [agendaType, setAgendaType] = React.useState( categories[0].value ); const [listDest, setListDest] = useState([]); @@ -78,6 +89,14 @@ const EventModal = ({ const [audioFile, setAudioFile] = useState(null); const [isRecording, setIsRecording] = useState(false); const [timer, setTimer] = useState(120); + const [imageFiles, setImageFiles] = useState([]); + const [videoFiles, setVideoFiles] = useState([]); + const [textFiles, setTextFiles] = useState([]); + const [audioFiles, setAudioFiles] = useState([]); + const [isImageUploadFinish, setIsImageUploadFinish] = useState(false); + const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false); + const [isTextUploadFinish, setIsTextUploadFinish] = useState(false); + const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false); const { register, @@ -96,8 +115,17 @@ const EventModal = ({ setIsLoading(true); try { const response = await getUserLevelForAssignments(); - setListDest(response?.data?.data.list); - const initialExpandedState = response?.data?.data.list.reduce( + const levelList = response?.data?.data.list; + let listFiltered = []; + if (agendaType == "polda") { + listFiltered = levelList.filter((level: any) => level.name != 'SATKER POLRI'); + } else if (agendaType == "polres") { + listFiltered = levelList.filter((level: any) => level.name != 'SATKER POLRI'); + } else if (agendaType == "satker") { + listFiltered = levelList.filter((level: any) => level.name == 'SATKER POLRI'); + } + setListDest(listFiltered); + const initialExpandedState = listFiltered.reduce( (acc: any, polda: any) => { acc[polda.id] = false; return acc; @@ -105,7 +133,6 @@ const EventModal = ({ {} ); setExpandedPolda(initialExpandedState); - console.log("polres", initialExpandedState); } catch (error) { console.error("Error fetching Polda/Polres data:", error); } finally { @@ -113,7 +140,8 @@ const EventModal = ({ } } fetchPoldaPolres(); - }, []); + console.log("Event", event); + }, [agendaType]); const handleCheckboxChange = (levelId: number) => { setCheckedLevels((prev) => { @@ -128,18 +156,15 @@ const EventModal = ({ }; const save = async (data: any) => { - if (!audioFile) return; - - const formData = new FormData(); - formData.append("voiceNote", audioFile); + // const formData = new FormData(); + // formData.append("voiceNote", audioFile); const reqData = { title: data.title, description: data.description, - agendaType: calendarProps, + agendaType: agendaType, startDate: format(startDate, "yyyy-MM-dd"), endDate: format(endDate, "yyyy-MM-dd"), - voiceNote: formData, }; console.log("Submitted Data:", reqData); @@ -150,20 +175,70 @@ const EventModal = ({ return false; } - Cookies.set("AgendaSetting", response?.data?.data.id, { - expires: 1, + const id = response?.data?.data.id; + + if (imageFiles?.length == 0) { + setIsImageUploadFinish(true); + } + imageFiles?.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + "1", + "0" + ); + }); + + if (videoFiles?.length == 0) { + setIsVideoUploadFinish(true); + } + videoFiles?.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + "2", + "0" + ); + }); + + if (textFiles?.length == 0) { + setIsTextUploadFinish(true); + } + textFiles?.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + "3", + "0" + ); + }); + + if (audioFiles?.length == 0) { + setIsAudioUploadFinish(true); + } + audioFiles?.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + "4", + "0" + ); }); // Optional: Use Swal for success feedback - MySwal.fire({ - title: "Sukses", - text: "Data berhasil disimpan.", - icon: "success", - confirmButtonColor: "#3085d6", - confirmButtonText: "OK", - }).then(() => { - router.push("en/contributor/agenda-setting"); - }); + // MySwal.fire({ + // title: "Sukses", + // text: "Data berhasil disimpan.", + // icon: "success", + // confirmButtonColor: "#3085d6", + // confirmButtonText: "OK", + // }).then(() => { + // router.push("en/contributor/agenda-setting"); + // }); }; const onSubmit = (data: any) => { @@ -179,7 +254,7 @@ const EventModal = ({ setStartDate(event?.event?.start); setEndDate(event?.event?.end); const eventCalendar = event?.event?.extendedProps?.calendar; - setCalendarProps(eventCalendar || categories[0].value); + setAgendaType(eventCalendar || categories[0].value); } setValue("title", event?.event?.title || ""); setValue("description", event?.event?.description || ""); @@ -197,6 +272,7 @@ const EventModal = ({ }; const toggleExpand = (poldaId: any) => { + console.log("Toogle : ", expandedPolda); setExpandedPolda((prev: any) => ({ ...prev, [poldaId]: !prev[poldaId], @@ -248,6 +324,89 @@ const EventModal = ({ audioElements.forEach((audio) => audio.remove()); }; + async function uploadResumableFile( + idx: number, + id: string, + file: any, + fileTypeId: string, + duration: string + ) { + console.log(idx, id, file, fileTypeId, duration); + + // const placements = getPlacement(file.placements); + // console.log("Placementttt: : ", placements); + + const upload = new Upload(file, { + endpoint: `${process.env.NEXT_PUBLIC_API}/agenda-settings/file/upload`, + retryDelays: [0, 3000, 6000, 12_000, 24_000], + chunkSize: 20_000, + metadata: { + agendaSettingId: id, + filename: file.name, + filetype: file.type, + fileTypeId: fileTypeId, + duration: "", + isWatermark: "true", // hardcode + }, + onError: async (e: any) => { + console.log("Error upload :", e); + error(e); + }, + onChunkComplete: ( + chunkSize: any, + bytesAccepted: any, + bytesTotal: any + ) => { + const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100); + // progressInfo[idx].percentage = uploadPersen; + // counterUpdateProgress++; + // console.log(counterUpdateProgress); + // setProgressList(progressInfo); + // setCounterProgress(counterUpdateProgress); + }, + onSuccess: async () => { + // uploadPersen = 100; + // progressInfo[idx].percentage = 100; + // counterUpdateProgress++; + // setCounterProgress(counterUpdateProgress); + successTodo(); + if (fileTypeId == '1'){ + setIsImageUploadFinish(true); + } else if (fileTypeId == '2'){ + setIsVideoUploadFinish(true); + } if (fileTypeId == '3'){ + setIsTextUploadFinish(true); + } if (fileTypeId == '4'){ + setIsAudioUploadFinish(true); + } + }, + }); + + upload.start(); + } + + useEffect(() => { + successTodo(); + }, [isImageUploadFinish, isVideoUploadFinish, isAudioUploadFinish, isTextUploadFinish]) + + function successTodo() { + if (isImageUploadFinish && isVideoUploadFinish && isAudioUploadFinish && isTextUploadFinish) { + successSubmit("/in/contributor/agenda-setting"); + } + } + + const successSubmit = (redirect: string) => { + MySwal.fire({ + title: "Sukses", + text: "Data berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push(redirect); + }); + }; + return ( <> - {event ? "Edit Agenda Setting" : "Create Agenda Setting"}{" "} + {event?.length > 1 ? "Edit Agenda Setting" : "Create Agenda Setting"}{" "} {event?.title} @@ -366,14 +525,14 @@ const EventModal = ({
- + ( setImageFiles(files)} />
- setImageFiles(files)} />
- setTextFiles(files)} />
@@ -542,6 +708,16 @@ const EventModal = ({ downloadOnSavePress={true} downloadFileExtension="webm" /> + setAudioFiles(files)} + className="mt-2" + />
{audioFile && (
@@ -568,15 +744,15 @@ const EventModal = ({ {isPending ? ( <> - {event ? "Updating..." : "Adding..."} + {event?.length > 1 ? "Updating..." : "Adding..."} - ) : event ? ( + ) : event?.length > 1 ? ( "Update Agenda Setting" ) : ( "Simpan Agenda Setting" )} - {event && ( + {event?.length > 1 && ( -
+ + {status} {/* Tetap tampilkan nilai asli */} + ); }, }, @@ -139,12 +147,18 @@ const columns: ColumnDef[] = [ - + View - + + + + + Edit + + Delete diff --git a/app/[locale]/(protected)/contributor/content/audio/detail/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/audio/detail/[id]/page.tsx new file mode 100644 index 00000000..c3d08051 --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/audio/detail/[id]/page.tsx @@ -0,0 +1,16 @@ +import SiteBreadcrumb from "@/components/site-breadcrumb"; +import FormImageDetail from "@/components/form/content/image-detail-form"; +import FormAudioDetail from "@/components/form/content/audio-detail-form"; + +const AudioDetailPage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default AudioDetailPage; diff --git a/app/[locale]/(protected)/contributor/content/audio/update/[id]/page.tsx b/app/[locale]/(protected)/contributor/content/audio/update/[id]/page.tsx new file mode 100644 index 00000000..c0690cb7 --- /dev/null +++ b/app/[locale]/(protected)/contributor/content/audio/update/[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 FormAudioUpdate from "@/components/form/content/audio-update-form"; + +const AudioUpdatePage = async () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default AudioUpdatePage; diff --git a/app/[locale]/(protected)/contributor/content/image/components/columns.tsx b/app/[locale]/(protected)/contributor/content/image/components/columns.tsx index d6b0328c..acbada79 100644 --- a/app/[locale]/(protected)/contributor/content/image/components/columns.tsx +++ b/app/[locale]/(protected)/contributor/content/image/components/columns.tsx @@ -100,7 +100,6 @@ const columns: ColumnDef[] = [ ); }, }, - { accessorKey: "statusName", header: "Status", diff --git a/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx b/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx index 3e3af62b..bb3cfc77 100644 --- a/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx +++ b/app/[locale]/(protected)/contributor/content/teks/components/columns.tsx @@ -102,23 +102,31 @@ const columns: ColumnDef[] = [ }, { - accessorKey: "isDone", + accessorKey: "statusName", header: "Status", cell: ({ row }) => { - const isDone = row.getValue("isDone"); + const statusColors: Record = { + diterima: "bg-green-100 text-green-600", + "menunggu review": "bg-orange-100 text-orange-600", + }; + + // Mengambil `statusName` dari data API + const status = row.getValue("statusName") as string; + const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil + + // Gunakan `statusName` untuk pencocokan + const statusStyles = + statusColors[statusName] || "bg-gray-100 text-gray-600"; + return ( -
- -
+ + {status} {/* Tetap tampilkan nilai asli */} + ); }, }, diff --git a/app/[locale]/(protected)/contributor/content/video/components/columns.tsx b/app/[locale]/(protected)/contributor/content/video/components/columns.tsx index b58dd079..9a92144a 100644 --- a/app/[locale]/(protected)/contributor/content/video/components/columns.tsx +++ b/app/[locale]/(protected)/contributor/content/video/components/columns.tsx @@ -102,23 +102,31 @@ const columns: ColumnDef[] = [ }, { - accessorKey: "isDone", + accessorKey: "statusName", header: "Status", cell: ({ row }) => { - const isDone = row.getValue("isDone"); + const statusColors: Record = { + diterima: "bg-green-100 text-green-600", + "menunggu review": "bg-orange-100 text-orange-600", + }; + + // Mengambil `statusName` dari data API + const status = row.getValue("statusName") as string; + const statusName = status?.toLocaleLowerCase(); // Ubah ke huruf kecil + + // Gunakan `statusName` untuk pencocokan + const statusStyles = + statusColors[statusName] || "bg-gray-100 text-gray-600"; + return ( -
- -
+ + {status} {/* Tetap tampilkan nilai asli */} + ); }, }, diff --git a/components/form/content/audio-detail-form.tsx b/components/form/content/audio-detail-form.tsx new file mode 100644 index 00000000..4ba57d19 --- /dev/null +++ b/components/form/content/audio-detail-form.tsx @@ -0,0 +1,752 @@ +"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 { 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 { register } from "module"; +import { Switch } from "@/components/ui/switch"; +import Cookies from "js-cookie"; +import { + createMedia, + getTagsBySubCategoryId, + listEnableCategory, + rejectFiles, + submitApproval, +} from "@/service/content/content"; +import { detailMedia } from "@/service/curated-content/curated-content"; +import { Badge } from "@/components/ui/badge"; +import { MailIcon } from "lucide-react"; +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 { + DialogHeader, + DialogFooter, + Dialog, + DialogContent, + DialogTitle, +} from "@/components/ui/dialog"; +import { Textarea } from "@/components/ui/textarea"; +import { loading } from "@/config/swal"; +import { getCookiesDecrypt } from "@/lib/utils"; +import { Icon } from "@iconify/react/dist/iconify.js"; +import { error } from "@/lib/swal"; + +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 FileType = { + id: number; + url: string; + thumbnailFileUrl: string; + fileName: string; +}; + +type Detail = { + id: string; + title: string; + description: string; + slug: string; + category: { + id: number; + name: string; + }; + categoryName: string; + creatorName: string; + thumbnailLink: string; + tags: string; + statusName: string; + isPublish: boolean; + needApprovalFromLevel: number; + files: FileType[]; + uploadedById: number; +}; + +export default function FormAudioDetail() { + const MySwal = withReactContent(Swal); + const router = useRouter(); + const userId = getCookiesDecrypt("uie"); + const userLevelId = getCookiesDecrypt("ulie"); + const roleId = getCookiesDecrypt("urie"); + + const [modalOpen, setModalOpen] = useState(false); + const { id } = useParams() as { id: string }; + console.log(id); + const editor = useRef(null); + type ImageSchema = z.infer; + + const [selectedFiles, setSelectedFiles] = useState([]); + const taskId = Cookies.get("taskId"); + const scheduleId = Cookies.get("scheduleId"); + const scheduleType = Cookies.get("scheduleType"); + const [status, setStatus] = useState(""); + 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 [description, setDescription] = useState(""); + const [main, setMain] = useState([]); + const [detailThumb, setDetailThumb] = useState([]); + const [thumbsSwiper, setThumbsSwiper] = useState(null); + + const [selectedTarget, setSelectedTarget] = useState(""); + const [files, setFiles] = useState([]); + const [rejectedFiles, setRejectedFiles] = useState([]); + const [isMabesApprover, setIsMabesApprover] = useState(false); + + let fileTypeId = "4"; + + 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 + // } + // } + // }; + + useEffect(() => { + if ( + userLevelId != undefined && + roleId != undefined && + userLevelId == "216" && + roleId == "3" + ) { + setIsMabesApprover(true); + } + }, [userLevelId, roleId]); + + 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(() => { + async function initState() { + if (id) { + const response = await detailMedia(id); + const details = response.data?.data; + console.log("detail", details); + setFiles(details?.files); + setDetail(details); + setMain({ + type: details?.fileType.name, + url: details?.files[0]?.url, + names: details?.files[0]?.fileName, + format: details?.files[0]?.format, + }); + + if (details.publishedForObject) { + const publisherIds = details.publishedForObject.map( + (obj: any) => obj.id + ); + setSelectedPublishers(publisherIds); + } + + const matchingCategory = categories.find( + (category) => category.id === details.categoryId + ); + + if (matchingCategory) { + setSelectedTarget(matchingCategory.name); + } + + setSelectedTarget(details.categoryId); // Untuk dropdown + + const filesData = details.files || []; + const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) => + file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg" + ); + setDetailThumb(fileUrls); + } + } + initState(); + }, [refresh, setValue]); + + const actionApproval = (e: string) => { + setStatus(e); + setModalOpen(true); + setDescription(""); + }; + + const submit = async () => { + if ( + (description?.length > 1 && Number(status) == 3) || + Number(status) == 2 || + Number(status) == 4 + ) { + MySwal.fire({ + title: "Simpan Approval", + text: "", + icon: "warning", + showCancelButton: true, + cancelButtonColor: "#d33", + confirmButtonColor: "#3085d6", + confirmButtonText: "Simpan", + }).then((result) => { + if (result.isConfirmed) { + save(); + } + }); + } + }; + + async function save() { + const data = { + mediaUploadId: id, + statusId: status, + message: description, + files: [], + // files: isMabesApprover ? getPlacement() : [], + }; + + loading(); + const response = await submitApproval(data); + + if (response?.error) { + error(response.message); + return false; + } + + const dataReject = { + listFiles: rejectedFiles, + }; + + const resReject = await rejectFiles(dataReject); + + if (resReject?.error) { + error(resReject.message); + return false; + } + + close(); + + return false; + } + + function handleDeleteFileApproval(id: number) { + const selectedFiles = files.filter((file) => file.id != id); + setFiles(selectedFiles); + const rejects = rejectedFiles; + rejects.push(id); + setRejectedFiles(rejects); + } + const handleMain = ( + type: string, + url: string, + names: string, + format: string + ) => { + console.log("Test 3 :", type, url, names, format); + setMain({ + type, + url, + names, + format, + }); + return false; + }; + + const submitApprovalSuccesss = () => { + MySwal.fire({ + title: "Sukses", + text: "Data berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push("/in/contributor/content/image"); + }); + }; + + return ( +
+ {detail !== undefined ? ( +
+ +
+

Form Konten Foto

+
+ {/* Input Title */} +
+ + ( + + )} + /> + {errors.title?.message && ( +

+ {errors.title.message} +

+ )} +
+
+
+ + +
+
+ +
+ + ( + + )} + /> + {errors.description?.message && ( +

+ {errors.description.message} +

+ )} +
+ + +
+ + {detailThumb?.map((data: any) => ( + + {` + + ))} + +
+ + {detailThumb?.map((data: any) => ( + + {` + + ))} + +
+
+
+
+
+
+ +
+
+ + ( + + )} + /> + {errors.creatorName?.message && ( +

+ {errors.creatorName.message} +

+ )} +
+
+ +
+
+ +
+ {detail?.tags + ?.split(",") + .map((tag: string, index: number) => ( + + {tag.trim()} + + ))} +
+
+
+
+
+ +
+ handleCheckboxChange(5)} + /> + +
+
+ handleCheckboxChange(6)} + /> + +
+
+ handleCheckboxChange(7)} + /> + +
+
+ handleCheckboxChange(8)} + /> + +
+
+
+
+ +

Kotak Saran (0)

+
+
+

Keterangan:

+

{detail?.statusName}

+
+ {/* {detail?.isPublish == false ? ( +
+ +
+ ) : ( + "" + )} */} + {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? ( + Number(detail?.uploadedById) == Number(userId) ? ( + "" + ) : ( +
+ + + +
+ ) + ) : ( + "" + )} + + + + + Berikan Komentar + + {status == "2" + ? files?.map((file) => ( +
+ +
+
+ {file.fileName} + + + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+
+ )) + : ""} +
+