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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import SiteBreadcrumb from "@/components/site-breadcrumb"; import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 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 { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -76,65 +76,76 @@ const CuratedContentPage = () => {
<div className="flex justify-between items-center mx-3"> <div className="flex justify-between items-center mx-3">
<Label className="text-base">Audio Visual</Label> <Label className="text-base">Audio Visual</Label>
<Label> <div className="flex items-center">
{" "} <Label>
<Link {" "}
href={ <Link
"/shared/curated-content/giat-routine/video/all" href={
} "/shared/curated-content/giat-routine/video/all"
> }
Lihat Semua >
</Link> Lihat Semua
</Label> </Link>
</Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div> </div>
<div className="px-5 my-5"> <div className="px-5 my-5">
<VideoSliderPage /> <VideoSliderPage />
</div> </div>
<div className="flex justify-between items-center mx-3"> <div className="flex justify-between items-center mx-3">
<Label className="text-base">Audio</Label> <Label className="text-base">Audio</Label>
<div className="flex items-center">
<Label> <Label>
{" "} {" "}
<Link <Link
href={ href={
"/shared/curated-content/giat-routine/audio/all" "/shared/curated-content/giat-routine/audio/all"
} }
> >
Lihat Semua Lihat Semua
</Link> </Link>
</Label> </Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div> </div>
<div className="px-5 my-5"> <div className="px-5 my-5">
<AudioSliderPage /> <AudioSliderPage />
</div> </div>
<div className="flex justify-between items-center mx-3"> <div className="flex justify-between items-center mx-3">
<Label className="text-base">Foto</Label> <Label className="text-base">Foto</Label>
<div className="flex items-center">
<Label> <Label>
{" "} {" "}
<Link <Link
href={ href={
"/shared/curated-content/giat-routine/image/all" "/shared/curated-content/giat-routine/image/all"
} }
> >
Lihat Semua Lihat Semua
</Link> </Link>
</Label> </Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div> </div>
<div className="px-5 my-5"> <div className="px-5 my-5">
<ImageSliderPage /> <ImageSliderPage />
</div> </div>
<div className="flex justify-between items-center mx-3"> <div className="flex justify-between items-center mx-3">
<Label className="text-base">Teks</Label> <Label className="text-base">Teks</Label>
<Label> <div className="flex items-center">
<Link <Label>
href={ {" "}
"/shared/curated-content/giat-routine/document/all" <Link
} href={
> "/shared/curated-content/giat-routine/document/all"
Lihat Semua }
</Link> >
</Label> Lihat Semua
</Link>
</Label>
<ArrowRight size={18} className="text-slate-600" />
</div>
</div> </div>
<div className="px-5 my-5"> <div className="px-5 my-5">
<TeksSliderPage /> <TeksSliderPage />

View File

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

View File

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

View File

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

View File

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