feat:update curated content,kontent
This commit is contained in:
parent
079d238d1f
commit
35e96a7bee
|
|
@ -21,8 +21,20 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||
import Cookies from "js-cookie";
|
||||
import { postBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import {
|
||||
DotSquare,
|
||||
InboxIcon,
|
||||
Music,
|
||||
PaperclipIcon,
|
||||
SmileIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
deleteMediaCurationMessage,
|
||||
detailMedia,
|
||||
getMediaCurationMessage,
|
||||
saveMediaCurationMessage,
|
||||
} from "@/service/curated-content/curated-content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -33,6 +45,15 @@ import "swiper/css";
|
|||
import "swiper/css/navigation";
|
||||
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import WavesurferPlayer from "@wavesurfer/react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -69,6 +90,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -119,10 +147,14 @@ export default function DetailAudio() {
|
|||
// files: [],
|
||||
// fileType: null,
|
||||
// });
|
||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const [detail, setDetail] = useState<curationDetail>();
|
||||
const [refresh] = useState(false);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [detailAudio, setDetailAudio] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -136,11 +168,30 @@ export default function DetailAudio() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedFileId, setSelectedFileId] = useState(null);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
const onReady = (ws: any) => {
|
||||
setWavesurfer(ws);
|
||||
setIsPlaying(false);
|
||||
};
|
||||
|
||||
const onPlayPause = () => {
|
||||
wavesurfer && wavesurfer.playPause();
|
||||
};
|
||||
|
||||
const addReply = (commentId: number) => {
|
||||
if (replyText.trim()) {
|
||||
const newCommentData = commentsData.map((comment: any) => {
|
||||
|
|
@ -166,23 +217,131 @@ export default function DetailAudio() {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
// loading();
|
||||
const response = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("data", response?.data?.data);
|
||||
console.log("userLvl", userLevelNumber);
|
||||
setListData(response?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
initState();
|
||||
}, [selectedFileId]);
|
||||
|
||||
const postData = async () => {
|
||||
if (message?.length > 1 && selectedFileId) {
|
||||
try {
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log("Komentar terkirim:", response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
setMessage("");
|
||||
} catch (error) {
|
||||
console.error("Error posting comment:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Pesan atau file ID tidak valid.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReplyData = async (parentId: number) => {
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
if (inputElement?.value?.length > 1 && selectedFileId) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log("Sending reply:", data);
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log(response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("Updated comments:", responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
inputElement.value = "";
|
||||
close();
|
||||
setReplyingTo(null);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteMediaCurationMessage(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailMedia(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
|
||||
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
secondaryUrl: file.secondaryUrl || "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
setDetailAudio(fileUrls);
|
||||
const fileUrlsThumbnail = filesData.map(
|
||||
(file: { thumbnailFileUrl: string; placements: string }) => ({
|
||||
thumbnailFileUrl: file.thumbnailFileUrl
|
||||
? file.thumbnailFileUrl
|
||||
: "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
})
|
||||
);
|
||||
setDetailThumb(fileUrls);
|
||||
|
||||
setDetailThumb(fileUrlsThumbnail);
|
||||
// setDetailThumb(fileUrls);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleFileClick = async (fileId: any) => {
|
||||
setSelectedFileId(fileId);
|
||||
try {
|
||||
const response = await getMediaCurationMessage(fileId);
|
||||
console.log("Data komentar:", response?.data?.data);
|
||||
setListData(response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -242,7 +401,7 @@ export default function DetailAudio() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -263,24 +422,25 @@ export default function DetailAudio() {
|
|||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -288,9 +448,16 @@ export default function DetailAudio() {
|
|||
<div className=" py-3">
|
||||
<div className="space-y-2">
|
||||
<Label>Tag</Label>
|
||||
<p className="border rounded-md text-blue-600 px-2 py-2">
|
||||
{detail?.tags}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{detail?.tags?.split(",").map((tag, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="border rounded-md px-2 py-2"
|
||||
>
|
||||
{tag.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className=" py-3">
|
||||
|
|
@ -327,40 +494,62 @@ export default function DetailAudio() {
|
|||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<audio
|
||||
className="w-full"
|
||||
src={data}
|
||||
controls
|
||||
title={`Audio ${data.id}`}
|
||||
{detailAudio?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<WavesurferPlayer
|
||||
height={500}
|
||||
waveColor="red"
|
||||
url={data.secondaryUrl}
|
||||
onReady={onReady}
|
||||
key={`File ID: ${data.id}`}
|
||||
onPlay={() => setIsPlaying(true)}
|
||||
onPause={() => setIsPlaying(false)}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={onPlayPause}
|
||||
disabled={isPlaying}
|
||||
className={`flex items-center gap-2 ${
|
||||
isPlaying
|
||||
? "bg-gray-300 cursor-not-allowed"
|
||||
: "bg-primary text-white"
|
||||
} p-2 rounded`}
|
||||
>
|
||||
{isPlaying ? "Pause" : "Play"}
|
||||
<Icon
|
||||
icon={
|
||||
isPlaying
|
||||
? "carbon:pause-outline"
|
||||
: "famicons:play-sharp"
|
||||
}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</Button>
|
||||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-700">
|
||||
Audio {data.id}
|
||||
</span>
|
||||
<audio
|
||||
className="h-[24px] w-[80px]"
|
||||
src={data}
|
||||
controls
|
||||
title={`Audio ${data.id}`}
|
||||
{detailAudio?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={"/assets/music-icon.jpg"}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
|
@ -390,35 +579,33 @@ export default function DetailAudio() {
|
|||
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
|
||||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
alt={`Article ${data.id}`}
|
||||
/>
|
||||
<Music className="object-cover w-32 h-32" />
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -432,110 +619,184 @@ export default function DetailAudio() {
|
|||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Berikan Komentar</Label>
|
||||
<div className="flex items-start gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="/images/avatar/avatar-1.png"
|
||||
alt="@shadcn"
|
||||
<div className="mt-4 border p-4 rounded bg-gray-50">
|
||||
<Textarea
|
||||
placeholder="Tulis tanggapan Anda di sini..."
|
||||
value={message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Avatar>
|
||||
|
||||
<textarea
|
||||
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
|
||||
placeholder="Tuliskan komentar Anda di sini.."
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="flex items-center mt-2 gap-3">
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<PaperclipIcon className="w-5 h-5" />
|
||||
Lampirkan
|
||||
</button>
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<SmileIcon className="w-5 h-5" />
|
||||
Emoticon
|
||||
</button>
|
||||
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Komentar</Label>
|
||||
{commentsData.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="flex items-start gap-3 mt-2"
|
||||
<div className="flex justify-end mt-3">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => postData()}
|
||||
type="button"
|
||||
>
|
||||
<Avatar>
|
||||
Kirim Komentar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{listData?.map((item: any) => (
|
||||
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={comment.avatar}
|
||||
alt={`@${comment.username}`}
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${item.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{comment.username}
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{comment.date}
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">{comment.text}</p>
|
||||
<div
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">{item.message}</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(comment.id)}
|
||||
onClick={() => handleReply(item.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div>
|
||||
{comment.replies.length > 0 && (
|
||||
<div className="ml-8 mt-2">
|
||||
{comment.replies.map((reply: any, index: any) => (
|
||||
</div> */}
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 mt-1"
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(item.id)}
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt={`@${reply.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{reply.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{reply.date}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{reply.text}
|
||||
</p>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{replyingTo !== null && (
|
||||
<div className="mt-4">
|
||||
{replyingTo === item.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
className="w-full p-2 border rounded-md"
|
||||
rows={3}
|
||||
placeholder="Tulis balasan..."
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
></textarea>
|
||||
id={`input-comment-${item.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
|
||||
onClick={() => addReply(replyingTo)}
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(item.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{item.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col">
|
||||
{item.children.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(child.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{replyingTo === child.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${child.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(child.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{child.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col mb-3">
|
||||
{child.children.map((child2: any) => (
|
||||
<div
|
||||
key={child2.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child2.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,19 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||
import Cookies from "js-cookie";
|
||||
import { postBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import {
|
||||
DotSquare,
|
||||
InboxIcon,
|
||||
PaperclipIcon,
|
||||
SmileIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
deleteMediaCurationMessage,
|
||||
detailMedia,
|
||||
getMediaCurationMessage,
|
||||
saveMediaCurationMessage,
|
||||
} from "@/service/curated-content/curated-content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -35,6 +46,11 @@ import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
|||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import JoditEditor from "jodit-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -51,6 +67,13 @@ type Category = {
|
|||
categoryName: string;
|
||||
};
|
||||
|
||||
type PublishedForObject = {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type curationDetail = {
|
||||
id: number;
|
||||
title: string;
|
||||
|
|
@ -71,6 +94,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -109,7 +139,8 @@ export default function DetailDocument() {
|
|||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type DetailSchema = z.infer<typeof detailSchema>;
|
||||
|
||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
|
|
@ -125,6 +156,7 @@ export default function DetailDocument() {
|
|||
const [refresh] = useState(false);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -138,11 +170,18 @@ export default function DetailDocument() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedFileId, setSelectedFileId] = useState(null);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
const addReply = (commentId: number) => {
|
||||
if (replyText.trim()) {
|
||||
const newCommentData = commentsData.map((comment: any) => {
|
||||
|
|
@ -168,6 +207,85 @@ export default function DetailDocument() {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
// loading();
|
||||
const response = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("data", response?.data?.data);
|
||||
console.log("userLvl", userLevelNumber);
|
||||
setListData(response?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
initState();
|
||||
}, [selectedFileId]);
|
||||
|
||||
const postData = async () => {
|
||||
if (message?.length > 1 && selectedFileId) {
|
||||
try {
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log("Komentar terkirim:", response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
setMessage("");
|
||||
} catch (error) {
|
||||
console.error("Error posting comment:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Pesan atau file ID tidak valid.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReplyData = async (parentId: number) => {
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
if (inputElement?.value?.length > 1 && selectedFileId) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log("Sending reply:", data);
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log(response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("Updated comments:", responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
inputElement.value = "";
|
||||
close();
|
||||
setReplyingTo(null);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteMediaCurationMessage(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -175,16 +293,37 @@ export default function DetailDocument() {
|
|||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
|
||||
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
|
||||
);
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
url: file.secondaryUrl || "default-image.jpg",
|
||||
format: file.format,
|
||||
fileName: file.fileName,
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetailThumb(fileUrls);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleFileClick = async (fileId: any) => {
|
||||
setSelectedFileId(fileId);
|
||||
try {
|
||||
const response = await getMediaCurationMessage(fileId);
|
||||
console.log("Data komentar:", response?.data?.data);
|
||||
setListData(response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -244,7 +383,7 @@ export default function DetailDocument() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -265,24 +404,25 @@ export default function DetailDocument() {
|
|||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -330,18 +470,90 @@ export default function DetailDocument() {
|
|||
<div className="space-y-2 py-3">
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
<div className="w-full ">
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
value={detail?.description}
|
||||
onChange={onChange}
|
||||
className="dark:text-black"
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
// Menampilkan gambar
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.url}
|
||||
alt={data.fileName || "File"}
|
||||
/>
|
||||
) : data.format === ".pdf" ? (
|
||||
// Menampilkan PDF menggunakan iframe
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={data.url}
|
||||
title={data.fileName || "PDF File"}
|
||||
/>
|
||||
) : [".docx", ".ppt", ".pptx"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
// Menampilkan file dokumen menggunakan Office Viewer
|
||||
<iframe
|
||||
className="w-full h-96 rounded-md"
|
||||
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
|
||||
data.url
|
||||
)}`}
|
||||
title={data.fileName || "Document"}
|
||||
/>
|
||||
) : (
|
||||
// Menampilkan link jika format tidak dikenali
|
||||
<a
|
||||
href={data.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block text-blue-500 underline"
|
||||
>
|
||||
View {data.fileName || "File"}
|
||||
</a>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className="mt-2">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
{[".jpg", ".jpeg", ".png", ".webp"].includes(
|
||||
data.format
|
||||
) ? (
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={"/assets/docx-icon.jpg"}
|
||||
alt={data.fileName}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
|
||||
{data?.format
|
||||
?.replace(".", "")
|
||||
.toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -357,45 +569,42 @@ export default function DetailDocument() {
|
|||
<p>Penempatan</p>
|
||||
</div>
|
||||
<p className="bg-black h-1 w-full rounded-lg"></p>
|
||||
{detailThumb?.map((data: any) => (
|
||||
{detailThumb.map((data: any, index: number) => (
|
||||
<div
|
||||
key={data.id}
|
||||
key={index}
|
||||
className="flex items-center gap-3 mt-2"
|
||||
>
|
||||
{/* <img
|
||||
className="object-cover w-20 h-20"
|
||||
src={data.thumbnailUrl} // Assuming `thumbnailUrl` is the property that contains the URL for the thumbnail image
|
||||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={"/assets/docx-icon.jpg"}
|
||||
alt={data.fileName}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -409,110 +618,184 @@ export default function DetailDocument() {
|
|||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Berikan Komentar</Label>
|
||||
<div className="flex items-start gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="/images/avatar/avatar-1.png"
|
||||
alt="@shadcn"
|
||||
<div className="mt-4 border p-4 rounded bg-gray-50">
|
||||
<Textarea
|
||||
placeholder="Tulis tanggapan Anda di sini..."
|
||||
value={message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Avatar>
|
||||
|
||||
<textarea
|
||||
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
|
||||
placeholder="Tuliskan komentar Anda di sini.."
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="flex items-center mt-2 gap-3">
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<PaperclipIcon className="w-5 h-5" />
|
||||
Lampirkan
|
||||
</button>
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<SmileIcon className="w-5 h-5" />
|
||||
Emoticon
|
||||
</button>
|
||||
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Komentar</Label>
|
||||
{commentsData.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="flex items-start gap-3 mt-2"
|
||||
<div className="flex justify-end mt-3">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => postData()}
|
||||
type="button"
|
||||
>
|
||||
<Avatar>
|
||||
Kirim Komentar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{listData?.map((item: any) => (
|
||||
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={comment.avatar}
|
||||
alt={`@${comment.username}`}
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${item.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{comment.username}
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{comment.date}
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">{comment.text}</p>
|
||||
<div
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">{item.message}</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(comment.id)}
|
||||
onClick={() => handleReply(item.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div>
|
||||
{comment.replies.length > 0 && (
|
||||
<div className="ml-8 mt-2">
|
||||
{comment.replies.map((reply: any, index: any) => (
|
||||
</div> */}
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 mt-1"
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(item.id)}
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt={`@${reply.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{reply.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{reply.date}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{reply.text}
|
||||
</p>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{replyingTo !== null && (
|
||||
<div className="mt-4">
|
||||
{replyingTo === item.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
className="w-full p-2 border rounded-md"
|
||||
rows={3}
|
||||
placeholder="Tulis balasan..."
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
></textarea>
|
||||
id={`input-comment-${item.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
|
||||
onClick={() => addReply(replyingTo)}
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(item.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{item.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col">
|
||||
{item.children.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(child.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{replyingTo === child.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${child.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(child.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{child.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col mb-3">
|
||||
{child.children.map((child2: any) => (
|
||||
<div
|
||||
key={child2.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child2.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { listCuratedContent } from "@/service/curated-content/curated-content";
|
||||
import { getListContent } from "@/service/landing/landing";
|
||||
import {
|
||||
formatDateToIndonesian,
|
||||
|
|
@ -21,10 +22,12 @@ const TeksSliderPage = () => {
|
|||
const [documentData, setDocumentData] = useState<any>();
|
||||
const [displayDocument, setDisplayDocument] = useState<any[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [limit, setLimit] = React.useState(10);
|
||||
const [search, setSearch] = React.useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, []);
|
||||
}, [page, limit, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (documentData?.length > 0) {
|
||||
|
|
@ -35,14 +38,12 @@ const TeksSliderPage = () => {
|
|||
}, [documentData]);
|
||||
|
||||
const initFetch = async () => {
|
||||
const response = await getListContent({
|
||||
page: page - 1,
|
||||
size: 12,
|
||||
sortBy: "createdAt",
|
||||
contentTypeId: "3",
|
||||
});
|
||||
const response = await listCuratedContent(search, limit, page - 1, 3, "1");
|
||||
console.log(response);
|
||||
setDocumentData(response?.data?.data?.content);
|
||||
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setDocumentData(contentData);
|
||||
};
|
||||
const shuffleAndSetVideos = () => {
|
||||
const shuffled = shuffleArray([...documentData]);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ import {
|
|||
} from "@/service/task";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, loading } from "@/lib/swal";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -107,6 +109,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -173,6 +182,7 @@ export default function DetailImage() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
|
|
@ -272,11 +282,13 @@ export default function DetailImage() {
|
|||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
thumbnailFileUrl: file.thumbnailFileUrl || "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetailThumb(fileUrls);
|
||||
}
|
||||
|
|
@ -294,6 +306,9 @@ export default function DetailImage() {
|
|||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
|
|
@ -354,7 +369,7 @@ export default function DetailImage() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -375,24 +390,25 @@ export default function DetailImage() {
|
|||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -452,7 +468,7 @@ export default function DetailImage() {
|
|||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
|
|
@ -476,7 +492,7 @@ export default function DetailImage() {
|
|||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
|
|
@ -510,34 +526,36 @@ export default function DetailImage() {
|
|||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
className="object-cover w-36 h-32 rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`Article ${data.id}`}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,19 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||
import Cookies from "js-cookie";
|
||||
import { postBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import {
|
||||
DotSquare,
|
||||
InboxIcon,
|
||||
PaperclipIcon,
|
||||
SmileIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
deleteMediaCurationMessage,
|
||||
detailMedia,
|
||||
getMediaCurationMessage,
|
||||
saveMediaCurationMessage,
|
||||
} from "@/service/curated-content/curated-content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -34,6 +45,11 @@ import "swiper/css/navigation";
|
|||
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { formatDate } from "@fullcalendar/core/index.js";
|
||||
import { loading } from "@/lib/swal";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -70,6 +86,13 @@ export type curationDetail = {
|
|||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
|
|
@ -108,7 +131,7 @@ export default function DetailImage() {
|
|||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type DetailSchema = z.infer<typeof detailSchema>;
|
||||
|
||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
|
|
@ -125,6 +148,7 @@ export default function DetailImage() {
|
|||
const [detailVideo, setDetailVideo] = useState<any>([]);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const {
|
||||
control,
|
||||
|
|
@ -138,59 +162,156 @@ export default function DetailImage() {
|
|||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedFileId, setSelectedFileId] = useState(null);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
};
|
||||
|
||||
const addReply = (commentId: number) => {
|
||||
if (replyText.trim()) {
|
||||
const newCommentData = commentsData.map((comment: any) => {
|
||||
if (comment.id === commentId) {
|
||||
return {
|
||||
...comment,
|
||||
replies: [
|
||||
...comment.replies,
|
||||
{
|
||||
text: replyText,
|
||||
username: "You",
|
||||
date: new Date().toLocaleString(),
|
||||
},
|
||||
],
|
||||
};
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
// loading();
|
||||
const response = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("data", response?.data?.data);
|
||||
console.log("userLvl", userLevelNumber);
|
||||
setListData(response?.data?.data);
|
||||
close();
|
||||
}
|
||||
return comment;
|
||||
});
|
||||
|
||||
setCommentsData(newCommentData);
|
||||
setReplyText("");
|
||||
initState();
|
||||
}, [selectedFileId]);
|
||||
|
||||
const postData = async () => {
|
||||
if (message?.length > 1 && selectedFileId) {
|
||||
try {
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log("Komentar terkirim:", response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
setMessage("");
|
||||
} catch (error) {
|
||||
console.error("Error posting comment:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Pesan atau file ID tidak valid.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReplyData = async (parentId: number) => {
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
if (inputElement?.value?.length > 1 && selectedFileId) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log("Sending reply:", data);
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log(response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("Updated comments:", responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
inputElement.value = "";
|
||||
close();
|
||||
setReplyingTo(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// async function initState() {
|
||||
// // loading();
|
||||
// const response = await getMediaCurationMessage(selectedFileId);
|
||||
// console.log("data", response?.data?.data);
|
||||
// console.log("userLvl", userLevelNumber);
|
||||
// setListData(response?.data?.data);
|
||||
// close();
|
||||
// }
|
||||
|
||||
// initState();
|
||||
// }, [selectedFileId]);
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteMediaCurationMessage(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailMedia(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: { url: string }) =>
|
||||
file.url ? file.url : "default-image.jpg"
|
||||
);
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
url: file.url || "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
setDetailVideo(fileUrls);
|
||||
const filesDataThumbnail = details.files || [];
|
||||
const fileUrlsThumbnail = filesDataThumbnail.map(
|
||||
(file: { thumbnailFileUrl: string }) =>
|
||||
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
|
||||
const fileUrlsThumbnail = filesData.map(
|
||||
(file: { thumbnailFileUrl: string; placements: string }) => ({
|
||||
thumbnailFileUrl: file.thumbnailFileUrl
|
||||
? file.thumbnailFileUrl
|
||||
: "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
})
|
||||
);
|
||||
|
||||
setDetailThumb(fileUrlsThumbnail);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleFileClick = async (fileId: any) => {
|
||||
setSelectedFileId(fileId);
|
||||
try {
|
||||
const response = await getMediaCurationMessage(fileId);
|
||||
console.log("Data komentar:", response?.data?.data);
|
||||
setListData(response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
{detail !== undefined ? (
|
||||
|
|
@ -250,7 +371,7 @@ export default function DetailImage() {
|
|||
name="description"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail.description}
|
||||
value={htmlToString(detail.description)}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Meta"
|
||||
cols={5}
|
||||
|
|
@ -271,24 +392,25 @@ export default function DetailImage() {
|
|||
<div className="mt-5">
|
||||
<Label>Jenis Penugasan</Label>
|
||||
<RadioGroup
|
||||
// value={type} // State yang dipetakan ke value RadioGroup
|
||||
// onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
|
||||
value={selectedValue} // Set selected value
|
||||
onValueChange={handleValueChange} // Update state on change
|
||||
className="flex flex-wrap gap-3"
|
||||
>
|
||||
{/* Static list of radio buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="1" id="publication" />
|
||||
<RadioGroupItem value="5" id="umum" />
|
||||
<Label htmlFor="umum">Umum</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="2" id="amplification" />
|
||||
<RadioGroupItem value="8" id="ksp" />
|
||||
<Label htmlFor="ksp">Ksp</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="3" id="contra" />
|
||||
<RadioGroupItem value="6" id="journalist" />
|
||||
<Label htmlFor="journalist">Journalist</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="4" id="contra" />
|
||||
<RadioGroupItem value="7" id="polri" />
|
||||
<Label htmlFor="polri">Polri</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
|
@ -343,10 +465,13 @@ export default function DetailImage() {
|
|||
className="w-full"
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<video
|
||||
className="object-fill h-full w-full"
|
||||
src={data}
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.url}
|
||||
controls
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
|
|
@ -364,10 +489,13 @@ export default function DetailImage() {
|
|||
modules={[Pagination, Thumbs]}
|
||||
>
|
||||
{detailVideo?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<video
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
src={data}
|
||||
className="object-cover h-[60px] w-[80px] rounded-md"
|
||||
src={data.url}
|
||||
muted
|
||||
title={`Video ${data.id}`} // Mengganti alt dengan title
|
||||
/>
|
||||
|
|
@ -401,34 +529,36 @@ export default function DetailImage() {
|
|||
alt={`Thumbnail ${index}`}
|
||||
/> */}
|
||||
<img
|
||||
className="object-cover w-36 h-32"
|
||||
src={data}
|
||||
className="object-cover w-32 h-32"
|
||||
src={"/assets/video-icon.webp"}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
<div className="flex flex-row gap-3 items-center">
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Nasional"
|
||||
{/* Mabes Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "mabes"} // Automatically checks if placement matches
|
||||
disabled // To reflect read-only behavior
|
||||
/>
|
||||
Nasional
|
||||
<span>Nasional</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="Wilayah"
|
||||
|
||||
{/* Polda Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "polda"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
Wilayah
|
||||
<span>Wilayah</span>
|
||||
</label>
|
||||
<label className="text-blue-500 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="placement"
|
||||
value="International"
|
||||
|
||||
{/* International Checkbox */}
|
||||
<label className=" cursor-pointer flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={data.placements === "international"} // Automatically checks if placement matches
|
||||
disabled
|
||||
/>
|
||||
International
|
||||
<span>International</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -440,112 +570,186 @@ export default function DetailImage() {
|
|||
</CardContent>
|
||||
<CardContent>
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Berikan Komentar</Label>
|
||||
<div className="flex items-start gap-3">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="/images/avatar/avatar-1.png"
|
||||
alt="@shadcn"
|
||||
/>
|
||||
</Avatar>
|
||||
|
||||
<textarea
|
||||
className="flex-grow p-2 border rounded-lg focus:outline-none focus:border-yellow-400"
|
||||
placeholder="Tuliskan komentar Anda di sini.."
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="flex items-center mt-2 gap-3">
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<PaperclipIcon className="w-5 h-5" />
|
||||
Lampirkan
|
||||
</button>
|
||||
<button className="flex items-center text-gray-600 hover:text-gray-800">
|
||||
<SmileIcon className="w-5 h-5" />
|
||||
Emoticon
|
||||
</button>
|
||||
<button className="ml-auto px-4 py-1 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600">
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Komentar</Label>
|
||||
{commentsData.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="flex items-start gap-3 mt-2"
|
||||
<div className="mt-4 border p-4 rounded bg-gray-50">
|
||||
<Textarea
|
||||
placeholder="Tulis tanggapan Anda di sini..."
|
||||
value={message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<div className="flex justify-end mt-3">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => postData()}
|
||||
type="button"
|
||||
>
|
||||
<Avatar>
|
||||
Kirim Komentar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{listData?.map((item: any) => (
|
||||
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={comment.avatar}
|
||||
alt={`@${comment.username}`}
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${item.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{comment.username}
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{comment.date}
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">{comment.text}</p>
|
||||
<div
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">{item.message}</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(comment.id)}
|
||||
onClick={() => handleReply(item.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div>
|
||||
{comment.replies.length > 0 && (
|
||||
<div className="ml-8 mt-2">
|
||||
{comment.replies.map((reply: any, index: any) => (
|
||||
</div> */}
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-3 mt-1"
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(item.id)}
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src="https://github.com/shadcn.png"
|
||||
alt={`@${reply.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{reply.username}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{reply.date}
|
||||
</span>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{reply.text}
|
||||
</p>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{replyingTo !== null && (
|
||||
<div className="mt-4">
|
||||
{replyingTo === item.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
className="w-full p-2 border rounded-md"
|
||||
rows={3}
|
||||
placeholder="Tulis balasan..."
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
></textarea>
|
||||
id={`input-comment-${item.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
|
||||
onClick={() => addReply(replyingTo)}
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(item.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{item.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col">
|
||||
{item.children.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(child.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{replyingTo === child.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${child.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(child.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{child.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col mb-3">
|
||||
{child.children.map((child2: any) => (
|
||||
<div
|
||||
key={child2.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child2.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { ArrowRight, Rows, Search, UploadIcon } from "lucide-react";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -76,6 +76,7 @@ const CuratedContentPage = () => {
|
|||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio Visual</Label>
|
||||
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
|
|
@ -86,13 +87,15 @@ const CuratedContentPage = () => {
|
|||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<VideoSliderPage />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Audio</Label>
|
||||
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
|
|
@ -103,13 +106,15 @@ const CuratedContentPage = () => {
|
|||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<AudioSliderPage />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Foto</Label>
|
||||
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
|
|
@ -120,13 +125,17 @@ const CuratedContentPage = () => {
|
|||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<ImageSliderPage />
|
||||
</div>
|
||||
<div className="flex justify-between items-center mx-3">
|
||||
<Label className="text-base">Teks</Label>
|
||||
<div className="flex items-center">
|
||||
<Label>
|
||||
{" "}
|
||||
<Link
|
||||
href={
|
||||
"/shared/curated-content/giat-routine/document/all"
|
||||
|
|
@ -135,6 +144,8 @@ const CuratedContentPage = () => {
|
|||
Lihat Semua
|
||||
</Link>
|
||||
</Label>
|
||||
<ArrowRight size={18} className="text-slate-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 my-5">
|
||||
<TeksSliderPage />
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import {
|
|||
} from "@/service/content/content";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { MailIcon } from "lucide-react";
|
||||
import { MailIcon, Music } from "lucide-react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
|
|
@ -138,25 +138,23 @@ export default function FormAudioDetail() {
|
|||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
const [isMabesApprover, setIsMabesApprover] = useState(false);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
const [audioPlaying, setAudioPlaying] = useState<any>(null);
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
|
||||
const waveSurferRef = useRef<any>(null);
|
||||
|
||||
|
||||
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
|
||||
const [isPlaying, setIsPlaying] = useState(false)
|
||||
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
const onReady = (ws: any) => {
|
||||
setWavesurfer(ws)
|
||||
setWavesurfer(ws);
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onPlayPause = () => {
|
||||
wavesurfer && wavesurfer.playPause();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let fileTypeId = "4";
|
||||
|
||||
|
|
@ -187,7 +185,7 @@ export default function FormAudioDetail() {
|
|||
userLevelId == "216" &&
|
||||
roleId == "3"
|
||||
) {
|
||||
setIsMabesApprover(true);
|
||||
setIsUserMabesApprover(true);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
|
|
@ -230,6 +228,14 @@ export default function FormAudioDetail() {
|
|||
}
|
||||
};
|
||||
|
||||
const setupPlacementCheck = (length: number) => {
|
||||
const temp = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -244,6 +250,7 @@ export default function FormAudioDetail() {
|
|||
names: details?.files[0]?.fileName,
|
||||
format: details?.files[0]?.format,
|
||||
});
|
||||
setupPlacementCheck(details?.files?.length);
|
||||
|
||||
if (details.publishedForObject) {
|
||||
const publisherIds = details.publishedForObject.map(
|
||||
|
|
@ -281,9 +288,16 @@ export default function FormAudioDetail() {
|
|||
}, [refresh, setValue]);
|
||||
|
||||
const actionApproval = (e: string) => {
|
||||
const temp = [];
|
||||
for (const element of detail.files) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
setStatus(e);
|
||||
setModalOpen(true);
|
||||
setFiles(detail.files);
|
||||
|
||||
setDescription("");
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
|
|
@ -308,13 +322,26 @@ export default function FormAudioDetail() {
|
|||
}
|
||||
};
|
||||
|
||||
const getPlacement = () => {
|
||||
console.log("getPlaa", filePlacements);
|
||||
const temp = [];
|
||||
for (let i = 0; i < filePlacements?.length; i++) {
|
||||
if (filePlacements[i].length !== 0) {
|
||||
const now = filePlacements[i].filter((a) => a !== "all");
|
||||
const data = { mediaFileId: files[i].id, placement: now.join(",") };
|
||||
temp.push(data);
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
|
||||
async function save() {
|
||||
const data = {
|
||||
mediaUploadId: id,
|
||||
statusId: status,
|
||||
message: description,
|
||||
files: [],
|
||||
// files: isMabesApprover ? getPlacement() : [],
|
||||
// files: [],
|
||||
files: isUserMabesApprover ? getPlacement() : [],
|
||||
};
|
||||
|
||||
loading();
|
||||
|
|
@ -337,6 +364,7 @@ export default function FormAudioDetail() {
|
|||
}
|
||||
|
||||
close();
|
||||
submitApprovalSuccesss();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -348,6 +376,40 @@ export default function FormAudioDetail() {
|
|||
rejects.push(id);
|
||||
setRejectedFiles(rejects);
|
||||
}
|
||||
|
||||
const setupPlacement = (
|
||||
index: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
) => {
|
||||
let temp = [...filePlacements];
|
||||
if (checked) {
|
||||
if (placement === "all") {
|
||||
temp[index] = ["all", "mabes", "polda", "international"];
|
||||
} else {
|
||||
const now = temp[index];
|
||||
now.push(placement);
|
||||
if (now.length === 3 && !now.includes("all")) {
|
||||
now.push("all");
|
||||
}
|
||||
temp[index] = now;
|
||||
}
|
||||
} else {
|
||||
if (placement === "all") {
|
||||
temp[index] = [];
|
||||
} else {
|
||||
const now = temp[index].filter((a) => a !== placement);
|
||||
console.log("now", now);
|
||||
temp[index] = now;
|
||||
if (now.length === 3 && now.includes("all")) {
|
||||
const newData = now.filter((b) => b !== "all");
|
||||
temp[index] = newData;
|
||||
}
|
||||
}
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
const handleMain = (
|
||||
type: string,
|
||||
url: string,
|
||||
|
|
@ -380,7 +442,7 @@ export default function FormAudioDetail() {
|
|||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then(() => {
|
||||
router.push("/in/contributor/content/image");
|
||||
router.push("/in/contributor/content/audio");
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -469,7 +531,27 @@ export default function FormAudioDetail() {
|
|||
/>
|
||||
</div>
|
||||
))}
|
||||
<p onClick={onPlayPause}>{isPlaying ? "Pause" : "Play"}</p>
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={onPlayPause}
|
||||
disabled={isPlaying}
|
||||
className={`flex items-center gap-2 ${
|
||||
isPlaying
|
||||
? "bg-gray-300 cursor-not-allowed"
|
||||
: "bg-primary text-white"
|
||||
} p-2 rounded`}
|
||||
>
|
||||
{isPlaying ? "Pause" : "Play"}
|
||||
<Icon
|
||||
icon={
|
||||
isPlaying
|
||||
? "carbon:pause-outline"
|
||||
: "famicons:play-sharp"
|
||||
}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -609,7 +691,7 @@ export default function FormAudioDetail() {
|
|||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file) => (
|
||||
? files?.map((file, index) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
|
|
@ -618,13 +700,27 @@ export default function FormAudioDetail() {
|
|||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between text-sm">
|
||||
{file.fileName}
|
||||
<a>
|
||||
<a
|
||||
onClick={() =>
|
||||
handleDeleteFileApproval(file.id)
|
||||
}
|
||||
>
|
||||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a>
|
||||
</div>
|
||||
{isUserMabesApprover && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
value="all"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"all"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "all", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -633,7 +729,15 @@ export default function FormAudioDetail() {
|
|||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"mabes"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "mabes", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -642,7 +746,15 @@ export default function FormAudioDetail() {
|
|||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"polda"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "polda", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -652,7 +764,19 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"international"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(
|
||||
index,
|
||||
"international",
|
||||
Boolean(e)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -661,6 +785,7 @@ export default function FormAudioDetail() {
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
@ -743,14 +868,16 @@ export default function FormAudioDetail() {
|
|||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={submitApprovalSuccesss}
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="destructive"
|
||||
onClick={() => setModalOpen(false)}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -355,10 +355,8 @@ export default function FormConvertSPIT() {
|
|||
const handleCheckboxChange = (id: string): void => {
|
||||
if (id === "all") {
|
||||
if (publishedFor.includes("all")) {
|
||||
// Uncheck all checkboxes
|
||||
setPublishedFor([]);
|
||||
} else {
|
||||
// Select all checkboxes
|
||||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
|
|
@ -370,7 +368,6 @@ export default function FormConvertSPIT() {
|
|||
? publishedFor.filter((item) => item !== id)
|
||||
: [...publishedFor, id];
|
||||
|
||||
// Remove "all" if any checkbox is unchecked
|
||||
if (publishedFor.includes("all") && id !== "all") {
|
||||
setPublishedFor(updatedPublishedFor.filter((item) => item !== "all"));
|
||||
} else {
|
||||
|
|
@ -416,10 +413,10 @@ export default function FormConvertSPIT() {
|
|||
description,
|
||||
htmlDescription: description,
|
||||
tags: "siap",
|
||||
categoryId: 1,
|
||||
categoryId: selectedCategoryId,
|
||||
publishedFor: publishedFor.join(","),
|
||||
creator: data.contentCreator,
|
||||
files: isUserMabesApprover ? getPlacement() : [], // Include placement data
|
||||
files: isUserMabesApprover ? getPlacement() : [],
|
||||
};
|
||||
|
||||
const response = await convertSPIT(requestData);
|
||||
|
|
@ -698,7 +695,7 @@ export default function FormConvertSPIT() {
|
|||
</RadioGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xl text-black">File Media</Label>
|
||||
<Label className="text-xl">File Media</Label>
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
|
|
@ -709,7 +706,7 @@ export default function FormConvertSPIT() {
|
|||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<img
|
||||
className="object-fill h-full w-full"
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
|
|
@ -719,7 +716,7 @@ export default function FormConvertSPIT() {
|
|||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
|
|
@ -730,7 +727,7 @@ export default function FormConvertSPIT() {
|
|||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide key={data.id}>
|
||||
<img
|
||||
className="object-cover h-[60px] w-[80px]"
|
||||
className="object-cover h-[60px] w-[80px] rounded-md"
|
||||
src={data}
|
||||
alt={` ${data.id}`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -138,6 +138,9 @@ export default function FormTeksDetail() {
|
|||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
const [isMabesApprover, setIsMabesApprover] = useState(false);
|
||||
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
const [isUserMabesApprover, setIsUserMabesApprover] = useState(false);
|
||||
|
||||
let fileTypeId = "3";
|
||||
|
||||
const {
|
||||
|
|
@ -167,7 +170,7 @@ export default function FormTeksDetail() {
|
|||
userLevelId == "216" &&
|
||||
roleId == "3"
|
||||
) {
|
||||
setIsMabesApprover(true);
|
||||
setIsUserMabesApprover(true);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
|
|
@ -210,6 +213,14 @@ export default function FormTeksDetail() {
|
|||
}
|
||||
};
|
||||
|
||||
const setupPlacementCheck = (length: number) => {
|
||||
const temp = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
|
|
@ -255,9 +266,16 @@ export default function FormTeksDetail() {
|
|||
}, [refresh, setValue]);
|
||||
|
||||
const actionApproval = (e: string) => {
|
||||
const temp = [];
|
||||
for (const element of detail.files) {
|
||||
temp.push([]);
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
setStatus(e);
|
||||
setModalOpen(true);
|
||||
setFiles(detail.files);
|
||||
|
||||
setDescription("");
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
|
|
@ -266,20 +284,34 @@ export default function FormTeksDetail() {
|
|||
Number(status) == 2 ||
|
||||
Number(status) == 4
|
||||
) {
|
||||
MySwal.fire({
|
||||
title: "Simpan Approval",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Simpan",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
save();
|
||||
// MySwal.fire({
|
||||
// title: "Simpan Approval",
|
||||
// text: "",
|
||||
// icon: "warning",
|
||||
// showCancelButton: true,
|
||||
// cancelButtonColor: "#d33",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "Simpan",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
|
||||
// }
|
||||
// });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getPlacement = () => {
|
||||
console.log("getPlaa", filePlacements);
|
||||
const temp = [];
|
||||
for (let i = 0; i < filePlacements?.length; i++) {
|
||||
if (filePlacements[i].length !== 0) {
|
||||
const now = filePlacements[i].filter((a) => a !== "all");
|
||||
const data = { mediaFileId: files[i].id, placement: now.join(",") };
|
||||
temp.push(data);
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
|
||||
async function save() {
|
||||
|
|
@ -287,8 +319,7 @@ export default function FormTeksDetail() {
|
|||
mediaUploadId: id,
|
||||
statusId: status,
|
||||
message: description,
|
||||
files: [],
|
||||
// files: isMabesApprover ? getPlacement() : [],
|
||||
files: isUserMabesApprover ? getPlacement() : [],
|
||||
};
|
||||
|
||||
loading();
|
||||
|
|
@ -311,10 +342,44 @@ export default function FormTeksDetail() {
|
|||
}
|
||||
|
||||
close();
|
||||
submitApprovalSuccesss();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const setupPlacement = (
|
||||
index: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
) => {
|
||||
let temp = [...filePlacements];
|
||||
if (checked) {
|
||||
if (placement === "all") {
|
||||
temp[index] = ["all", "mabes", "polda", "international"];
|
||||
} else {
|
||||
const now = temp[index];
|
||||
now.push(placement);
|
||||
if (now.length === 3 && !now.includes("all")) {
|
||||
now.push("all");
|
||||
}
|
||||
temp[index] = now;
|
||||
}
|
||||
} else {
|
||||
if (placement === "all") {
|
||||
temp[index] = [];
|
||||
} else {
|
||||
const now = temp[index].filter((a) => a !== placement);
|
||||
console.log("now", now);
|
||||
temp[index] = now;
|
||||
if (now.length === 3 && now.includes("all")) {
|
||||
const newData = now.filter((b) => b !== "all");
|
||||
temp[index] = newData;
|
||||
}
|
||||
}
|
||||
}
|
||||
setFilePlacements(temp);
|
||||
};
|
||||
|
||||
function handleDeleteFileApproval(id: number) {
|
||||
const selectedFiles = files.filter((file) => file.id != id);
|
||||
setFiles(selectedFiles);
|
||||
|
|
@ -473,7 +538,7 @@ export default function FormTeksDetail() {
|
|||
<div className="mt-2">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
pagination={{ clickable: true }}
|
||||
modules={[Pagination, Thumbs]}
|
||||
|
|
@ -644,7 +709,7 @@ export default function FormTeksDetail() {
|
|||
<DialogTitle>Berikan Komentar</DialogTitle>
|
||||
</DialogHeader>
|
||||
{status == "2"
|
||||
? files?.map((file) => (
|
||||
? files?.map((file, index) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
|
|
@ -653,13 +718,27 @@ export default function FormTeksDetail() {
|
|||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex justify-between text-sm">
|
||||
{file.fileName}
|
||||
<a>
|
||||
<a
|
||||
onClick={() =>
|
||||
handleDeleteFileApproval(file.id)
|
||||
}
|
||||
>
|
||||
<Icon icon="humbleicons:times" color="red" />
|
||||
</a>
|
||||
</div>
|
||||
{isUserMabesApprover && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
value="all"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"all"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "all", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -668,7 +747,15 @@ export default function FormTeksDetail() {
|
|||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"mabes"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "mabes", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -677,7 +764,15 @@ export default function FormTeksDetail() {
|
|||
</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"polda"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(index, "polda", Boolean(e))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -687,7 +782,19 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={filePlacements[index]?.includes(
|
||||
"international"
|
||||
)}
|
||||
onCheckedChange={(e) =>
|
||||
setupPlacement(
|
||||
index,
|
||||
"international",
|
||||
Boolean(e)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
|
|
@ -696,6 +803,7 @@ export default function FormTeksDetail() {
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
|
@ -778,14 +886,16 @@ export default function FormTeksDetail() {
|
|||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
onClick={submitApprovalSuccesss}
|
||||
onClick={() => submit()}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="destructive"
|
||||
onClick={() => setModalOpen(false)}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1329,6 +1329,7 @@ export default function FormTaskDetail() {
|
|||
<div className="flex gap-2">
|
||||
<div className="">
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
variant={"outline"}
|
||||
onClick={handleToggleInput}
|
||||
|
|
@ -1338,6 +1339,7 @@ export default function FormTaskDetail() {
|
|||
</div>
|
||||
<div className="">
|
||||
<Button
|
||||
type="button"
|
||||
className="btn btn-primary ml-3 mr-3"
|
||||
style={
|
||||
statusAcceptance ||
|
||||
|
|
|
|||
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 |
Loading…
Reference in New Issue