feat:curated-content page,detail, detail planning, detail task

This commit is contained in:
Anang Yusman 2024-12-17 21:27:47 +07:00
parent ace0fc2200
commit fa35e4d2f5
21 changed files with 3047 additions and 396 deletions

View File

@ -19,7 +19,7 @@ const BlogPage = async () => {
</div>
<div className="flex-none">
<Link href={"/contributor/blog/create"}>
<Button fullWidth size="md" color="primary">
<Button fullWidth color="primary">
<Plus className="w-6 h-6 me-1.5" />
Add Index
</Button>

View File

@ -12,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [
{
@ -91,15 +92,18 @@ const columns: ColumnDef<any>[] = [
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link
href={`/contributor/planning/mediahub/publish/${row.original.id}`}
>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
Publish
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
</Link>
<DropdownMenuItem
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
// onClick={() => deletePlan(row.id)}
>
<Trash2 className="w-4 h-4 me-1.5" />
Delete
</DropdownMenuItem>

View File

@ -0,0 +1,17 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTask from "@/components/form/task/task-form";
import PublishMediahub from "@/components/form/planning/mediahub-publish";
const MediahubPublishPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<PublishMediahub />
</div>
</div>
);
};
export default MediahubPublishPage;

View File

@ -12,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [
{
@ -91,14 +92,15 @@ const columns: ColumnDef<any>[] = [
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link
href={`/contributor/planning/medsos-mediahub/publish/${row.original.id}`}
>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
Publish
</DropdownMenuItem>
</Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
<Trash2 className="w-4 h-4 me-1.5" />
Delete

View File

@ -0,0 +1,16 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import PublishMedsos from "@/components/form/planning/medsos-publish";
const MedsosPublishPage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<PublishMedsos />
</div>
</div>
);
};
export default MedsosPublishPage;

View File

@ -124,7 +124,7 @@ const PressReleaseTable = () => {
return (
<div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5">
<div className="flex justify-between items-center px-5 mt-3">
<div>
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">

View File

@ -12,6 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [
{
@ -111,10 +112,12 @@ const columns: ColumnDef<any>[] = [
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link href={`/contributor/task/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit

View File

@ -10,6 +10,7 @@ import PressConferenceTable from "../contributor/schedule/press-release/componen
import BlogTable from "../contributor/blog/components/blog-table";
import ContentTable from "./routine-task/components/content-table";
import RecentActivity from "./routine-task/components/recent-activity";
import { Link } from "@/components/navigation";
const DashboardPage = () => {
const t = useTranslations("AnalyticsDashboard");
@ -110,14 +111,16 @@ const DashboardPage = () => {
Table Penugasan
</div>
<div>
<Link href={"/contributor/task/create"}>
<Button color="primary" className="text-white">
<UploadIcon />
Buat Penugasan
</Button>
</Link>
</div>
</div>
</Card>
<CardContent className="p-0">
<CardContent className="p-0 mt-3">
<TaskTable />
</CardContent>
</Card>
@ -145,14 +148,16 @@ const DashboardPage = () => {
Table Indeks
</div>
<div>
<Link href={"/contributor/blog/create"}>
<Button color="primary" className="text-white">
<UploadIcon />
Tambah Indeks
</Button>
</Link>
</div>
</div>
</Card>
<CardContent className="p-0">
<CardContent className="p-0 mt-3">
<BlogTable />
</CardContent>
</Card>

View File

@ -62,8 +62,8 @@ const AudioSliderPage = () => {
<div className="mx-3 px-5">
<div className=" grid grid-cols-1 gap-6 ">
{displayAudio?.map((audio: any) => (
<a
href="#"
<Link
href={`/shared/curated-content//giat-routine/audio/detail/${audio.id}`}
key={audio?.id}
className="flex flex-col sm:flex-row items-center hover:scale-110 transition-transform duration-300 bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
>
@ -118,7 +118,7 @@ const AudioSliderPage = () => {
</svg>
</div>
</div>
</a>
</Link>
))}
</div>
</div>

View File

@ -0,0 +1,549 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
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 { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
categoryName: string;
};
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailAudio() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [selectedTarget, setSelectedTarget] = useState("");
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
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(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
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"
);
setDetailThumb(fileUrls);
}
}
initState();
}, [id, refresh]);
return (
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</div>
<div className="w-6/12">
<div className="gap-5 mb-5">
<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
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
</div>
<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>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
<div className="flex flex-row items-center gap-2 text-blue-500">
<InboxIcon />
<p className="text-blue-500">Kotak Saran (0)</p>
</div>
</div>
<Button>Content Rewrite</Button>
</div>
</div>
</div>
</CardContent>
<CardContent className="border rounded-md mt-5">
<div className="flex flex-row gap-5">
<div className="w-6/12 border px-3 mt-3 rounded-md">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<div className="w-full">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<audio
className="w-full"
src={data}
controls
title={`Audio ${data.id}`}
/>
</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}>
<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}`}
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
</div>
<div className="w-6/12 border px-3 rounded-md mt-3">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
<div className="flex flex-row justify-between items-center">
<p>file</p>
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
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={`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"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</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"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<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>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
);
}

