feat:update curated content,kontent

This commit is contained in:
Anang Yusman 2025-01-23 20:16:41 +08:00
parent 079d238d1f
commit 35e96a7bee
13 changed files with 1678 additions and 664 deletions

View File

@ -21,8 +21,20 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import Cookies from "js-cookie";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import {
DotSquare,
InboxIcon,
Music,
PaperclipIcon,
SmileIcon,
TrashIcon,
} from "lucide-react";
import {
deleteMediaCurationMessage,
detailMedia,
getMediaCurationMessage,
saveMediaCurationMessage,
} from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
@ -33,6 +45,15 @@ import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
import { htmlToString } from "@/utils/globals";
import { getCookiesDecrypt } from "@/lib/utils";
import { loading } from "@/lib/swal";
import { formatDate } from "@fullcalendar/core/index.js";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -69,6 +90,13 @@ export type curationDetail = {
userGroupId: number;
};
};
publishedFor: string; // ID for selected radio button
publishedForObject: {
id: number;
name: string;
isInternal: boolean;
code: string;
}[];
tags: string;
provinceId: string;
is_active: string;
@ -119,10 +147,14 @@ export default function DetailAudio() {
// files: [],
// fileType: null,
// });
const userLevelNumber = getCookiesDecrypt("ulne");
const userId = getCookiesDecrypt("uie");
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [detailAudio, setDetailAudio] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [selectedValue, setSelectedValue] = useState<string>("");
const {
control,
@ -136,11 +168,30 @@ export default function DetailAudio() {
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const [selectedFileId, setSelectedFileId] = useState(null);
const [listData, setListData] = useState([]);
const [message, setMessage] = useState("");
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false);
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);
};
const onReady = (ws: any) => {
setWavesurfer(ws);
setIsPlaying(false);
};
const onPlayPause = () => {
wavesurfer && wavesurfer.playPause();
};
const addReply = (commentId: number) => {
if (replyText.trim()) {
const newCommentData = commentsData.map((comment: any) => {
@ -166,23 +217,131 @@ export default function DetailAudio() {
}
};
useEffect(() => {
async function initState() {
// loading();
const response = await getMediaCurationMessage(selectedFileId);
console.log("data", response?.data?.data);
console.log("userLvl", userLevelNumber);
setListData(response?.data?.data);
close();
}
initState();
}, [selectedFileId]);
const postData = async () => {
if (message?.length > 1 && selectedFileId) {
try {
const data = {
mediaUploadFileId: selectedFileId,
message,
parentId: null,
};
const response = await saveMediaCurationMessage(data);
console.log("Komentar terkirim:", response);
const responseGet = await getMediaCurationMessage(selectedFileId);
setListData(responseGet?.data?.data);
setMessage("");
} catch (error) {
console.error("Error posting comment:", error);
}
} else {
console.log("Pesan atau file ID tidak valid.");
}
};
const sendReplyData = async (parentId: number) => {
const inputElement = document.querySelector(
`#input-comment-${parentId}`
) as HTMLTextAreaElement;
if (inputElement?.value?.length > 1 && selectedFileId) {
loading();
const data = {
mediaUploadFileId: selectedFileId,
message: inputElement.value,
parentId,
};
console.log("Sending reply:", data);
const response = await saveMediaCurationMessage(data);
console.log(response);
const responseGet = await getMediaCurationMessage(selectedFileId);
console.log("Updated comments:", responseGet?.data?.data);
setListData(responseGet?.data?.data);
inputElement.value = "";
close();
setReplyingTo(null);
}
};
async function deleteDataSuggestion(dataId: any) {
loading();
const response = await deleteMediaCurationMessage(dataId);
console.log(response);
const responseGet = await getMediaCurationMessage(selectedFileId);
console.log(responseGet?.data?.data);
setListData(responseGet?.data?.data);
close();
}
const deleteData = (dataId: any) => {
deleteDataSuggestion(dataId);
console.log(dataId);
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response?.data?.data;
setDetail(details);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
const fileUrls = filesData.map((file: any) => ({
id: file.id,
secondaryUrl: file.secondaryUrl || "default-image.jpg",
placements: file.placements || "",
}));
setDetail(details);
setSelectedValue(details?.publishedFor || "");
setSelectedFileId(details?.files[0]?.id);
setDetailAudio(fileUrls);
const fileUrlsThumbnail = filesData.map(
(file: { thumbnailFileUrl: string; placements: string }) => ({
thumbnailFileUrl: file.thumbnailFileUrl
? file.thumbnailFileUrl
: "default-image.jpg",
placements: file.placements || "",
})
);
setDetailThumb(fileUrls);
setDetailThumb(fileUrlsThumbnail);
// setDetailThumb(fileUrls);
}
}
initState();
}, [id, refresh]);
const handleFileClick = async (fileId: any) => {
setSelectedFileId(fileId);
try {
const response = await getMediaCurationMessage(fileId);
console.log("Data komentar:", response?.data?.data);
setListData(response?.data?.data);
} catch (error) {
console.error("Error fetching comments:", error);
}
};
const handleValueChange = (value: string) => {
setSelectedValue(value);
};
return (
<div className="flex gap-10">
{detail !== undefined ? (
@ -242,7 +401,7 @@ export default function DetailAudio() {
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
value={htmlToString(detail.description)}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
@ -263,24 +422,25 @@ export default function DetailAudio() {
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
value={selectedValue} // Set selected value
onValueChange={handleValueChange} // Update state on change
className="flex flex-wrap gap-3"
>
{/* Static list of radio buttons */}
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<RadioGroupItem value="5" id="umum" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<RadioGroupItem value="8" id="ksp" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<RadioGroupItem value="6" id="journalist" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<RadioGroupItem value="7" id="polri" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
@ -288,9 +448,16 @@ export default function DetailAudio() {
<div className=" py-3">
<div className="space-y-2">
<Label>Tag</Label>
<p className="border rounded-md text-blue-600 px-2 py-2">
{detail?.tags}
</p>
<div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
key={index}
className="border rounded-md px-2 py-2"
>
{tag.trim()}
</Badge>
))}
</div>
</div>
</div>
<div className=" py-3">
@ -327,40 +494,62 @@ export default function DetailAudio() {
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<audio
className="w-full"
src={data}
controls
title={`Audio ${data.id}`}
{detailAudio?.map((data: any) => (
<SwiperSlide
key={data.id}
onClick={() => handleFileClick(data.id)}
>
<WavesurferPlayer
height={500}
waveColor="red"
url={data.secondaryUrl}
onReady={onReady}
key={`File ID: ${data.id}`}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
</SwiperSlide>
))}
</Swiper>
<div className="mt-2">
<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 className=" mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
pagination={{ clickable: true }}
modules={[Pagination, Thumbs]}
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-700">
Audio {data.id}
</span>
<audio
className="h-[24px] w-[80px]"
src={data}
controls
title={`Audio ${data.id}`}
{detailAudio?.map((data: any) => (
<SwiperSlide
key={data.id}
onClick={() => handleFileClick(data.id)}
>
<img
className="object-fill h-full w-full rounded-md"
src={"/assets/music-icon.jpg"}
alt={`File ID: ${data.id}`}
/>
</div>
</SwiperSlide>
))}
</Swiper>
@ -390,35 +579,33 @@ export default function DetailAudio() {
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
alt={`Article ${data.id}`}
/>
<Music className="object-cover w-32 h-32" />
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
{/* Mabes Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "mabes"} // Automatically checks if placement matches
disabled // To reflect read-only behavior
/>
Nasional
<span>Nasional</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
{/* Polda Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "polda"} // Automatically checks if placement matches
disabled
/>
Wilayah
<span>Wilayah</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
{/* International Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "international"} // Automatically checks if placement matches
disabled
/>
International
<span>International</span>
</label>
</div>
</div>
@ -432,110 +619,184 @@ export default function DetailAudio() {
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">Berikan Komentar</Label>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage
src="/images/avatar/avatar-1.png"
alt="@shadcn"
<div className="mt-4 border p-4 rounded bg-gray-50">
<Textarea
placeholder="Tulis tanggapan Anda di sini..."
value={message}
onChange={handleInputChange}
/>
</Avatar>
<textarea
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
placeholder="Tuliskan komentar Anda di sini.."
></textarea>
</div>
<div className="flex items-center mt-2 gap-3">
<button className="flex items-center text-gray-600 hover:text-gray-800">
<PaperclipIcon className="w-5 h-5" />
Lampirkan
</button>
<button className="flex items-center text-gray-600 hover:text-gray-800">
<SmileIcon className="w-5 h-5" />
Emoticon
</button>
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Kirim
</button>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Komentar</Label>
{commentsData.map((comment) => (
<div
key={comment.id}
className="flex items-start gap-3 mt-2"
<div className="flex justify-end mt-3">
<Button
color="primary"
onClick={() => postData()}
type="button"
>
<Avatar>
Kirim Komentar
</Button>
</div>
</div>
{listData?.map((item: any) => (
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
<div className="flex flex-row gap-3">
<Avatar className="mt-2">
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
src={"/assets/avatar-profile.png"}
alt={`@${item.username}`}
/>
</Avatar>
<div className="flex flex-col">
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{comment.username}
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
{formatDate(item.createdAt)}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
</div>
<p className="text-gray-800 mt-1">{item.message}</p>
<div className="flex flex-row gap-2">
{/* <div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
onClick={() => handleReply(item.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
</div> */}
<div
key={index}
className="flex items-start gap-3 mt-1"
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() => deleteData(item.id)}
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt={`@${reply.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{reply.username}
</span>
<span className="text-gray-500 text-sm">
{reply.date}
</span>
<p className="text-gray-800 mt-1">
{reply.text}
</p>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">Delete</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
{replyingTo === item.id && (
<div className="ml-10 mt-2">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
id={`input-comment-${item.id}`}
className="w-full p-2 border rounded"
placeholder="Masukkan tanggapan anda"
/>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
onClick={() => sendReplyData(item.id)}
>
Kirim
</button>
</div>
)}
{item.children?.length > 0 && (
<div className="ml-10 mt-2 flex flex-col">
{item.children.map((child: any) => (
<div
key={child.id}
className="flex flex-col gap-3 mt-2"
>
<div className="flex flex-row gap-3">
<Avatar className="mt-2">
<AvatarImage
src={"/assets/avatar-profile.png"}
alt={`@${child.username}`}
/>
</Avatar>
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{formatDate(item.createdAt)}
</span>
</div>
<p className="text-gray-800 mt-1">
{child.message}
</p>
<div className="flex flex-row gap-2">
{/* <div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(child.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div> */}
<div
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() => deleteData(child.id)}
>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">Delete</span>
</div>
</div>
</div>
</div>
{replyingTo === child.id && (
<div className="ml-10 mt-2">
<textarea
id={`input-comment-${child.id}`}
className="w-full p-2 border rounded"
placeholder="Masukkan tanggapan anda"
/>
<button
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
onClick={() => sendReplyData(child.id)}
>
Kirim
</button>
</div>
)}
{child.children?.length > 0 && (
<div className="ml-10 mt-2 flex flex-col mb-3">
{child.children.map((child2: any) => (
<div
key={child2.id}
className="flex flex-col gap-3 mt-2"
>
<div className="flex flex-row gap-3 ">
<Avatar className="mt-2">
<AvatarImage
src={"/assets/avatar-profile.png"}
alt={`@${child2.username}`}
/>
</Avatar>
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{formatDate(item.createdAt)}
</span>
</div>
<p className="text-gray-800 mt-1">
{child2.message}
</p>
<div className="flex flex-row gap-2">
<div
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() =>
deleteData(child2.id)
}
>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">
Delete
</span>
</div>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
</CardContent>

View File

@ -21,8 +21,19 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import Cookies from "js-cookie";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import {
DotSquare,
InboxIcon,
PaperclipIcon,
SmileIcon,
TrashIcon,
} from "lucide-react";
import {
deleteMediaCurationMessage,
detailMedia,
getMediaCurationMessage,
saveMediaCurationMessage,
} from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
@ -35,6 +46,11 @@ import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import JoditEditor from "jodit-react";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { htmlToString } from "@/utils/globals";
import { loading } from "@/lib/swal";
import { formatDate } from "@fullcalendar/core/index.js";
import { getCookiesDecrypt } from "@/lib/utils";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -51,6 +67,13 @@ type Category = {
categoryName: string;
};
type PublishedForObject = {
id: number;
name: string;
isInternal: boolean;
code: string;
};
export type curationDetail = {
id: number;
title: string;
@ -71,6 +94,13 @@ export type curationDetail = {
userGroupId: number;
};
};
publishedFor: string; // ID for selected radio button
publishedForObject: {
id: number;
name: string;
isInternal: boolean;
code: string;
}[];
tags: string;
provinceId: string;
is_active: string;
@ -109,7 +139,8 @@ export default function DetailDocument() {
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const userLevelNumber = getCookiesDecrypt("ulne");
const userId = getCookiesDecrypt("uie");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
@ -125,6 +156,7 @@ export default function DetailDocument() {
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [selectedValue, setSelectedValue] = useState<string>("");
const {
control,
@ -138,11 +170,18 @@ export default function DetailDocument() {
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const [selectedFileId, setSelectedFileId] = useState(null);
const [listData, setListData] = useState([]);
const [message, setMessage] = useState("");
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);
};
const addReply = (commentId: number) => {
if (replyText.trim()) {
const newCommentData = commentsData.map((comment: any) => {
@ -168,6 +207,85 @@ export default function DetailDocument() {
}
};
useEffect(() => {
async function initState() {
// loading();
const response = await getMediaCurationMessage(selectedFileId);
console.log("data", response?.data?.data);
console.log("userLvl", userLevelNumber);
setListData(response?.data?.data);
close();
}
initState();
}, [selectedFileId]);
const postData = async () => {
if (message?.length > 1 && selectedFileId) {
try {
const data = {
mediaUploadFileId: selectedFileId,
message,
parentId: null,
};
const response = await saveMediaCurationMessage(data);
console.log("Komentar terkirim:", response);
const responseGet = await getMediaCurationMessage(selectedFileId);
setListData(responseGet?.data?.data);
setMessage("");
} catch (error) {
console.error("Error posting comment:", error);
}
} else {
console.log("Pesan atau file ID tidak valid.");
}
};
const sendReplyData = async (parentId: number) => {
const inputElement = document.querySelector(
`#input-comment-${parentId}`
) as HTMLTextAreaElement;
if (inputElement?.value?.length > 1 && selectedFileId) {
loading();
const data = {
mediaUploadFileId: selectedFileId,
message: inputElement.value,
parentId,
};
console.log("Sending reply:", data);
const response = await saveMediaCurationMessage(data);
console.log(response);
const responseGet = await getMediaCurationMessage(selectedFileId);
console.log("Updated comments:", responseGet?.data?.data);
setListData(responseGet?.data?.data);
inputElement.value = "";
close();
setReplyingTo(null);
}
};
async function deleteDataSuggestion(dataId: any) {
loading();
const response = await deleteMediaCurationMessage(dataId);
console.log(response);
const responseGet = await getMediaCurationMessage(selectedFileId);
console.log(responseGet?.data?.data);
setListData(responseGet?.data?.data);
close();
}
const deleteData = (dataId: any) => {
deleteDataSuggestion(dataId);
console.log(dataId);
};
useEffect(() => {
async function initState() {
if (id) {
@ -175,16 +293,37 @@ export default function DetailDocument() {
const details = response?.data?.data;
setDetail(details);
setSelectedValue(details?.publishedFor || "");
setSelectedFileId(details?.files[0]?.id);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
const fileUrls = filesData.map((file: any) => ({
id: file.id,
url: file.secondaryUrl || "default-image.jpg",
format: file.format,
fileName: file.fileName,
placements: file.placements || "",
}));
setDetailThumb(fileUrls);
}
}
initState();
}, [id, refresh]);
const handleFileClick = async (fileId: any) => {
setSelectedFileId(fileId);
try {
const response = await getMediaCurationMessage(fileId);
console.log("Data komentar:", response?.data?.data);
setListData(response?.data?.data);
} catch (error) {
console.error("Error fetching comments:", error);
}
};
const handleValueChange = (value: string) => {
setSelectedValue(value);
};
return (
<div className="flex gap-10">
{detail !== undefined ? (
@ -244,7 +383,7 @@ export default function DetailDocument() {
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
value={htmlToString(detail.description)}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
@ -265,24 +404,25 @@ export default function DetailDocument() {
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
value={selectedValue} // Set selected value
onValueChange={handleValueChange} // Update state on change
className="flex flex-wrap gap-3"
>
{/* Static list of radio buttons */}
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<RadioGroupItem value="5" id="umum" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<RadioGroupItem value="8" id="ksp" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<RadioGroupItem value="6" id="journalist" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<RadioGroupItem value="7" id="polri" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
@ -330,18 +470,90 @@ export default function DetailDocument() {
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<div className="w-full ">
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide
key={data.id}
onClick={() => handleFileClick(data.id)}
>
{[".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>
)}
</SwiperSlide>
))}
</Swiper>
<div className="mt-2">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{ clickable: true }}
modules={[Pagination, Thumbs]}
>
{detailThumb?.map((data: any) => (
<SwiperSlide
key={data.id}
onClick={() => handleFileClick(data.id)}
>
{[".jpg", ".jpeg", ".png", ".webp"].includes(
data.format
) ? (
<img
className="object-cover h-[60px] w-[80px]"
src={"/assets/docx-icon.jpg"}
alt={data.fileName}
/>
) : (
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
{data?.format
?.replace(".", "")
.toUpperCase()}
</div>
)}
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
@ -357,45 +569,42 @@ export default function DetailDocument() {
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
{detailThumb.map((data: any, index: number) => (
<div
key={data.id}
key={index}
className="flex items-center gap-3 mt-2"
>
{/* <img
className="object-cover w-20 h-20"
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
alt={` ${data.id}`}
className="object-cover h-[60px] w-[80px]"
src={"/assets/docx-icon.jpg"}
alt={data.fileName}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
{/* Mabes Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "mabes"} // Automatically checks if placement matches
disabled // To reflect read-only behavior
/>
Nasional
<span>Nasional</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
{/* Polda Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "polda"} // Automatically checks if placement matches
disabled
/>
Wilayah
<span>Wilayah</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
{/* International Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "international"} // Automatically checks if placement matches
disabled
/>
International
<span>International</span>
</label>
</div>
</div>
@ -409,110 +618,184 @@ export default function DetailDocument() {
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">Berikan Komentar</Label>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage
src="/images/avatar/avatar-1.png"
alt="@shadcn"
<div className="mt-4 border p-4 rounded bg-gray-50">
<Textarea
placeholder="Tulis tanggapan Anda di sini..."
value={message}
onChange={handleInputChange}
/>
</Avatar>
<textarea
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
placeholder="Tuliskan komentar Anda di sini.."
></textarea>
</div>
<div className="flex items-center mt-2 gap-3">
<button className="flex items-center text-gray-600 hover:text-gray-800">
<PaperclipIcon className="w-5 h-5" />
Lampirkan
</button>
<button className="flex items-center text-gray-600 hover:text-gray-800">
<SmileIcon className="w-5 h-5" />
Emoticon
</button>
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Kirim
</button>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Komentar</Label>
{commentsData.map((comment) => (
<div
key={comment.id}
className="flex items-start gap-3 mt-2"
<div className="flex justify-end mt-3">
<Button
color="primary"
onClick={() => postData()}
type="button"
>
<Avatar>
Kirim Komentar
</Button>
</div>
</div>
{listData?.map((item: any) => (
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
<div className="flex flex-row gap-3">
<Avatar className="mt-2">
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
src={"/assets/avatar-profile.png"}
alt={`@${item.username}`}
/>
</Avatar>
<div className="flex flex-col">
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{comment.username}
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
{formatDate(item.createdAt)}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
</div>
<p className="text-gray-800 mt-1">{item.message}</p>
<div className="flex flex-row gap-2">
{/* <div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
onClick={() => handleReply(item.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
</div> */}
<div
key={index}
className="flex items-start gap-3 mt-1"
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() => deleteData(item.id)}
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt={`@${reply.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{reply.username}
</span>
<span className="text-gray-500 text-sm">
{reply.date}
</span>
<p className="text-gray-800 mt-1">
{reply.text}
</p>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">Delete</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
{replyingTo === item.id && (
<div className="ml-10 mt-2">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
id={`input-comment-${item.id}`}
className="w-full p-2 border rounded"
placeholder="Masukkan tanggapan anda"
/>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
onClick={() => sendReplyData(item.id)}
>
Kirim
</button>
</div>
)}
{item.children?.length > 0 && (
<div className="ml-10 mt-2 flex flex-col">
{item.children.map((child: any) => (
<div
key={child.id}
className="flex flex-col gap-3 mt-2"
>
<div className="flex flex-row gap-3">
<Avatar className="mt-2">
<AvatarImage
src={"/assets/avatar-profile.png"}
alt={`@${child.username}`}
/>
</Avatar>
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{formatDate(item.createdAt)}
</span>
</div>
<p className="text-gray-800 mt-1">
{child.message}
</p>
<div className="flex flex-row gap-2">
{/* <div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(child.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div> */}
<div
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() => deleteData(child.id)}
>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">Delete</span>
</div>
</div>
</div>
</div>
{replyingTo === child.id && (
<div className="ml-10 mt-2">
<textarea
id={`input-comment-${child.id}`}
className="w-full p-2 border rounded"
placeholder="Masukkan tanggapan anda"
/>
<button
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
onClick={() => sendReplyData(child.id)}
>
Kirim
</button>
</div>
)}
{child.children?.length > 0 && (
<div className="ml-10 mt-2 flex flex-col mb-3">
{child.children.map((child2: any) => (
<div
key={child2.id}
className="flex flex-col gap-3 mt-2"
>
<div className="flex flex-row gap-3 ">
<Avatar className="mt-2">
<AvatarImage
src={"/assets/avatar-profile.png"}
alt={`@${child2.username}`}
/>
</Avatar>
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{formatDate(item.createdAt)}
</span>
</div>
<p className="text-gray-800 mt-1">
{child2.message}
</p>
<div className="flex flex-row gap-2">
<div
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() =>
deleteData(child2.id)
}
>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">
Delete
</span>
</div>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
</CardContent>

View File

@ -7,6 +7,7 @@ import {
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { listCuratedContent } from "@/service/curated-content/curated-content";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
@ -21,10 +22,12 @@ const TeksSliderPage = () => {
const [documentData, setDocumentData] = useState<any>();
const [displayDocument, setDisplayDocument] = useState<any[]>([]);
const [page, setPage] = useState(1);
const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState("");
useEffect(() => {
initFetch();
}, []);
}, [page, limit, search]);
useEffect(() => {
if (documentData?.length > 0) {
@ -35,14 +38,12 @@ const TeksSliderPage = () => {
}, [documentData]);
const initFetch = async () => {
const response = await getListContent({
page: page - 1,
size: 12,
sortBy: "createdAt",
contentTypeId: "3",
});
const response = await listCuratedContent(search, limit, page - 1, 3, "1");
console.log(response);
setDocumentData(response?.data?.data?.content);
const data = response?.data?.data;
const contentData = data?.content;
setDocumentData(contentData);
};
const shuffleAndSetVideos = () => {
const shuffled = shuffleArray([...documentData]);

View File

@ -53,6 +53,8 @@ import {
} from "@/service/task";
import { getCookiesDecrypt } from "@/lib/utils";
import { close, loading } from "@/lib/swal";
import { Checkbox } from "@/components/ui/checkbox";
import { htmlToString } from "@/utils/globals";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -107,6 +109,13 @@ export type curationDetail = {
userGroupId: number;
};
};
publishedFor: string; // ID for selected radio button
publishedForObject: {
id: number;
name: string;
isInternal: boolean;
code: string;
}[];
tags: string;
provinceId: string;
is_active: string;
@ -173,6 +182,7 @@ export default function DetailImage() {
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const [selectedValue, setSelectedValue] = useState<string>("");
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
@ -272,11 +282,13 @@ export default function DetailImage() {
const details = response?.data?.data;
setDetail(details);
setSelectedValue(details?.publishedFor || "");
setSelectedFileId(details?.files[0]?.id);
const filesData = details.files || [];
const fileUrls = filesData.map((file: any) => ({
id: file.id,
thumbnailFileUrl: file.thumbnailFileUrl || "default-image.jpg",
placements: file.placements || "",
}));
setDetailThumb(fileUrls);
}
@ -294,6 +306,9 @@ export default function DetailImage() {
console.error("Error fetching comments:", error);
}
};
const handleValueChange = (value: string) => {
setSelectedValue(value);
};
return (
<div className="flex gap-10">
@ -354,7 +369,7 @@ export default function DetailImage() {
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
value={htmlToString(detail.description)}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
@ -375,24 +390,25 @@ export default function DetailImage() {
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
value={selectedValue} // Set selected value
onValueChange={handleValueChange} // Update state on change
className="flex flex-wrap gap-3"
>
{/* Static list of radio buttons */}
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<RadioGroupItem value="5" id="umum" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<RadioGroupItem value="8" id="ksp" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<RadioGroupItem value="6" id="journalist" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<RadioGroupItem value="7" id="polri" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
@ -452,7 +468,7 @@ export default function DetailImage() {
onClick={() => handleFileClick(data.id)}
>
<img
className="object-fill h-full w-full"
className="object-fill h-full w-full rounded-md"
src={data.thumbnailFileUrl}
alt={`File ID: ${data.id}`}
/>
@ -476,7 +492,7 @@ export default function DetailImage() {
onClick={() => handleFileClick(data.id)}
>
<img
className="object-fill h-full w-full"
className="object-fill h-full w-full rounded-md"
src={data.thumbnailFileUrl}
alt={`File ID: ${data.id}`}
/>
@ -510,34 +526,36 @@ export default function DetailImage() {
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
className="object-cover w-36 h-32 rounded-md"
src={data.thumbnailFileUrl}
alt={`Article ${data.id}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
{/* Mabes Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "mabes"} // Automatically checks if placement matches
disabled // To reflect read-only behavior
/>
Nasional
<span>Nasional</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
{/* Polda Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "polda"} // Automatically checks if placement matches
disabled
/>
Wilayah
<span>Wilayah</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
{/* International Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "international"} // Automatically checks if placement matches
disabled
/>
International
<span>International</span>
</label>
</div>
</div>

View File

@ -21,8 +21,19 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import Cookies from "js-cookie";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import {
DotSquare,
InboxIcon,
PaperclipIcon,
SmileIcon,
TrashIcon,
} from "lucide-react";
import {
deleteMediaCurationMessage,
detailMedia,
getMediaCurationMessage,
saveMediaCurationMessage,
} from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
@ -34,6 +45,11 @@ import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { getCookiesDecrypt } from "@/lib/utils";
import { formatDate } from "@fullcalendar/core/index.js";
import { loading } from "@/lib/swal";
import { htmlToString } from "@/utils/globals";
import { Checkbox } from "@/components/ui/checkbox";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -70,6 +86,13 @@ export type curationDetail = {
userGroupId: number;
};
};
publishedFor: string; // ID for selected radio button
publishedForObject: {
id: number;
name: string;
isInternal: boolean;
code: string;
}[];
tags: string;
provinceId: string;
is_active: string;
@ -108,7 +131,7 @@ export default function DetailImage() {
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const userLevelNumber = getCookiesDecrypt("ulne");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
@ -125,6 +148,7 @@ export default function DetailImage() {
const [detailVideo, setDetailVideo] = useState<any>([]);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const [selectedValue, setSelectedValue] = useState<string>("");
const {
control,
@ -138,59 +162,156 @@ export default function DetailImage() {
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const [selectedFileId, setSelectedFileId] = useState(null);
const [listData, setListData] = useState([]);
const [message, setMessage] = useState("");
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const addReply = (commentId: number) => {
if (replyText.trim()) {
const newCommentData = commentsData.map((comment: any) => {
if (comment.id === commentId) {
return {
...comment,
replies: [
...comment.replies,
{
text: replyText,
username: "You",
date: new Date().toLocaleString(),
},
],
};
useEffect(() => {
async function initState() {
// loading();
const response = await getMediaCurationMessage(selectedFileId);
console.log("data", response?.data?.data);
console.log("userLvl", userLevelNumber);
setListData(response?.data?.data);
close();
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
initState();
}, [selectedFileId]);
const postData = async () => {
if (message?.length > 1 && selectedFileId) {
try {
const data = {
mediaUploadFileId: selectedFileId,
message,
parentId: null,
};
const response = await saveMediaCurationMessage(data);
console.log("Komentar terkirim:", response);
const responseGet = await getMediaCurationMessage(selectedFileId);
setListData(responseGet?.data?.data);
setMessage("");
} catch (error) {
console.error("Error posting comment:", error);
}
} else {
console.log("Pesan atau file ID tidak valid.");
}
};
const sendReplyData = async (parentId: number) => {
const inputElement = document.querySelector(
`#input-comment-${parentId}`
) as HTMLTextAreaElement;
if (inputElement?.value?.length > 1 && selectedFileId) {
loading();
const data = {
mediaUploadFileId: selectedFileId,
message: inputElement.value,
parentId,
};
console.log("Sending reply:", data);
const response = await saveMediaCurationMessage(data);
console.log(response);
const responseGet = await getMediaCurationMessage(selectedFileId);
console.log("Updated comments:", responseGet?.data?.data);
setListData(responseGet?.data?.data);
inputElement.value = "";
close();
setReplyingTo(null);
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setMessage(e.target.value);
};
// useEffect(() => {
// async function initState() {
// // loading();
// const response = await getMediaCurationMessage(selectedFileId);
// console.log("data", response?.data?.data);
// console.log("userLvl", userLevelNumber);
// setListData(response?.data?.data);
// close();
// }
// initState();
// }, [selectedFileId]);
async function deleteDataSuggestion(dataId: any) {
loading();
const response = await deleteMediaCurationMessage(dataId);
console.log(response);
const responseGet = await getMediaCurationMessage(selectedFileId);
console.log(responseGet?.data?.data);
setListData(responseGet?.data?.data);
close();
}
const deleteData = (dataId: any) => {
deleteDataSuggestion(dataId);
console.log(dataId);
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response?.data?.data;
setDetail(details);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { url: string }) =>
file.url ? file.url : "default-image.jpg"
);
const fileUrls = filesData.map((file: any) => ({
id: file.id,
url: file.url || "default-image.jpg",
placements: file.placements || "",
}));
setDetail(details);
setSelectedValue(details?.publishedFor || "");
setSelectedFileId(details?.files[0]?.id);
setDetailVideo(fileUrls);
const filesDataThumbnail = details.files || [];
const fileUrlsThumbnail = filesDataThumbnail.map(
(file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
const fileUrlsThumbnail = filesData.map(
(file: { thumbnailFileUrl: string; placements: string }) => ({
thumbnailFileUrl: file.thumbnailFileUrl
? file.thumbnailFileUrl
: "default-image.jpg",
placements: file.placements || "",
})
);
setDetailThumb(fileUrlsThumbnail);
}
}
initState();
}, [id, refresh]);
const handleFileClick = async (fileId: any) => {
setSelectedFileId(fileId);
try {
const response = await getMediaCurationMessage(fileId);
console.log("Data komentar:", response?.data?.data);
setListData(response?.data?.data);
} catch (error) {
console.error("Error fetching comments:", error);
}
};
const handleValueChange = (value: string) => {
setSelectedValue(value);
};
return (
<div className="flex gap-10">
{detail !== undefined ? (
@ -250,7 +371,7 @@ export default function DetailImage() {
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
value={htmlToString(detail.description)}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
@ -271,24 +392,25 @@ export default function DetailImage() {
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
// value={type} // State yang dipetakan ke value RadioGroup
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
value={selectedValue} // Set selected value
onValueChange={handleValueChange} // Update state on change
className="flex flex-wrap gap-3"
>
{/* Static list of radio buttons */}
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<RadioGroupItem value="5" id="umum" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<RadioGroupItem value="8" id="ksp" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<RadioGroupItem value="6" id="journalist" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<RadioGroupItem value="7" id="polri" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
@ -343,10 +465,13 @@ export default function DetailImage() {
className="w-full"
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<SwiperSlide
key={data.id}
onClick={() => handleFileClick(data.id)}
>
<video
className="object-fill h-full w-full"
src={data}
className="object-fill h-full w-full rounded-md"
src={data.url}
controls
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
@ -364,10 +489,13 @@ export default function DetailImage() {
modules={[Pagination, Thumbs]}
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<SwiperSlide
key={data.id}
onClick={() => handleFileClick(data.id)}
>
<video
className="object-cover h-[60px] w-[80px]"
src={data}
className="object-cover h-[60px] w-[80px] rounded-md"
src={data.url}
muted
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
@ -401,34 +529,36 @@ export default function DetailImage() {
alt={`Thumbnail ${index}`}
/> */}
<img
className="object-cover w-36 h-32"
src={data}
className="object-cover w-32 h-32"
src={"/assets/video-icon.webp"}
alt={` ${data.id}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
{/* Mabes Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "mabes"} // Automatically checks if placement matches
disabled // To reflect read-only behavior
/>
Nasional
<span>Nasional</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
{/* Polda Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "polda"} // Automatically checks if placement matches
disabled
/>
Wilayah
<span>Wilayah</span>
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
{/* International Checkbox */}
<label className=" cursor-pointer flex items-center gap-2">
<Checkbox
checked={data.placements === "international"} // Automatically checks if placement matches
disabled
/>
International
<span>International</span>
</label>
</div>
</div>
@ -440,112 +570,186 @@ export default function DetailImage() {
</CardContent>
<CardContent>
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">Berikan Komentar</Label>
<div className="flex items-start gap-3">
<Avatar>
<AvatarImage
src="/images/avatar/avatar-1.png"
alt="@shadcn"
/>
</Avatar>
<textarea
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
placeholder="Tuliskan komentar Anda di sini.."
></textarea>
</div>
<div className="flex items-center mt-2 gap-3">
<button className="flex items-center text-gray-600 hover:text-gray-800">
<PaperclipIcon className="w-5 h-5" />
Lampirkan
</button>
<button className="flex items-center text-gray-600 hover:text-gray-800">
<SmileIcon className="w-5 h-5" />
Emoticon
</button>
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
Kirim
</button>
</div>
</div>
<div className="mt-5">
<Label className="text-xl text-black">Komentar</Label>
{commentsData.map((comment) => (
<div
key={comment.id}
className="flex items-start gap-3 mt-2"
<div className="mt-4 border p-4 rounded bg-gray-50">
<Textarea
placeholder="Tulis tanggapan Anda di sini..."
value={message}
onChange={handleInputChange}
/>
<div className="flex justify-end mt-3">
<Button
color="primary"
onClick={() => postData()}
type="button"
>
<Avatar>
Kirim Komentar
</Button>
</div>
</div>
{listData?.map((item: any) => (
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
<div className="flex flex-row gap-3">
<Avatar className="mt-2">
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
src={"/assets/avatar-profile.png"}
alt={`@${item.username}`}
/>
</Avatar>
<div className="flex flex-col">
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{comment.username}
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
{formatDate(item.createdAt)}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
</div>
<p className="text-gray-800 mt-1">{item.message}</p>
<div className="flex flex-row gap-2">
{/* <div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
onClick={() => handleReply(item.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
</div> */}
<div
key={index}
className="flex items-start gap-3 mt-1"
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() => deleteData(item.id)}
>
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt={`@${reply.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{reply.username}
</span>
<span className="text-gray-500 text-sm">
{reply.date}
</span>
<p className="text-gray-800 mt-1">
{reply.text}
</p>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">Delete</span>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
{replyingTo === item.id && (
<div className="ml-10 mt-2">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
id={`input-comment-${item.id}`}
className="w-full p-2 border rounded"
placeholder="Masukkan tanggapan anda"
/>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
onClick={() => sendReplyData(item.id)}
>
Kirim
</button>
</div>
)}
{item.children?.length > 0 && (
<div className="ml-10 mt-2 flex flex-col">
{item.children.map((child: any) => (
<div
key={child.id}
className="flex flex-col gap-3 mt-2"
>
<div className="flex flex-row gap-3">
<Avatar className="mt-2">
<AvatarImage
src={"/assets/avatar-profile.png"}
alt={`@${child.username}`}
/>
</Avatar>
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{formatDate(item.createdAt)}
</span>
</div>
<p className="text-gray-800 mt-1">
{child.message}
</p>
<div className="flex flex-row gap-2">
{/* <div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(child.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div> */}
<div
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() => deleteData(child.id)}
>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">Delete</span>
</div>
</div>
</div>
</div>
{replyingTo === child.id && (
<div className="ml-10 mt-2">
<textarea
id={`input-comment-${child.id}`}
className="w-full p-2 border rounded"
placeholder="Masukkan tanggapan anda"
/>
<button
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
onClick={() => sendReplyData(child.id)}
>
Kirim
</button>
</div>
)}
{child.children?.length > 0 && (
<div className="ml-10 mt-2 flex flex-col mb-3">
{child.children.map((child2: any) => (
<div
key={child2.id}
className="flex flex-col gap-3 mt-2"
>
<div className="flex flex-row gap-3 ">
<Avatar className="mt-2">
<AvatarImage
src={"/assets/avatar-profile.png"}
alt={`@${child2.username}`}
/>
</Avatar>
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
<div className="flex items-center justify-between">
<span className="text-gray-700 font-semibold">
{item.messageFrom.fullname}
</span>
<span className="text-gray-500 text-sm">
{formatDate(item.createdAt)}
</span>
</div>
<p className="text-gray-800 mt-1">
{child2.message}
</p>
<div className="flex flex-row gap-2">
<div
className="flex items-center mt-1 text-red-500 cursor-pointer"
onClick={() =>
deleteData(child2.id)
}
>
<TrashIcon className="w-4 h-4" />
<span className="ml-1">
Delete
</span>
</div>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
</CardContent>

View File

@ -1,7 +1,7 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Rows, Search, UploadIcon } from "lucide-react";
import { ArrowRight, Rows, Search, UploadIcon } from "lucide-react";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@ -76,6 +76,7 @@ const CuratedContentPage = () => {
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Audio Visual</Label>
<div className="flex items-center">
<Label>
{" "}
<Link
@ -86,13 +87,15 @@ const CuratedContentPage = () => {
Lihat Semua
</Link>
</Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div>
<div className="px-5 my-5">
<VideoSliderPage />
</div>
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Audio</Label>
<div className="flex items-center">
<Label>
{" "}
<Link
@ -103,13 +106,15 @@ const CuratedContentPage = () => {
Lihat Semua
</Link>
</Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div>
<div className="px-5 my-5">
<AudioSliderPage />
</div>
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Foto</Label>
<div className="flex items-center">
<Label>
{" "}
<Link
@ -120,13 +125,17 @@ const CuratedContentPage = () => {
Lihat Semua
</Link>
</Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div>
<div className="px-5 my-5">
<ImageSliderPage />
</div>
<div className="flex justify-between items-center mx-3">
<Label className="text-base">Teks</Label>
<div className="flex items-center">
<Label>
{" "}
<Link
href={
"/shared/curated-content/giat-routine/document/all"
@ -135,6 +144,8 @@ const CuratedContentPage = () => {
Lihat Semua
</Link>
</Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div>
<div className="px-5 my-5">
<TeksSliderPage />

View File

@ -32,7 +32,7 @@ import {
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { MailIcon } from "lucide-react";
import { MailIcon, Music } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
@ -138,25 +138,23 @@ export default function FormAudioDetail() {
const [selectedTarget, setSelectedTarget] = useState("");
const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
const [isMabesApprover, setIsMabesApprover] = useState(false);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
const [audioPlaying, setAudioPlaying] = useState<any>(null);
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
const waveSurferRef = useRef<any>(null);
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false)
const [isPlaying, setIsPlaying] = useState(false);
const onReady = (ws: any) => {
setWavesurfer(ws)
setWavesurfer(ws);
setIsPlaying(false);
}
};
const onPlayPause = () => {
wavesurfer && wavesurfer.playPause();
}
};
let fileTypeId = "4";
@ -187,7 +185,7 @@ export default function FormAudioDetail() {
userLevelId == "216" &&
roleId == "3"
) {
setIsMabesApprover(true);
setIsUserMabesApprover(true);
}
}, [userLevelId, roleId]);
@ -230,6 +228,14 @@ export default function FormAudioDetail() {
}
};
const setupPlacementCheck = (length: number) => {
const temp = [];
for (let i = 0; i < length; i++) {
temp.push([]);
}
setFilePlacements(temp);
};
useEffect(() => {
async function initState() {
if (id) {
@ -244,6 +250,7 @@ export default function FormAudioDetail() {
names: details?.files[0]?.fileName,
format: details?.files[0]?.format,
});
setupPlacementCheck(details?.files?.length);
if (details.publishedForObject) {
const publisherIds = details.publishedForObject.map(
@ -281,9 +288,16 @@ export default function FormAudioDetail() {
}, [refresh, setValue]);
const actionApproval = (e: string) => {
const temp = [];
for (const element of detail.files) {
temp.push([]);
}
setFilePlacements(temp);
setStatus(e);
setModalOpen(true);
setFiles(detail.files);
setDescription("");
setModalOpen(true);
};
const submit = async () => {
@ -308,13 +322,26 @@ export default function FormAudioDetail() {
}
};
const getPlacement = () => {
console.log("getPlaa", filePlacements);
const temp = [];
for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i].length !== 0) {
const now = filePlacements[i].filter((a) => a !== "all");
const data = { mediaFileId: files[i].id, placement: now.join(",") };
temp.push(data);
}
}
return temp;
};
async function save() {
const data = {
mediaUploadId: id,
statusId: status,
message: description,
files: [],
// files: isMabesApprover ? getPlacement() : [],
// files: [],
files: isUserMabesApprover ? getPlacement() : [],
};
loading();
@ -337,6 +364,7 @@ export default function FormAudioDetail() {
}
close();
submitApprovalSuccesss();
return false;
}
@ -348,6 +376,40 @@ export default function FormAudioDetail() {
rejects.push(id);
setRejectedFiles(rejects);
}
const setupPlacement = (
index: number,
placement: string,
checked: boolean
) => {
let temp = [...filePlacements];
if (checked) {
if (placement === "all") {
temp[index] = ["all", "mabes", "polda", "international"];
} else {
const now = temp[index];
now.push(placement);
if (now.length === 3 && !now.includes("all")) {
now.push("all");
}
temp[index] = now;
}
} else {
if (placement === "all") {
temp[index] = [];
} else {
const now = temp[index].filter((a) => a !== placement);
console.log("now", now);
temp[index] = now;
if (now.length === 3 && now.includes("all")) {
const newData = now.filter((b) => b !== "all");
temp[index] = newData;
}
}
}
setFilePlacements(temp);
};
const handleMain = (
type: string,
url: string,
@ -380,7 +442,7 @@ export default function FormAudioDetail() {
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/in/contributor/content/image");
router.push("/in/contributor/content/audio");
});
};
@ -469,7 +531,27 @@ export default function FormAudioDetail() {
/>
</div>
))}
<p onClick={onPlayPause}>{isPlaying ? "Pause" : "Play"}</p>
<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>
@ -609,7 +691,7 @@ export default function FormAudioDetail() {
<DialogTitle>Berikan Komentar</DialogTitle>
</DialogHeader>
{status == "2"
? files?.map((file) => (
? files?.map((file, index) => (
<div
key={file.id}
className="flex flex-row gap-2 items-center"
@ -618,13 +700,27 @@ export default function FormAudioDetail() {
<div className="flex flex-col gap-2 w-full">
<div className="flex justify-between text-sm">
{file.fileName}
<a>
<a
onClick={() =>
handleDeleteFileApproval(file.id)
}
>
<Icon icon="humbleicons:times" color="red" />
</a>
</div>
{isUserMabesApprover && (
<div className="flex flex-row gap-2">
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
value="all"
checked={filePlacements[index]?.includes(
"all"
)}
onCheckedChange={(e) =>
setupPlacement(index, "all", Boolean(e))
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -633,7 +729,15 @@ export default function FormAudioDetail() {
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"mabes"
)}
onCheckedChange={(e) =>
setupPlacement(index, "mabes", Boolean(e))
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -642,7 +746,15 @@ export default function FormAudioDetail() {
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"polda"
)}
onCheckedChange={(e) =>
setupPlacement(index, "polda", Boolean(e))
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -652,7 +764,19 @@ export default function FormAudioDetail() {
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"international"
)}
onCheckedChange={(e) =>
setupPlacement(
index,
"international",
Boolean(e)
)
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -661,6 +785,7 @@ export default function FormAudioDetail() {
</label>
</div>
</div>
)}
</div>
</div>
))
@ -743,14 +868,16 @@ export default function FormAudioDetail() {
<Button
type="button"
color="primary"
onClick={submitApprovalSuccesss}
onClick={() => submit()}
>
Submit
</Button>
<Button
type="button"
color="destructive"
onClick={() => setModalOpen(false)}
onClick={() => {
setModalOpen(false);
}}
>
Cancel
</Button>

View File

@ -355,10 +355,8 @@ export default function FormConvertSPIT() {
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")
@ -370,7 +368,6 @@ export default function FormConvertSPIT() {
? 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 {
@ -416,10 +413,10 @@ export default function FormConvertSPIT() {
description,
htmlDescription: description,
tags: "siap",
categoryId: 1,
categoryId: selectedCategoryId,
publishedFor: publishedFor.join(","),
creator: data.contentCreator,
files: isUserMabesApprover ? getPlacement() : [], // Include placement data
files: isUserMabesApprover ? getPlacement() : [],
};
const response = await convertSPIT(requestData);
@ -698,7 +695,7 @@ export default function FormConvertSPIT() {
</RadioGroup>
</div>
<div>
<Label className="text-xl text-black">File Media</Label>
<Label className="text-xl">File Media</Label>
<div className="w-full ">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
@ -709,7 +706,7 @@ export default function FormConvertSPIT() {
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-fill h-full w-full"
className="object-fill h-full w-full rounded-md"
src={data}
alt={` ${data.id}`}
/>
@ -719,7 +716,7 @@ export default function FormConvertSPIT() {
<div className=" mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
slidesPerView={8}
spaceBetween={8}
pagination={{
clickable: true,
@ -730,7 +727,7 @@ export default function FormConvertSPIT() {
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-cover h-[60px] w-[80px]"
className="object-cover h-[60px] w-[80px] rounded-md"
src={data}
alt={` ${data.id}`}
/>

View File

@ -138,6 +138,9 @@ export default function FormTeksDetail() {
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
const [isMabesApprover, setIsMabesApprover] = useState(false);
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
let fileTypeId = "3";
const {
@ -167,7 +170,7 @@ export default function FormTeksDetail() {
userLevelId == "216" &&
roleId == "3"
) {
setIsMabesApprover(true);
setIsUserMabesApprover(true);
}
}, [userLevelId, roleId]);
@ -210,6 +213,14 @@ export default function FormTeksDetail() {
}
};
const setupPlacementCheck = (length: number) => {
const temp = [];
for (let i = 0; i < length; i++) {
temp.push([]);
}
setFilePlacements(temp);
};
useEffect(() => {
async function initState() {
if (id) {
@ -255,9 +266,16 @@ export default function FormTeksDetail() {
}, [refresh, setValue]);
const actionApproval = (e: string) => {
const temp = [];
for (const element of detail.files) {
temp.push([]);
}
setFilePlacements(temp);
setStatus(e);
setModalOpen(true);
setFiles(detail.files);
setDescription("");
setModalOpen(true);
};
const submit = async () => {
@ -266,20 +284,34 @@ export default function FormTeksDetail() {
Number(status) == 2 ||
Number(status) == 4
) {
MySwal.fire({
title: "Simpan Approval",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save();
// MySwal.fire({
// title: "Simpan Approval",
// text: "",
// icon: "warning",
// showCancelButton: true,
// cancelButtonColor: "#d33",
// confirmButtonColor: "#3085d6",
// confirmButtonText: "Simpan",
// }).then((result) => {
// if (result.isConfirmed) {
// }
// });
}
});
};
const getPlacement = () => {
console.log("getPlaa", filePlacements);
const temp = [];
for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i].length !== 0) {
const now = filePlacements[i].filter((a) => a !== "all");
const data = { mediaFileId: files[i].id, placement: now.join(",") };
temp.push(data);
}
}
return temp;
};
async function save() {
@ -287,8 +319,7 @@ export default function FormTeksDetail() {
mediaUploadId: id,
statusId: status,
message: description,
files: [],
// files: isMabesApprover ? getPlacement() : [],
files: isUserMabesApprover ? getPlacement() : [],
};
loading();
@ -311,10 +342,44 @@ export default function FormTeksDetail() {
}
close();
submitApprovalSuccesss();
return false;
}
const setupPlacement = (
index: number,
placement: string,
checked: boolean
) => {
let temp = [...filePlacements];
if (checked) {
if (placement === "all") {
temp[index] = ["all", "mabes", "polda", "international"];
} else {
const now = temp[index];
now.push(placement);
if (now.length === 3 && !now.includes("all")) {
now.push("all");
}
temp[index] = now;
}
} else {
if (placement === "all") {
temp[index] = [];
} else {
const now = temp[index].filter((a) => a !== placement);
console.log("now", now);
temp[index] = now;
if (now.length === 3 && now.includes("all")) {
const newData = now.filter((b) => b !== "all");
temp[index] = newData;
}
}
}
setFilePlacements(temp);
};
function handleDeleteFileApproval(id: number) {
const selectedFiles = files.filter((file) => file.id != id);
setFiles(selectedFiles);
@ -473,7 +538,7 @@ export default function FormTeksDetail() {
<div className="mt-2">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
slidesPerView={8}
spaceBetween={8}
pagination={{ clickable: true }}
modules={[Pagination, Thumbs]}
@ -644,7 +709,7 @@ export default function FormTeksDetail() {
<DialogTitle>Berikan Komentar</DialogTitle>
</DialogHeader>
{status == "2"
? files?.map((file) => (
? files?.map((file, index) => (
<div
key={file.id}
className="flex flex-row gap-2 items-center"
@ -653,13 +718,27 @@ export default function FormTeksDetail() {
<div className="flex flex-col gap-2 w-full">
<div className="flex justify-between text-sm">
{file.fileName}
<a>
<a
onClick={() =>
handleDeleteFileApproval(file.id)
}
>
<Icon icon="humbleicons:times" color="red" />
</a>
</div>
{isUserMabesApprover && (
<div className="flex flex-row gap-2">
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
value="all"
checked={filePlacements[index]?.includes(
"all"
)}
onCheckedChange={(e) =>
setupPlacement(index, "all", Boolean(e))
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -668,7 +747,15 @@ export default function FormTeksDetail() {
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"mabes"
)}
onCheckedChange={(e) =>
setupPlacement(index, "mabes", Boolean(e))
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -677,7 +764,15 @@ export default function FormTeksDetail() {
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"polda"
)}
onCheckedChange={(e) =>
setupPlacement(index, "polda", Boolean(e))
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -687,7 +782,19 @@ export default function FormTeksDetail() {
</div>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<Checkbox
id="terms"
checked={filePlacements[index]?.includes(
"international"
)}
onCheckedChange={(e) =>
setupPlacement(
index,
"international",
Boolean(e)
)
}
/>
<label
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
@ -696,6 +803,7 @@ export default function FormTeksDetail() {
</label>
</div>
</div>
)}
</div>
</div>
))
@ -778,14 +886,16 @@ export default function FormTeksDetail() {
<Button
type="button"
color="primary"
onClick={submitApprovalSuccesss}
onClick={() => submit()}
>
Submit
</Button>
<Button
type="button"
color="destructive"
onClick={() => setModalOpen(false)}
onClick={() => {
setModalOpen(false);
}}
>
Cancel
</Button>

View File

@ -1329,6 +1329,7 @@ export default function FormTaskDetail() {
<div className="flex gap-2">
<div className="">
<Button
type="button"
color="primary"
variant={"outline"}
onClick={handleToggleInput}
@ -1338,6 +1339,7 @@ export default function FormTaskDetail() {
</div>
<div className="">
<Button
type="button"
className="btn btn-primary ml-3 mr-3"
style={
statusAcceptance ||

BIN
public/assets/docx-icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB