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 { formatDateToIndonesian } from "@/utils/globals";
|
||||
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import AudioPlayer from "@/components/audio-player";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -139,32 +141,54 @@ export default function FormAudioDetail() {
|
|||
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||
const [description, setDescription] = useState("");
|
||||
const [main, setMain] = useState<any>([]);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
const [audioPlaying, setAudioPlaying] = useState<any>(null);
|
||||
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 waveSurfersRef = useRef<WaveSurfer[]>([]);
|
||||
const [isPlayingIndex, setIsPlayingIndex] = useState<number | null>(null);
|
||||
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [approval, setApproval] = useState<any>();
|
||||
|
||||
const onReady = (ws: any) => {
|
||||
setWavesurfer(ws);
|
||||
setIsPlaying(false);
|
||||
const onDrop = (acceptedFiles: File[]) => {
|
||||
setUploadedFiles(acceptedFiles);
|
||||
const blobUrls = acceptedFiles.map((file) => URL.createObjectURL(file));
|
||||
setDetailThumb(blobUrls);
|
||||
};
|
||||
|
||||
const onPlayPause = () => {
|
||||
wavesurfer && wavesurfer.playPause();
|
||||
const onReady = (ws: WaveSurfer, index: number) => {
|
||||
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";
|
||||
|
||||
const {
|
||||
|
|
@ -261,8 +285,8 @@ export default function FormAudioDetail() {
|
|||
});
|
||||
setupPlacementCheck(details?.files?.length);
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
if (details?.publishedForObject) {
|
||||
const publisherIds = details?.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
|
|
@ -276,9 +300,9 @@ export default function FormAudioDetail() {
|
|||
setSelectedTarget(matchingCategory.name);
|
||||
}
|
||||
|
||||
setSelectedTarget(details.categoryId); // Untuk dropdown
|
||||
setSelectedTarget(details?.categoryId);
|
||||
|
||||
const filesData = details.files || [];
|
||||
const filesData = details?.files || [];
|
||||
const audioFiles = filesData.filter(
|
||||
(file: any) =>
|
||||
file.contentType && file.contentType.startsWith("video/webm")
|
||||
|
|
@ -439,9 +463,9 @@ export default function FormAudioDetail() {
|
|||
|
||||
const handleAudioPlayPause = (audioSrc: string) => {
|
||||
if (audioPlaying === audioSrc) {
|
||||
setAudioPlaying(null); // Pause if the same audio is clicked
|
||||
setAudioPlaying(null);
|
||||
} 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">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<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">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
|
|
@ -491,7 +517,7 @@ export default function FormAudioDetail() {
|
|||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||
<Select
|
||||
value={detail?.category.name} // Nilai default berdasarkan detail
|
||||
value={detail?.category.name}
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category:", id);
|
||||
setSelectedTarget(id);
|
||||
|
|
@ -512,7 +538,9 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -527,42 +555,25 @@ export default function FormAudioDetail() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<Label className="text-xl space-y-2">{t("file-media", { defaultValue: "File Media" })}</Label>
|
||||
<div className="w-full">
|
||||
<div className={"container example"}>
|
||||
{detailThumb?.map((url: any, index: number) => (
|
||||
<div key={url.id}>
|
||||
<WavesurferPlayer
|
||||
height={500}
|
||||
waveColor="red"
|
||||
<Label className="text-xl space-y-2">
|
||||
{t("file-media", { defaultValue: "File Media" })}
|
||||
</Label>
|
||||
<div className="container example">
|
||||
{detailThumb.map((url, index) => (
|
||||
<div key={index}>
|
||||
{/* <WavesurferPlayer
|
||||
url={url}
|
||||
onReady={onReady}
|
||||
onPlay={() => setIsPlaying(true)}
|
||||
onPause={() => setIsPlaying(false)}
|
||||
waveColor="red"
|
||||
height={80}
|
||||
onReady={(ws: any) => onReady(ws, index)}
|
||||
/>
|
||||
<Button onClick={() => onPlayPause(index)}>
|
||||
{isPlayingIndex === index ? "Pause" : "Play"}
|
||||
</Button> */}
|
||||
<AudioPlayer urlAudio={url} />
|
||||
</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>
|
||||
|
|
@ -613,7 +624,9 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<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">
|
||||
<Checkbox
|
||||
id="5"
|
||||
|
|
@ -675,7 +688,9 @@ export default function FormAudioDetail() {
|
|||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("leave-comment", { defaultValue: "Leave Comment" })}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{t("leave-comment", { defaultValue: "Leave Comment" })}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file, index) => (
|
||||
|
|
@ -893,7 +908,8 @@ export default function FormAudioDetail() {
|
|||
color="primary"
|
||||
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
|
||||
onClick={() => actionApproval("3")}
|
||||
|
|
|
|||
|
|
@ -156,20 +156,33 @@ export default function FormAudio() {
|
|||
{ id: "8", label: "KSP" },
|
||||
];
|
||||
|
||||
const audioRefs = useRef<HTMLAudioElement[]>([]);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
multiple: true,
|
||||
accept: { "audio/*": [] },
|
||||
onDrop: (acceptedFiles) => {
|
||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||
},
|
||||
accept: {
|
||||
"audio/*": [],
|
||||
const filesWithPreview = acceptedFiles.map((file) =>
|
||||
Object.assign(file, { preview: URL.createObjectURL(file) })
|
||||
);
|
||||
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({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: z.string().optional(), // Original editor
|
||||
rewriteDescription: z.string().optional(), // Rewrite editor
|
||||
descriptionOri: z.string().optional(),
|
||||
rewriteDescription: z.string().optional(),
|
||||
creatorName: z.string().min(1, { message: "Creator 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">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<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">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
|
|
@ -830,7 +845,7 @@ export default function FormAudio() {
|
|||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||
<Select
|
||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
||||
value={selectedCategory}
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedCategory(id);
|
||||
|
|
@ -853,7 +868,9 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -869,7 +886,9 @@ export default function FormAudio() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -881,7 +900,9 @@ export default function FormAudio() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -900,7 +921,9 @@ export default function FormAudio() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -921,7 +944,9 @@ export default function FormAudio() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<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
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -978,9 +1003,13 @@ export default function FormAudio() {
|
|||
</Button>
|
||||
</div>
|
||||
<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 className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -990,7 +1019,12 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -1056,7 +1090,9 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -1100,7 +1136,9 @@ export default function FormAudio() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
|
|
@ -1160,7 +1198,11 @@ export default function FormAudio() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
||||
<Label>
|
||||
{t("file-rewrite", {
|
||||
defaultValue: "File Rewrite",
|
||||
})}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="rewriteDescription"
|
||||
|
|
@ -1189,36 +1231,51 @@ export default function FormAudio() {
|
|||
</>
|
||||
)}
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Label>
|
||||
{t("select-file", { defaultValue: "Select File" })}
|
||||
</Label>
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<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">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
<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-audio-max", { defaultValue: "Upload File Audio Max" })}
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("upload-file-audio-max", {
|
||||
defaultValue: "Upload File Audio Max",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>Gunakan Watermark</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
<div className="space-y-4 mt-4">
|
||||
{files.map((file, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex flex-col gap-2 border p-2 rounded-md"
|
||||
>
|
||||
<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 className="flex justify-between gap-2 mt-3">
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
|
|
@ -1259,7 +1316,9 @@ export default function FormAudio() {
|
|||
</div>
|
||||
</div>
|
||||
<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
|
||||
type="text"
|
||||
|
|
@ -1288,7 +1347,9 @@ export default function FormAudio() {
|
|||
</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>
|
||||
<Label>
|
||||
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||
</Label>
|
||||
{options.map((option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<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>;
|
||||
const params = useParams();
|
||||
const locale = params?.locale;
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
|
|
@ -171,11 +170,17 @@ export default function FormImage() {
|
|||
});
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
title: z.string().min(1, { message: t("titleRequired") }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: 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 {
|
||||
|
|
@ -183,6 +188,7 @@ export default function FormImage() {
|
|||
handleSubmit,
|
||||
getValues,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<ImageSchema>({
|
||||
resolver: zodResolver(imageSchema),
|
||||
|
|
@ -190,6 +196,12 @@ export default function FormImage() {
|
|||
description: "",
|
||||
descriptionOri: "",
|
||||
rewriteDescription: "",
|
||||
title: "",
|
||||
creatorName: "",
|
||||
categoryId: "",
|
||||
tags: [],
|
||||
publishedFor: [],
|
||||
files: [],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -232,10 +244,10 @@ export default function FormImage() {
|
|||
}
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "WARNING",
|
||||
text: "Please provide a valid main keyword.",
|
||||
});
|
||||
icon: "warning",
|
||||
title: "WARNING",
|
||||
text: "Please provide a valid main keyword.",
|
||||
});
|
||||
console.error("Please provide a valid main keyword.");
|
||||
}
|
||||
};
|
||||
|
|
@ -263,11 +275,11 @@ export default function FormImage() {
|
|||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "WARNING",
|
||||
text: "Please provide a valid title.",
|
||||
});
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "WARNING",
|
||||
text: "Please provide a valid title.",
|
||||
});
|
||||
console.error("Please provide a valid main keyword.");
|
||||
}
|
||||
};
|
||||
|
|
@ -295,11 +307,11 @@ export default function FormImage() {
|
|||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "WARNING",
|
||||
text: "Please provide a valid keyword.",
|
||||
});
|
||||
Swal.fire({
|
||||
icon: "warning",
|
||||
title: "WARNING",
|
||||
text: "Please provide a valid keyword.",
|
||||
});
|
||||
console.error("Please provide a valid main keyword.");
|
||||
}
|
||||
};
|
||||
|
|
@ -390,9 +402,9 @@ export default function FormImage() {
|
|||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]);
|
||||
setTags((prevTags) => [...prevTags, newTag]);
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = "";
|
||||
inputRef.current.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -431,7 +443,7 @@ export default function FormImage() {
|
|||
|
||||
if (findCategory) {
|
||||
// setValue("categoryId", findCategory.id);
|
||||
setSelectedCategory(findCategory.id);
|
||||
setSelectedCategory(findCategory.id);
|
||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||
setTags(response?.data?.data);
|
||||
}
|
||||
|
|
@ -749,7 +761,7 @@ export default function FormImage() {
|
|||
lang: "id",
|
||||
contextType: "text",
|
||||
urlContext: null,
|
||||
context: editorContent,
|
||||
context: editorContent,
|
||||
createdBy: roleId,
|
||||
sentiment: "Humorous",
|
||||
clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ",
|
||||
|
|
@ -785,7 +797,9 @@ export default function FormImage() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<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">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
|
|
@ -799,7 +813,9 @@ export default function FormImage() {
|
|||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
placeholder={t("enterTitle", {
|
||||
defaultValue: "Masukkan Title",
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -811,8 +827,8 @@ export default function FormImage() {
|
|||
<div className="flex items-center">
|
||||
<div className="py-3 space-y-2 w-full">
|
||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||
<Select
|
||||
value={selectedCategory}
|
||||
{/* <Select
|
||||
value={selectedCategory}
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedCategory(id);
|
||||
|
|
@ -831,11 +847,39 @@ export default function FormImage() {
|
|||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</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>
|
||||
)}
|
||||
/>
|
||||
{errors.categoryId?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.categoryId?.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -851,7 +895,9 @@ export default function FormImage() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -863,7 +909,9 @@ export default function FormImage() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -882,7 +930,9 @@ export default function FormImage() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -903,7 +953,9 @@ export default function FormImage() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<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
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -960,9 +1012,13 @@ export default function FormImage() {
|
|||
</Button>
|
||||
</div>
|
||||
<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 className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -972,7 +1028,12 @@ export default function FormImage() {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -1005,7 +1066,7 @@ export default function FormImage() {
|
|||
{articleIds.map((id: string, index: number) => (
|
||||
<p
|
||||
key={index}
|
||||
className={`mr-3 px-3 py-2 rounded-md ${
|
||||
className={`mr-3 px-3 py-2 rounded-md cursor-pointer ${
|
||||
selectedArticleId === id
|
||||
? "bg-green-500 text-white"
|
||||
: "border-2 border-green-500 text-green-500"
|
||||
|
|
@ -1038,7 +1099,9 @@ export default function FormImage() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -1082,7 +1145,9 @@ export default function FormImage() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
|
|
@ -1142,7 +1207,11 @@ export default function FormImage() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
||||
<Label>
|
||||
{t("file-rewrite", {
|
||||
defaultValue: "File Rewrite",
|
||||
})}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="rewriteDescription"
|
||||
|
|
@ -1170,53 +1239,163 @@ export default function FormImage() {
|
|||
</RadioGroup>
|
||||
</>
|
||||
)}
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
{t("drag-file", { defaultValue: "Drag File" })}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
{t("upload-file-max", { defaultValue: "Upload File Max" })}
|
||||
|
||||
<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">
|
||||
<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 100MB (.jpg, .jpeg, .png)",
|
||||
})}
|
||||
</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>
|
||||
</div>
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>{t("watermark", { defaultValue: "Watermark" })}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div> */}
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
{t("remove-all", { defaultValue: "Remove All" })}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
{files.length ? (
|
||||
<>
|
||||
<div>{fileList}</div>
|
||||
<div className="flex justify-between gap-2">
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
{t("remove-all", { defaultValue: "Remove All" })}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{errors.files?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.files.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[500px]">
|
||||
<div className="px-3 py-3">
|
||||
|
|
@ -1242,8 +1421,10 @@ export default function FormImage() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 space-y-2">
|
||||
<Label htmlFor="tags">{t("tags", { defaultValue: "Tags" })}</Label>
|
||||
{/* <div className="px-3 py-3 space-y-2">
|
||||
<Label htmlFor="tags">
|
||||
{t("tags", { defaultValue: "Tags" })}
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
|
|
@ -1268,10 +1449,70 @@ export default function FormImage() {
|
|||
</span>
|
||||
))}
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 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="px-3 py-3">
|
||||
<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) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
|
|
@ -1289,6 +1530,62 @@ export default function FormImage() {
|
|||
</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>
|
||||
</Card>
|
||||
<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 { formatDateToIndonesian } from "@/utils/globals";
|
||||
import ApprovalHistoryModal from "@/components/modal/approval-history-modal";
|
||||
import FileTextPreview from "./file-preview-text";
|
||||
import FileTextThumbnail from "./file-text-thumbnail";
|
||||
|
||||
const imageSchema = z.object({
|
||||
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">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<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">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
|
|
@ -484,7 +488,9 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -499,7 +505,9 @@ export default function FormTeksDetail() {
|
|||
)}
|
||||
</div>
|
||||
<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">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
|
|
@ -507,50 +515,14 @@ export default function FormTeksDetail() {
|
|||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any, index: number) => (
|
||||
{detailThumb?.map((file: any, index: any) => (
|
||||
<SwiperSlide key={index}>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
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>
|
||||
)}
|
||||
<FileTextPreview file={file} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className="mt-2 ">
|
||||
|
||||
<div className="mt-2">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={8}
|
||||
|
|
@ -558,21 +530,9 @@ export default function FormTeksDetail() {
|
|||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailThumb?.map((data: any, index: number) => (
|
||||
{detailThumb?.map((file: any, index: any) => (
|
||||
<SwiperSlide key={index}>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
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>
|
||||
)}
|
||||
<FileTextThumbnail file={file} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
|
@ -636,7 +596,9 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<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">
|
||||
<Checkbox
|
||||
id="5"
|
||||
|
|
@ -698,7 +660,9 @@ export default function FormTeksDetail() {
|
|||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("leave-comment", { defaultValue: "Leave Comment" })}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{t("leave-comment", { defaultValue: "Leave Comment" })}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file, index) => (
|
||||
|
|
@ -916,7 +880,8 @@ export default function FormTeksDetail() {
|
|||
color="primary"
|
||||
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
|
||||
onClick={() => actionApproval("3")}
|
||||
|
|
|
|||
|
|
@ -154,24 +154,72 @@ export default function FormTeks() {
|
|||
];
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: (acceptedFiles) => {
|
||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||
},
|
||||
accept: {
|
||||
"application/pdf": [],
|
||||
"application/msword": [], // .doc
|
||||
"application/msword": [],
|
||||
"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({
|
||||
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" }),
|
||||
// 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 {
|
||||
|
|
@ -183,6 +231,11 @@ export default function FormTeks() {
|
|||
} = useForm<TeksSchema>({
|
||||
resolver: zodResolver(teksSchema),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
creatorName: "",
|
||||
category: "",
|
||||
tags: [],
|
||||
files: [],
|
||||
description: "",
|
||||
descriptionOri: "",
|
||||
rewriteDescription: "",
|
||||
|
|
@ -381,21 +434,22 @@ export default function FormTeks() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
||||
const handleAddTag = (
|
||||
e: React.KeyboardEvent<HTMLInputElement>,
|
||||
field: any
|
||||
) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Clear input field
|
||||
}
|
||||
const value = e.currentTarget.value.trim();
|
||||
if (value && !field.value.includes(value)) {
|
||||
field.onChange([...field.value, value]);
|
||||
e.currentTarget.value = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // Remove tag
|
||||
const handleRemoveTag = (index: number, field: any) => {
|
||||
const newTags = field.value.filter((_: any, i: number) => i !== index);
|
||||
field.onChange(newTags);
|
||||
};
|
||||
|
||||
const handleRemoveImage = (index: number) => {
|
||||
|
|
@ -792,7 +846,9 @@ export default function FormTeks() {
|
|||
<div className="flex flex-col lg:flex-row gap-10">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<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">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
|
|
@ -818,31 +874,44 @@ export default function FormTeks() {
|
|||
<div className="flex items-center">
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||
<Select
|
||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedCategory(id);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem
|
||||
key={category.id}
|
||||
value={category.id.toString()}
|
||||
>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Controller
|
||||
control={control}
|
||||
name="category"
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(val) => {
|
||||
field.onChange(val);
|
||||
setSelectedCategory(val);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem
|
||||
key={category.id}
|
||||
value={category.id.toString()}
|
||||
>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{errors.category?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.category.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -858,7 +927,9 @@ export default function FormTeks() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -870,7 +941,9 @@ export default function FormTeks() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -889,7 +962,9 @@ export default function FormTeks() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -910,7 +985,9 @@ export default function FormTeks() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<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
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -967,9 +1044,13 @@ export default function FormTeks() {
|
|||
</Button>
|
||||
</div>
|
||||
<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 className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -979,7 +1060,12 @@ export default function FormTeks() {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -1045,7 +1131,9 @@ export default function FormTeks() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -1089,7 +1177,9 @@ export default function FormTeks() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
|
|
@ -1130,7 +1220,7 @@ export default function FormTeks() {
|
|||
<Button
|
||||
type="button"
|
||||
key={index}
|
||||
className={`mr-3 px-3 py-2 rounded-md ${
|
||||
className={`mr-3 px-3 py-2 rounded-md cursor-pointer ${
|
||||
selectedArticleId === id
|
||||
? "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"
|
||||
|
|
@ -1149,7 +1239,11 @@ export default function FormTeks() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
||||
<Label>
|
||||
{t("file-rewrite", {
|
||||
defaultValue: "File Rewrite",
|
||||
})}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="rewriteDescription"
|
||||
|
|
@ -1178,46 +1272,52 @@ export default function FormTeks() {
|
|||
</>
|
||||
)}
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
{t("drag-file", { defaultValue: "Drag File" })}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
{t("upload-file-text-max", { defaultValue: "Upload File Text Max" })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>{t("watermark", { defaultValue: "Watermark" })}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
<Label>
|
||||
{t("select-file", { defaultValue: "Select File" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="files"
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<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-text-max", {
|
||||
defaultValue: "Upload File Text Max",
|
||||
})}
|
||||
</div>
|
||||
</div> */}
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
{t("remove-all", { defaultValue: "Remove All" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Fragment>
|
||||
{files.length ? (
|
||||
<>
|
||||
<div>{fileList}</div>
|
||||
<div className="flex justify-between gap-2">
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={() => {
|
||||
setFiles([]);
|
||||
field.onChange([]);
|
||||
}}
|
||||
>
|
||||
{t("remove-all", { defaultValue: "Remove All" })}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
{errors.files?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.files.message}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1250,36 +1350,51 @@ export default function FormTeks() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="px-3 py-3 space-y-2">
|
||||
<Label htmlFor="tags">{t("tags", { defaultValue: "Tags" })}</Label>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
<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) => handleAddTag(e, field)} // pass field ke fungsi
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="mt-3">
|
||||
{field.value.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||
>
|
||||
{tag}{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index, field)}
|
||||
className="remove-tag-button"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-3 ">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||
>
|
||||
{tag}{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{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">
|
||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
||||
<Label>
|
||||
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||
</Label>
|
||||
{options.map((option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
|
|
|
|||
|
|
@ -157,22 +157,54 @@ export default function FormVideo() {
|
|||
{ id: "8", label: "KSP" },
|
||||
];
|
||||
|
||||
const MAX_FILE_SIZE = 100 * 1024 * 1024;
|
||||
const ACCEPTED_FILE_TYPES = ["video/mp4", "video/quicktime"];
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: (acceptedFiles) => {
|
||||
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||
},
|
||||
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({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: z.string().optional(), // Original editor
|
||||
descriptionOri: z.string().optional(),
|
||||
rewriteDescription: z.string().optional(),
|
||||
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 {
|
||||
|
|
@ -181,12 +213,15 @@ export default function FormVideo() {
|
|||
getValues,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<VideoSchema>({
|
||||
} = useForm<z.infer<typeof videoSchema>>({
|
||||
resolver: zodResolver(videoSchema),
|
||||
defaultValues: {
|
||||
description: "",
|
||||
descriptionOri: "",
|
||||
rewriteDescription: "",
|
||||
category: "",
|
||||
files: [],
|
||||
tags: [],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -387,16 +422,16 @@ export default function FormVideo() {
|
|||
e.preventDefault();
|
||||
const newTag = e.currentTarget.value.trim();
|
||||
if (!tags.includes(newTag)) {
|
||||
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
|
||||
setTags((prevTags) => [...prevTags, newTag]);
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = ""; // Clear input field
|
||||
inputRef.current.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTag = (index: number) => {
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index)); // Remove tag
|
||||
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleRemoveImage = (index: number) => {
|
||||
|
|
@ -428,7 +463,7 @@ export default function FormVideo() {
|
|||
|
||||
if (findCategory) {
|
||||
// setValue("categoryId", findCategory.id);
|
||||
setSelectedCategory(findCategory.id); // Set the selected category
|
||||
setSelectedCategory(findCategory.id);
|
||||
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||
setTags(response?.data?.data);
|
||||
}
|
||||
|
|
@ -441,10 +476,8 @@ export default function FormVideo() {
|
|||
const handleCheckboxChange = (id: string): void => {
|
||||
if (id === "all") {
|
||||
if (publishedFor.includes("all")) {
|
||||
// Uncheck all checkboxes
|
||||
setPublishedFor([]);
|
||||
} else {
|
||||
// Select all checkboxes
|
||||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
|
|
@ -456,7 +489,6 @@ export default function FormVideo() {
|
|||
? publishedFor.filter((item) => item !== id)
|
||||
: [...publishedFor, id];
|
||||
|
||||
// Remove "all" if any checkbox is unchecked
|
||||
if (publishedFor.includes("all") && id !== "all") {
|
||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
||||
} else {
|
||||
|
|
@ -467,7 +499,6 @@ export default function FormVideo() {
|
|||
|
||||
useEffect(() => {
|
||||
if (articleBody) {
|
||||
// Set ke dua field jika rewrite juga aktif
|
||||
setValue("description", articleBody);
|
||||
setValue("rewriteDescription", articleBody);
|
||||
}
|
||||
|
|
@ -503,7 +534,7 @@ export default function FormVideo() {
|
|||
tags: string;
|
||||
isYoutube: boolean;
|
||||
isInternationalMedia: boolean;
|
||||
attachFromScheduleId?: number; // ✅ Tambahkan properti ini
|
||||
attachFromScheduleId?: number;
|
||||
} = {
|
||||
...data,
|
||||
title: finalTitle,
|
||||
|
|
@ -548,8 +579,6 @@ export default function FormVideo() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upload File
|
||||
const progressInfoArr = [];
|
||||
for (const item of files) {
|
||||
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">
|
||||
<Card className="w-full lg:w-8/12">
|
||||
<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="space-y-2 py-3">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
|
|
@ -838,31 +869,45 @@ export default function FormVideo() {
|
|||
<div className="flex items-center">
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||
<Select
|
||||
value={selectedCategory} // Ensure selectedTarget is updated correctly
|
||||
onValueChange={(id) => {
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedCategory(id);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem
|
||||
key={category.id}
|
||||
value={category.id.toString()}
|
||||
>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Controller
|
||||
control={control}
|
||||
name="category"
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(id) => {
|
||||
field.onChange(id);
|
||||
console.log("Selected Category ID:", id);
|
||||
setSelectedCategory(id); // tetap set ini kalau mau
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categories.map((category) => (
|
||||
<SelectItem
|
||||
key={category.id}
|
||||
value={category.id.toString()}
|
||||
>
|
||||
{category.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{errors.category && (
|
||||
<p className="text-red-500 text-sm">
|
||||
{errors.category.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Switch
|
||||
defaultChecked={isSwitchOn}
|
||||
|
|
@ -878,7 +923,9 @@ export default function FormVideo() {
|
|||
<div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -890,7 +937,9 @@ export default function FormVideo() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -909,7 +958,9 @@ export default function FormVideo() {
|
|||
</Select>
|
||||
</div>
|
||||
<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}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih" />
|
||||
|
|
@ -930,7 +981,9 @@ export default function FormVideo() {
|
|||
</div>
|
||||
<div className="mt-5">
|
||||
<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
|
||||
variant="outline"
|
||||
color="primary"
|
||||
|
|
@ -987,9 +1040,13 @@ export default function FormVideo() {
|
|||
</Button>
|
||||
</div>
|
||||
<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 className="text-sm">{t("title-key", { defaultValue: "Title Key" })}</p>
|
||||
<div className="mt-3">
|
||||
<Textarea
|
||||
value={selectedSEO}
|
||||
|
|
@ -999,7 +1056,12 @@ export default function FormVideo() {
|
|||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -1065,7 +1127,9 @@ export default function FormVideo() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
|
|
@ -1109,7 +1173,9 @@ export default function FormVideo() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("description", { defaultValue: "Description" })}</Label>
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
|
|
@ -1169,7 +1235,11 @@ export default function FormVideo() {
|
|||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("file-rewrite", { defaultValue: "File Rewrite" })}</Label>
|
||||
<Label>
|
||||
{t("file-rewrite", {
|
||||
defaultValue: "File Rewrite",
|
||||
})}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="rewriteDescription"
|
||||
|
|
@ -1197,55 +1267,67 @@ export default function FormVideo() {
|
|||
</RadioGroup>
|
||||
</>
|
||||
)}
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>{t("select-file", { defaultValue: "Select File" })}</Label>
|
||||
{/* <Input
|
||||
id="fileInput"
|
||||
type="file"
|
||||
onChange={handleImageChange}
|
||||
/> */}
|
||||
<Fragment>
|
||||
<div {...getRootProps({ className: "dropzone" })}>
|
||||
<input {...getInputProps()} />
|
||||
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
|
||||
<CloudUpload className="text-default-300 w-10 h-10" />
|
||||
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
|
||||
{/* Drop files here or click to upload. */}
|
||||
{t("drag-file", { defaultValue: "Drag File" })}
|
||||
</h4>
|
||||
<div className=" text-xs text-muted-foreground">
|
||||
{t("upload-file-video-max", { defaultValue: "Upload File Video Max" })}
|
||||
<Controller
|
||||
control={control}
|
||||
name="files"
|
||||
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-video-max", {
|
||||
defaultValue: "Upload File Video Max",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{files.length ? (
|
||||
<Fragment>
|
||||
<div>{fileList}</div>
|
||||
<div className=" flex justify-between gap-2">
|
||||
{/* <div className="flex flex-row items-center gap-3 py-3">
|
||||
<Label>{t("watermark", { defaultValue: "Watermark" })}</Label>
|
||||
<div className="flex items-center gap-3">
|
||||
<Switch defaultChecked color="primary" id="c2" />
|
||||
</div>
|
||||
</div> */}
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
{t("remove-all", { defaultValue: "Remove All" })}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
{files.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
{files.map((file, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between rounded border border-default-200 dark:border-default-300 px-2 py-1 text-sm"
|
||||
>
|
||||
<span className="truncate">{file.name}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{(file.size / (1024 * 1024)).toFixed(2)} MB
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex justify-between gap-2 mt-1">
|
||||
<Button
|
||||
color="destructive"
|
||||
onClick={handleRemoveAllFiles}
|
||||
>
|
||||
{t("remove-all", { defaultValue: "Remove All" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.files && (
|
||||
<p className="text-red-500 text-sm">
|
||||
{errors.files.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="w-full lg:w-4/12">
|
||||
<Card className=" h-[800px]">
|
||||
<Card className="h-fit">
|
||||
<div className="px-3 py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>{t("creator", { defaultValue: "Creator" })}</Label>
|
||||
|
|
@ -1283,36 +1365,67 @@ export default function FormVideo() {
|
|||
</div>
|
||||
)}
|
||||
<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
|
||||
type="text"
|
||||
id="tags"
|
||||
placeholder="Add a tag and press Enter"
|
||||
onKeyDown={handleAddTag}
|
||||
ref={inputRef}
|
||||
<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 className="mt-3 ">
|
||||
{tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
|
||||
>
|
||||
{tag}{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveTag(index)}
|
||||
className="remove-tag-button"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<Label>{t("publish-target", { defaultValue: "Publish Target" })}</Label>
|
||||
<Label>
|
||||
{t("publish-target", { defaultValue: "Publish Target" })}
|
||||
</Label>
|
||||
{options.map((option) => (
|
||||
<div key={option.id} className="flex gap-2 items-center">
|
||||
<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"
|
||||
},
|
||||
"Form": {
|
||||
"titleRequired": "Title required",
|
||||
"creatorRequired": "Creator required",
|
||||
"enterTitle": "Enter Title",
|
||||
"no": "No",
|
||||
"title": "Title",
|
||||
"category-name": "Category Name",
|
||||
|
|
@ -813,7 +816,7 @@
|
|||
"view-file": "View File",
|
||||
"update": "Update",
|
||||
"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",
|
||||
"file-rewrite": "File Rewrite",
|
||||
"file-placement": "File Placement",
|
||||
|
|
@ -849,6 +852,10 @@
|
|||
"data-media": "please complete the data! ",
|
||||
"title-media-online": "Online Media Name",
|
||||
"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"
|
||||
},
|
||||
"Form": {
|
||||
"titleRequired": "Judul diperlukan",
|
||||
"creatorRequired": "Kreator diperlukan",
|
||||
"enterTitle": "Masukkan Judul",
|
||||
|
||||
"no": "Nomor",
|
||||
"title": "Judul",
|
||||
"category-name": "Nama Kategori",
|
||||
|
|
@ -789,7 +793,7 @@
|
|||
"main-keyword": "Main Keyword",
|
||||
"seo": "Seo",
|
||||
"Keywords to include in the text": "Kata kunci untuk di sertakan dalam teks",
|
||||
"title-key": "JIka Anda tidak Memberikan kata kunci, kami akan secara otomatis membuat kata kunci yang relevan dari kata kunciutama untuk setiap bagian dan menggunakannya untuk membuat artikel. Untuk menambahkan kata kunci baru, ketik kata kunci",
|
||||
"title-key": "JIka Anda tidak Memberikan kata kunci, kami akan secara otomatis membuat kata kunci yang relevan dari kata kunci utama untuk setiap bagian dan menggunakannya untuk membuat artikel. Untuk menambahkan kata kunci baru, ketik kata kunci",
|
||||
"special-instructions": "Instruksi Khusus",
|
||||
"description": "Deskripsi",
|
||||
"select-file": "Pilih File",
|
||||
|
|
@ -814,7 +818,7 @@
|
|||
"view-file": "Lihat file",
|
||||
"update": "Edit",
|
||||
"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",
|
||||
"file-rewrite": "File hasil Rewrite",
|
||||
"file-placement": "Penempatan file",
|
||||
|
|
@ -849,6 +853,9 @@
|
|||
"data-media": "Silahkan Lengkapi Data!",
|
||||
"title-media-online": "Nama Media Online",
|
||||
"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",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 4000",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
|
|
|
|||
Loading…
Reference in New Issue