View File

@ -0,0 +1,518 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
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 { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import JoditEditor from "jodit-react";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
categoryName: string;
};
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailDocument() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [selectedTarget, setSelectedTarget] = useState("");
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
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(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
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"
);
setDetailThumb(fileUrls);
}
}
initState();
}, [id, refresh]);
return (
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</div>
<div className="w-6/12">
<div className="gap-5 mb-5">
<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
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
</div>
<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>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
<div className="flex flex-row items-center gap-2 text-blue-500">
<InboxIcon />
<p className="text-blue-500">Kotak Saran (0)</p>
</div>
</div>
<Button>Content Rewrite</Button>
</div>
</div>
</div>
</CardContent>
<CardContent className="border rounded-md mt-5">
<div className="flex flex-row gap-5">
<div className="w-6/12 border px-3 mt-3 rounded-md">
<div className="gap-5 mb-5">
<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"
/>
)}
/>
</div>
</div>
</div>
</div>
<div className="w-6/12 border px-3 rounded-md mt-3">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
<div className="flex flex-row justify-between items-center">
<p>file</p>
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
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}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</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"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<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>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
);
}

View File

@ -61,8 +61,8 @@ const TeksSliderPage = () => {
<div className="mx-3 px-5">
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
{displayDocument?.map((document: any) => (
<a
href="#"
<Link
href={`/shared/curated-content/giat-routine/document/detail/${document.id}`}
key={document?.id}
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
>
@ -105,7 +105,7 @@ const TeksSliderPage = () => {
Download Dokumen
</div>
</div>
</a>
</Link>
))}
</div>
</div>

View File

@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
@ -17,25 +17,26 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import { createTask } from "@/config/api";
import {
createMedia,
getTagsBySubCategoryId,
listEnableCategory,
} from "@/service/content/content";
import { postBlog } from "@/service/blog/blog";
import { Textarea } from "@/components/ui/textarea";
import { InboxIcon } from "lucide-react";
import { DotSquare, InboxIcon, PaperclipIcon, SmileIcon } from "lucide-react";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const taskSchema = z.object({
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
slug: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
@ -45,130 +46,151 @@ const taskSchema = z.object({
type Category = {
id: string;
name: string;
categoryName: string;
};
const initialCategories: Category[] = [
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: "1",
name: "Giat Polri",
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: "2",
name: "Giat Pimpinan",
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: "3",
name: "Liputan Kegiatan",
},
{
id: "4",
name: "Seputar Prestasi",
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailImage() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [categories] = useState<Category[]>(initialCategories); // State untuk kategori
const [selectedTarget, setSelectedTarget] = useState("");
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [isDraft, setIsDraft] = useState(false);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
let fileTypeId = "1";
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
const handleReply = (commentId: number) => {
setReplyingTo(commentId);
};
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const files = Array.from(event.target.files);
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
console.log("DATAFILE::", selectedFiles);
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(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
const handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response.data?.data;
const save = async (data: TaskSchema) => {
const requestData = {
...data,
title: data.title,
description: data.description,
categoryId: selectedTarget,
slug: data.slug,
metadata: data.meta,
// tags: data.tags,
isDraft,
};
const response = await postBlog(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/blog");
});
};
const onSubmit = (data: TaskSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
setDetail(details);
const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrls);
}
});
};
}
initState();
}, [id, refresh]);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-5">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
@ -183,6 +205,7 @@ export default function DetailImage() {
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
@ -195,27 +218,20 @@ export default function DetailImage() {
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Select
value={selectedTarget} // Ensure selectedTarget is updated correctly
onValueChange={(value) => {
console.log("Selected Category ID:", value);
setSelectedTarget(value);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.name}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
@ -223,18 +239,19 @@ export default function DetailImage() {
<Label>Description</Label>
<Controller
control={control}
name="meta"
name="description"
render={({ field }) => (
<Textarea
value={field.value}
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.meta?.message && (
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.meta.message}
{errors.description.message}
</p>
)}
</div>
@ -271,29 +288,18 @@ export default function DetailImage() {
<div className=" py-3">
<div className="space-y-2">
<Label>Tag</Label>
<Controller
control={control}
name="slug"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Slug"
/>
)}
/>
{errors.slug?.message && (
<p className="text-red-400 text-sm">
{errors.slug.message}
<p className="border rounded-md text-blue-600 px-2 py-2">
{detail?.tags}
</p>
)}
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Kontributor</Label>
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
@ -314,24 +320,46 @@ export default function DetailImage() {
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
<div className="w-full ">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-fill h-full w-full"
src={data}
alt={` ${data.id}`}
/>
)}
</SwiperSlide>
))}
</Swiper>
<div className=" mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
// className="mySwiper2"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-cover h-[60px] w-[80px]"
src={data}
alt={` ${data.id}`}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
</div>
@ -346,14 +374,170 @@ export default function DetailImage() {
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
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={`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"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</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"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<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>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
</form>
);
}

View File

@ -1,8 +1,10 @@
"use client";
import { Link } from "@/components/navigation";
import { Card, CardContent } from "@/components/ui/card";
import { getListContent } from "@/service/landing/landing";
import { formatDateToIndonesian } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js";
import image from "next/image";
import React, { useEffect, useState } from "react";
const VideoSliderPage = () => {
@ -52,6 +54,9 @@ const VideoSliderPage = () => {
<Card
key={video?.id}
className="hover:scale-110 transition-transform duration-300"
>
<Link
href={`/shared/curated-content/giat-routine/video/detail/${video.id}`}
>
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
<img
@ -79,6 +84,7 @@ const VideoSliderPage = () => {
{video?.title}
</div>
</CardContent>
</Link>
</Card>
))}
</div>

View File

@ -0,0 +1,551 @@
"use client";
import React, { ChangeEvent, useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
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 { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/free-mode";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/thumbs";
import "swiper/css";
import "swiper/css/navigation";
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const detailSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
meta: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
categoryName: string;
};
export type curationDetail = {
id: number;
title: string;
categoryName: string;
description: string;
uploadedBy: {
id: number;
fullname: string;
username: string | null;
email: string;
isActive: boolean;
isDefault: boolean;
isInternational: boolean;
userLevel: {
id: number;
name: string;
aliasName: string;
userGroupId: number;
};
};
tags: string;
provinceId: string;
is_active: string;
};
const initialComments = [
{
id: 1,
username: "Esther Howard",
date: "07-04-2023 20:00 WIB",
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 2,
username: "Brooklyn Simmons",
date: "07-04-2023 20:00 WIB",
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
{
id: 3,
username: "Leslie Alexander",
date: "07-04-2023 20:00 WIB",
text: "Sangat berguna. Terima Kasih!",
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
replies: [], // Komentar balasan
},
];
export default function DetailImage() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
console.log(id);
const editor = useRef(null);
type DetailSchema = z.infer<typeof detailSchema>;
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [selectedTarget, setSelectedTarget] = useState("");
// const [detail, setDetail] = useState({
// title: null,
// tags: null,
// files: [],
// fileType: null,
// });
const [detail, setDetail] = useState<curationDetail>();
const [refresh] = useState(false);
const [detailVideo, setDetailVideo] = useState<any>([]);
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<DetailSchema>({
resolver: zodResolver(detailSchema),
});
const [commentsData, setCommentsData] = useState(initialComments);
const [replyText, setReplyText] = useState("");
const [replyingTo, setReplyingTo] = useState<number | null>(null);
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(),
},
],
};
}
return comment;
});
setCommentsData(newCommentData);
setReplyText("");
setReplyingTo(null);
}
};
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"
);
setDetailVideo(fileUrls);
const filesDataThumbnail = details.files || [];
const fileUrlsThumbnail = filesDataThumbnail.map(
(file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
);
setDetailThumb(fileUrlsThumbnail);
}
}
initState();
}, [id, refresh]);
return (
<div className="flex gap-10">
{detail !== undefined ? (
<Card className="w-full ">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
<CardContent className="border rounded-md">
<div className="flex flex-row gap-10">
<div className="w-6/12">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.title}
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<Controller
control={control}
name="categoryName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
defaultValue={detail.categoryName}
/>
)}
/>
</div>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
value={detail.description}
onChange={field.onChange}
placeholder="Enter Meta"
cols={5}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
</div>
</div>
</div>
<div className="w-6/12">
<div className="gap-5 mb-5">
<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
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="umum">Umum</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="ksp">Ksp</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="journalist">Journalist</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="4" id="contra" />
<Label htmlFor="polri">Polri</Label>
</div>
</RadioGroup>
</div>
<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>
</div>
<div className=" py-3">
<div className="space-y-2">
<Label>
{" "}
{detail?.uploadedBy?.fullname ||
"Data tidak tersedia"}
</Label>
</div>
</div>
<div className=" py-3">
<div className="flex flex-row items-center gap-2 text-blue-500">
<InboxIcon />
<p className="text-blue-500">Kotak Saran (0)</p>
</div>
</div>
<Button>Content Rewrite</Button>
</div>
</div>
</div>
</CardContent>
<CardContent className="border rounded-md mt-5">
<div className="flex flex-row gap-5">
<div className="w-6/12 border px-3 mt-3 rounded-md">
<div className="gap-5 mb-5">
<div className="space-y-2 py-3">
<Label className="text-xl text-black">File Media</Label>
<div className="w-full">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<video
className="object-fill h-full w-full"
src={data}
controls
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
</SwiperSlide>
))}
</Swiper>
<div className="mt-2">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<video
className="object-cover h-[60px] w-[80px]"
src={data}
muted
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</div>
</div>
</div>
<div className="w-6/12 border px-3 rounded-md mt-3">
<div className="gap-5 mb-5">
<div className="mt-5">
<Label className="text-xl text-black">
Penempatan File
</Label>
<div className="flex flex-row justify-between items-center">
<p>file</p>
<p>Penempatan</p>
</div>
<p className="bg-black h-1 w-full rounded-lg"></p>
{detailThumb?.map((data: any) => (
<div
key={data.id}
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}`}
/>
<div className="flex flex-row gap-3 items-center">
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Nasional"
/>
Nasional
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="Wilayah"
/>
Wilayah
</label>
<label className="text-blue-500 cursor-pointer">
<input
type="checkbox"
name="placement"
value="International"
/>
International
</label>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</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"
>
<Avatar>
<AvatarImage
src={comment.avatar}
alt={`@${comment.username}`}
/>
</Avatar>
<div className="flex flex-col">
<span className="text-gray-700 font-semibold">
{comment.username}
</span>
<span className="text-gray-500 text-sm">
{comment.date}
</span>
<p className="text-gray-800 mt-1">{comment.text}</p>
<div
className="flex items-center mt-1 text-blue-500 cursor-pointer"
onClick={() => handleReply(comment.id)}
>
<DotSquare className="w-4 h-4" />
<span className="ml-1">Balas</span>
</div>
{comment.replies.length > 0 && (
<div className="ml-8 mt-2">
{comment.replies.map((reply: any, index: any) => (
<div
key={index}
className="flex items-start gap-3 mt-1"
>
<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>
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
{replyingTo !== null && (
<div className="mt-4">
<textarea
className="w-full p-2 border rounded-md"
rows={3}
placeholder="Tulis balasan..."
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
></textarea>
<button
className="mt-2 bg-yellow-500 text-white px-4 py-2 rounded-md"
onClick={() => addReply(replyingTo)}
>
Kirim
</button>
</div>
)}
</div>
</div>
</CardContent>
</div>
</Card>
) : (
""
)}
</div>
);
}

View File

@ -0,0 +1,352 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { createTask } from "@/service/task";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { id } from "date-fns/locale";
import { getPlanningById } from "@/service/planning/planning";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
});
export type mediahubDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignmentType: {
id: number;
name: string;
};
date: string;
description: string;
is_active: string;
};
export default function PublishMediahub() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
const [taskOutput, setTaskOutput] = useState({
all: false,
video: false,
audio: false,
image: false,
text: false,
});
const [mainType, setMainType] = useState<number>(1);
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all");
const [startDate, setStartDate] = useState<Date>(new Date());
const [detail, setDetail] = useState<mediahubDetail>();
const [refresh] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
const {
control,
handleSubmit,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
});
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = Number(event.target.value);
setMainType(selectedValue);
setPlatformTypeVisible(selectedValue === 2);
};
useEffect(() => {
async function initState() {
if (id) {
const response = await getPlanningById(id);
const details = response.data?.data;
setDetail(details);
if (details?.date) {
setStartDate(new Date(details.date)); // Konversi string tanggal ke objek Date
}
}
}
initState();
}, [id, refresh]);
useEffect(() => {
if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
setTaskOutput({
all: outputSet.has(0),
video: outputSet.has(2),
audio: outputSet.has(4),
image: outputSet.has(1),
text: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
useEffect(() => {
if (detail?.assignedToTopLevel) {
const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number)
); // Membagi string ke dalam array dan mengonversi ke nomor
setUnitSelection({
allUnit: outputSet.has(0),
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "3",
image: "4",
text: "5",
};
const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
.join(",");
const requestData = {
...data,
// assignmentType,
// assignmentCategory,
target: selectedTarget,
unitSelection,
assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentPurpose: "1",
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
id: null,
narration: data.naration,
platformType: "",
title: data.title,
};
const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/task");
});
};
const onSubmit = (data: TaskSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
return (
<Card>
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p>
{detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="mt-5">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3 mt-1">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="flex flex-row items-center">
<div className="mt-5">
<Label>Pelaksana Tugas</Label>
<div className="flex flex-wrap mt-1 gap-2">
{Object.keys(unitSelection).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={
unitSelection[key as keyof typeof unitSelection]
}
onCheckedChange={(value) =>
setUnitSelection({ ...unitSelection, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
</div>
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="publication">Publikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="amplification">Amplifikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="contra">Kontra</Label>
</div>
</RadioGroup>
</div>
<div className="mt-5">
<div className="flex flex-col">
<Label>Date</Label>
<div>
<Button
value={detail.date}
variant="outline"
size="md"
className={cn(
" justify-between text-left font-normal border-default-200 text-default-600 md:px-4 w-3/12",
!startDate && "text-muted-foreground"
)}
>
{startDate ? (
format(startDate, "PP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
<div className="mt-5">
<Label>Narasi Penugasan</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div>
</div>
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
</form>
) : (
""
)}
</div>
</Card>
);
}

View File

@ -0,0 +1,352 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { createTask } from "@/service/task";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import { id } from "date-fns/locale";
import { getPlanningById } from "@/service/planning/planning";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
});
export type medsosDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignmentType: {
id: number;
name: string;
};
date: string;
description: string;
is_active: string;
};
export default function PublishMedsos() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
const [taskOutput, setTaskOutput] = useState({
all: false,
video: false,
audio: false,
image: false,
text: false,
});
const [mainType, setMainType] = useState<number>(1);
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all");
const [startDate, setStartDate] = useState<Date>(new Date());
const [detail, setDetail] = useState<medsosDetail>();
const [refresh] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
const {
control,
handleSubmit,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
});
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = Number(event.target.value);
setMainType(selectedValue);
setPlatformTypeVisible(selectedValue === 2);
};
useEffect(() => {
async function initState() {
if (id) {
const response = await getPlanningById(id);
const details = response.data?.data;
setDetail(details);
if (details?.date) {
setStartDate(new Date(details.date)); // Konversi string tanggal ke objek Date
}
}
}
initState();
}, [id, refresh]);
useEffect(() => {
if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
setTaskOutput({
all: outputSet.has(0),
video: outputSet.has(2),
audio: outputSet.has(4),
image: outputSet.has(1),
text: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
useEffect(() => {
if (detail?.assignedToTopLevel) {
const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number)
); // Membagi string ke dalam array dan mengonversi ke nomor
setUnitSelection({
allUnit: outputSet.has(0),
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "3",
image: "4",
text: "5",
};
const selectedOutputs = Object.keys(taskOutput)
.filter((key) => taskOutput[key as keyof typeof taskOutput]) // Ambil hanya yang `true`
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
.join(",");
const requestData = {
...data,
// assignmentType,
// assignmentCategory,
target: selectedTarget,
unitSelection,
assignedToRole: "3",
taskType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentPurpose: "1",
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
id: null,
narration: data.naration,
platformType: "",
title: data.title,
};
const response = await createTask(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/task");
});
};
const onSubmit = (data: TaskSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
return (
<Card>
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p>
{detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="mt-5">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3 mt-1">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="flex flex-row items-center">
<div className="mt-5">
<Label>Pelaksana Tugas</Label>
<div className="flex flex-wrap mt-1 gap-2">
{Object.keys(unitSelection).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={
unitSelection[key as keyof typeof unitSelection]
}
onCheckedChange={(value) =>
setUnitSelection({ ...unitSelection, [key]: value })
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
</div>
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="1" id="publication" />
<Label htmlFor="publication">Publikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="2" id="amplification" />
<Label htmlFor="amplification">Amplifikasi</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="3" id="contra" />
<Label htmlFor="contra">Kontra</Label>
</div>
</RadioGroup>
</div>
<div className="mt-5">
<div className="flex flex-col">
<Label>Date</Label>
<div>
<Button
value={detail.date}
variant="outline"
size="md"
className={cn(
" justify-between text-left font-normal border-default-200 text-default-600 md:px-4 w-3/12",
!startDate && "text-muted-foreground"
)}
>
{startDate ? (
format(startDate, "PP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
<div className="mt-5">
<Label>Narasi Penugasan</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
onChange={onChange}
className="dark:text-black"
/>
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div>
</div>
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
Submit
</Button>
</div>
</form>
) : (
""
)}
</div>
</Card>
);
}

View File

@ -1,5 +1,5 @@
"use client";
import React, { useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useRouter } from "next/navigation";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
@ -20,7 +20,7 @@ import {
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import { createTask } from "@/service/task";
import { createTask, getTask } from "@/service/task";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -29,11 +29,32 @@ const taskSchema = z.object({
}),
});
export type taskDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignmentType: {
id: number;
name: string;
};
assignmentMainType: {
id: number;
name: string;
};
taskType: string;
broadcastType: string;
narration: string;
is_active: string;
};
export default function FormTask() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
// State for various form fields
const [taskOutput, setTaskOutput] = useState({
@ -46,11 +67,13 @@ export default function FormTask() {
// const [assignmentType, setAssignmentType] = useState("mediahub");
// const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<number>(1);
const [mainType, setMainType] = useState<string>("1");
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
const [broadcastType, setBroadcastType] = useState<string>(""); // untuk Tipe Penugasan
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("all");
const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
@ -68,12 +91,57 @@ export default function FormTask() {
resolver: zodResolver(taskSchema),
});
const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedValue = Number(event.target.value);
setMainType(selectedValue);
// const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// const selectedValue = Number(event.target.value);
// setMainType(selectedValue);
setPlatformTypeVisible(selectedValue === 2);
};
// setPlatformTypeVisible(selectedValue === 2);
// };
useEffect(() => {
async function initState() {
if (id) {
const response = await getTask(id);
const details = response.data?.data;
setDetail(details);
}
}
initState();
}, [id, refresh]);
useEffect(() => {
if (detail?.broadcastType) {
setBroadcastType(detail.broadcastType); // Mengatur nilai broadcastType dari API
}
}, [detail?.broadcastType]);
useEffect(() => {
if (detail?.fileTypeOutput) {
const outputSet = new Set(detail.fileTypeOutput.split(",").map(Number)); // Membagi string ke dalam array dan mengonversi ke nomor
setTaskOutput({
all: outputSet.has(0),
video: outputSet.has(2),
audio: outputSet.has(4),
image: outputSet.has(1),
text: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
useEffect(() => {
if (detail?.assignedToTopLevel) {
const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number)
);
setUnitSelection({
allUnit: outputSet.has(0),
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
});
}
}, [detail?.fileTypeOutput]);
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
@ -144,6 +212,7 @@ export default function FormTask() {
<Card>
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Penugasan</p>
{detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
@ -156,7 +225,7 @@ export default function FormTask() {
<Input
size="md"
type="text"
value={field.value}
value={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
@ -185,7 +254,9 @@ export default function FormTask() {
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={unitSelection[key as keyof typeof unitSelection]}
checked={
unitSelection[key as keyof typeof unitSelection]
}
onCheckedChange={(value) =>
setUnitSelection({ ...unitSelection, [key]: value })
}
@ -197,12 +268,13 @@ export default function FormTask() {
))}
</div>
</div>
<div className="mt-5">
<Label>Tipe Penugasan</Label>
<RadioGroup
value={String(mainType)}
onValueChange={(value) => setMainType(Number(value))}
value={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setMainType(value)}
// value={String(mainType)}
// onValueChange={(value) => setMainType(Number(value))}
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="1" id="mediahub" />
@ -214,13 +286,13 @@ export default function FormTask() {
<div className="mt-5">
<Label>Jenis Tugas </Label>
<RadioGroup
value={String(taskType)}
value={detail.taskType.toString()}
onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="atensi-khusus" id="mediahub" />
<RadioGroupItem value="atensi-khusus" id="khusus" />
<Label htmlFor="atensi-khusus">Atensi Khusus</Label>
<RadioGroupItem value="tugas-harian" id="medsos-mediahub" />
<RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor="tugas-harian">Tugas Harian</Label>
</RadioGroup>
</div>
@ -228,7 +300,7 @@ export default function FormTask() {
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<RadioGroup
value={type} // State yang dipetakan ke value RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
className="flex flex-wrap gap-3"
>
@ -246,7 +318,6 @@ export default function FormTask() {
</div>
</RadioGroup>
</div>
<div className="mt-5">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3">
@ -269,16 +340,22 @@ export default function FormTask() {
<div className="mt-5">
<Label>Broadcast </Label>
<RadioGroup
value={String(broadcastType)}
onValueChange={(value) => setBroadcastType(String(value))}
value={broadcastType} // Nilai terpilih diambil dari state broadcastType
onValueChange={(value) => setBroadcastType(value)} // Mengatur nilai saat radio berubah
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="all" id="mediahub" />
<div className="flex items-center gap-2">
<RadioGroupItem value="all" id="all" />
<Label htmlFor="all">Semua</Label>
<RadioGroupItem value="email" id="medsos-mediahub" />
<Label htmlFor="email-blast">Email Blast</Label>
<RadioGroupItem value="whatsapp" id="medsos-mediahub" />
<Label htmlFor="whatsapp-blast">WhatsApp Blast</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="email" id="email" />
<Label htmlFor="email">Email Blast</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="whatsapp" id="whatsapp" />
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
</div>
</RadioGroup>
</div>
<div className="mt-5">
@ -289,7 +366,7 @@ export default function FormTask() {
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value}
value={detail?.narration}
onChange={onChange}
className="dark:text-black"
/>
@ -310,6 +387,9 @@ export default function FormTask() {
</Button>
</div>
</form>
) : (
""
)}
</div>
</Card>
);

View File

@ -0,0 +1,6 @@
import { getAPIInterceptor } from "@/config/api";
export async function detailMedia(id: any) {
const url = `media?id=${id}`;
return getAPIInterceptor(url);
}

View File

@ -1,3 +1,4 @@
import { getAPIInterceptor } from "@/config/api";
import { httpGetInterceptor } from "../http-config/http-interceptor-service";
export async function getPlanningSentPagination(
@ -10,3 +11,8 @@ export async function getPlanningSentPagination(
`planning/pagination/sent?enablePage=1&size=${size}&page=${page}&typeId=${typeId}&title=${title}`
);
}
export async function getPlanningById(id: any) {
const url = `planning?id=${id}`;
return getAPIInterceptor(url);
}

View File

@ -38,7 +38,7 @@ export async function createTask(data: any) {
export async function getTask(id: any) {
const url = `/assignment?id=${id}`;
return getAPIInterceptor({ url });
return getAPIInterceptor(url);
}
export async function forwardTask(data: any) {