fixing
This commit is contained in:
parent
e047b97e58
commit
2602e831e3
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const AudioPlayer = (props: {urlAudio: string}) => {
|
||||||
|
const audioRef = useRef<HTMLAudioElement>(null);
|
||||||
|
const [currentTime, setCurrentTime] = useState(0);
|
||||||
|
const {urlAudio} = props
|
||||||
|
|
||||||
|
const playAudio = () => {
|
||||||
|
audioRef.current?.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
const pauseAudio = () => {
|
||||||
|
audioRef.current?.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopAudio = () => {
|
||||||
|
if (audioRef.current) {
|
||||||
|
audioRef.current.pause();
|
||||||
|
audioRef.current.currentTime = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const audio = audioRef.current;
|
||||||
|
if (!audio) return;
|
||||||
|
|
||||||
|
const updateTime = () => {
|
||||||
|
setCurrentTime(audio.currentTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
audio.addEventListener("timeupdate", updateTime);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
audio.removeEventListener("timeupdate", updateTime);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const formatTime = (time: number) => {
|
||||||
|
const minutes = Math.floor(time / 60);
|
||||||
|
const seconds = Math.floor(time % 60)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0");
|
||||||
|
return `${minutes}:${seconds}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: "center", marginTop: "50px" }}>
|
||||||
|
<h2>Pemutar Audio</h2>
|
||||||
|
<audio ref={audioRef} src={urlAudio} />
|
||||||
|
<div style={{ marginTop: "10px" }}>
|
||||||
|
<button onClick={playAudio}>▶️ Play</button>
|
||||||
|
<button onClick={pauseAudio}>⏸️ Pause</button>
|
||||||
|
<button onClick={stopAudio}>⏹️ Stop</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: "10px" }}>
|
||||||
|
<span>{formatTime(currentTime)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioPlayer;
|
||||||
|
|
@ -64,6 +64,8 @@ import { useTranslations } from "next-intl";
|
||||||
import SuggestionModal from "@/components/modal/suggestions-modal";
|
import SuggestionModal from "@/components/modal/suggestions-modal";
|
||||||
import { formatDateToIndonesian } from "@/utils/globals";
|
import { formatDateToIndonesian } from "@/utils/globals";
|
||||||
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import AudioPlayer from "@/components/audio-player";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -139,32 +141,54 @@ export default function FormAudioDetail() {
|
||||||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [main, setMain] = useState<any>([]);
|
const [main, setMain] = useState<any>([]);
|
||||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
|
||||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||||
|
|
||||||
const t = useTranslations("Form");
|
const t = useTranslations("Form");
|
||||||
const [selectedTarget, setSelectedTarget] = useState("");
|
const [selectedTarget, setSelectedTarget] = useState("");
|
||||||
const [files, setFiles] = useState<FileType[]>([]);
|
|
||||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||||
const [audioPlaying, setAudioPlaying] = useState<any>(null);
|
const [audioPlaying, setAudioPlaying] = useState<any>(null);
|
||||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||||
|
const [files, setFiles] = useState<FileType[]>([]);
|
||||||
|
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
|
||||||
|
const [detailThumb, setDetailThumb] = useState<string[]>([]);
|
||||||
|
|
||||||
const waveSurferRef = useRef<any>(null);
|
const waveSurferRef = useRef<any>(null);
|
||||||
|
const waveSurfersRef = useRef<WaveSurfer[]>([]);
|
||||||
|
const [isPlayingIndex, setIsPlayingIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [approval, setApproval] = useState<any>();
|
const [approval, setApproval] = useState<any>();
|
||||||
|
|
||||||
const onReady = (ws: any) => {
|
const onDrop = (acceptedFiles: File[]) => {
|
||||||
setWavesurfer(ws);
|
setUploadedFiles(acceptedFiles);
|
||||||
setIsPlaying(false);
|
const blobUrls = acceptedFiles.map((file) => URL.createObjectURL(file));
|
||||||
|
setDetailThumb(blobUrls);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPlayPause = () => {
|
const onReady = (ws: WaveSurfer, index: number) => {
|
||||||
wavesurfer && wavesurfer.playPause();
|
waveSurfersRef.current[index] = ws;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onPlayPause = (index: number) => {
|
||||||
|
waveSurfersRef.current.forEach((ws, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
ws.isPlaying() ? ws.pause() : ws.play();
|
||||||
|
setIsPlayingIndex(ws.isPlaying() ? index : null);
|
||||||
|
} else {
|
||||||
|
ws.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
onDrop,
|
||||||
|
accept: {
|
||||||
|
"audio/*": [],
|
||||||
|
},
|
||||||
|
multiple: true,
|
||||||
|
});
|
||||||
|
|
||||||
let fileTypeId = "4";
|
let fileTypeId = "4";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -261,8 +285,8 @@ export default function FormAudioDetail() {
|
||||||
});
|
});
|
||||||
setupPlacementCheck(details?.files?.length);
|
setupPlacementCheck(details?.files?.length);
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
@ -276,9 +300,9 @@ export default function FormAudioDetail() {
|
||||||
setSelectedTarget(matchingCategory.name);
|
setSelectedTarget(matchingCategory.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedTarget(details.categoryId); // Untuk dropdown
|
setSelectedTarget(details?.categoryId);
|
||||||
|
|
||||||
const filesData = details.files || [];
|
const filesData = details?.files || [];
|
||||||
const audioFiles = filesData.filter(
|
const audioFiles = filesData.filter(
|
||||||
(file: any) =>
|
(file: any) =>
|
||||||
file.contentType && file.contentType.startsWith("video/webm")
|
file.contentType && file.contentType.startsWith("video/webm")
|
||||||
|
|
@ -439,9 +463,9 @@ export default function FormAudioDetail() {
|
||||||
|
|
||||||
const handleAudioPlayPause = (audioSrc: string) => {
|
const handleAudioPlayPause = (audioSrc: string) => {
|
||||||
if (audioPlaying === audioSrc) {
|
if (audioPlaying === audioSrc) {
|
||||||
setAudioPlaying(null); // Pause if the same audio is clicked
|
setAudioPlaying(null);
|
||||||
} else {
|
} else {
|
||||||
setAudioPlaying(audioSrc); // Play the new audio
|
setAudioPlaying(audioSrc);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -463,7 +487,9 @@ export default function FormAudioDetail() {
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
<Card className="w-full lg:w-8/12">
|
<Card className="w-full lg:w-8/12">
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-audio", { defaultValue: "Form Audio" })}</p>
|
<p className="text-lg font-semibold mb-3">
|
||||||
|
{t("form-audio", { defaultValue: "Form Audio" })}
|
||||||
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
{/* Input Title */}
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
|
|
@ -491,7 +517,7 @@ export default function FormAudioDetail() {
|
||||||
<div className="py-3 w-full space-y-2">
|
<div className="py-3 w-full space-y-2">
|
||||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={detail?.category.name} // Nilai default berdasarkan detail
|
value={detail?.category.name}
|
||||||
onValueChange={(id) => {
|
onValueChange={(id) => {
|
||||||
console.log("Selected Category:", id);
|
console.log("Selected Category:", id);
|
||||||
setSelectedTarget(id);
|
setSelectedTarget(id);
|
||||||
|
|
@ -512,7 +538,9 @@ export default function FormAudioDetail() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -527,42 +555,25 @@ export default function FormAudioDetail() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Label className="text-xl space-y-2">{t("file-media", { defaultValue: "File Media" })}</Label>
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className={"container example"}>
|
<Label className="text-xl space-y-2">
|
||||||
{detailThumb?.map((url: any, index: number) => (
|
{t("file-media", { defaultValue: "File Media" })}
|
||||||
<div key={url.id}>
|
</Label>
|
||||||
<WavesurferPlayer
|
<div className="container example">
|
||||||
height={500}
|
{detailThumb.map((url, index) => (
|
||||||
waveColor="red"
|
<div key={index}>
|
||||||
|
{/* <WavesurferPlayer
|
||||||
url={url}
|
url={url}
|
||||||
onReady={onReady}
|
waveColor="red"
|
||||||
onPlay={() => setIsPlaying(true)}
|
height={80}
|
||||||
onPause={() => setIsPlaying(false)}
|
onReady={(ws: any) => onReady(ws, index)}
|
||||||
/>
|
/>
|
||||||
|
<Button onClick={() => onPlayPause(index)}>
|
||||||
|
{isPlayingIndex === index ? "Pause" : "Play"}
|
||||||
|
</Button> */}
|
||||||
|
<AudioPlayer urlAudio={url} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
type="button"
|
|
||||||
onClick={onPlayPause}
|
|
||||||
disabled={isPlaying}
|
|
||||||
className={`flex items-center gap-2 ${
|
|
||||||
isPlaying
|
|
||||||
? "bg-gray-300 cursor-not-allowed"
|
|
||||||
: "bg-primary text-white"
|
|
||||||
} p-2 rounded`}
|
|
||||||
>
|
|
||||||
{isPlaying ? "Pause" : "Play"}
|
|
||||||
<Icon
|
|
||||||
icon={
|
|
||||||
isPlaying
|
|
||||||
? "carbon:pause-outline"
|
|
||||||
: "famicons:play-sharp"
|
|
||||||
}
|
|
||||||
className="h-5 w-5"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -613,7 +624,9 @@ export default function FormAudioDetail() {
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-6 space-y-2">
|
<div className="flex flex-col gap-6 space-y-2">
|
||||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
<Label>
|
||||||
|
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||||
|
</Label>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="5"
|
id="5"
|
||||||
|
|
@ -675,7 +688,9 @@ export default function FormAudioDetail() {
|
||||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||||
<DialogContent size="md">
|
<DialogContent size="md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("leave-comment", { defaultValue: "Leave Comment" })}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{t("leave-comment", { defaultValue: "Leave Comment" })}
|
||||||
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{status == "2"
|
{status == "2"
|
||||||
? files?.map((file, index) => (
|
? files?.map((file, index) => (
|
||||||
|
|
@ -893,7 +908,8 @@ export default function FormAudioDetail() {
|
||||||
color="primary"
|
color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Icon icon="fa:check" className="mr-3" /> {t("accept", { defaultValue: "Accept" })}
|
<Icon icon="fa:check" className="mr-3" />{" "}
|
||||||
|
{t("accept", { defaultValue: "Accept" })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => actionApproval("3")}
|
onClick={() => actionApproval("3")}
|
||||||
|
|
|
||||||
|
|
@ -156,20 +156,33 @@ export default function FormAudio() {
|
||||||
{ id: "8", label: "KSP" },
|
{ id: "8", label: "KSP" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const audioRefs = useRef<HTMLAudioElement[]>([]);
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
multiple: true,
|
||||||
|
accept: { "audio/*": [] },
|
||||||
onDrop: (acceptedFiles) => {
|
onDrop: (acceptedFiles) => {
|
||||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
const filesWithPreview = acceptedFiles.map((file) =>
|
||||||
},
|
Object.assign(file, { preview: URL.createObjectURL(file) })
|
||||||
accept: {
|
);
|
||||||
"audio/*": [],
|
setFiles((prev) => [...prev, ...filesWithPreview]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handlePlay = (index: number) => {
|
||||||
|
audioRefs.current.forEach((audio, i) => {
|
||||||
|
if (audio && i !== index) {
|
||||||
|
audio.pause();
|
||||||
|
audio.currentTime = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const audioSchema = z.object({
|
const audioSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
descriptionOri: z.string().optional(), // Original editor
|
descriptionOri: z.string().optional(),
|
||||||
rewriteDescription: z.string().optional(), // Rewrite editor
|
rewriteDescription: z.string().optional(),
|
||||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||||
|
|
||||||
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -803,7 +816,9 @@ export default function FormAudio() {
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
<Card className="w-full lg:w-8/12">
|
<Card className="w-full lg:w-8/12">
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-audio", { defaultValue: "Form Audio" })}</p>
|
<p className="text-lg font-semibold mb-3">
|
||||||
|
{t("form-audio", { defaultValue: "Form Audio" })}
|
||||||
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
{/* Input Title */}
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
|
|
@ -830,7 +845,7 @@ export default function FormAudio() {
|
||||||
<div className="py-3 w-full space-y-2">
|
<div className="py-3 w-full space-y-2">
|
||||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||||
<Select
|
<Select
|
||||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
value={selectedCategory}
|
||||||
onValueChange={(id) => {
|
onValueChange={(id) => {
|
||||||
console.log("Selected Category ID:", id);
|
console.log("Selected Category ID:", id);
|
||||||
setSelectedCategory(id);
|
setSelectedCategory(id);
|
||||||
|
|
@ -853,7 +868,9 @@ export default function FormAudio() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-3 py-2">
|
<div className="flex flex-row items-center gap-3 py-2">
|
||||||
<Label>{t("ai-assistance", { defaultValue: "Ai Assistance" })}</Label>
|
<Label>
|
||||||
|
{t("ai-assistance", { defaultValue: "Ai Assistance" })}
|
||||||
|
</Label>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={isSwitchOn}
|
defaultChecked={isSwitchOn}
|
||||||
|
|
@ -869,7 +886,9 @@ export default function FormAudio() {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("language", { defaultValue: "Language" })}</Label>
|
<Label>
|
||||||
|
{t("language", { defaultValue: "Language" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedLanguage}>
|
<Select onValueChange={setSelectedLanguage}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -881,7 +900,9 @@ export default function FormAudio() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("writing-style", { defaultValue: "Writing Style" })}</Label>
|
<Label>
|
||||||
|
{t("writing-style", { defaultValue: "Writing Style" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedWritingStyle}>
|
<Select onValueChange={setSelectedWritingStyle}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -900,7 +921,9 @@ export default function FormAudio() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("article-size", { defaultValue: "Article Size" })}</Label>
|
<Label>
|
||||||
|
{t("article-size", { defaultValue: "Article Size" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedSize}>
|
<Select onValueChange={setSelectedSize}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -921,7 +944,9 @@ export default function FormAudio() {
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div className="flex flex-row items-center gap-3 mb-3">
|
<div className="flex flex-row items-center gap-3 mb-3">
|
||||||
<Label>{t("main-keyword", { defaultValue: "Main Keyword" })}</Label>
|
<Label>
|
||||||
|
{t("main-keyword", { defaultValue: "Main Keyword" })}
|
||||||
|
</Label>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
@ -978,9 +1003,13 @@ export default function FormAudio() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-semibold">
|
<p className="font-semibold">
|
||||||
{t("Keywords to include in the text", { defaultValue: "Keywords To Include In The Text" })}
|
{t("Keywords to include in the text", {
|
||||||
|
defaultValue: "Keywords To Include In The Text",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{t("title-key", { defaultValue: "Title Key" })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={selectedSEO}
|
value={selectedSEO}
|
||||||
|
|
@ -990,7 +1019,12 @@ export default function FormAudio() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<Label>{t("special-instructions", { defaultValue: "Special Instructions" })}(Optional)</Label>
|
<Label>
|
||||||
|
{t("special-instructions", {
|
||||||
|
defaultValue: "Special Instructions",
|
||||||
|
})}
|
||||||
|
(Optional)
|
||||||
|
</Label>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -1056,7 +1090,9 @@ export default function FormAudio() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -1100,7 +1136,9 @@ export default function FormAudio() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="descriptionOri"
|
name="descriptionOri"
|
||||||
|
|
@ -1160,7 +1198,11 @@ export default function FormAudio() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
<Label>
|
||||||
|
{t("file-rewrite", {
|
||||||
|
defaultValue: "File Rewrite",
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="rewriteDescription"
|
name="rewriteDescription"
|
||||||
|
|
@ -1189,36 +1231,51 @@ export default function FormAudio() {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
<Label>
|
||||||
{/* <Input
|
{t("select-file", { defaultValue: "Select File" })}
|
||||||
id="fileInput"
|
</Label>
|
||||||
type="file"
|
|
||||||
onChange={handleImageChange}
|
|
||||||
/> */}
|
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div {...getRootProps({ className: "dropzone" })}>
|
<div {...getRootProps({ className: "dropzone" })}>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps({ multiple: true })} />
|
||||||
<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">
|
<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" />
|
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||||
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||||
{/* Drop files here or click to upload. */}
|
|
||||||
{t("drag-file", { defaultValue: "Drag File" })}
|
{t("drag-file", { defaultValue: "Drag File" })}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{t("upload-file-audio-max", { defaultValue: "Upload File Audio Max" })}
|
{t("upload-file-audio-max", {
|
||||||
|
defaultValue: "Upload File Audio Max",
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{files.length ? (
|
{files.length ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div>{fileList}</div>
|
<div className="space-y-4 mt-4">
|
||||||
<div className=" flex justify-between gap-2">
|
{files.map((file, idx) => (
|
||||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
<div
|
||||||
<Label>Gunakan Watermark</Label>
|
key={idx}
|
||||||
<div className="flex items-center gap-3">
|
className="flex flex-col gap-2 border p-2 rounded-md"
|
||||||
<Switch defaultChecked color="primary" id="c2" />
|
>
|
||||||
|
<p className="text-sm font-medium truncate">
|
||||||
|
{file.name}
|
||||||
|
</p>
|
||||||
|
<audio
|
||||||
|
controls
|
||||||
|
src={file.preview ?? URL.createObjectURL(file)}
|
||||||
|
ref={(el) => {
|
||||||
|
if (el) audioRefs.current[idx] = el;
|
||||||
|
}}
|
||||||
|
onPlay={() => handlePlay(idx)}
|
||||||
|
className="w-full rounded"
|
||||||
|
>
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between gap-2 mt-3">
|
||||||
<Button
|
<Button
|
||||||
color="destructive"
|
color="destructive"
|
||||||
onClick={handleRemoveAllFiles}
|
onClick={handleRemoveAllFiles}
|
||||||
|
|
@ -1259,7 +1316,9 @@ export default function FormAudio() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3 space-y-2">
|
<div className="px-3 py-3 space-y-2">
|
||||||
<Label htmlFor="tags">{t("tags", { defaultValue: "Tags" })}</Label>
|
<Label htmlFor="tags">
|
||||||
|
{t("tags", { defaultValue: "Tags" })}
|
||||||
|
</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -1288,7 +1347,9 @@ export default function FormAudio() {
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-3 space-y-2">
|
<div className="flex flex-col gap-3 space-y-2">
|
||||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
<Label>
|
||||||
|
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||||
|
</Label>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<div key={option.id} className="flex gap-2 items-center">
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type FileData = {
|
||||||
|
url: string;
|
||||||
|
format: string; // extension with dot, e.g. ".pdf"
|
||||||
|
fileName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FilePreviewProps {
|
||||||
|
file: FileData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileTextPreview: React.FC<FilePreviewProps> = ({ file }) => {
|
||||||
|
const format = file.format.toLowerCase();
|
||||||
|
|
||||||
|
if ([".jpg", ".jpeg", ".png", ".webp"].includes(format)) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className="object-fill h-full w-full rounded-md"
|
||||||
|
src={file.url}
|
||||||
|
alt={file.fileName || "File"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === ".pdf") {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className="w-full h-96 rounded-md"
|
||||||
|
src={`https://drive.google.com/viewerng/viewer?embedded=true&url=${encodeURIComponent(file.url)}`}
|
||||||
|
title={file.fileName || "PDF File"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx"].includes(format)) {
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
className="w-full h-96 rounded-md"
|
||||||
|
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(file.url)}`}
|
||||||
|
title={file.fileName || "Document"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback → unknown format
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={file.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="block text-blue-500 underline"
|
||||||
|
>
|
||||||
|
View {file.fileName || "File"}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileTextPreview;
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type FileData = {
|
||||||
|
url: string;
|
||||||
|
format: string;
|
||||||
|
fileName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FileThumbnailProps {
|
||||||
|
file: FileData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileTextThumbnail: React.FC<FileThumbnailProps> = ({ file }) => {
|
||||||
|
const format = file.format.toLowerCase();
|
||||||
|
|
||||||
|
if ([".jpg", ".jpeg", ".png", ".webp"].includes(format)) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className="object-cover h-[60px] w-[80px] rounded-md"
|
||||||
|
src={file.url}
|
||||||
|
alt={file.fileName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
|
||||||
|
{format.replace(".", "").toUpperCase()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileTextThumbnail;
|
||||||
|
|
@ -88,7 +88,6 @@ export default function FormImage() {
|
||||||
type ImageSchema = z.infer<typeof imageSchema>;
|
type ImageSchema = z.infer<typeof imageSchema>;
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const locale = params?.locale;
|
const locale = params?.locale;
|
||||||
|
|
||||||
const t = useTranslations("Form");
|
const t = useTranslations("Form");
|
||||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
const taskId = Cookies.get("taskId");
|
const taskId = Cookies.get("taskId");
|
||||||
|
|
@ -171,11 +170,17 @@ export default function FormImage() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: t("titleRequired") }),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
descriptionOri: z.string().optional(),
|
descriptionOri: z.string().optional(),
|
||||||
rewriteDescription: z.string().optional(),
|
rewriteDescription: z.string().optional(),
|
||||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
creatorName: z.string().min(1, { message: t("creatorRequired") }),
|
||||||
|
categoryId: z.string().min(1, { message: "Kategori diperlukan" }),
|
||||||
|
tags: z.array(z.string()).min(1, { message: "Minimal 1 tag diperlukan" }),
|
||||||
|
publishedFor: z
|
||||||
|
.array(z.string())
|
||||||
|
.min(1, { message: "Pilih target publish" }),
|
||||||
|
files: z.array(z.any()).min(1, { message: "File harus diupload" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -183,6 +188,7 @@ export default function FormImage() {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
getValues,
|
getValues,
|
||||||
setValue,
|
setValue,
|
||||||
|
watch,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<ImageSchema>({
|
} = useForm<ImageSchema>({
|
||||||
resolver: zodResolver(imageSchema),
|
resolver: zodResolver(imageSchema),
|
||||||
|
|
@ -190,6 +196,12 @@ export default function FormImage() {
|
||||||
description: "",
|
description: "",
|
||||||
descriptionOri: "",
|
descriptionOri: "",
|
||||||
rewriteDescription: "",
|
rewriteDescription: "",
|
||||||
|
title: "",
|
||||||
|
creatorName: "",
|
||||||
|
categoryId: "",
|
||||||
|
tags: [],
|
||||||
|
publishedFor: [],
|
||||||
|
files: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -785,7 +797,9 @@ export default function FormImage() {
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
<Card className="w-full lg:w-8/12">
|
<Card className="w-full lg:w-8/12">
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-image", { defaultValue: "Form Image" })}</p>
|
<p className="text-lg font-semibold mb-3">
|
||||||
|
{t("form-image", { defaultValue: "Form Image" })}
|
||||||
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
{/* Input Title */}
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
|
|
@ -799,7 +813,9 @@ export default function FormImage() {
|
||||||
type="text"
|
type="text"
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={field.onChange}
|
onChange={field.onChange}
|
||||||
placeholder="Enter Title"
|
placeholder={t("enterTitle", {
|
||||||
|
defaultValue: "Masukkan Title",
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -811,7 +827,7 @@ export default function FormImage() {
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="py-3 space-y-2 w-full">
|
<div className="py-3 space-y-2 w-full">
|
||||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||||
<Select
|
{/* <Select
|
||||||
value={selectedCategory}
|
value={selectedCategory}
|
||||||
onValueChange={(id) => {
|
onValueChange={(id) => {
|
||||||
console.log("Selected Category ID:", id);
|
console.log("Selected Category ID:", id);
|
||||||
|
|
@ -831,11 +847,39 @@ export default function FormImage() {
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
</Select> */}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="categoryId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<SelectItem key={cat.id} value={cat.id.toString()}>
|
||||||
|
{cat.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.categoryId?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.categoryId?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-3 py-3 ">
|
<div className="flex flex-row items-center gap-3 py-3 ">
|
||||||
<Label>{t("ai-assistance", { defaultValue: "Ai Assistance" })}</Label>
|
<Label>
|
||||||
|
{t("ai-assistance", { defaultValue: "Ai Assistance" })}
|
||||||
|
</Label>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={isSwitchOn}
|
defaultChecked={isSwitchOn}
|
||||||
|
|
@ -851,7 +895,9 @@ export default function FormImage() {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("language", { defaultValue: "Language" })}</Label>
|
<Label>
|
||||||
|
{t("language", { defaultValue: "Language" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedLanguage}>
|
<Select onValueChange={setSelectedLanguage}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -863,7 +909,9 @@ export default function FormImage() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("writing-style", { defaultValue: "Writing Style" })}</Label>
|
<Label>
|
||||||
|
{t("writing-style", { defaultValue: "Writing Style" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedWritingStyle}>
|
<Select onValueChange={setSelectedWritingStyle}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -882,7 +930,9 @@ export default function FormImage() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("article-size", { defaultValue: "Article Size" })}</Label>
|
<Label>
|
||||||
|
{t("article-size", { defaultValue: "Article Size" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedSize}>
|
<Select onValueChange={setSelectedSize}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -903,7 +953,9 @@ export default function FormImage() {
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div className="flex flex-row items-center gap-3 mb-3">
|
<div className="flex flex-row items-center gap-3 mb-3">
|
||||||
<Label>{t("main-keyword", { defaultValue: "Main Keyword" })}</Label>
|
<Label>
|
||||||
|
{t("main-keyword", { defaultValue: "Main Keyword" })}
|
||||||
|
</Label>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
@ -960,9 +1012,13 @@ export default function FormImage() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-semibold">
|
<p className="font-semibold">
|
||||||
{t("Keywords to include in the text", { defaultValue: "Keywords To Include In The Text" })}
|
{t("Keywords to include in the text", {
|
||||||
|
defaultValue: "Keywords To Include In The Text",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{t("title-key", { defaultValue: "Title Key" })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={selectedSEO}
|
value={selectedSEO}
|
||||||
|
|
@ -972,7 +1028,12 @@ export default function FormImage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<Label>{t("special-instructions", { defaultValue: "Special Instructions" })}(Optional)</Label>
|
<Label>
|
||||||
|
{t("special-instructions", {
|
||||||
|
defaultValue: "Special Instructions",
|
||||||
|
})}
|
||||||
|
(Optional)
|
||||||
|
</Label>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -1005,7 +1066,7 @@ export default function FormImage() {
|
||||||
{articleIds.map((id: string, index: number) => (
|
{articleIds.map((id: string, index: number) => (
|
||||||
<p
|
<p
|
||||||
key={index}
|
key={index}
|
||||||
className={`mr-3 px-3 py-2 rounded-md ${
|
className={`mr-3 px-3 py-2 rounded-md cursor-pointer ${
|
||||||
selectedArticleId === id
|
selectedArticleId === id
|
||||||
? "bg-green-500 text-white"
|
? "bg-green-500 text-white"
|
||||||
: "border-2 border-green-500 text-green-500"
|
: "border-2 border-green-500 text-green-500"
|
||||||
|
|
@ -1038,7 +1099,9 @@ export default function FormImage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -1082,7 +1145,9 @@ export default function FormImage() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="descriptionOri"
|
name="descriptionOri"
|
||||||
|
|
@ -1142,7 +1207,11 @@ export default function FormImage() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
<Label>
|
||||||
|
{t("file-rewrite", {
|
||||||
|
defaultValue: "File Rewrite",
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="rewriteDescription"
|
name="rewriteDescription"
|
||||||
|
|
@ -1170,37 +1239,141 @@ export default function FormImage() {
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="files"
|
||||||
|
render={({ field }) => {
|
||||||
|
const maxSize = 100 * 1024 * 1024;
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps, fileRejections } =
|
||||||
|
useDropzone({
|
||||||
|
accept: {
|
||||||
|
"image/jpeg": [".jpeg", ".jpg"],
|
||||||
|
"image/png": [".png"],
|
||||||
|
},
|
||||||
|
maxSize,
|
||||||
|
multiple: false,
|
||||||
|
onDrop: (acceptedFiles) => {
|
||||||
|
field.onChange(acceptedFiles);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
<Label>
|
||||||
{/* <Input
|
{t("select-file", { defaultValue: "Select File" })}
|
||||||
id="fileInput"
|
</Label>
|
||||||
type="file"
|
|
||||||
onChange={handleImageChange}
|
|
||||||
/> */}
|
|
||||||
<Fragment>
|
|
||||||
<div {...getRootProps({ className: "dropzone" })}>
|
<div {...getRootProps({ className: "dropzone" })}>
|
||||||
<input {...getInputProps()} />
|
<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">
|
<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" />
|
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||||
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||||
{/* Drop files here or click to upload. */}
|
|
||||||
{t("drag-file", { defaultValue: "Drag File" })}
|
{t("drag-file", { defaultValue: "Drag File" })}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{t("upload-file-max", { defaultValue: "Upload File Max" })}
|
{t("upload-file-max", {
|
||||||
|
defaultValue:
|
||||||
|
"Upload file max 100MB (.jpg, .jpeg, .png)",
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{field.value && field.value.length > 0 && (
|
||||||
|
<>
|
||||||
|
<ul className="mt-2 text-sm">
|
||||||
|
{field.value.map((file, idx) => (
|
||||||
|
<li key={idx}>
|
||||||
|
{file.name} (
|
||||||
|
{(file.size / (1024 * 1024)).toFixed(2)} MB)
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div className="flex justify-end mt-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="destructive"
|
||||||
|
onClick={() => field.onChange([])}
|
||||||
|
>
|
||||||
|
{t("remove-all", {
|
||||||
|
defaultValue: "Remove All",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{errors.files?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.files.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fileRejections.length > 0 && (
|
||||||
|
<div className="text-red-400 text-sm space-y-1 mt-2">
|
||||||
|
{fileRejections.map(({ file, errors }, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<p>{file.name}:</p>
|
||||||
|
<ul className="ml-4 list-disc">
|
||||||
|
{errors.map((e, idx) => (
|
||||||
|
<li key={idx}>
|
||||||
|
{e.code === "file-too-large" &&
|
||||||
|
t("size", {
|
||||||
|
defaultValue:
|
||||||
|
"File too large. Max 100MB.",
|
||||||
|
})}
|
||||||
|
{e.code === "file-invalid-type" &&
|
||||||
|
t("only", {
|
||||||
|
defaultValue:
|
||||||
|
"Invalid file type. Only .jpg, .jpeg, .png allowed.",
|
||||||
|
})}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* <Controller
|
||||||
|
name="files"
|
||||||
|
control={control}
|
||||||
|
rules={{
|
||||||
|
required: t("file-required", {
|
||||||
|
defaultValue: "File is required",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
render={({ field }) => (
|
||||||
|
<div className="py-3 space-y-2">
|
||||||
|
<Label>
|
||||||
|
{t("select-file", { defaultValue: "Select File" })}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
{t("drag-file", { defaultValue: "Drag File" })}
|
||||||
|
</h4>
|
||||||
|
<div className=" text-xs text-muted-foreground">
|
||||||
|
{t("upload-file-max", {
|
||||||
|
defaultValue: "Upload File Max",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{files.length ? (
|
{files.length ? (
|
||||||
<Fragment>
|
<>
|
||||||
<div>{fileList}</div>
|
<div>{fileList}</div>
|
||||||
<div className="flex justify-between gap-2">
|
<div className="flex justify-between gap-2">
|
||||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
|
||||||
<Label>{t("watermark", { defaultValue: "Watermark" })}</Label>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Switch defaultChecked color="primary" id="c2" />
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
<Button
|
<Button
|
||||||
color="destructive"
|
color="destructive"
|
||||||
onClick={handleRemoveAllFiles}
|
onClick={handleRemoveAllFiles}
|
||||||
|
|
@ -1208,15 +1381,21 @@ export default function FormImage() {
|
||||||
{t("remove-all", { defaultValue: "Remove All" })}
|
{t("remove-all", { defaultValue: "Remove All" })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Fragment>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Submit Button */}
|
{errors.files?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.files.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div className="w-full lg:w-4/12">
|
<div className="w-full lg:w-4/12">
|
||||||
<Card className=" h-[500px]">
|
<Card className=" h-[500px]">
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
|
|
@ -1242,8 +1421,10 @@ export default function FormImage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3 space-y-2">
|
{/* <div className="px-3 py-3 space-y-2">
|
||||||
<Label htmlFor="tags">{t("tags", { defaultValue: "Tags" })}</Label>
|
<Label htmlFor="tags">
|
||||||
|
{t("tags", { defaultValue: "Tags" })}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
id="tags"
|
id="tags"
|
||||||
|
|
@ -1268,10 +1449,70 @@ export default function FormImage() {
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div> */}
|
||||||
|
<div className="px-3 py-3 space-y-2">
|
||||||
|
<Label htmlFor="tags">
|
||||||
|
{t("tags", { defaultValue: "Tags" })}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field }) => (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="tags"
|
||||||
|
placeholder="Add a tag and press Enter"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
field.onChange([
|
||||||
|
...field.value,
|
||||||
|
e.currentTarget.value.trim(),
|
||||||
|
]);
|
||||||
|
e.currentTarget.value = "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-3">
|
||||||
|
{field.value.map((tag: string, index: number) => (
|
||||||
|
<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={() => {
|
||||||
|
const updatedTags = field.value.filter(
|
||||||
|
(_, i) => i !== index
|
||||||
|
);
|
||||||
|
field.onChange(updatedTags);
|
||||||
|
}}
|
||||||
|
className="remove-tag-button"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3">
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Tampilkan error */}
|
||||||
|
{errors.tags?.message && (
|
||||||
|
<p className="text-red-400 text-sm">{errors.tags.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-3 space-y-2">
|
<div className="flex flex-col gap-3 space-y-2">
|
||||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
<Label>
|
||||||
|
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||||
|
</Label>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<div key={option.id} className="flex gap-2 items-center">
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
@ -1289,6 +1530,62 @@ export default function FormImage() {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div> */}
|
||||||
|
<div className="px-3 py-3">
|
||||||
|
<div className="flex flex-col gap-3 space-y-2">
|
||||||
|
<Label>
|
||||||
|
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="publishedFor"
|
||||||
|
render={({ field }) => (
|
||||||
|
<>
|
||||||
|
{options.map((option) => (
|
||||||
|
<div
|
||||||
|
key={option.id}
|
||||||
|
className="flex gap-2 items-center"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={option.id}
|
||||||
|
checked={field.value.includes(option.id)}
|
||||||
|
onCheckedChange={(checked: boolean) => {
|
||||||
|
let updated: string[] = [...field.value];
|
||||||
|
if (checked) {
|
||||||
|
updated.push(option.id);
|
||||||
|
} else {
|
||||||
|
updated = updated.filter(
|
||||||
|
(id) => id !== option.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option.id === "all") {
|
||||||
|
if (checked) {
|
||||||
|
updated = options
|
||||||
|
.filter((opt) => opt.id !== "all")
|
||||||
|
.map((opt) => opt.id);
|
||||||
|
} else {
|
||||||
|
updated = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.onChange(updated);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={option.id}>{option.label}</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Tampilkan error */}
|
||||||
|
{errors.publishedFor?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.publishedFor.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="flex flex-row justify-end gap-3">
|
<div className="flex flex-row justify-end gap-3">
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ import { useTranslations } from "next-intl";
|
||||||
import SuggestionModal from "@/components/modal/suggestions-modal";
|
import SuggestionModal from "@/components/modal/suggestions-modal";
|
||||||
import { formatDateToIndonesian } from "@/utils/globals";
|
import { formatDateToIndonesian } from "@/utils/globals";
|
||||||
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||||
|
import FileTextPreview from "./file-preview-text";
|
||||||
|
import FileTextThumbnail from "./file-text-thumbnail";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -435,7 +437,9 @@ export default function FormTeksDetail() {
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
<Card className="w-full lg:w-8/12">
|
<Card className="w-full lg:w-8/12">
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-text", { defaultValue: "Form Text" })}</p>
|
<p className="text-lg font-semibold mb-3">
|
||||||
|
{t("form-text", { defaultValue: "Form Text" })}
|
||||||
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
{/* Input Title */}
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
|
|
@ -484,7 +488,9 @@ export default function FormTeksDetail() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -499,7 +505,9 @@ export default function FormTeksDetail() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xl">{t("file-media", { defaultValue: "File Media" })} </Label>
|
<Label className="text-xl">
|
||||||
|
{t("file-media", { defaultValue: "File Media" })}{" "}
|
||||||
|
</Label>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Swiper
|
<Swiper
|
||||||
thumbs={{ swiper: thumbsSwiper }}
|
thumbs={{ swiper: thumbsSwiper }}
|
||||||
|
|
@ -507,49 +515,13 @@ export default function FormTeksDetail() {
|
||||||
navigation={false}
|
navigation={false}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{detailThumb?.map((data: any, index: number) => (
|
{detailThumb?.map((file: any, index: any) => (
|
||||||
<SwiperSlide key={index}>
|
<SwiperSlide key={index}>
|
||||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
<FileTextPreview file={file} />
|
||||||
data.format
|
|
||||||
) ? (
|
|
||||||
// Menampilkan gambar
|
|
||||||
<img
|
|
||||||
className="object-fill h-full w-full rounded-md"
|
|
||||||
src={data.url}
|
|
||||||
alt={data.fileName || "File"}
|
|
||||||
/>
|
|
||||||
) : data.format === ".pdf" ? (
|
|
||||||
// Menampilkan PDF menggunakan iframe
|
|
||||||
<iframe
|
|
||||||
className="w-full h-96 rounded-md"
|
|
||||||
src={data.url}
|
|
||||||
title={data.fileName || "PDF File"}
|
|
||||||
/>
|
|
||||||
) : [".docx", ".ppt", ".pptx"].includes(
|
|
||||||
data.format
|
|
||||||
) ? (
|
|
||||||
// Menampilkan file dokumen menggunakan Office Viewer
|
|
||||||
<iframe
|
|
||||||
className="w-full h-96 rounded-md"
|
|
||||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
|
||||||
data.url
|
|
||||||
)}`}
|
|
||||||
title={data.fileName || "Document"}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
// Menampilkan link jika format tidak dikenali
|
|
||||||
<a
|
|
||||||
href={data.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-blue-500 underline"
|
|
||||||
>
|
|
||||||
View {data.fileName || "File"}
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<Swiper
|
<Swiper
|
||||||
onSwiper={setThumbsSwiper}
|
onSwiper={setThumbsSwiper}
|
||||||
|
|
@ -558,21 +530,9 @@ export default function FormTeksDetail() {
|
||||||
pagination={{ clickable: true }}
|
pagination={{ clickable: true }}
|
||||||
modules={[Pagination, Thumbs]}
|
modules={[Pagination, Thumbs]}
|
||||||
>
|
>
|
||||||
{detailThumb?.map((data: any, index: number) => (
|
{detailThumb?.map((file: any, index: any) => (
|
||||||
<SwiperSlide key={index}>
|
<SwiperSlide key={index}>
|
||||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
<FileTextThumbnail file={file} />
|
||||||
data.format
|
|
||||||
) ? (
|
|
||||||
<img
|
|
||||||
className="object-cover h-[60px] w-[80px]"
|
|
||||||
src={data.url}
|
|
||||||
alt={data.fileName}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
|
|
||||||
{data?.format?.replace(".", "").toUpperCase()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
|
|
@ -636,7 +596,9 @@ export default function FormTeksDetail() {
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-6 space-y-2">
|
<div className="flex flex-col gap-6 space-y-2">
|
||||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
<Label>
|
||||||
|
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||||
|
</Label>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="5"
|
id="5"
|
||||||
|
|
@ -698,7 +660,9 @@ export default function FormTeksDetail() {
|
||||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||||
<DialogContent size="md">
|
<DialogContent size="md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("leave-comment", { defaultValue: "Leave Comment" })}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{t("leave-comment", { defaultValue: "Leave Comment" })}
|
||||||
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{status == "2"
|
{status == "2"
|
||||||
? files?.map((file, index) => (
|
? files?.map((file, index) => (
|
||||||
|
|
@ -916,7 +880,8 @@ export default function FormTeksDetail() {
|
||||||
color="primary"
|
color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<Icon icon="fa:check" className="mr-3" /> {t("accept", { defaultValue: "Accept" })}
|
<Icon icon="fa:check" className="mr-3" />{" "}
|
||||||
|
{t("accept", { defaultValue: "Accept" })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => actionApproval("3")}
|
onClick={() => actionApproval("3")}
|
||||||
|
|
|
||||||
|
|
@ -154,24 +154,72 @@ export default function FormTeks() {
|
||||||
];
|
];
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
onDrop: (acceptedFiles) => {
|
|
||||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
|
||||||
},
|
|
||||||
accept: {
|
accept: {
|
||||||
"application/pdf": [],
|
"application/pdf": [],
|
||||||
"application/msword": [], // .doc
|
"application/msword": [],
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||||
[], // .docx
|
[],
|
||||||
|
"application/vnd.ms-powerpoint": [],
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
||||||
|
[],
|
||||||
|
},
|
||||||
|
maxSize: 20 * 1024 * 1024,
|
||||||
|
onDrop: (acceptedFiles) => {
|
||||||
|
const filtered = acceptedFiles.filter(
|
||||||
|
(file) =>
|
||||||
|
[
|
||||||
|
"application/pdf",
|
||||||
|
"application/msword",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
"application/vnd.ms-powerpoint",
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
].includes(file.type) && file.size <= 20 * 1024 * 1024
|
||||||
|
);
|
||||||
|
|
||||||
|
const filesWithPreview = filtered.map((file) =>
|
||||||
|
Object.assign(file, {
|
||||||
|
preview: URL.createObjectURL(file),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setFiles(filesWithPreview);
|
||||||
|
setValue("files", filesWithPreview);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const teksSchema = z.object({
|
const teksSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
description: z.string().optional(),
|
|
||||||
descriptionOri: z.string().optional(), // Original editor
|
|
||||||
rewriteDescription: z.string().optional(),
|
|
||||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||||
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
category: z.string().min(1, { message: "Kategori harus dipilih" }),
|
||||||
|
tags: z
|
||||||
|
.array(z.string().min(1))
|
||||||
|
.min(1, { message: "Minimal 1 tag diperlukan" }),
|
||||||
|
files: z
|
||||||
|
.array(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
size: z
|
||||||
|
.number()
|
||||||
|
.max(20 * 1024 * 1024, { message: "Max file size 20 MB" }),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(file) =>
|
||||||
|
[
|
||||||
|
"application/pdf",
|
||||||
|
"application/msword",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
"application/vnd.ms-powerpoint",
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
].includes(file.type),
|
||||||
|
{ message: "Format file tidak didukung" }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.min(1, { message: "File wajib diunggah" }),
|
||||||
|
description: z.string().optional(),
|
||||||
|
descriptionOri: z.string().optional(),
|
||||||
|
rewriteDescription: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -183,6 +231,11 @@ export default function FormTeks() {
|
||||||
} = useForm<TeksSchema>({
|
} = useForm<TeksSchema>({
|
||||||
resolver: zodResolver(teksSchema),
|
resolver: zodResolver(teksSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
title: "",
|
||||||
|
creatorName: "",
|
||||||
|
category: "",
|
||||||
|
tags: [],
|
||||||
|
files: [],
|
||||||
description: "",
|
description: "",
|
||||||
descriptionOri: "",
|
descriptionOri: "",
|
||||||
rewriteDescription: "",
|
rewriteDescription: "",
|
||||||
|
|
@ -381,21 +434,22 @@ export default function FormTeks() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleAddTag = (
|
||||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
e: React.KeyboardEvent<HTMLInputElement>,
|
||||||
|
field: any
|
||||||
|
) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newTag = e.currentTarget.value.trim();
|
const value = e.currentTarget.value.trim();
|
||||||
if (!tags.includes(newTag)) {
|
if (value && !field.value.includes(value)) {
|
||||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
field.onChange([...field.value, value]);
|
||||||
if (inputRef.current) {
|
e.currentTarget.value = "";
|
||||||
inputRef.current.value = ""; // Clear input field
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleRemoveTag = (index: number, field: any) => {
|
||||||
const handleRemoveTag = (index: number) => {
|
const newTags = field.value.filter((_: any, i: number) => i !== index);
|
||||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // Remove tag
|
field.onChange(newTags);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveImage = (index: number) => {
|
const handleRemoveImage = (index: number) => {
|
||||||
|
|
@ -792,7 +846,9 @@ export default function FormTeks() {
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
<Card className="w-full lg:w-8/12">
|
<Card className="w-full lg:w-8/12">
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-text", { defaultValue: "Form Text" })}</p>
|
<p className="text-lg font-semibold mb-3">
|
||||||
|
{t("form-text", { defaultValue: "Form Text" })}
|
||||||
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
{/* Input Title */}
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
|
|
@ -818,11 +874,15 @@ export default function FormTeks() {
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="py-3 w-full space-y-2">
|
<div className="py-3 w-full space-y-2">
|
||||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="category"
|
||||||
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
value={field.value}
|
||||||
onValueChange={(id) => {
|
onValueChange={(val) => {
|
||||||
console.log("Selected Category ID:", id);
|
field.onChange(val);
|
||||||
setSelectedCategory(id);
|
setSelectedCategory(val);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
|
|
@ -839,10 +899,19 @@ export default function FormTeks() {
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.category?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.category.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-3 py-2">
|
<div className="flex flex-row items-center gap-3 py-2">
|
||||||
<Label>{t("ai-assistance", { defaultValue: "Ai Assistance" })}</Label>
|
<Label>
|
||||||
|
{t("ai-assistance", { defaultValue: "Ai Assistance" })}
|
||||||
|
</Label>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={isSwitchOn}
|
defaultChecked={isSwitchOn}
|
||||||
|
|
@ -858,7 +927,9 @@ export default function FormTeks() {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("language", { defaultValue: "Language" })}</Label>
|
<Label>
|
||||||
|
{t("language", { defaultValue: "Language" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedLanguage}>
|
<Select onValueChange={setSelectedLanguage}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -870,7 +941,9 @@ export default function FormTeks() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("writing-style", { defaultValue: "Writing Style" })}</Label>
|
<Label>
|
||||||
|
{t("writing-style", { defaultValue: "Writing Style" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedWritingStyle}>
|
<Select onValueChange={setSelectedWritingStyle}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -889,7 +962,9 @@ export default function FormTeks() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("article-size", { defaultValue: "Article Size" })}</Label>
|
<Label>
|
||||||
|
{t("article-size", { defaultValue: "Article Size" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedSize}>
|
<Select onValueChange={setSelectedSize}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -910,7 +985,9 @@ export default function FormTeks() {
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div className="flex flex-row items-center gap-3 mb-3">
|
<div className="flex flex-row items-center gap-3 mb-3">
|
||||||
<Label>{t("main-keyword", { defaultValue: "Main Keyword" })}</Label>
|
<Label>
|
||||||
|
{t("main-keyword", { defaultValue: "Main Keyword" })}
|
||||||
|
</Label>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
@ -967,9 +1044,13 @@ export default function FormTeks() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-semibold">
|
<p className="font-semibold">
|
||||||
{t("Keywords to include in the text", { defaultValue: "Keywords To Include In The Text" })}
|
{t("Keywords to include in the text", {
|
||||||
|
defaultValue: "Keywords To Include In The Text",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{t("title-key", { defaultValue: "Title Key" })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={selectedSEO}
|
value={selectedSEO}
|
||||||
|
|
@ -979,7 +1060,12 @@ export default function FormTeks() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<Label>{t("special-instructions", { defaultValue: "Special Instructions" })}(Optional)</Label>
|
<Label>
|
||||||
|
{t("special-instructions", {
|
||||||
|
defaultValue: "Special Instructions",
|
||||||
|
})}
|
||||||
|
(Optional)
|
||||||
|
</Label>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -1045,7 +1131,9 @@ export default function FormTeks() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -1089,7 +1177,9 @@ export default function FormTeks() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="descriptionOri"
|
name="descriptionOri"
|
||||||
|
|
@ -1130,7 +1220,7 @@ export default function FormTeks() {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
key={index}
|
key={index}
|
||||||
className={`mr-3 px-3 py-2 rounded-md ${
|
className={`mr-3 px-3 py-2 rounded-md cursor-pointer ${
|
||||||
selectedArticleId === id
|
selectedArticleId === id
|
||||||
? "bg-green-500 text-white"
|
? "bg-green-500 text-white"
|
||||||
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
|
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
|
||||||
|
|
@ -1149,7 +1239,11 @@ export default function FormTeks() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
<Label>
|
||||||
|
{t("file-rewrite", {
|
||||||
|
defaultValue: "File Rewrite",
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="rewriteDescription"
|
name="rewriteDescription"
|
||||||
|
|
@ -1178,46 +1272,52 @@ export default function FormTeks() {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
<Label>
|
||||||
{/* <Input
|
{t("select-file", { defaultValue: "Select File" })}
|
||||||
id="fileInput"
|
</Label>
|
||||||
type="file"
|
<Controller
|
||||||
onChange={handleImageChange}
|
control={control}
|
||||||
/> */}
|
name="files"
|
||||||
<Fragment>
|
render={({ field }) => (
|
||||||
|
<>
|
||||||
<div {...getRootProps({ className: "dropzone" })}>
|
<div {...getRootProps({ className: "dropzone" })}>
|
||||||
<input {...getInputProps()} />
|
<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">
|
<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" />
|
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||||
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||||
{/* Drop files here or click to upload. */}
|
|
||||||
{t("drag-file", { defaultValue: "Drag File" })}
|
{t("drag-file", { defaultValue: "Drag File" })}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{t("upload-file-text-max", { defaultValue: "Upload File Text Max" })}
|
{t("upload-file-text-max", {
|
||||||
|
defaultValue: "Upload File Text Max",
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{files.length ? (
|
{files.length ? (
|
||||||
<Fragment>
|
<>
|
||||||
<div>{fileList}</div>
|
<div>{fileList}</div>
|
||||||
<div className="flex justify-between gap-2">
|
<div className="flex justify-between gap-2">
|
||||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
|
||||||
<Label>{t("watermark", { defaultValue: "Watermark" })}</Label>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Switch defaultChecked color="primary" id="c2" />
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
<Button
|
<Button
|
||||||
color="destructive"
|
color="destructive"
|
||||||
onClick={handleRemoveAllFiles}
|
onClick={() => {
|
||||||
|
setFiles([]);
|
||||||
|
field.onChange([]);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("remove-all", { defaultValue: "Remove All" })}
|
{t("remove-all", { defaultValue: "Remove All" })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Fragment>
|
{errors.files?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.files.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1250,17 +1350,23 @@ export default function FormTeks() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3 space-y-2">
|
<div className="px-3 py-3 space-y-2">
|
||||||
<Label htmlFor="tags">{t("tags", { defaultValue: "Tags" })}</Label>
|
<Label htmlFor="tags">
|
||||||
|
{t("tags", { defaultValue: "Tags" })}
|
||||||
|
</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field }) => (
|
||||||
|
<>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
id="tags"
|
id="tags"
|
||||||
placeholder="Add a tag and press Enter"
|
placeholder="Add a tag and press Enter"
|
||||||
onKeyDown={handleAddTag}
|
onKeyDown={(e) => handleAddTag(e, field)} // pass field ke fungsi
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
{tags.map((tag, index) => (
|
{field.value.map((tag, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
className="px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||||
|
|
@ -1268,7 +1374,7 @@ export default function FormTeks() {
|
||||||
{tag}{" "}
|
{tag}{" "}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleRemoveTag(index)}
|
onClick={() => handleRemoveTag(index, field)}
|
||||||
className="remove-tag-button"
|
className="remove-tag-button"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
|
|
@ -1276,10 +1382,19 @@ export default function FormTeks() {
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.tags?.message && (
|
||||||
|
<p className="text-red-400 text-sm">{errors.tags.message}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-3 space-y-2">
|
<div className="flex flex-col gap-3 space-y-2">
|
||||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
<Label>
|
||||||
|
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||||
|
</Label>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<div key={option.id} className="flex gap-2 items-center">
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
||||||
|
|
@ -157,22 +157,54 @@ export default function FormVideo() {
|
||||||
{ id: "8", label: "KSP" },
|
{ id: "8", label: "KSP" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 100 * 1024 * 1024;
|
||||||
|
const ACCEPTED_FILE_TYPES = ["video/mp4", "video/quicktime"];
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
onDrop: (acceptedFiles) => {
|
|
||||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
|
||||||
},
|
|
||||||
accept: {
|
accept: {
|
||||||
"video/*": [],
|
"video/mp4": [".mp4"],
|
||||||
|
"video/quicktime": [".mov"],
|
||||||
|
},
|
||||||
|
maxSize: MAX_FILE_SIZE,
|
||||||
|
onDrop: (acceptedFiles: File[]) => {
|
||||||
|
const filteredFiles = acceptedFiles.filter(
|
||||||
|
(file) =>
|
||||||
|
ACCEPTED_FILE_TYPES.includes(file.type) && file.size <= MAX_FILE_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
|
const filesWithPreview = filteredFiles.map((file) =>
|
||||||
|
Object.assign(file, {
|
||||||
|
preview: URL.createObjectURL(file),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setFiles(filesWithPreview);
|
||||||
|
setValue("files", filesWithPreview);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const videoSchema = z.object({
|
const videoSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
descriptionOri: z.string().optional(), // Original editor
|
descriptionOri: z.string().optional(),
|
||||||
rewriteDescription: z.string().optional(),
|
rewriteDescription: z.string().optional(),
|
||||||
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||||
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
category: z.string().min(1, { message: "Kategori harus dipilih" }),
|
||||||
|
tags: z
|
||||||
|
.array(z.string().min(1))
|
||||||
|
.min(1, { message: "Minimal 1 tag diperlukan" }),
|
||||||
|
files: z
|
||||||
|
.array(z.any())
|
||||||
|
.min(1, { message: "File video harus dipilih" })
|
||||||
|
.refine(
|
||||||
|
(files) =>
|
||||||
|
files.every((file: File) => ACCEPTED_FILE_TYPES.includes(file.type)),
|
||||||
|
{ message: "File harus berformat mp4 atau mov" }
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(files) => files.every((file: File) => file.size <= MAX_FILE_SIZE),
|
||||||
|
{ message: "Ukuran file maksimal 100 MB" }
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -181,12 +213,15 @@ export default function FormVideo() {
|
||||||
getValues,
|
getValues,
|
||||||
setValue,
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<VideoSchema>({
|
} = useForm<z.infer<typeof videoSchema>>({
|
||||||
resolver: zodResolver(videoSchema),
|
resolver: zodResolver(videoSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
description: "",
|
description: "",
|
||||||
descriptionOri: "",
|
descriptionOri: "",
|
||||||
rewriteDescription: "",
|
rewriteDescription: "",
|
||||||
|
category: "",
|
||||||
|
files: [],
|
||||||
|
tags: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -387,16 +422,16 @@ export default function FormVideo() {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newTag = e.currentTarget.value.trim();
|
const newTag = e.currentTarget.value.trim();
|
||||||
if (!tags.includes(newTag)) {
|
if (!tags.includes(newTag)) {
|
||||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
setTags((prevTags) => [...prevTags, newTag]);
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.value = ""; // Clear input field
|
inputRef.current.value = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveTag = (index: number) => {
|
const handleRemoveTag = (index: number) => {
|
||||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // Remove tag
|
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveImage = (index: number) => {
|
const handleRemoveImage = (index: number) => {
|
||||||
|
|
@ -428,7 +463,7 @@ export default function FormVideo() {
|
||||||
|
|
||||||
if (findCategory) {
|
if (findCategory) {
|
||||||
// setValue("categoryId", findCategory.id);
|
// setValue("categoryId", findCategory.id);
|
||||||
setSelectedCategory(findCategory.id); // Set the selected category
|
setSelectedCategory(findCategory.id);
|
||||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
setTags(response?.data?.data);
|
setTags(response?.data?.data);
|
||||||
}
|
}
|
||||||
|
|
@ -441,10 +476,8 @@ export default function FormVideo() {
|
||||||
const handleCheckboxChange = (id: string): void => {
|
const handleCheckboxChange = (id: string): void => {
|
||||||
if (id === "all") {
|
if (id === "all") {
|
||||||
if (publishedFor.includes("all")) {
|
if (publishedFor.includes("all")) {
|
||||||
// Uncheck all checkboxes
|
|
||||||
setPublishedFor([]);
|
setPublishedFor([]);
|
||||||
} else {
|
} else {
|
||||||
// Select all checkboxes
|
|
||||||
setPublishedFor(
|
setPublishedFor(
|
||||||
options
|
options
|
||||||
.filter((opt: any) => opt.id !== "all")
|
.filter((opt: any) => opt.id !== "all")
|
||||||
|
|
@ -456,7 +489,6 @@ export default function FormVideo() {
|
||||||
? publishedFor.filter((item) => item !== id)
|
? publishedFor.filter((item) => item !== id)
|
||||||
: [...publishedFor, id];
|
: [...publishedFor, id];
|
||||||
|
|
||||||
// Remove "all" if any checkbox is unchecked
|
|
||||||
if (publishedFor.includes("all") && id !== "all") {
|
if (publishedFor.includes("all") && id !== "all") {
|
||||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -467,7 +499,6 @@ export default function FormVideo() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (articleBody) {
|
if (articleBody) {
|
||||||
// Set ke dua field jika rewrite juga aktif
|
|
||||||
setValue("description", articleBody);
|
setValue("description", articleBody);
|
||||||
setValue("rewriteDescription", articleBody);
|
setValue("rewriteDescription", articleBody);
|
||||||
}
|
}
|
||||||
|
|
@ -503,7 +534,7 @@ export default function FormVideo() {
|
||||||
tags: string;
|
tags: string;
|
||||||
isYoutube: boolean;
|
isYoutube: boolean;
|
||||||
isInternationalMedia: boolean;
|
isInternationalMedia: boolean;
|
||||||
attachFromScheduleId?: number; // ✅ Tambahkan properti ini
|
attachFromScheduleId?: number;
|
||||||
} = {
|
} = {
|
||||||
...data,
|
...data,
|
||||||
title: finalTitle,
|
title: finalTitle,
|
||||||
|
|
@ -548,8 +579,6 @@ export default function FormVideo() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload File
|
|
||||||
const progressInfoArr = [];
|
const progressInfoArr = [];
|
||||||
for (const item of files) {
|
for (const item of files) {
|
||||||
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||||
|
|
@ -813,7 +842,9 @@ export default function FormVideo() {
|
||||||
<div className="flex flex-col lg:flex-row gap-10">
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
<Card className="w-full lg:w-8/12">
|
<Card className="w-full lg:w-8/12">
|
||||||
<div className="px-6 py-6">
|
<div className="px-6 py-6">
|
||||||
<p className="text-lg font-semibold mb-3">{t("form-video", { defaultValue: "Form Video" })}</p>
|
<p className="text-lg font-semibold mb-3">
|
||||||
|
{t("form-video", { defaultValue: "Form Video" })}
|
||||||
|
</p>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
<div className="space-y-2 py-3">
|
<div className="space-y-2 py-3">
|
||||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||||
|
|
@ -838,11 +869,16 @@ export default function FormVideo() {
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="py-3 w-full space-y-2">
|
<div className="py-3 w-full space-y-2">
|
||||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="category"
|
||||||
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
value={field.value}
|
||||||
onValueChange={(id) => {
|
onValueChange={(id) => {
|
||||||
|
field.onChange(id);
|
||||||
console.log("Selected Category ID:", id);
|
console.log("Selected Category ID:", id);
|
||||||
setSelectedCategory(id);
|
setSelectedCategory(id); // tetap set ini kalau mau
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
|
|
@ -859,10 +895,19 @@ export default function FormVideo() {
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.category && (
|
||||||
|
<p className="text-red-500 text-sm">
|
||||||
|
{errors.category.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-3 py-2">
|
<div className="flex flex-row items-center gap-3 py-2">
|
||||||
<Label>{t("ai-assistance", { defaultValue: "Ai Assistance" })}</Label>
|
<Label>
|
||||||
|
{t("ai-assistance", { defaultValue: "Ai Assistance" })}
|
||||||
|
</Label>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={isSwitchOn}
|
defaultChecked={isSwitchOn}
|
||||||
|
|
@ -878,7 +923,9 @@ export default function FormVideo() {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("language", { defaultValue: "Language" })}</Label>
|
<Label>
|
||||||
|
{t("language", { defaultValue: "Language" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedLanguage}>
|
<Select onValueChange={setSelectedLanguage}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -890,7 +937,9 @@ export default function FormVideo() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("writing-style", { defaultValue: "Writing Style" })}</Label>
|
<Label>
|
||||||
|
{t("writing-style", { defaultValue: "Writing Style" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedWritingStyle}>
|
<Select onValueChange={setSelectedWritingStyle}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -909,7 +958,9 @@ export default function FormVideo() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 py-3 w-4/12">
|
<div className="space-y-2 py-3 w-4/12">
|
||||||
<Label>{t("article-size", { defaultValue: "Article Size" })}</Label>
|
<Label>
|
||||||
|
{t("article-size", { defaultValue: "Article Size" })}
|
||||||
|
</Label>
|
||||||
<Select onValueChange={setSelectedSize}>
|
<Select onValueChange={setSelectedSize}>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger size="md">
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
|
|
@ -930,7 +981,9 @@ export default function FormVideo() {
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div className="flex flex-row items-center gap-3 mb-3">
|
<div className="flex flex-row items-center gap-3 mb-3">
|
||||||
<Label>{t("main-keyword", { defaultValue: "Main Keyword" })}</Label>
|
<Label>
|
||||||
|
{t("main-keyword", { defaultValue: "Main Keyword" })}
|
||||||
|
</Label>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
@ -987,9 +1040,13 @@ export default function FormVideo() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-semibold">
|
<p className="font-semibold">
|
||||||
{t("Keywords to include in the text", { defaultValue: "Keywords To Include In The Text" })}
|
{t("Keywords to include in the text", {
|
||||||
|
defaultValue: "Keywords To Include In The Text",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{t("title-key", { defaultValue: "Title Key" })}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={selectedSEO}
|
value={selectedSEO}
|
||||||
|
|
@ -999,7 +1056,12 @@ export default function FormVideo() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<Label>{t("special-instructions", { defaultValue: "Special Instructions" })}(Optional)</Label>
|
<Label>
|
||||||
|
{t("special-instructions", {
|
||||||
|
defaultValue: "Special Instructions",
|
||||||
|
})}
|
||||||
|
(Optional)
|
||||||
|
</Label>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -1065,7 +1127,9 @@ export default function FormVideo() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="description"
|
||||||
|
|
@ -1109,7 +1173,9 @@ export default function FormVideo() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="descriptionOri"
|
name="descriptionOri"
|
||||||
|
|
@ -1169,7 +1235,11 @@ export default function FormVideo() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
<Label>
|
||||||
|
{t("file-rewrite", {
|
||||||
|
defaultValue: "File Rewrite",
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="rewriteDescription"
|
name="rewriteDescription"
|
||||||
|
|
@ -1197,37 +1267,43 @@ export default function FormVideo() {
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="files"
|
||||||
|
render={({ field }) => (
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
<Label>
|
||||||
{/* <Input
|
{t("select-file", { defaultValue: "Select File" })}
|
||||||
id="fileInput"
|
</Label>
|
||||||
type="file"
|
|
||||||
onChange={handleImageChange}
|
|
||||||
/> */}
|
|
||||||
<Fragment>
|
|
||||||
<div {...getRootProps({ className: "dropzone" })}>
|
<div {...getRootProps({ className: "dropzone" })}>
|
||||||
<input {...getInputProps()} />
|
<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">
|
<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" />
|
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||||
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
<h4 className="text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||||
{/* Drop files here or click to upload. */}
|
|
||||||
{t("drag-file", { defaultValue: "Drag File" })}
|
{t("drag-file", { defaultValue: "Drag File" })}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{t("upload-file-video-max", { defaultValue: "Upload File Video Max" })}
|
{t("upload-file-video-max", {
|
||||||
|
defaultValue: "Upload File Video Max",
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{files.length ? (
|
|
||||||
<Fragment>
|
{files.length > 0 && (
|
||||||
<div>{fileList}</div>
|
<div className="mt-2 space-y-1">
|
||||||
<div className=" flex justify-between gap-2">
|
{files.map((file, idx) => (
|
||||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
<div
|
||||||
<Label>{t("watermark", { defaultValue: "Watermark" })}</Label>
|
key={idx}
|
||||||
<div className="flex items-center gap-3">
|
className="flex items-center justify-between rounded border border-default-200 dark:border-default-300 px-2 py-1 text-sm"
|
||||||
<Switch defaultChecked color="primary" id="c2" />
|
>
|
||||||
|
<span className="truncate">{file.name}</span>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{(file.size / (1024 * 1024)).toFixed(2)} MB
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div> */}
|
))}
|
||||||
|
<div className="flex justify-between gap-2 mt-1">
|
||||||
<Button
|
<Button
|
||||||
color="destructive"
|
color="destructive"
|
||||||
onClick={handleRemoveAllFiles}
|
onClick={handleRemoveAllFiles}
|
||||||
|
|
@ -1235,17 +1311,23 @@ export default function FormVideo() {
|
||||||
{t("remove-all", { defaultValue: "Remove All" })}
|
{t("remove-all", { defaultValue: "Remove All" })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
|
||||||
) : null}
|
|
||||||
</Fragment>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Submit Button */}
|
{errors.files && (
|
||||||
|
<p className="text-red-500 text-sm">
|
||||||
|
{errors.files.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div className="w-full lg:w-4/12">
|
<div className="w-full lg:w-4/12">
|
||||||
<Card className=" h-[800px]">
|
<Card className="h-fit">
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t("creator", { defaultValue: "Creator" })}</Label>
|
<Label>{t("creator", { defaultValue: "Creator" })}</Label>
|
||||||
|
|
@ -1283,17 +1365,33 @@ export default function FormVideo() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="px-3 py-3 space-y-2">
|
<div className="px-3 py-3 space-y-2">
|
||||||
<Label htmlFor="tags">{t("tags", { defaultValue: "Tags" })}</Label>
|
<Label htmlFor="tags">
|
||||||
|
{t("tags", { defaultValue: "Tags" })}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="tags"
|
||||||
|
render={({ field }) => (
|
||||||
|
<>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
id="tags"
|
id="tags"
|
||||||
placeholder="Add a tag and press Enter"
|
placeholder="Add a tag and press Enter"
|
||||||
onKeyDown={handleAddTag}
|
onKeyDown={(e) => {
|
||||||
ref={inputRef}
|
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
field.onChange([
|
||||||
|
...field.value,
|
||||||
|
e.currentTarget.value.trim(),
|
||||||
|
]);
|
||||||
|
e.currentTarget.value = "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
{tags.map((tag, index) => (
|
{field.value.map((tag: string, index: number) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
className="px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||||
|
|
@ -1301,7 +1399,12 @@ export default function FormVideo() {
|
||||||
{tag}{" "}
|
{tag}{" "}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleRemoveTag(index)}
|
onClick={() => {
|
||||||
|
const updatedTags = field.value.filter(
|
||||||
|
(_, i) => i !== index
|
||||||
|
);
|
||||||
|
field.onChange(updatedTags);
|
||||||
|
}}
|
||||||
className="remove-tag-button"
|
className="remove-tag-button"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
|
|
@ -1309,10 +1412,20 @@ export default function FormVideo() {
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Tampilkan error */}
|
||||||
|
{errors.tags?.message && (
|
||||||
|
<p className="text-red-400 text-sm">{errors.tags.message}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="flex flex-col gap-3 space-y-2">
|
<div className="flex flex-col gap-3 space-y-2">
|
||||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
<Label>
|
||||||
|
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||||
|
</Label>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<div key={option.id} className="flex gap-2 items-center">
|
<div key={option.id} className="flex gap-2 items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// components/WavesurferPlayer.tsx
|
||||||
|
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
|
||||||
|
import WaveSurfer from "wavesurfer.js";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string;
|
||||||
|
height?: number;
|
||||||
|
waveColor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WavesurferPlayerHandle = {
|
||||||
|
play: () => void;
|
||||||
|
pause: () => void;
|
||||||
|
isPlaying: () => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WavesurferPlayer = forwardRef<WavesurferPlayerHandle, Props>(({ url, height = 100, waveColor = "red" }, ref) => {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const waveSurferRef = useRef<WaveSurfer | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
waveSurferRef.current = WaveSurfer.create({
|
||||||
|
container: containerRef.current,
|
||||||
|
waveColor,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
waveSurferRef.current.load(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
waveSurferRef.current?.destroy();
|
||||||
|
};
|
||||||
|
}, [url, waveColor, height]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
play: () => { waveSurferRef.current?.play(); },
|
||||||
|
pause: () => { waveSurferRef.current?.pause(); },
|
||||||
|
isPlaying: () => !!waveSurferRef.current?.isPlaying(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return <div ref={containerRef} className="w-full rounded-md overflow-hidden" />;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default WavesurferPlayer;
|
||||||
|
|
@ -747,6 +747,9 @@
|
||||||
"action": "Actions"
|
"action": "Actions"
|
||||||
},
|
},
|
||||||
"Form": {
|
"Form": {
|
||||||
|
"titleRequired": "Title required",
|
||||||
|
"creatorRequired": "Creator required",
|
||||||
|
"enterTitle": "Enter Title",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"category-name": "Category Name",
|
"category-name": "Category Name",
|
||||||
|
|
@ -813,7 +816,7 @@
|
||||||
"view-file": "View File",
|
"view-file": "View File",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
"upload-file-video-max": " Upload files with mp4 or mov Maximum size 100mb.",
|
"upload-file-video-max": " Upload files with mp4 or mov Maximum size 100mb.",
|
||||||
"upload-file-text-max": " Upload files in .doc, .docx, .pdf, .ppt, or .pptx format. Maximum size 100mb.",
|
"upload-file-text-max": " Upload files in .doc, .docx, .pdf, .ppt, or .pptx format. Maximum size 20mb.",
|
||||||
"upload-file-audio-max": " Upload file in mp3 atau wav Maximum size 100mb",
|
"upload-file-audio-max": " Upload file in mp3 atau wav Maximum size 100mb",
|
||||||
"file-rewrite": "File Rewrite",
|
"file-rewrite": "File Rewrite",
|
||||||
"file-placement": "File Placement",
|
"file-placement": "File Placement",
|
||||||
|
|
@ -849,6 +852,10 @@
|
||||||
"data-media": "please complete the data! ",
|
"data-media": "please complete the data! ",
|
||||||
"title-media-online": "Online Media Name",
|
"title-media-online": "Online Media Name",
|
||||||
"url": "Url",
|
"url": "Url",
|
||||||
"coverage-area": "Coverage Area"
|
"coverage-area": "Coverage Area",
|
||||||
|
"only": "Only .jpg, .jpeg, .png files are allowed",
|
||||||
|
"size": "File too large. Max 100MB",
|
||||||
|
"onlyVd": "Upload files with mp4 or mov Maximum size 100mb."
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,10 @@
|
||||||
"action": "Aksi"
|
"action": "Aksi"
|
||||||
},
|
},
|
||||||
"Form": {
|
"Form": {
|
||||||
|
"titleRequired": "Judul diperlukan",
|
||||||
|
"creatorRequired": "Kreator diperlukan",
|
||||||
|
"enterTitle": "Masukkan Judul",
|
||||||
|
|
||||||
"no": "Nomor",
|
"no": "Nomor",
|
||||||
"title": "Judul",
|
"title": "Judul",
|
||||||
"category-name": "Nama Kategori",
|
"category-name": "Nama Kategori",
|
||||||
|
|
@ -814,7 +818,7 @@
|
||||||
"view-file": "Lihat file",
|
"view-file": "Lihat file",
|
||||||
"update": "Edit",
|
"update": "Edit",
|
||||||
"upload-file-video-max": " Upload file dengan mp4 atau mov Ukuran maksimal 100mb.",
|
"upload-file-video-max": " Upload file dengan mp4 atau mov Ukuran maksimal 100mb.",
|
||||||
"upload-file-text-max": "Upload file dengan format .doc, .docx, .pdf, .ppt, atau .pptx Ukuran maksimal 100mb. ",
|
"upload-file-text-max": "Upload file dengan format .doc, .docx, .pdf, .ppt, atau .pptx Ukuran maksimal 20mb. ",
|
||||||
"upload-file-audio-max": " Upload file dengan mp3 atau wav maksimal ukuran 100mb",
|
"upload-file-audio-max": " Upload file dengan mp3 atau wav maksimal ukuran 100mb",
|
||||||
"file-rewrite": "File hasil Rewrite",
|
"file-rewrite": "File hasil Rewrite",
|
||||||
"file-placement": "Penempatan file",
|
"file-placement": "Penempatan file",
|
||||||
|
|
@ -849,6 +853,9 @@
|
||||||
"data-media": "Silahkan Lengkapi Data!",
|
"data-media": "Silahkan Lengkapi Data!",
|
||||||
"title-media-online": "Nama Media Online",
|
"title-media-online": "Nama Media Online",
|
||||||
"url": "Url",
|
"url": "Url",
|
||||||
"coverage-area": "Cakupan Wilayah"
|
"coverage-area": "Cakupan Wilayah",
|
||||||
|
"only": "Hanya file .jpg, .jpeg, .png yang diizinkan",
|
||||||
|
"size": "File terlalu besar. Maksimal 100MB",
|
||||||
|
"onlyVd": "Upload file dengan mp4 atau mov Ukuran maksimal 100mb."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 4000",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue