From 279edbd6cdfb950ba063b5d7737def0145836c22 Mon Sep 17 00:00:00 2001
From: Anang Yusman
Date: Sun, 12 Jan 2025 23:19:27 +0800
Subject: [PATCH] feat:update konten, update task, fix header admin
---
components/form/content/audio-update-form.tsx | 461 +++++++++++++---
components/form/content/image-form.tsx | 8 +-
components/form/content/image-update-form.tsx | 436 ++++++++++++++-
components/form/content/teks-update-form.tsx | 515 ++++++++++++++++--
components/form/content/video-update-form.tsx | 460 ++++++++++++++--
components/form/task/task-detail-form.tsx | 152 +++++-
components/form/task/task-edit-form.tsx | 170 +++++-
components/form/task/task-form.tsx | 8 +-
components/landing-page/navbar.tsx | 376 +++++++++++--
components/logo.tsx | 7 +-
components/partials/header/header-logo.tsx | 24 +-
components/partials/sidebar/menu/icon-nav.tsx | 192 ++++---
.../partials/sidebar/menu/sheet-menu.tsx | 80 +--
13 files changed, 2500 insertions(+), 389 deletions(-)
diff --git a/components/form/content/audio-update-form.tsx b/components/form/content/audio-update-form.tsx
index 0d441faa..d83fa363 100644
--- a/components/form/content/audio-update-form.tsx
+++ b/components/form/content/audio-update-form.tsx
@@ -1,5 +1,11 @@
"use client";
-import React, { ChangeEvent, useEffect, useRef, useState } from "react";
+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";
@@ -27,15 +33,22 @@ import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
+ uploadThumbnail,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
-import { MailIcon } from "lucide-react";
+import { CloudUpload, MailIcon } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
import ReactAudioPlayer from "react-audio-player";
import { FreeMode, Navigation, Thumbs } from "swiper/modules";
+import { useDropzone } from "react-dropzone";
+import Image from "next/image";
+import { Icon } from "@iconify/react/dist/iconify.js";
+import { error } from "@/lib/swal";
+import { getCsrfToken } from "@/service/auth";
+import { Upload } from "tus-js-client";
-const imageSchema = z.object({
+const audioSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
@@ -64,6 +77,10 @@ type Detail = {
tags: string;
};
+interface FileWithPreview extends File {
+ preview: string;
+}
+
export default function FormAudioUpdate() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@@ -71,7 +88,14 @@ export default function FormAudioUpdate() {
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
- type ImageSchema = z.infer;
+ type AudioSchema = 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");
@@ -87,22 +111,32 @@ export default function FormAudioUpdate() {
const [detailThumb, setDetailThumb] = useState([]);
const [thumbsSwiper, setThumbsSwiper] = useState(null);
const [selectedTarget, setSelectedTarget] = useState("");
+ const inputRef = useRef(null);
+ const [selectedOptions, setSelectedOptions] = useState<{
+ [fileId: number]: string;
+ }>({});
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
+ const [files, setFiles] = useState([]);
let fileTypeId = "4";
+ const { getRootProps, getInputProps } = useDropzone({
+ onDrop: (acceptedFiles) => {
+ setFiles(acceptedFiles.map((file) => Object.assign(file)));
+ },
+ });
const {
control,
handleSubmit,
setValue,
formState: { errors },
- } = useForm({
- resolver: zodResolver(imageSchema),
+ } = useForm({
+ resolver: zodResolver(audioSchema),
});
// const handleKeyDown = (e: any) => {
@@ -179,6 +213,10 @@ export default function FormAudioUpdate() {
setDetail(details);
+ if (details?.files) {
+ setFiles(details.files);
+ }
+
if (details.publishedForObject) {
const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id
@@ -206,7 +244,7 @@ export default function FormAudioUpdate() {
initState();
}, [refresh, setValue]);
- const save = async (data: ImageSchema) => {
+ const save = async (data: AudioSchema) => {
const requestData = {
...data,
id: detail?.id,
@@ -228,6 +266,34 @@ export default function FormAudioUpdate() {
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.",
@@ -239,7 +305,69 @@ export default function FormAudioUpdate() {
});
};
- const onSubmit = (data: ImageSchema) => {
+ 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: "false", // 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: AudioSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
@@ -255,6 +383,123 @@ export default function FormAudioUpdate() {
});
};
+ 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/audio");
+ }
+ }
+
+ const handleRemoveAllFiles = () => {
+ setFiles([]);
+ };
+
+ const renderFilePreview = (file: FileWithPreview) => {
+ if (file?.type?.startsWith("image")) {
+ return (
+
+ );
+ } else {
+ return ;
+ }
+ };
+
+ const handleRemoveFile = (file: FileWithPreview) => {
+ const uploadedFiles = files;
+ const filtered = uploadedFiles.filter((i) => i.name !== file.name);
+ setFiles([...filtered]);
+ };
+
+ const fileList = files.map((file) => (
+
+
+
{renderFilePreview(file)}
+
+
{file.name}
+
+ {Math.round(file.size / 100) / 10 > 1000 ? (
+ <>{(Math.round(file.size / 100) / 10000).toFixed(1)}>
+ ) : (
+ <>{(Math.round(file.size / 100) / 10).toFixed(1)}>
+ )}
+ {" kb"}
+
+
+
+
+
+
+ ));
+
+ const handleCheckboxChangeImage = (fileId: number, value: string) => {
+ setSelectedOptions((prev: any) => {
+ const currentSelections = prev[fileId] || [];
+ if (value === "all") {
+ // If "all" is clicked, toggle all options
+ if (currentSelections.includes("all")) {
+ return { ...prev, [fileId]: [] }; // Deselect all
+ }
+ return {
+ ...prev,
+ [fileId]: ["all", "nasional", "wilayah", "internasional"],
+ }; // Select all
+ } else {
+ // If any other checkbox is clicked, toggle that checkbox
+ const updatedSelections = currentSelections.includes(value)
+ ? currentSelections.filter((option: any) => option !== value)
+ : [...currentSelections, value];
+
+ // If all individual options are selected, include "all" automatically
+ const isAllSelected = ["nasional", "wilayah", "internasional"].every(
+ (opt) => updatedSelections.includes(opt)
+ );
+ return {
+ ...prev,
+ [fileId]: isAllSelected
+ ? ["all", ...updatedSelections]
+ : updatedSelections.filter((opt: any) => opt !== "all"),
+ };
+ }
+ });
+ };
+
return (
)}
-
-
-
- {detailThumb?.map((data: any) => {
- return (
-
-
-
- {/* */}
-
-
- );
- })}
-
-
- {/*
-
- {detailThumb?.map((data: any) => {
- return (
-
-
-
-
+
+
+ {/*
*/}
+
+
+
+
+
+
+ {/* Drop files here or click to upload. */}
+ Tarik file disini atau klik untuk upload.
+
+
+ ( Upload file dengan format .jpg, .jpeg, atau .png.
+ Ukuran maksimal 100mb.)
+
+
+
+ {files.length ? (
+
+ {fileList}
+
+
+
+
+
-
- );
- })}
-
-
*/}
+
+
+
+
+ ) : null}
+ {files.length > 0 && (
+
+
+
+ {files.map((file: any) => (
+
+

+
+
+ ))}
+
+
+ )}
+
diff --git a/components/form/content/image-form.tsx b/components/form/content/image-form.tsx
index a429a018..9920e898 100644
--- a/components/form/content/image-form.tsx
+++ b/components/form/content/image-form.tsx
@@ -538,12 +538,12 @@ export default function FormImage() {
// 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
+ "X-XSRF-TOKEN": csrfToken,
};
const upload = new Upload(file, {
@@ -559,8 +559,8 @@ export default function FormImage() {
isWatermark: "true", // hardcode
},
onBeforeRequest: function (req) {
- var xhr = req.getUnderlyingObject()
- xhr.withCredentials = true
+ var xhr = req.getUnderlyingObject();
+ xhr.withCredentials = true;
},
onError: async (e: any) => {
console.log("Error upload :", e);
diff --git a/components/form/content/image-update-form.tsx b/components/form/content/image-update-form.tsx
index ae938971..cf412bbe 100644
--- a/components/form/content/image-update-form.tsx
+++ b/components/form/content/image-update-form.tsx
@@ -1,5 +1,11 @@
"use client";
-import React, { ChangeEvent, useEffect, useRef, useState } from "react";
+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";
@@ -27,11 +33,18 @@ import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
+ uploadThumbnail,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
-import { MailIcon } from "lucide-react";
+import { CloudUpload, MailIcon } 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 } from "@/lib/swal";
+import { getCsrfToken } from "@/service/auth";
+import { Upload } from "tus-js-client";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@@ -69,6 +82,10 @@ const CustomEditor = dynamic(
{ ssr: false }
);
+interface FileWithPreview extends File {
+ preview: string;
+}
+
export default function FormImageUpdate() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@@ -78,6 +95,13 @@ export default function FormImageUpdate() {
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");
@@ -90,6 +114,13 @@ export default function FormImageUpdate() {
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 [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
@@ -101,6 +132,12 @@ export default function FormImageUpdate() {
let fileTypeId = "1";
+ const { getRootProps, getInputProps } = useDropzone({
+ onDrop: (acceptedFiles) => {
+ setFiles(acceptedFiles.map((file) => Object.assign(file)));
+ },
+ });
+
const {
control,
handleSubmit,
@@ -151,6 +188,19 @@ export default function FormImageUpdate() {
initState();
}, []);
+ const handleAddTag = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && e.currentTarget.value.trim()) {
+ e.preventDefault();
+ const newTag = e.currentTarget.value.trim();
+ if (!tags.includes(newTag)) {
+ setTags((prevTags) => [...prevTags, newTag]); // Add new tag
+ if (inputRef.current) {
+ inputRef.current.value = ""; // Clear input field
+ }
+ }
+ }
+ };
+
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
@@ -184,6 +234,10 @@ export default function FormImageUpdate() {
setDetail(details);
+ if (details?.files) {
+ setFiles(details.files);
+ }
+
if (details.publishedForObject) {
const publisherIds = details.publishedForObject.map(
(obj: any) => obj.id
@@ -227,6 +281,34 @@ export default function FormImageUpdate() {
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.",
@@ -238,6 +320,68 @@ export default function FormImageUpdate() {
});
};
+ 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",
@@ -254,6 +398,123 @@ export default function FormImageUpdate() {
});
};
+ 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 handleRemoveAllFiles = () => {
+ setFiles([]);
+ };
+
+ const renderFilePreview = (file: FileWithPreview) => {
+ if (file?.type?.startsWith("image")) {
+ return (
+
+ );
+ } else {
+ return ;
+ }
+ };
+
+ const handleRemoveFile = (file: FileWithPreview) => {
+ const uploadedFiles = files;
+ const filtered = uploadedFiles.filter((i) => i.name !== file.name);
+ setFiles([...filtered]);
+ };
+
+ const fileList = files.map((file) => (
+
+
+
{renderFilePreview(file)}
+
+
{file.name}
+
+ {Math.round(file.size / 100) / 10 > 1000 ? (
+ <>{(Math.round(file.size / 100) / 10000).toFixed(1)}>
+ ) : (
+ <>{(Math.round(file.size / 100) / 10).toFixed(1)}>
+ )}
+ {" kb"}
+
+
+
+
+
+
+ ));
+
+ const handleCheckboxChangeImage = (fileId: number, value: string) => {
+ setSelectedOptions((prev: any) => {
+ const currentSelections = prev[fileId] || [];
+ if (value === "all") {
+ // If "all" is clicked, toggle all options
+ if (currentSelections.includes("all")) {
+ return { ...prev, [fileId]: [] }; // Deselect all
+ }
+ return {
+ ...prev,
+ [fileId]: ["all", "nasional", "wilayah", "internasional"],
+ }; // Select all
+ } else {
+ // If any other checkbox is clicked, toggle that checkbox
+ const updatedSelections = currentSelections.includes(value)
+ ? currentSelections.filter((option: any) => option !== value)
+ : [...currentSelections, value];
+
+ // If all individual options are selected, include "all" automatically
+ const isAllSelected = ["nasional", "wilayah", "internasional"].every(
+ (opt) => updatedSelections.includes(opt)
+ );
+ return {
+ ...prev,
+ [fileId]: isAllSelected
+ ? ["all", ...updatedSelections]
+ : updatedSelections.filter((opt: any) => opt !== "all"),
+ };
+ }
+ });
+ };
+
return (