feat:update konten, update task, fix header admin
This commit is contained in:
parent
cd0b020bf7
commit
279edbd6cd
|
|
@ -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<typeof imageSchema>;
|
||||
type AudioSchema = z.infer<typeof audioSchema>;
|
||||
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
let uploadPersen = 0;
|
||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
|
|
@ -87,22 +111,32 @@ export default function FormAudioUpdate() {
|
|||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
}>({});
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
|
||||
let fileTypeId = "4";
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: (acceptedFiles) => {
|
||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||
},
|
||||
});
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<ImageSchema>({
|
||||
resolver: zodResolver(imageSchema),
|
||||
} = useForm<AudioSchema>({
|
||||
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 (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={file.name}
|
||||
src={URL.createObjectURL(file)}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Icon icon="tabler:file-description" />;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: FileWithPreview) => {
|
||||
const uploadedFiles = files;
|
||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
|
||||
)}
|
||||
{" kb"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
||||
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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -329,62 +574,152 @@ export default function FormAudioUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Label className="text-xl text-black">File Mediaaa</Label>
|
||||
<div className="w-full">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => {
|
||||
return (
|
||||
<SwiperSlide key={data.id}>
|
||||
<div className="relative">
|
||||
<ReactAudioPlayer src={data} autoPlay controls />
|
||||
{/* <button
|
||||
className="absolute bottom-2 left-2 text-white bg-black p-1 rounded"
|
||||
onClick={() => handleAudioPlayPause(data)}
|
||||
>
|
||||
{audioPlaying === data ? "Pause" : "Play"}
|
||||
</button> */}
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
|
||||
{/* <div className="mt-2">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailThumb?.map((data: any) => {
|
||||
return (
|
||||
<SwiperSlide key={data.id}>
|
||||
<div className="relative">
|
||||
<ReactAudioPlayer src={data} autoPlay controls />
|
||||
<button
|
||||
className="absolute bottom-2 left-2 text-white bg-black p-1 rounded"
|
||||
onClick={() =>
|
||||
handleAudioPlayPause(data.secondaryUrl)
|
||||
}
|
||||
>
|
||||
{audioPlaying === data.secondaryUrl
|
||||
? "Pause"
|
||||
: "Play"}
|
||||
</button>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .jpg, .jpeg, atau .png.
|
||||
Ukuran maksimal 100mb.)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
);
|
||||
})}
|
||||
</Swiper>
|
||||
</div> */}
|
||||
</div>
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center border p-2 rounded-md"
|
||||
>
|
||||
<img
|
||||
src={file.thumbnailFileUrl}
|
||||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("all")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"all"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("nasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"nasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Nasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("wilayah")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"wilayah"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Wilayah</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("internasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"internasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Internasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<typeof imageSchema>;
|
||||
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
let uploadPersen = 0;
|
||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
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<number[]>([]);
|
||||
const [articleBody, setArticleBody] = useState<string>("");
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
const [filesTemp, setFilesTemp] = useState<File[]>([]);
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={file.name}
|
||||
src={URL.createObjectURL(file)}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Icon icon="tabler:file-description" />;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: FileWithPreview) => {
|
||||
const uploadedFiles = files;
|
||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
|
||||
)}
|
||||
{" kb"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
||||
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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -326,6 +587,153 @@ export default function FormImageUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .jpg, .jpeg, atau .png.
|
||||
Ukuran maksimal 100mb.)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center border p-2 rounded-md"
|
||||
>
|
||||
<img
|
||||
src={file.thumbnailFileUrl}
|
||||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("all")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"all"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("nasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"nasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Nasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("wilayah")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"wilayah"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Wilayah</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("internasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"internasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Internasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
@ -367,6 +775,30 @@ export default function FormImageUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3 ">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||
>
|
||||
{tag}{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
|
|
|
|||
|
|
@ -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,12 +33,19 @@ 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 { useDropzone } from "react-dropzone";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import Image from "next/image";
|
||||
import { error } from "@/lib/swal";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
|
||||
const imageSchema = z.object({
|
||||
const teksSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z
|
||||
.string()
|
||||
|
|
@ -55,12 +68,25 @@ type Detail = {
|
|||
id: number;
|
||||
name: string;
|
||||
};
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
creatorName: string;
|
||||
categoryName: string;
|
||||
thumbnailLink: string;
|
||||
tags: string;
|
||||
};
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
}
|
||||
|
||||
type Option = {
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export default function FormTeksUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -68,7 +94,14 @@ export default function FormTeksUpdate() {
|
|||
const { id } = useParams() as { id: string };
|
||||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type ImageSchema = z.infer<typeof imageSchema>;
|
||||
type TeksSchema = z.infer<typeof teksSchema>;
|
||||
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
let uploadPersen = 0;
|
||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
|
|
@ -82,6 +115,10 @@ export default function FormTeksUpdate() {
|
|||
const [refresh, setRefresh] = useState(false);
|
||||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
}>({});
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -89,16 +126,31 @@ export default function FormTeksUpdate() {
|
|||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
|
||||
let fileTypeId = "3";
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: (acceptedFiles) => {
|
||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||
},
|
||||
});
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
{ id: "5", label: "UMUM" },
|
||||
{ id: "6", label: "JOURNALIS" },
|
||||
{ id: "7", label: "POLRI" },
|
||||
{ id: "8", label: "KSP" },
|
||||
];
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<ImageSchema>({
|
||||
resolver: zodResolver(imageSchema),
|
||||
} = useForm<TeksSchema>({
|
||||
resolver: zodResolver(teksSchema),
|
||||
});
|
||||
|
||||
// const handleKeyDown = (e: any) => {
|
||||
|
|
@ -128,11 +180,11 @@ export default function FormTeksUpdate() {
|
|||
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
// const handleCheckboxChange = (id: number) => {
|
||||
// setSelectedPublishers((prev) =>
|
||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
// );
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
|
|
@ -175,6 +227,10 @@ export default function FormTeksUpdate() {
|
|||
|
||||
setDetail(details);
|
||||
|
||||
if (details?.files) {
|
||||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
|
|
@ -196,7 +252,34 @@ export default function FormTeksUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const save = async (data: ImageSchema) => {
|
||||
const handleCheckboxChange = (id: string): void => {
|
||||
if (id === "all") {
|
||||
if (publishedFor.includes("all")) {
|
||||
// Uncheck all checkboxes
|
||||
setPublishedFor([]);
|
||||
} else {
|
||||
// Select all checkboxes
|
||||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
.map((opt: any) => opt.id)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const updatedPublishedFor = publishedFor.includes(id)
|
||||
? publishedFor.filter((item) => item !== id)
|
||||
: [...publishedFor, id];
|
||||
|
||||
// Remove "all" if any checkbox is unchecked
|
||||
if (publishedFor.includes("all") && id !== "all") {
|
||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
||||
} else {
|
||||
setPublishedFor(updatedPublishedFor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const save = async (data: TeksSchema) => {
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -208,7 +291,7 @@ export default function FormTeksUpdate() {
|
|||
subCategoryId: selectedTarget,
|
||||
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
statusId: "1",
|
||||
publishedFor: "6",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: "siap",
|
||||
isYoutube: false,
|
||||
|
|
@ -218,6 +301,34 @@ export default function FormTeksUpdate() {
|
|||
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.",
|
||||
|
|
@ -229,7 +340,69 @@ export default function FormTeksUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
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: "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: TeksSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
|
|
@ -245,6 +418,123 @@ export default function FormTeksUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
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/teks");
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveAllFiles = () => {
|
||||
setFiles([]);
|
||||
};
|
||||
|
||||
const renderFilePreview = (file: FileWithPreview) => {
|
||||
if (file?.type?.startsWith("image")) {
|
||||
return (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={file.name}
|
||||
src={URL.createObjectURL(file)}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Icon icon="tabler:file-description" />;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: FileWithPreview) => {
|
||||
const uploadedFiles = files;
|
||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
|
||||
)}
|
||||
{" kb"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
||||
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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -300,7 +590,6 @@ export default function FormTeksUpdate() {
|
|||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-3">
|
||||
<Label>Deskripsi</Label>
|
||||
<Controller
|
||||
|
|
@ -321,6 +610,153 @@ export default function FormTeksUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .doc, .docx, .pdf, .ppt,
|
||||
atau .pptx Ukuran maksimal 100mb.)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center border p-2 rounded-md"
|
||||
>
|
||||
<img
|
||||
src={file.thumbnailFileUrl}
|
||||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("all")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"all"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("nasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"nasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Nasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("wilayah")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"wilayah"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Wilayah</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("internasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"internasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Internasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
@ -377,38 +813,23 @@ export default function FormTeksUpdate() {
|
|||
<div className="px-3 py-3">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Label>Target Publish</Label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="5"
|
||||
checked={selectedPublishers.includes(5)}
|
||||
onChange={() => handleCheckboxChange(5)}
|
||||
/>
|
||||
<Label htmlFor="5">UMUM</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="6"
|
||||
checked={selectedPublishers.includes(6)}
|
||||
onChange={() => handleCheckboxChange(6)}
|
||||
/>
|
||||
<Label htmlFor="6">JOURNALIS</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="7"
|
||||
checked={selectedPublishers.includes(7)}
|
||||
onChange={() => handleCheckboxChange(7)}
|
||||
/>
|
||||
<Label htmlFor="7">POLRI</Label>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id="8"
|
||||
checked={selectedPublishers.includes(8)}
|
||||
onChange={() => handleCheckboxChange(8)}
|
||||
/>
|
||||
<Label htmlFor="8">KSP</Label>
|
||||
</div>
|
||||
{options.map((option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
value={detail?.publishedForObject.name}
|
||||
checked={
|
||||
option.id === "all"
|
||||
? publishedFor.length ===
|
||||
options.filter((opt: any) => opt.id !== "all")
|
||||
.length
|
||||
: publishedFor.includes(option.id)
|
||||
}
|
||||
onCheckedChange={() => handleCheckboxChange(option.id)}
|
||||
/>
|
||||
<Label htmlFor={option.id}>{option.label}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
|
||||
|
|
|
|||
|
|
@ -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,10 +33,11 @@ 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 "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -40,8 +47,15 @@ import "swiper/css/thumbs";
|
|||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
||||
import { files } from "@/app/[locale]/(protected)/app/projects/[id]/data";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import Image from "next/image";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Upload } from "tus-js-client";
|
||||
import { getCsrfToken } from "@/service/auth";
|
||||
import { error } from "@/lib/swal";
|
||||
|
||||
const imageSchema = z.object({
|
||||
const videoSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z
|
||||
.string()
|
||||
|
|
@ -70,6 +84,10 @@ type Detail = {
|
|||
tags: string;
|
||||
};
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
}
|
||||
|
||||
export default function FormVideoUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -77,7 +95,14 @@ export default function FormVideoUpdate() {
|
|||
const { id } = useParams() as { id: string };
|
||||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type ImageSchema = z.infer<typeof imageSchema>;
|
||||
type VideoSchema = z.infer<typeof videoSchema>;
|
||||
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
let uploadPersen = 0;
|
||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
|
|
@ -93,6 +118,7 @@ export default function FormVideoUpdate() {
|
|||
const [detailVideo, setDetailVideo] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
allUnit: false,
|
||||
|
|
@ -100,16 +126,26 @@ export default function FormVideoUpdate() {
|
|||
polda: false,
|
||||
polres: false,
|
||||
});
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
}>({});
|
||||
|
||||
let fileTypeId = "2";
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: (acceptedFiles) => {
|
||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<ImageSchema>({
|
||||
resolver: zodResolver(imageSchema),
|
||||
} = useForm<VideoSchema>({
|
||||
resolver: zodResolver(videoSchema),
|
||||
});
|
||||
|
||||
// const handleKeyDown = (e: any) => {
|
||||
|
|
@ -186,6 +222,10 @@ export default function FormVideoUpdate() {
|
|||
|
||||
setDetail(details);
|
||||
|
||||
if (details?.files) {
|
||||
setFiles(details.files);
|
||||
}
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
|
|
@ -213,7 +253,7 @@ export default function FormVideoUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const save = async (data: ImageSchema) => {
|
||||
const save = async (data: VideoSchema) => {
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
|
|
@ -235,6 +275,44 @@ export default function FormVideoUpdate() {
|
|||
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");
|
||||
});
|
||||
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
text: "Data berhasil disimpan.",
|
||||
|
|
@ -246,7 +324,68 @@ export default function FormVideoUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
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: VideoSchema) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||
|
|
@ -262,6 +401,123 @@ export default function FormVideoUpdate() {
|
|||
});
|
||||
};
|
||||
|
||||
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/video");
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveAllFiles = () => {
|
||||
setFiles([]);
|
||||
};
|
||||
|
||||
const renderFilePreview = (file: FileWithPreview) => {
|
||||
if (file?.type?.startsWith("image")) {
|
||||
return (
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
alt={file.name}
|
||||
src={URL.createObjectURL(file)}
|
||||
className=" rounded border p-0.5"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Icon icon="tabler:file-description" />;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (file: FileWithPreview) => {
|
||||
const uploadedFiles = files;
|
||||
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||
setFiles([...filtered]);
|
||||
};
|
||||
|
||||
const fileList = files.map((file) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="file-preview">{renderFilePreview(file)}</div>
|
||||
<div>
|
||||
<div className=" text-sm text-card-foreground">{file.name}</div>
|
||||
<div className=" text-xs font-light text-muted-foreground">
|
||||
{Math.round(file.size / 100) / 10 > 1000 ? (
|
||||
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
|
||||
) : (
|
||||
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
|
||||
)}
|
||||
{" kb"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
color="destructive"
|
||||
variant="outline"
|
||||
className=" border-none rounded-full"
|
||||
onClick={() => handleRemoveFile(file)}
|
||||
>
|
||||
<Icon icon="tabler:x" className=" h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
));
|
||||
|
||||
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 (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -338,48 +594,152 @@ export default function FormVideoUpdate() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Label className="text-xl text-black">File Mediaa</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<video
|
||||
className="object-fill h-full w-full"
|
||||
src={data}
|
||||
controls
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
modules={[Pagination, Thumbs]}
|
||||
// className="mySwiper2"
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<video
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={data}
|
||||
muted
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
<div className="py-3">
|
||||
<Label>Pilih File</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
Tarik file disini atau klik untuk upload.
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
( Upload file dengan format .jpg, .jpeg, atau .png.
|
||||
Ukuran maksimal 100mb.)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
<div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
Remove All
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-lg font-semibold">File</Label>
|
||||
<div className="grid gap-4">
|
||||
{files.map((file: any) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center border p-2 rounded-md"
|
||||
>
|
||||
<img
|
||||
src={file.thumbnailFileUrl}
|
||||
alt={file.fileName}
|
||||
className="w-16 h-16 object-cover rounded-md mr-4"
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center ">
|
||||
<div className="flex-grow">
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
<a
|
||||
href={file.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 text-sm"
|
||||
>
|
||||
Lihat File
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("all")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"all"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Semua</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("nasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"nasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Nasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("wilayah")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"wilayah"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Wilayah</span>
|
||||
</Label>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedOptions[
|
||||
file.id
|
||||
]?.includes("internasional")}
|
||||
onChange={() =>
|
||||
handleCheckboxChangeImage(
|
||||
file.id,
|
||||
"internasional"
|
||||
)
|
||||
}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<span>Internasional</span>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ import { close, error, loading } from "@/lib/swal";
|
|||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { successCallback } from "@/config/swal";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||
|
||||
const taskSchema = z.object({
|
||||
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -145,6 +147,10 @@ interface AcceptanceData {
|
|||
};
|
||||
}
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
}
|
||||
|
||||
export default function FormTaskDetail() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -187,11 +193,24 @@ export default function FormTaskDetail() {
|
|||
const [acceptAcceptance, setAcceptAcceptance] = useState<AcceptanceData[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
|
||||
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
|
||||
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
|
||||
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
|
||||
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
|
||||
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
|
||||
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
|
||||
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
|
||||
const [voiceNoteLink, setVoiceNoteLink] = useState("");
|
||||
const [statusAcceptance, setStatusAcceptance] = useState();
|
||||
const [modalType, setModalType] = useState<"terkirim" | "diterima" | "">("");
|
||||
const [Isloading, setLoading] = useState<boolean>(false);
|
||||
const [refreshAcceptance, setRefreshAcceptance] = useState<boolean>(false);
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
|
||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
|
|
@ -583,7 +602,7 @@ export default function FormTaskDetail() {
|
|||
};
|
||||
|
||||
const getModalContent = (type: "terkirim" | "diterima") => (
|
||||
<div className="overflow-x-auto ">
|
||||
<div className="overflow-x-auto overflow-y-auto ">
|
||||
{Isloading ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
|
|
@ -674,6 +693,49 @@ export default function FormTaskDetail() {
|
|||
});
|
||||
}
|
||||
|
||||
const addAudioElement = (blob: Blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const audio = document.createElement("audio");
|
||||
audio.src = url;
|
||||
audio.controls = true;
|
||||
document.body.appendChild(audio);
|
||||
|
||||
// Convert Blob to File
|
||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
||||
setAudioFile(file);
|
||||
};
|
||||
const handleDeleteAudio = () => {
|
||||
// Remove the audio file by setting state to null
|
||||
setAudioFile(null);
|
||||
const audioElements = document.querySelectorAll("audio");
|
||||
audioElements.forEach((audio) => audio.remove());
|
||||
};
|
||||
|
||||
const onRecordingStart = () => {
|
||||
setIsRecording(true);
|
||||
|
||||
const countdown = setInterval(() => {
|
||||
setTimer((prevTimer) => {
|
||||
if (prevTimer <= 1) {
|
||||
clearInterval(countdown);
|
||||
return 0;
|
||||
}
|
||||
return prevTimer - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
if (isRecording) {
|
||||
handleStopRecording();
|
||||
}
|
||||
}, 120000);
|
||||
};
|
||||
|
||||
const handleStopRecording = () => {
|
||||
setIsRecording(false);
|
||||
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -711,7 +773,7 @@ export default function FormTaskDetail() {
|
|||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Detail Status Penugasan</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
|
@ -991,6 +1053,92 @@ export default function FormTaskDetail() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
"mov/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Voice Note</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
}}
|
||||
downloadOnSavePress={true}
|
||||
downloadFileExtension="webm"
|
||||
/>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp3/*": [],
|
||||
"wav/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleDeleteAudio}
|
||||
size="sm"
|
||||
color="destructive"
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="">
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import {
|
|||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import FileUploader from "../shared/file-uploader";
|
||||
import { AudioRecorder } from "react-audio-voice-recorder";
|
||||
|
||||
const taskSchema = z.object({
|
||||
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -63,6 +65,10 @@ export type taskDetail = {
|
|||
is_active: string;
|
||||
};
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
preview: string;
|
||||
}
|
||||
|
||||
export default function FormTaskEdit() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
|
@ -80,8 +86,15 @@ export default function FormTaskEdit() {
|
|||
text: false,
|
||||
});
|
||||
|
||||
// const [assignmentType, setAssignmentType] = useState("mediahub");
|
||||
// const [assignmentCategory, setAssignmentCategory] = useState("publication");
|
||||
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
|
||||
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
|
||||
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
|
||||
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
|
||||
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
|
||||
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
|
||||
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
|
||||
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
|
||||
const [voiceNoteLink, setVoiceNoteLink] = useState("");
|
||||
const [mainType, setMainType] = useState<string>("1");
|
||||
const [taskType, setTaskType] = useState<string>("atensi-khusus");
|
||||
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
|
||||
|
|
@ -93,6 +106,9 @@ export default function FormTaskEdit() {
|
|||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
|
||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
|
|
@ -306,6 +322,49 @@ export default function FormTaskEdit() {
|
|||
}));
|
||||
};
|
||||
|
||||
const addAudioElement = (blob: Blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const audio = document.createElement("audio");
|
||||
audio.src = url;
|
||||
audio.controls = true;
|
||||
document.body.appendChild(audio);
|
||||
|
||||
// Convert Blob to File
|
||||
const file = new File([blob], "voiceNote.webm", { type: "audio/webm" });
|
||||
setAudioFile(file);
|
||||
};
|
||||
const handleDeleteAudio = () => {
|
||||
// Remove the audio file by setting state to null
|
||||
setAudioFile(null);
|
||||
const audioElements = document.querySelectorAll("audio");
|
||||
audioElements.forEach((audio) => audio.remove());
|
||||
};
|
||||
|
||||
const onRecordingStart = () => {
|
||||
setIsRecording(true);
|
||||
|
||||
const countdown = setInterval(() => {
|
||||
setTimer((prevTimer) => {
|
||||
if (prevTimer <= 1) {
|
||||
clearInterval(countdown);
|
||||
return 0;
|
||||
}
|
||||
return prevTimer - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
if (isRecording) {
|
||||
handleStopRecording();
|
||||
}
|
||||
}, 120000);
|
||||
};
|
||||
|
||||
const handleStopRecording = () => {
|
||||
setIsRecording(false);
|
||||
setTimer(120); // Reset the timer to 2 minutes for the next recording
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="px-6 py-6">
|
||||
|
|
@ -537,27 +596,6 @@ export default function FormTaskEdit() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Broadcast </Label>
|
||||
<RadioGroup
|
||||
value={broadcastType} // Nilai terpilih diambil dari state broadcastType
|
||||
onValueChange={(value) => setBroadcastType(value)} // Mengatur nilai saat radio berubah
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="all" id="all" />
|
||||
<Label htmlFor="all">Semua</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="email" id="email" />
|
||||
<Label htmlFor="email">Email Blast</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="whatsapp" id="whatsapp" />
|
||||
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Label>Narasi Penugasan</Label>
|
||||
<Controller
|
||||
|
|
@ -578,6 +616,92 @@ export default function FormTaskEdit() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5 mt-5">
|
||||
<Label htmlFor="attachments">Lampiran</Label>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label>Video</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
"mov/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Foto</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"image/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .png, .jpg, atau .jpeg."
|
||||
onDrop={(files) => setImageFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .pdf."
|
||||
onDrop={(files) => setTextFiles(files)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Voice Note</Label>
|
||||
<AudioRecorder
|
||||
onRecordingComplete={addAudioElement}
|
||||
audioTrackConstraints={{
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
}}
|
||||
downloadOnSavePress={true}
|
||||
downloadFileExtension="webm"
|
||||
/>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp3/*": [],
|
||||
"wav/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
onDrop={(files) => setAudioFiles(files)}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
{audioFile && (
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<p>Voice note ready to submit: {audioFile.name}</p>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleDeleteAudio}
|
||||
size="sm"
|
||||
color="destructive"
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
|
||||
{/* Display remaining time */}
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="voiceNoteLink">Link Berita</Label>
|
||||
<Input
|
||||
id="voiceNoteLink"
|
||||
type="url"
|
||||
placeholder="Masukkan link voice note"
|
||||
value={voiceNoteLink}
|
||||
onChange={(e) => setVoiceNoteLink(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
|
|
|
|||
|
|
@ -306,23 +306,21 @@ export default function FormTask() {
|
|||
const onRecordingStart = () => {
|
||||
setIsRecording(true);
|
||||
|
||||
// Start a timer that stops the recording after 2 minutes (120 seconds)
|
||||
const countdown = setInterval(() => {
|
||||
setTimer((prevTimer) => {
|
||||
if (prevTimer <= 1) {
|
||||
clearInterval(countdown); // Stop the timer when it reaches 0
|
||||
clearInterval(countdown);
|
||||
return 0;
|
||||
}
|
||||
return prevTimer - 1;
|
||||
});
|
||||
}, 1000); // Update every second
|
||||
}, 1000);
|
||||
|
||||
// Automatically stop recording after 2 minutes
|
||||
setTimeout(() => {
|
||||
if (isRecording) {
|
||||
handleStopRecording();
|
||||
}
|
||||
}, 120000); // Stop after 2 minutes (120,000 ms)
|
||||
}, 120000);
|
||||
};
|
||||
|
||||
const handleStopRecording = () => {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,23 @@ import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi";
|
|||
import { useParams, usePathname } from "next/navigation";
|
||||
import { generateLocalizedPath } from "@/utils/globals";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../ui/dropdown-menu";
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "@/components/ui/navigation-menu";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
import Image from "next/image";
|
||||
import { Icon } from "../ui/icon";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
|
|
@ -17,7 +32,16 @@ import { useTranslations } from "next-intl";
|
|||
import { useRouter } from "@/i18n/routing";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import LocalSwitcher from "../partials/header/locale-switcher";
|
||||
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { listRole } from "@/service/landing/landing";
|
||||
|
||||
type Detail = {
|
||||
|
|
@ -54,7 +78,11 @@ const Navbar = () => {
|
|||
const [menuActive, setMenuActive] = useState<string>();
|
||||
const [category, setCategory] = useState<any>();
|
||||
|
||||
let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/";
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "/";
|
||||
|
||||
let active = "";
|
||||
let menu = "";
|
||||
|
|
@ -141,18 +169,41 @@ const Navbar = () => {
|
|||
<div className="flex items-center justify-between px-4 lg:px-20 py-4 gap-3">
|
||||
{/* Logo */}
|
||||
<Link href={prefixPath} className="flex items-center">
|
||||
<img src="/assets/mediahub-logo.gif" alt="Media Hub Logo" className="object-contain h-20" />
|
||||
<img
|
||||
src="/assets/mediahub-logo.gif"
|
||||
alt="Media Hub Logo"
|
||||
className="object-contain h-20"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
{/* Mobile Menu Toggle */}
|
||||
<button className="text-black dark:text-white right-0 size-20 h-10 w-10 lg:hidden" onClick={() => setMenuOpen(!menuOpen)}>
|
||||
<button
|
||||
className="text-black dark:text-white right-0 size-20 h-10 w-10 lg:hidden"
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
>
|
||||
{menuOpen ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="#000" d="m13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29l-4.3 4.29a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l4.29-4.3l4.29 4.3a1 1 0 0 0 1.42 0a1 1 0 0 0 0-1.42Z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="#000"
|
||||
d="m13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29l-4.3 4.29a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l4.29-4.3l4.29 4.3a1 1 0 0 0 1.42 0a1 1 0 0 0 0-1.42Z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="#000" d="M4 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m1 5a1 1 0 1 0 0 2h14a1 1 0 1 0 0-2z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="#000"
|
||||
d="M4 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m1 5a1 1 0 1 0 0 2h14a1 1 0 1 0 0-2z"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
|
@ -165,7 +216,14 @@ const Navbar = () => {
|
|||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger className="">
|
||||
<a className="dark:text-white text-black flex flex-row justify-center items-center cursor-pointer">
|
||||
<svg className="mx-2 dark:" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="mx-2 dark:"
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20 7.5H5C4.6023 7.5004 4.221 7.65856 3.93978 7.93978C3.65856 8.221 3.5004 8.6023 3.5 9V19.5C3.5004 19.8977 3.65856 20.279 3.93978 20.5602C4.221 20.8414 4.6023 20.9996 5 21H20C20.3977 20.9996 20.779 20.8414 21.0602 20.5602C21.3414 20.279 21.4996 19.8977 21.5 19.5V9C21.4996 8.6023 21.3414 8.221 21.0602 7.93978C20.779 7.65856 20.3977 7.5004 20 7.5ZM10.25 17.25V11.25L15.5 14.25L10.25 17.25ZM5 4.5H20V6H5V4.5ZM6.5 1.5H18.5V3H6.5V1.5Z"
|
||||
fill="currentColor"
|
||||
|
|
@ -175,13 +233,19 @@ const Navbar = () => {
|
|||
</a>
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent className=" rounded-md overflow-hidden w-">
|
||||
<NavigationMenuLink onClick={() => router.push(prefixPath+"/image/filter")} className="flex place-items-start gap-1.5 p-2">
|
||||
<NavigationMenuLink
|
||||
onClick={() => router.push(prefixPath + "/image/filter")}
|
||||
className="flex place-items-start gap-1.5 p-2"
|
||||
>
|
||||
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
|
||||
<FiImage className="mr-2" />
|
||||
{t("image")}
|
||||
</p>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink onClick={() => router.push(prefixPath+"/video/filter")} className="flex items-start gap-1.5 p-2 ">
|
||||
<NavigationMenuLink
|
||||
onClick={() => router.push(prefixPath + "/video/filter")}
|
||||
className="flex items-start gap-1.5 p-2 "
|
||||
>
|
||||
{pathname?.split("/")[1] == "in" ? (
|
||||
<>
|
||||
<p className="text-slate-600 text-sm dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-0 py-2 cursor-pointer">
|
||||
|
|
@ -202,13 +266,19 @@ const Navbar = () => {
|
|||
{t("video")}
|
||||
</p> */}
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink onClick={() => router.push(prefixPath+"/document/filter")} className="flex place-items-start gap-1.5 p-2">
|
||||
<NavigationMenuLink
|
||||
onClick={() => router.push(prefixPath + "/document/filter")}
|
||||
className="flex place-items-start gap-1.5 p-2"
|
||||
>
|
||||
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
|
||||
<FiFile className="mr-2" />
|
||||
{t("text")}
|
||||
</p>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink onClick={() => router.push(prefixPath+"/audio/filter")} className="flex place-items-start gap-1.5 p-2 ">
|
||||
<NavigationMenuLink
|
||||
onClick={() => router.push(prefixPath + "/audio/filter")}
|
||||
className="flex place-items-start gap-1.5 p-2 "
|
||||
>
|
||||
<p className="text-slate-600 dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
|
||||
<FiMusic className="mr-2" />
|
||||
{t("audio")}{" "}
|
||||
|
|
@ -217,10 +287,17 @@ const Navbar = () => {
|
|||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link href={prefixPath+"/schedule"} legacyBehavior passHref>
|
||||
<Link href={prefixPath + "/schedule"} legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
<span>
|
||||
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="mr-2"
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.5 4H18.5V3C18.5 2.4 18.1 2 17.5 2C16.9 2 16.5 2.4 16.5 3V4H8.5V3C8.5 2.4 8.1 2 7.5 2C6.9 2 6.5 2.4 6.5 3V4H5.5C3.8 4 2.5 5.3 2.5 7V8H22.5V7C22.5 5.3 21.2 4 19.5 4ZM2.5 19C2.5 20.7 3.8 22 5.5 22H19.5C21.2 22 22.5 20.7 22.5 19V10H2.5V19ZM17.5 12C18.1 12 18.5 12.4 18.5 13C18.5 13.6 18.1 14 17.5 14C16.9 14 16.5 13.6 16.5 13C16.5 12.4 16.9 12 17.5 12ZM17.5 16C18.1 16 18.5 16.4 18.5 17C18.5 17.6 18.1 18 17.5 18C16.9 18 16.5 17.6 16.5 17C16.5 16.4 16.9 16 17.5 16ZM12.5 12C13.1 12 13.5 12.4 13.5 13C13.5 13.6 13.1 14 12.5 14C11.9 14 11.5 13.6 11.5 13C11.5 12.4 11.9 12 12.5 12ZM12.5 16C13.1 16 13.5 16.4 13.5 17C13.5 17.6 13.1 18 12.5 18C11.9 18 11.5 17.6 11.5 17C11.5 16.4 11.9 16 12.5 16ZM7.5 12C8.1 12 8.5 12.4 8.5 13C8.5 13.6 8.1 14 7.5 14C6.9 14 6.5 13.6 6.5 13C6.5 12.4 6.9 12 7.5 12ZM7.5 16C8.1 16 8.5 16.4 8.5 17C8.5 17.6 8.1 18 7.5 18C6.9 18 6.5 17.6 6.5 17C6.5 16.4 6.9 16 7.5 16Z"
|
||||
fill="currentColor"
|
||||
|
|
@ -232,10 +309,17 @@ const Navbar = () => {
|
|||
</Link>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link href={prefixPath+"/indeks"} legacyBehavior passHref>
|
||||
<Link href={prefixPath + "/indeks"} legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
<span>
|
||||
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="mr-2"
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
|
|
@ -257,7 +341,10 @@ const Navbar = () => {
|
|||
</Link>
|
||||
<div className="flex items-center space-x-1 ">
|
||||
<a href="https://tvradio.polri.go.id/">
|
||||
<img src="/assets/polriTv.png" className="object-contain h-10 flex-auto " />
|
||||
<img
|
||||
src="/assets/polriTv.png"
|
||||
className="object-contain h-10 flex-auto "
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
@ -279,7 +366,12 @@ const Navbar = () => {
|
|||
className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white"
|
||||
/>
|
||||
<span className="absolute left-4 top-1/2 transform -translate-y-1/2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
|
|
@ -335,15 +427,28 @@ const Navbar = () => {
|
|||
)}
|
||||
</div> */}
|
||||
|
||||
{roleId === "5" || roleId === "6" || roleId === "7" || roleId === "8" ? (
|
||||
{roleId === "5" ||
|
||||
roleId === "6" ||
|
||||
roleId === "7" ||
|
||||
roleId === "8" ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="cursor-pointer">
|
||||
{detail !== undefined ? (
|
||||
<div className="flex items-center gap-3 text-default-800">
|
||||
<Image src={"/assets/avatar-profile.png"} alt={"Image"} width={36} height={36} className="rounded-full" />
|
||||
<Image
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={"Image"}
|
||||
width={36}
|
||||
height={36}
|
||||
className="rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium capitalize lg:block hidden whitespace-nowrap overflow-hidden truncate">{detail?.fullname}</div>
|
||||
<p className="text-xs whitespace-nowrap overflow-hidden truncate">({detail?.fullname})</p>
|
||||
<div className="text-sm font-medium capitalize lg:block hidden whitespace-nowrap overflow-hidden truncate">
|
||||
{detail?.fullname}
|
||||
</div>
|
||||
<p className="text-xs whitespace-nowrap overflow-hidden truncate">
|
||||
({detail?.fullname})
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-base me-2.5 lg:inline-block hidden">
|
||||
<Icon icon="heroicons-outline:chevron-down"></Icon>
|
||||
|
|
@ -367,7 +472,11 @@ const Navbar = () => {
|
|||
href: "/content-management/galery",
|
||||
},
|
||||
].map((item, index) => (
|
||||
<Link href={item.href} key={`info-menu-${index}`} className="cursor-pointer">
|
||||
<Link
|
||||
href={item.href}
|
||||
key={`info-menu-${index}`}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize px-3 py-1.5 cursor-pointer">
|
||||
<Icon icon={item.icon} className="w-4 h-4" />
|
||||
{item.name}
|
||||
|
|
@ -379,7 +488,11 @@ const Navbar = () => {
|
|||
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize my-1 px-3 cursor-pointer">
|
||||
<div>
|
||||
<Link href={"/"}>
|
||||
<button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full flex items-center gap-2"
|
||||
onClick={onLogout}
|
||||
>
|
||||
<Icon icon="heroicons:power" className="w-4 h-4" />
|
||||
{t("logOut")}
|
||||
</button>
|
||||
|
|
@ -388,16 +501,33 @@ const Navbar = () => {
|
|||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : roleId === "2" || roleId === "3" || roleId === "4" || roleId === "9" || roleId === "10" || roleId === "11" || roleId === "12" || roleId === "13" ? (
|
||||
) : roleId === "2" ||
|
||||
roleId === "3" ||
|
||||
roleId === "4" ||
|
||||
roleId === "9" ||
|
||||
roleId === "10" ||
|
||||
roleId === "11" ||
|
||||
roleId === "12" ||
|
||||
roleId === "13" ? (
|
||||
// Dropdown menu for roleId === 3
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild className="cursor-pointer">
|
||||
{detail !== undefined ? (
|
||||
<div className="flex items-center gap-3 text-default-800">
|
||||
<Image src={"/assets/avatar-profile.png"} alt={"Image"} width={36} height={36} className="rounded-full" />
|
||||
<Image
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={"Image"}
|
||||
width={36}
|
||||
height={36}
|
||||
className="rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium capitalize lg:block hidden whitespace-nowrap overflow-hidden truncate">{detail?.fullname}</div>
|
||||
<p className="text-xs whitespace-nowrap overflow-hidden truncate">({detail?.fullname})</p>
|
||||
<div className="text-sm font-medium capitalize lg:block hidden whitespace-nowrap overflow-hidden truncate">
|
||||
{detail?.fullname}
|
||||
</div>
|
||||
<p className="text-xs whitespace-nowrap overflow-hidden truncate">
|
||||
({detail?.fullname})
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-base me-2.5 lg:inline-block hidden">
|
||||
<Icon icon="heroicons-outline:chevron-down"></Icon>
|
||||
|
|
@ -421,7 +551,11 @@ const Navbar = () => {
|
|||
href: "/dashboard",
|
||||
},
|
||||
].map((item, index) => (
|
||||
<Link href={item.href} key={`info-menu-${index}`} className="cursor-pointer">
|
||||
<Link
|
||||
href={item.href}
|
||||
key={`info-menu-${index}`}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize px-3 py-1.5 cursor-pointer">
|
||||
<Icon icon={item.icon} className="w-4 h-4" />
|
||||
{item.name}
|
||||
|
|
@ -433,7 +567,11 @@ const Navbar = () => {
|
|||
<DropdownMenuItem className="flex items-center gap-2 text-sm font-medium text-default-600 capitalize my-1 px-3 cursor-pointer">
|
||||
<div>
|
||||
<Link href={"/"}>
|
||||
<button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full flex items-center gap-2"
|
||||
onClick={onLogout}
|
||||
>
|
||||
<Icon icon="heroicons:power" className="w-4 h-4" />
|
||||
{t("logOut")}
|
||||
</button>
|
||||
|
|
@ -445,22 +583,39 @@ const Navbar = () => {
|
|||
) : (
|
||||
// Masuk and Daftar buttons for roleId === null
|
||||
<div className="flex justify-center items-center mx-3 gap-5">
|
||||
<Link href="/auth" className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center">
|
||||
<Link
|
||||
href="/auth"
|
||||
className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"
|
||||
>
|
||||
{t("logIn")}
|
||||
</Link>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full lg:w-fit px-4 h-8 border bg-white border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] hover:text-white">{t("register")}</Button>
|
||||
<Button className="w-full lg:w-fit px-4 h-8 border bg-white border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] hover:text-white">
|
||||
{t("register")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="sm" className="sm:max-w-[425px]">
|
||||
<div className="flex flex-col w-full gap-1">
|
||||
<p className="text-lg font-semibold text-center">Kategori Registrasi</p>
|
||||
<p className="text-base text-center">Silahkan pilih salah satu</p>
|
||||
<p className="text-lg font-semibold text-center">
|
||||
Kategori Registrasi
|
||||
</p>
|
||||
<p className="text-base text-center">
|
||||
Silahkan pilih salah satu
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{role?.map((row: any) => (
|
||||
<div key={row.id}>
|
||||
<input type="radio" id={`category${row.id}`} name="category" className="" value={row.id} checked={category == `${row.id}`} onChange={(event) => setCategory(event.target.value)} />
|
||||
<input
|
||||
type="radio"
|
||||
id={`category${row.id}`}
|
||||
name="category"
|
||||
className=""
|
||||
value={row.id}
|
||||
checked={category == `${row.id}`}
|
||||
onChange={(event) => setCategory(event.target.value)}
|
||||
/>
|
||||
<label className="ml-2" htmlFor={`category${row.id}`}>
|
||||
{row.name}
|
||||
</label>
|
||||
|
|
@ -469,7 +624,11 @@ const Navbar = () => {
|
|||
</div>
|
||||
<div className="border-b-2 border-black"></div>
|
||||
<DialogFooter>
|
||||
<Link href={`/auth/registration?category=${category}`} className="bg-red-500 px-4 py-1 rounded-md border border-black text-white" type="submit">
|
||||
<Link
|
||||
href={`/auth/registration?category=${category}`}
|
||||
className="bg-red-500 px-4 py-1 rounded-md border border-black text-white"
|
||||
type="submit"
|
||||
>
|
||||
Selanjutnya{" "}
|
||||
</Link>
|
||||
</DialogFooter>
|
||||
|
|
@ -488,7 +647,14 @@ const Navbar = () => {
|
|||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>
|
||||
<a className="dark:text-white text-black flex flex-row justify-center items-center cursor-pointer">
|
||||
<svg className="mx-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="mx-2"
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20 7.5H5C4.6023 7.5004 4.221 7.65856 3.93978 7.93978C3.65856 8.221 3.5004 8.6023 3.5 9V19.5C3.5004 19.8977 3.65856 20.279 3.93978 20.5602C4.221 20.8414 4.6023 20.9996 5 21H20C20.3977 20.9996 20.779 20.8414 21.0602 20.5602C21.3414 20.279 21.4996 19.8977 21.5 19.5V9C21.4996 8.6023 21.3414 8.221 21.0602 7.93978C20.779 7.65856 20.3977 7.5004 20 7.5ZM10.25 17.25V11.25L15.5 14.25L10.25 17.25ZM5 4.5H20V6H5V4.5ZM6.5 1.5H18.5V3H6.5V1.5Z"
|
||||
fill="currentColor"
|
||||
|
|
@ -498,25 +664,56 @@ const Navbar = () => {
|
|||
</a>
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent className="p-0 rounded-md overflow-hidden w-full">
|
||||
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/video/filter", String(locale)))} className="flex items-start gap-1.5 p-2 hover:bg-white">
|
||||
<NavigationMenuLink
|
||||
onClick={() =>
|
||||
router.push(
|
||||
generateLocalizedPath("/video/filter", String(locale))
|
||||
)
|
||||
}
|
||||
className="flex items-start gap-1.5 p-2 hover:bg-white"
|
||||
>
|
||||
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
|
||||
<FiYoutube className="mr-2" />
|
||||
{t("video")}
|
||||
</p>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/audio/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
|
||||
<NavigationMenuLink
|
||||
onClick={() =>
|
||||
router.push(
|
||||
generateLocalizedPath("/audio/filter", String(locale))
|
||||
)
|
||||
}
|
||||
className="flex place-items-start gap-1.5 p-2 hover:bg-white"
|
||||
>
|
||||
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
|
||||
<FiMusic className="mr-2" />
|
||||
{t("audio")}
|
||||
</p>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/image/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
|
||||
<NavigationMenuLink
|
||||
onClick={() =>
|
||||
router.push(
|
||||
generateLocalizedPath("/image/filter", String(locale))
|
||||
)
|
||||
}
|
||||
className="flex place-items-start gap-1.5 p-2 hover:bg-white"
|
||||
>
|
||||
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
|
||||
<FiImage className="mr-2" />
|
||||
{t("image")}
|
||||
</p>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink onClick={() => router.push(generateLocalizedPath("/document/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white">
|
||||
<NavigationMenuLink
|
||||
onClick={() =>
|
||||
router.push(
|
||||
generateLocalizedPath(
|
||||
"/document/filter",
|
||||
String(locale)
|
||||
)
|
||||
)
|
||||
}
|
||||
className="flex place-items-start gap-1.5 p-2 hover:bg-white"
|
||||
>
|
||||
<p className="text-slate-600 hover:text-[#bb3523] flex flex-row justify-center items-center px-5 py-2 cursor-pointer">
|
||||
<FiFile className="mr-2" />
|
||||
{t("text")}
|
||||
|
|
@ -528,7 +725,14 @@ const Navbar = () => {
|
|||
<Link href="/schedule" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
<span>
|
||||
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="mr-2"
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.5 4H18.5V3C18.5 2.4 18.1 2 17.5 2C16.9 2 16.5 2.4 16.5 3V4H8.5V3C8.5 2.4 8.1 2 7.5 2C6.9 2 6.5 2.4 6.5 3V4H5.5C3.8 4 2.5 5.3 2.5 7V8H22.5V7C22.5 5.3 21.2 4 19.5 4ZM2.5 19C2.5 20.7 3.8 22 5.5 22H19.5C21.2 22 22.5 20.7 22.5 19V10H2.5V19ZM17.5 12C18.1 12 18.5 12.4 18.5 13C18.5 13.6 18.1 14 17.5 14C16.9 14 16.5 13.6 16.5 13C16.5 12.4 16.9 12 17.5 12ZM17.5 16C18.1 16 18.5 16.4 18.5 17C18.5 17.6 18.1 18 17.5 18C16.9 18 16.5 17.6 16.5 17C16.5 16.4 16.9 16 17.5 16ZM12.5 12C13.1 12 13.5 12.4 13.5 13C13.5 13.6 13.1 14 12.5 14C11.9 14 11.5 13.6 11.5 13C11.5 12.4 11.9 12 12.5 12ZM12.5 16C13.1 16 13.5 16.4 13.5 17C13.5 17.6 13.1 18 12.5 18C11.9 18 11.5 17.6 11.5 17C11.5 16.4 11.9 16 12.5 16ZM7.5 12C8.1 12 8.5 12.4 8.5 13C8.5 13.6 8.1 14 7.5 14C6.9 14 6.5 13.6 6.5 13C6.5 12.4 6.9 12 7.5 12ZM7.5 16C8.1 16 8.5 16.4 8.5 17C8.5 17.6 8.1 18 7.5 18C6.9 18 6.5 17.6 6.5 17C6.5 16.4 6.9 16 7.5 16Z"
|
||||
fill="currentColor"
|
||||
|
|
@ -543,7 +747,14 @@ const Navbar = () => {
|
|||
<Link href="/indeks" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
<span>
|
||||
<svg className="mr-2" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
className="mr-2"
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
|
|
@ -565,7 +776,10 @@ const Navbar = () => {
|
|||
</div>
|
||||
<div className="flex items-center space-x-1 mx-3 text-yellow-600 font-medium">
|
||||
<a href="https://tvradio.polri.go.id/">
|
||||
<img src="/assets/polriTv.png" className="object-contain h-11 flex items-center" />
|
||||
<img
|
||||
src="/assets/polriTv.png"
|
||||
className="object-contain h-11 flex items-center"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
@ -604,14 +818,22 @@ const Navbar = () => {
|
|||
</div>
|
||||
|
||||
<div className=" py-1 flex items-center mx-3">
|
||||
<input type="text" placeholder="Pencarian" className="border rounded-full w-full text-sm text-center text-gray-600" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pencarian"
|
||||
className="border rounded-full w-full text-sm text-center text-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center items-center mx-3 gap-5">
|
||||
{fullName ? (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="flex items-center gap-1">
|
||||
<img className="h-12 w-12" src="/assets/avatar-profile.png" alt="avatar-profile" />
|
||||
<img
|
||||
className="h-12 w-12"
|
||||
src="/assets/avatar-profile.png"
|
||||
alt="avatar-profile"
|
||||
/>
|
||||
<a className="gap-2">
|
||||
<p className="text-xs font-semibold">{fullName}</p>
|
||||
<p className="text-xs">{`(${roleName})`}</p>
|
||||
|
|
@ -619,20 +841,33 @@ const Navbar = () => {
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<Link href="/profile" className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg">
|
||||
<Link
|
||||
href="/profile"
|
||||
className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg"
|
||||
>
|
||||
<Icon icon="iconamoon:profile-circle-fill" />
|
||||
{t("profile")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Link href="/content-management/galery" className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg">
|
||||
<Link
|
||||
href="/content-management/galery"
|
||||
className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg"
|
||||
>
|
||||
<Icon icon="stash:save-ribbon-light" />
|
||||
{t("contentManagement")}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Link href={"/"} className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg">
|
||||
<button type="submit" className="w-full flex items-center gap-2" onClick={onLogout}>
|
||||
<Link
|
||||
href={"/"}
|
||||
className="flex items-center gap-1 hover:bg-slate-600 w-full rounded-lg"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full flex items-center gap-2"
|
||||
onClick={onLogout}
|
||||
>
|
||||
<Icon icon="iconamoon:exit-bold" />
|
||||
{t("logOut")}
|
||||
</button>
|
||||
|
|
@ -643,22 +878,41 @@ const Navbar = () => {
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<Link href="/auth" className="px-4 py-[10px] bg-[#bb3523] text-white font-semibold rounded-md hover:bg-[#bb3523]">
|
||||
<Link
|
||||
href="/auth"
|
||||
className="px-4 py-[10px] bg-[#bb3523] text-white font-semibold rounded-md hover:bg-[#bb3523]"
|
||||
>
|
||||
{t("logIn")}
|
||||
</Link>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center">{t("register")}</Button>
|
||||
<Button className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center">
|
||||
{t("register")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="sm" className="sm:max-w-[425px]">
|
||||
<div className="flex flex-col w-full gap-1">
|
||||
<p className="text-lg font-semibold text-center">Kategori Registrasi</p>
|
||||
<p className="text-base text-center">Silahkan pilih salah satu</p>
|
||||
<p className="text-lg font-semibold text-center">
|
||||
Kategori Registrasi
|
||||
</p>
|
||||
<p className="text-base text-center">
|
||||
Silahkan pilih salah satu
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{role?.map((row: any) => (
|
||||
<div key={row.id}>
|
||||
<input type="radio" id={`category${row.id}`} name="category" className="" value={row.id} checked={category == `${row.id}`} onChange={(event) => setCategory(event.target.value)} />
|
||||
<input
|
||||
type="radio"
|
||||
id={`category${row.id}`}
|
||||
name="category"
|
||||
className=""
|
||||
value={row.id}
|
||||
checked={category == `${row.id}`}
|
||||
onChange={(event) =>
|
||||
setCategory(event.target.value)
|
||||
}
|
||||
/>
|
||||
<label className="ml-2" htmlFor={`category${row.id}`}>
|
||||
{row.name}
|
||||
</label>
|
||||
|
|
@ -667,7 +921,11 @@ const Navbar = () => {
|
|||
</div>
|
||||
<div className="border-b-2 border-black"></div>
|
||||
<DialogFooter>
|
||||
<Link href={`/auth/registration?category=${category}`} className="bg-red-500 px-4 py-1 rounded-md border border-black text-white" type="submit">
|
||||
<Link
|
||||
href={`/auth/registration?category=${category}`}
|
||||
className="bg-red-500 px-4 py-1 rounded-md border border-black text-white"
|
||||
type="submit"
|
||||
>
|
||||
Selanjutnya{" "}
|
||||
</Link>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ const Logo = () => {
|
|||
{(!config?.collapsed || hovered) && (
|
||||
<h1 className="text-xl font-semibold text-default-900 ">D</h1>
|
||||
)} */}
|
||||
<img className="w-100" src="/../images/all-img/mediahub-logo.png" alt="logo" width={150} />
|
||||
<img
|
||||
className="w-100"
|
||||
src="/../images/all-img/mediahub-logo.png"
|
||||
alt="logo"
|
||||
width={150}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,19 +11,23 @@ const HeaderLogo = () => {
|
|||
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
||||
|
||||
return config.layout === "horizontal" ? (
|
||||
<Link href="/dashboard/analytics" className="flex gap-2 items-center ">
|
||||
<DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" />
|
||||
<h1 className="text-xl font-semibold text-default-900 lg:block hidden ">
|
||||
DashCode
|
||||
</h1>
|
||||
<Link href="/" className="flex gap-2 items-center ">
|
||||
<img
|
||||
className="w-100"
|
||||
src="/../images/all-img/mediahub-logo.png"
|
||||
alt="logo"
|
||||
width={150}
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
!isDesktop && (
|
||||
<Link href="/dashboard/analytics" className="flex gap-2 items-center ">
|
||||
<DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" />
|
||||
<h1 className="text-xl font-semibold text-default-900 lg:block hidden ">
|
||||
DashCode
|
||||
</h1>
|
||||
<Link href="/" className="flex gap-2 items-center ">
|
||||
<img
|
||||
className="w-100"
|
||||
src="/../images/all-img/mediahub-logo.png"
|
||||
alt="logo"
|
||||
width={150}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,98 +1,112 @@
|
|||
'use client'
|
||||
import React from 'react'
|
||||
"use client";
|
||||
import React from "react";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import DashCodeLogo from '@/components/dascode-logo';
|
||||
import { Group, Submenu } from '@/lib/menus';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Icon } from '@/components/ui/icon';
|
||||
import DashCodeLogo from "@/components/dascode-logo";
|
||||
import { Group, Submenu } from "@/lib/menus";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import { Link } from '@/i18n/routing';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useConfig } from "@/hooks/use-config";
|
||||
|
||||
interface IconNavProps {
|
||||
menuList: Group[]
|
||||
|
||||
menuList: Group[];
|
||||
}
|
||||
const IconNav = ({ menuList }: IconNavProps) => {
|
||||
const [config, setConfig] = useConfig();
|
||||
|
||||
const [config, setConfig] = useConfig();
|
||||
return (
|
||||
<div className=" h-full bg-sidebar border-r border-default-200 dark:border-secondary border-dashed w-[72px]">
|
||||
<div className="text-center py-5">
|
||||
<DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background mx-auto" />
|
||||
</div>
|
||||
<ScrollArea className="[&>div>div[style]]:!block h-full">
|
||||
<nav className="mt-8 h-full w-full ">
|
||||
<ul className=" h-full flex flex-col min-h-[calc(100vh-48px-36px-16px-32px)] lg:min-h-[calc(100vh-32px-40px-32px)] items-start space-y-2 ">
|
||||
{menuList?.map(({ groupLabel, menus }, index) => (
|
||||
<li key={index} className=" block w-full">
|
||||
{menus?.map(
|
||||
({ href, label, icon, active, id, submenus }, menuIndex) => (
|
||||
<TooltipProvider disableHoverableContent key={menuIndex}>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
{submenus.length === 0 ? (
|
||||
<Button
|
||||
onClick={() =>
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
hasSubMenu: false,
|
||||
subMenu: true,
|
||||
}))
|
||||
}
|
||||
asChild
|
||||
size="icon"
|
||||
color="secondary"
|
||||
variant={active ? "default" : "ghost"}
|
||||
className={cn(
|
||||
"h-12 w-12 mx-auto mb-2 hover:ring-1 hover:ring-offset-0 hover:ring-default-200 dark:hover:ring-menu-arrow-active hover:bg-default-100 dark:hover:bg-secondary ",
|
||||
{
|
||||
"bg-default-100 dark:bg-secondary hover:bg-default-200/80 dark:hover:bg-menu-arrow-active ring-1 ring-default-200 dark:ring-menu-arrow-active":
|
||||
active,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Link href={href}>
|
||||
<Icon
|
||||
icon={icon}
|
||||
className=" w-6 h-6 text-default-500 dark:text-secondary-foreground "
|
||||
/>
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() =>
|
||||
setConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
hasSubMenu: true,
|
||||
subMenu: false,
|
||||
}))
|
||||
}
|
||||
asChild
|
||||
size="icon"
|
||||
color="secondary"
|
||||
variant={active ? "default" : "ghost"}
|
||||
className={cn(
|
||||
"h-12 w-12 mx-auto mb-2 hover:ring-1 hover:ring-offset-0 hover:ring-default-200 dark:hover:ring-menu-arrow-active hover:bg-default-100 dark:hover:bg-secondary ",
|
||||
{
|
||||
"bg-default-100 dark:bg-secondary hover:bg-default-200/80 dark:hover:bg-menu-arrow-active ring-1 ring-default-200 dark:ring-menu-arrow-active":
|
||||
active,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Link href={href}>
|
||||
<Icon
|
||||
icon={icon}
|
||||
className=" w-6 h-6 text-default-500 dark:text-secondary-foreground "
|
||||
/>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent side="right">{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className=' h-full bg-sidebar border-r border-default-200 dark:border-secondary border-dashed w-[72px]'>
|
||||
<div className="text-center py-5">
|
||||
<DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background mx-auto" />
|
||||
</div>
|
||||
<ScrollArea className="[&>div>div[style]]:!block h-full">
|
||||
<nav className="mt-8 h-full w-full ">
|
||||
<ul className=" h-full flex flex-col min-h-[calc(100vh-48px-36px-16px-32px)] lg:min-h-[calc(100vh-32px-40px-32px)] items-start space-y-2 ">
|
||||
{menuList?.map(({ groupLabel, menus }, index) => (
|
||||
<li key={index} className=' block w-full'>
|
||||
{menus?.map(({ href, label, icon, active, id, submenus }, menuIndex) => (
|
||||
|
||||
<TooltipProvider disableHoverableContent key={menuIndex}>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
{submenus.length === 0 ? (
|
||||
<Button onClick={() => setConfig((prevConfig) => ({ ...prevConfig, hasSubMenu: false, subMenu: true }))} asChild size="icon" color="secondary" variant={active ? 'default' : 'ghost'}
|
||||
className={cn('h-12 w-12 mx-auto mb-2 hover:ring-1 hover:ring-offset-0 hover:ring-default-200 dark:hover:ring-menu-arrow-active hover:bg-default-100 dark:hover:bg-secondary ', {
|
||||
'bg-default-100 dark:bg-secondary hover:bg-default-200/80 dark:hover:bg-menu-arrow-active ring-1 ring-default-200 dark:ring-menu-arrow-active': active
|
||||
})}
|
||||
>
|
||||
|
||||
<Link href={href}>
|
||||
|
||||
<Icon icon={icon} className=' w-6 h-6 text-default-500 dark:text-secondary-foreground ' />
|
||||
|
||||
</Link>
|
||||
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => setConfig((prevConfig) => ({ ...prevConfig, hasSubMenu: true, subMenu: false }))}
|
||||
asChild size="icon" color="secondary" variant={active ? 'default' : 'ghost'}
|
||||
className={cn('h-12 w-12 mx-auto mb-2 hover:ring-1 hover:ring-offset-0 hover:ring-default-200 dark:hover:ring-menu-arrow-active hover:bg-default-100 dark:hover:bg-secondary ', {
|
||||
'bg-default-100 dark:bg-secondary hover:bg-default-200/80 dark:hover:bg-menu-arrow-active ring-1 ring-default-200 dark:ring-menu-arrow-active': active
|
||||
})}
|
||||
>
|
||||
|
||||
<Link href={href}>
|
||||
|
||||
<Icon icon={icon} className=' w-6 h-6 text-default-500 dark:text-secondary-foreground ' />
|
||||
|
||||
</Link>
|
||||
|
||||
</Button>
|
||||
|
||||
)}
|
||||
|
||||
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent side="right">
|
||||
{label}
|
||||
</TooltipContent>
|
||||
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
))}
|
||||
</li>
|
||||
))}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default IconNav
|
||||
export default IconNav;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
'use client'
|
||||
import { Link } from '@/i18n/routing';
|
||||
"use client";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { MenuIcon, PanelsTopLeft } from "lucide-react";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Menu } from "@/components/partials/sidebar/menu";
|
||||
import {
|
||||
Sheet,
|
||||
SheetHeader,
|
||||
SheetContent,
|
||||
SheetTrigger,
|
||||
Sheet,
|
||||
SheetHeader,
|
||||
SheetContent,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { MenuClassic } from "./menu-classic";
|
||||
import DashCodeLogo from "@/components/dascode-logo";
|
||||
|
|
@ -17,33 +17,45 @@ import { useMediaQuery } from "@/hooks/use-media-query";
|
|||
import { useConfig } from "@/hooks/use-config";
|
||||
|
||||
export function SheetMenu() {
|
||||
const [mobileMenuConfig, setMobileMenuConfig] = useMobileMenuConfig();
|
||||
const [config, setConfig] = useConfig()
|
||||
const { isOpen } = mobileMenuConfig;
|
||||
const [mobileMenuConfig, setMobileMenuConfig] = useMobileMenuConfig();
|
||||
const [config, setConfig] = useConfig();
|
||||
const { isOpen } = mobileMenuConfig;
|
||||
|
||||
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
||||
if (isDesktop) return null;
|
||||
return (
|
||||
<Sheet open={isOpen} onOpenChange={() => setMobileMenuConfig({ isOpen: !isOpen })}>
|
||||
<SheetTrigger className="xl:hidden" asChild>
|
||||
<Button className="h-8" variant="ghost" size="icon" onClick={() => setConfig({
|
||||
...config, collapsed: false,
|
||||
})} >
|
||||
|
||||
<Icon icon="heroicons:bars-3-bottom-right" className="h-5 w-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:w-72 px-3 h-full flex flex-col" side="left">
|
||||
<SheetHeader>
|
||||
<Link href="/dashboard/analytics" className="flex gap-2 items-center ">
|
||||
<DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" />
|
||||
<h1 className="text-xl font-semibold text-default-900 ">
|
||||
DashCode
|
||||
</h1>
|
||||
</Link>
|
||||
</SheetHeader>
|
||||
<MenuClassic />
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
||||
if (isDesktop) return null;
|
||||
return (
|
||||
<Sheet
|
||||
open={isOpen}
|
||||
onOpenChange={() => setMobileMenuConfig({ isOpen: !isOpen })}
|
||||
>
|
||||
<SheetTrigger className="xl:hidden" asChild>
|
||||
<Button
|
||||
className="h-8"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
setConfig({
|
||||
...config,
|
||||
collapsed: false,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Icon icon="heroicons:bars-3-bottom-right" className="h-5 w-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="sm:w-72 px-3 h-full flex flex-col" side="left">
|
||||
<SheetHeader>
|
||||
<Link href="/" className="flex gap-2 items-center ">
|
||||
<img
|
||||
className="w-100"
|
||||
src="/../images/all-img/mediahub-logo.png"
|
||||
alt="logo"
|
||||
width={150}
|
||||
/>
|
||||
</Link>
|
||||
</SheetHeader>
|
||||
<MenuClassic />
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue