This commit is contained in:
Sabda Yagra 2025-07-19 19:51:23 +07:00
parent 978c8b364f
commit 968f2642cc
4 changed files with 83 additions and 146 deletions

View File

@ -2,34 +2,19 @@ import React, { useRef, useState, useEffect } from "react";
interface AudioPlayerProps { interface AudioPlayerProps {
urlAudio: string; urlAudio: string;
fileName: string; // ✅ Tambahkan props ini fileName: string;
} }
const AudioPlayer: React.FC<AudioPlayerProps> = ({ urlAudio, fileName }) => { const AudioPlayer: React.FC<AudioPlayerProps> = ({ urlAudio, fileName }) => {
const audioRef = useRef<HTMLAudioElement>(null); const audioRef = useRef<HTMLAudioElement>(null);
const [currentTime, setCurrentTime] = useState(0); const [currentTime, setCurrentTime] = useState(0);
const playAudio = () => {
audioRef.current?.play();
};
const pauseAudio = () => {
audioRef.current?.pause();
};
const stopAudio = () => {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.currentTime = 0;
}
};
useEffect(() => { useEffect(() => {
const audio = audioRef.current; const audio = audioRef.current;
if (!audio) return; if (!audio) return;
const updateTime = () => { const updateTime = () => {
setCurrentTime(audio.currentTime); setCurrentTime(audio?.currentTime);
}; };
audio.addEventListener("timeupdate", updateTime); audio.addEventListener("timeupdate", updateTime);
@ -38,22 +23,17 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({ urlAudio, fileName }) => {
}; };
}, []); }, []);
const formatTime = (time: number) => {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60).toString().padStart(2, "0");
return `${minutes}:${seconds}`;
};
return ( return (
<div className="mt-2"> <div className="mt-2">
<h2 className="text-lg font-semibold">{fileName}</h2> <h2 className="text-lg font-semibold">{fileName}</h2>
{/* <a href={urlAudio} target="_blank">{urlAudio}</a> */}
<audio ref={audioRef} src={urlAudio} controls className="mt-1 w-full" /> <audio ref={audioRef} src={urlAudio} controls className="mt-1 w-full" />
<div className="mt-2 space-x-2"> {/* <div className="mt-2 space-x-2">
<button onClick={playAudio}> Play</button> <button onClick={playAudio}> Play</button>
<button onClick={pauseAudio}> Pause</button> <button onClick={pauseAudio}> Pause</button>
<button onClick={stopAudio}> Stop</button> <button onClick={stopAudio}> Stop</button>
</div> </div>
<div className="mt-1 text-sm text-gray-500">{formatTime(currentTime)}</div> <div className="mt-1 text-sm text-gray-500">{formatTime(currentTime)}</div> */}
</div> </div>
); );
}; };

View File

@ -83,7 +83,7 @@ type Category = {
type FileType = { type FileType = {
id: number; id: number;
url: string; secondaryUrl: string;
thumbnailFileUrl: string; thumbnailFileUrl: string;
fileName: string; fileName: string;
}; };
@ -121,13 +121,11 @@ export default function FormAudioDetail() {
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
console.log(id); console.log(id);
const editor = useRef(null); const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>; type ImageSchema = z.infer<typeof imageSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
@ -249,7 +247,7 @@ export default function FormAudioDetail() {
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);
} }
@ -274,6 +272,8 @@ export default function FormAudioDetail() {
const details = response?.data?.data; const details = response?.data?.data;
console.log("detail", details); console.log("detail", details);
setFiles(details?.files); setFiles(details?.files);
console.log("ISI FILES:", details?.files);
setDetail(details); setDetail(details);
setMain({ setMain({
type: details?.fileType.name, type: details?.fileType.name,
@ -293,19 +293,23 @@ export default function FormAudioDetail() {
setSelectedTarget(String(details.category.id)); setSelectedTarget(String(details.category.id));
const filesData = details?.files || []; const filesData = details?.files || [];
const audioFiles = filesData.filter( // const audioFiles = filesData.filter(
(file: any) => // (file: any) =>
file.contentType && // file.contentType &&
(file.contentType.startsWith("audio/") || // (file.contentType.startsWith("audio/") ||
file.contentType.includes("mpeg")) // file.contentType.includes("mpeg"))
); // );
// const audioFiles = filesData.filter(
// (file: any) =>
// file.contentType && /^audio\/(mpeg|mp3|wav)$/.test(file.contentType)
// );
const fileUrls = audioFiles.map((file: { url: string }) => // const fileUrls = audioFiles.map((file: { secondaryUrl: string }) =>
file.url ? file.url : "" // file.secondaryUrl ? file.secondaryUrl : ""
); // );
console.log("Audio file URLs:", fileUrls); // console.log("Audio file URLs:", fileUrls);
setDetailThumb(fileUrls); // setDetailThumb(fileUrls);
const approvals = await getDataApprovalByMediaUpload(details?.id); const approvals = await getDataApprovalByMediaUpload(details?.id);
setApproval(approvals?.data?.data); setApproval(approvals?.data?.data);
@ -570,17 +574,19 @@ export default function FormAudioDetail() {
{t("file-media", { defaultValue: "File Media" })} {t("file-media", { defaultValue: "File Media" })}
</Label> </Label>
<div className="w-full"> <div className="w-full">
{detailThumb.map((url, index) => ( {files.length === 0 ? (
<div key={index}> <p className="text-center text-gray-500">
{files.map((file, index) => ( Tidak ada file media
</p>
) : (
files.map((file, index) => (
<AudioPlayer <AudioPlayer
key={index} key={index}
urlAudio={file.url} urlAudio={file?.secondaryUrl}
fileName={file.fileName} fileName={file?.fileName}
/> />
))} ))
</div> )}
))}
</div> </div>
</div> </div>
</div> </div>

View File

@ -211,9 +211,6 @@ export default function FormAudio() {
tags: z tags: z
.array(z.string().min(1)) .array(z.string().min(1))
.min(1, { message: "Wajib isi minimal 1 tag" }), .min(1, { message: "Wajib isi minimal 1 tag" }),
publishedFor: z
.array(z.string())
.min(1, { message: "Minimal 1 target publish harus dipilih." }),
}); });
const { const {
@ -230,7 +227,6 @@ export default function FormAudio() {
rewriteDescription: "", rewriteDescription: "",
category: "", category: "",
tags: [], tags: [],
publishedFor: [],
}, },
}); });
@ -483,8 +479,10 @@ export default function FormAudio() {
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")
@ -495,6 +493,8 @@ export default function FormAudio() {
const updatedPublishedFor = publishedFor.includes(id) const updatedPublishedFor = publishedFor.includes(id)
? 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 {
@ -505,6 +505,7 @@ export default function FormAudio() {
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);
} }
@ -512,21 +513,13 @@ export default function FormAudio() {
const save = async (data: AudioSchema) => { const save = async (data: AudioSchema) => {
loading(); loading();
if (files.length === 0) {
MySwal.fire("Error", "Minimal 1 file harus diunggah.", "error");
return;
}
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = isSwitchOn ? title : data.title; const finalTitle = isSwitchOn ? title : data.title;
// const finalDescription = articleBody || data.description;
const finalDescription = isSwitchOn const finalDescription = isSwitchOn
? data.description ? data.description
: selectedFileType === "rewrite" : selectedFileType === "rewrite"
? data.rewriteDescription ? data.rewriteDescription
: data.descriptionOri; : data.descriptionOri;
if (!finalDescription?.trim()) { if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return; return;
@ -574,36 +567,40 @@ export default function FormAudio() {
const response = await createMedia(requestData); const response = await createMedia(requestData);
console.log("Form Data Submitted:", requestData); console.log("Form Data Submitted:", requestData);
if (response?.error) {
MySwal.fire("Error", response?.message, "error");
return;
}
Cookies.set("idCreate", response?.data?.data, { expires: 1 }); Cookies.set("idCreate", response?.data?.data, { expires: 1 });
id = response?.data?.data; id = response?.data?.data;
const formMedia = new FormData(); const formMedia = new FormData();
const thumbnail = files[0]; console.log("Thumbnail : ", files[0]);
formMedia.append("file", thumbnail); formMedia.append("file", files[0]);
const responseThumbnail = await uploadThumbnail(id, formMedia); const responseThumbnail = await uploadThumbnail(id, formMedia);
if (responseThumbnail?.error == true) { if (responseThumbnail?.error == true) {
error(responseThumbnail?.message); error(responseThumbnail?.message);
return false; return false;
} }
} }
const progressInfoArr = files.map((item) => ({
percentage: 0, const progressInfoArr = [];
fileName: item.name, for (const item of files) {
})); progressInfoArr.push({ percentage: 0, fileName: item.name });
}
progressInfo = progressInfoArr; progressInfo = progressInfoArr;
setIsStartUpload(true); setIsStartUpload(true);
setProgressList(progressInfoArr); setProgressList(progressInfoArr);
close(); close();
// showProgress();
files.map(async (item: any, index: number) => { files.map(async (item: any, index: number) => {
await uploadResumableFile( await uploadResumableFile(index, String(id), item, "0");
index,
String(id),
item,
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
);
}); });
Cookies.remove("idCreate"); Cookies.remove("idCreate");
// MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
}; };
const onSubmit = (data: AudioSchema) => { const onSubmit = (data: AudioSchema) => {
@ -1416,73 +1413,29 @@ export default function FormAudio() {
)} )}
</div> </div>
<Controller
control={control}
name="publishedFor"
render={({ field }) => (
<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> <Label>
{t("publish-target", { defaultValue: "Publish Target" })} {t("publish-target", { defaultValue: "Publish Target" })}
</Label> </Label>
{options.map((option) => (
{options.map((option) => { <div key={option.id} className="flex gap-2 items-center">
const isAllChecked =
field.value.length ===
options.filter((opt: any) => opt.id !== "all").length;
const isChecked =
option.id === "all"
? isAllChecked
: field.value.includes(option.id);
const handleChange = () => {
let updated: string[] = [];
if (option.id === "all") {
updated = isAllChecked
? []
: options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id);
} else {
updated = isChecked
? field.value.filter((val) => val !== option.id)
: [...field.value, option.id];
if (isAllChecked && option.id !== "all") {
updated = updated.filter((val) => val !== "all");
}
}
field.onChange(updated);
setPublishedFor(updated);
};
return (
<div
key={option.id}
className="flex gap-2 items-center"
>
<Checkbox <Checkbox
id={option.id} id={option.id}
checked={isChecked} checked={
onCheckedChange={handleChange} option.id === "all"
? publishedFor.length ===
options.filter((opt: any) => opt.id !== "all")
.length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/> />
<Label htmlFor={option.id}>{option.label}</Label> <Label htmlFor={option.id}>{option.label}</Label>
</div> </div>
); ))}
})}
{errors.publishedFor && (
<p className="text-red-500 text-sm">
{errors.publishedFor.message}
</p>
)}
</div> </div>
</div> </div>
)}
/>
</Card> </Card>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">

View File

@ -99,9 +99,7 @@ const DetailAudio = () => {
const initFetch = async () => { const initFetch = async () => {
const response = await getDetail(String(slug)); const response = await getDetail(String(slug));
console.log("detailAudio", response);
const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]); const responseGet = await getPublicSuggestionList(slug?.split("-")?.[0]);
setIsFromSPIT(response?.data?.data?.isFromSPIT); setIsFromSPIT(response?.data?.data?.isFromSPIT);
setWidth(window.innerWidth); setWidth(window.innerWidth);
setContent(response?.data?.data); setContent(response?.data?.data);
@ -405,7 +403,7 @@ const DetailAudio = () => {
const { default: WaveSurfer } = await import("wavesurfer.js"); const { default: WaveSurfer } = await import("wavesurfer.js");
if (wavesurfer.current) { if (wavesurfer.current) {
wavesurfer.current.destroy(); // 🔥 Hapus instance lama sebelum membuat yang baru wavesurfer.current.destroy();
} }
setPlaying(false); setPlaying(false);
@ -414,7 +412,7 @@ const DetailAudio = () => {
return [ return [
Math.floor((time % 3600) / 60), Math.floor((time % 3600) / 60),
// minutes // minutes
`00${Math.floor(time % 60)}`.slice(-2), // seconds `00${Math.floor(time % 60)}`.slice(-2),
].join(":"); ].join(":");
}; };
@ -445,7 +443,7 @@ const DetailAudio = () => {
return () => { return () => {
if (wavesurfer.current) { if (wavesurfer.current) {
wavesurfer.current.destroy(); // 🔥 Hapus saat unmount wavesurfer.current.destroy();
} }
}; };
} }