feat:schedule, detail konten,task,blog

This commit is contained in:
Anang Yusman 2025-01-18 23:13:47 +08:00
parent 95860da6d6
commit e643eda2c6
21 changed files with 707 additions and 195 deletions

View File

@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useForm, Controller } from "react-hook-form";
import { cn } from "@/lib/utils";
import { cn, getCookiesDecrypt } from "@/lib/utils";
import { format } from "date-fns";
import {
Popover,
@ -84,6 +84,7 @@ const EventModal = ({
event: any;
selectedDate: any;
}) => {
const roleId = Number(getCookiesDecrypt("urie")) || 0;
const [detail, setDetail] = useState<any>();
const [startDate, setStartDate] = useState<Date>(new Date());
const [endDate, setEndDate] = useState<Date>(new Date());
@ -1082,7 +1083,18 @@ const EventModal = ({
</div>
</div>
<div className="flex flex-wrap gap-2 mt-10">
<Button type="submit" disabled={isPending} className="flex-1">
<Button
style={
roleId > 11
? {
display: "none",
}
: {}
}
type="submit"
disabled={isPending}
className="flex-1"
>
{isPending ? (
<>
<Loader2 className="me-2 h-4 w-4 animate-spin" />
@ -1095,6 +1107,13 @@ const EventModal = ({
)}
</Button>
<Button
style={
roleId > 11
? {
display: "none",
}
: {}
}
className="flex-1 bg-red-500 text-white"
type="button"
onClick={() => handleDelete(event?.event?.id)}

View File

@ -12,7 +12,11 @@ import {
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
import { Link, useRouter } from "@/components/navigation";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { deleteBlog } from "@/service/blog/blog";
import { error, loading } from "@/lib/swal";
const columns: ColumnDef<any>[] = [
{
@ -84,6 +88,48 @@ const columns: ColumnDef<any>[] = [
header: "Actions",
enableHiding: false,
cell: ({ row }) => {
const router = useRouter();
const MySwal = withReactContent(Swal);
async function deleteProcess(id: any) {
loading();
const resDelete = await deleteBlog(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const handleDeleteBlog = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
deleteProcess(id);
}
});
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -108,7 +154,10 @@ const columns: ColumnDef<any>[] = [
Edit
</DropdownMenuItem>
</Link>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
<DropdownMenuItem
onClick={() => handleDeleteBlog(row.original.id)}
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
</DropdownMenuItem>

View File

@ -12,14 +12,14 @@ import { Button } from "@/components/ui/button";
import InternalTable from "./internal/components/internal-table";
const CommunicationPage = () => {
const [tab, setTab] = useState("Komunikasi");
const [tab, setTab] = useState("Pertanyaan Internal");
return (
<div>
<SiteBreadcrumb />
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<div className="flex justify-between py-3">
<p className="text-lg">{tab}</p>
{tab === "Komunikasi" && (
{tab === "Pertanyaan Internal" && (
<Link href="/shared/communication/internal/create">
<Button color="primary" size="md">
<PlusIcon />
@ -39,15 +39,15 @@ const CommunicationPage = () => {
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
<Button
rounded="md"
onClick={() => setTab("Komunikasi")}
onClick={() => setTab("Pertanyaan Internal")}
className={` hover:text-white
${
tab === "Komunikasi"
tab === "Pertanyaan Internal"
? "bg-black text-white "
: "bg-white text-black "
}`}
>
Komunikasi
Pertanyaan Internal
</Button>
<Button
rounded="md"
@ -74,7 +74,7 @@ const CommunicationPage = () => {
Kolaborasi
</Button>
</div>
{tab === "Komunikasi" && <InternalTable />}
{tab === "Pertanyaan Internal" && <InternalTable />}
{tab === "Eskalasi" && <EscalationTable />}
{tab === "Kolaborasi" && <CollaborationTable />}
</div>

View File

@ -7,6 +7,7 @@ import {
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { listCuratedContent } from "@/service/curated-content/curated-content";
import { getListContent } from "@/service/landing/landing";
import {
formatDateToIndonesian,
@ -21,10 +22,12 @@ const AudioSliderPage = () => {
const [audioData, setAudioData] = useState<any>();
const [displayAudio, setDisplayAudio] = 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 (audioData?.length > 0) {
@ -35,14 +38,12 @@ const AudioSliderPage = () => {
}, [audioData]);
const initFetch = async () => {
const response = await getListContent({
page: page - 1,
size: 12,
sortBy: "createdAt",
contentTypeId: "4",
});
const response = await listCuratedContent(search, limit, page - 1, 4, "1");
console.log(response);
setAudioData(response?.data?.data?.content);
const data = response?.data?.data;
const contentData = data?.content;
setAudioData(contentData);
};
const shuffleAndSetVideos = () => {

View File

@ -28,7 +28,7 @@ const VideoSliderPage = () => {
}, [allVideoData]);
const fetchData = async () => {
const response = await listCuratedContent(search, limit, page - 1, 1, "2");
const response = await listCuratedContent(search, limit, page - 1, 2, "1");
console.log(response);
const data = response?.data?.data;

View File

@ -30,6 +30,7 @@ import {
} from "@/service/content/content";
import { getBlog, postBlog } from "@/service/blog/blog";
import { Badge } from "@/components/ui/badge";
import dynamic from "next/dynamic";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -76,6 +77,13 @@ const initialCategories: Category[] = [
},
];
const ViewEditor = dynamic(
() => {
return import("@/components/editor/view-editor");
},
{ ssr: false }
);
export default function FormBlogDetail() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -236,12 +244,7 @@ export default function FormBlogDetail() {
control={control}
name="narration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail.narration}
onChange={onChange}
className="dark:text-black"
/>
<ViewEditor initialData={detail?.narration} />
)}
/>
{errors.narration?.message && (
@ -300,7 +303,7 @@ export default function FormBlogDetail() {
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[600px]">
<Card className=" h-[650px]">
<div className="px-3 py-3">
<Label htmlFor="fileInput">Gambar Utama</Label>
<Input

View File

@ -28,8 +28,10 @@ import {
getTagsBySubCategoryId,
listEnableCategory,
} from "@/service/content/content";
import { getBlog, postBlog } from "@/service/blog/blog";
import { getBlog, postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
import { Badge } from "@/components/ui/badge";
import dynamic from "next/dynamic";
import { loading } from "@/lib/swal";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -38,7 +40,7 @@ const taskSchema = z.object({
narration: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
categoryName: z.string().min(1, { message: "Kategori diperlukan" }),
categoryId: z.string().min(1, { message: "Kategori diperlukan" }),
});
type Category = {
@ -77,6 +79,13 @@ const initialCategories: Category[] = [
},
];
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormBlogUpdate() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -98,6 +107,9 @@ export default function FormBlogUpdate() {
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
@ -117,18 +129,6 @@ export default function FormBlogUpdate() {
resolver: zodResolver(taskSchema),
});
const handleRemoveTag = (index: any) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
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 handleRemoveImage = (index: number) => {
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
};
@ -141,15 +141,20 @@ export default function FormBlogUpdate() {
setDetail(details);
// Set categoryId dari API ke form dan Select
setValue("categoryName", details?.categoryName);
setSelectedTarget(details?.categoryId); // Untuk dropdown
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
}
setValue("categoryId", details?.categoryName);
setSelectedTarget(details?.categoryId);
}
}
initState();
}, [refresh, setValue]);
const save = async (data: TaskSchema) => {
loading();
const finalTags = tags.join(", ");
const requestData = {
...data,
id: detail?.id,
@ -158,7 +163,7 @@ export default function FormBlogUpdate() {
categoryId: selectedTarget,
slug: data.slug,
metadata: data.metadata,
tags: "siap",
tags: finalTags,
isDraft,
};
@ -166,6 +171,30 @@ export default function FormBlogUpdate() {
console.log("Form Data Submitted:", requestData);
console.log("response", response);
if (response?.error) {
MySwal.fire("Error", response?.message, "error");
return;
}
const blogId = response?.data?.data.id;
if (blogId) {
if (thumbnail) {
const formMedia = new FormData();
formMedia.append("file", thumbnail); // Tambahkan file ke FormData
console.log("FormMedia:", formMedia.get("file"));
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
if (responseThumbnail?.error) {
MySwal.fire("Error", responseThumbnail?.message, "error");
return;
}
} else {
console.log("No thumbnail to upload");
}
}
close();
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
@ -193,6 +222,17 @@ export default function FormBlogUpdate() {
});
};
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setThumbnail(file); // Simpan file ke state
setPreview(URL.createObjectURL(file)); // Buat URL untuk preview
console.log("Selected Thumbnail:", file);
} else {
console.log("No file selected");
}
};
const handlePublish = () => {
setIsDraft(false);
};
@ -201,6 +241,29 @@ export default function FormBlogUpdate() {
setIsDraft(false);
};
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Tambahkan tag baru
if (inputRef.current) {
inputRef.current.value = ""; // Kosongkan input
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
@ -238,11 +301,9 @@ export default function FormBlogUpdate() {
control={control}
name="narration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail.narration}
<CustomEditor
onChange={onChange}
className="dark:text-black"
initialData={detail?.narration || value}
/>
)}
/>
@ -302,31 +363,39 @@ export default function FormBlogUpdate() {
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[600px]">
<Card className=" h-[700px]">
<div className="px-3 py-3">
<Label htmlFor="fileInput">Gambar Utama</Label>
<Input
<input
id="fileInput"
type="file"
// onChange={(e) => {
// const file = e.target.files[0];
// if (file) {
// console.log("Selected File:", file);
// // Tambahkan logika jika diperlukan, misalnya upload file ke server
// }
// }}
className=""
onChange={handleImageChange}
/>
</div>
<div className="mt-3 px-3">
<Label>Pratinjau Gambar Utama</Label>
<Card className="mt-2">
<img
src={detail.thumbnailLink}
alt="Thumbnail Gambar Utama"
className="w-full h-auto rounded"
/>
</Card>
{preview ? (
// Menampilkan pratinjau gambar yang baru dipilih
<div className="mt-3 px-3">
<img
src={preview}
alt="Thumbnail Preview"
className="w-full h-auto rounded"
/>
</div>
) : (
// Menampilkan gambar dari `detail.thumbnailLink` jika tidak ada file yang dipilih
detail?.thumbnailLink && (
<div className="mt-3 px-3">
<Label>Pratinjau Gambar Utama</Label>
<Card className="mt-2">
<img
src={detail.thumbnailLink}
alt="Thumbnail Gambar Utama"
className="w-full h-auto rounded"
/>
</Card>
</div>
)
)}
</div>
<div className="px-3 py-3 mt-6">
<label
@ -336,7 +405,7 @@ export default function FormBlogUpdate() {
Kategori
</label>
<Controller
name="categoryName"
name="categoryId"
control={control}
render={({ field }) => (
<Select
@ -350,12 +419,9 @@ export default function FormBlogUpdate() {
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category.id}
value={category.categoryName}
>
{category.id}
{categories.map((category: any) => (
<SelectItem key={category.id} value={category.id}>
{category.categoryName}
</SelectItem>
))}
</SelectContent>
@ -366,14 +432,34 @@ export default function FormBlogUpdate() {
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Tag</Label>
<div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index}
className="border rounded-md px-2 py-2"
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
>
{tag.trim()}
</Badge>
<input
type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
className="bg-black text-white border-none focus:outline-none w-auto"
/>
<button
value={tag}
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))}
</div>
</div>

View File

@ -29,6 +29,9 @@ import {
listEnableCategory,
} from "@/service/content/content";
import { postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
import dynamic from "next/dynamic";
import { error } from "console";
import { loading } from "@/lib/swal";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -37,7 +40,6 @@ const taskSchema = z.object({
narration: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
@ -64,6 +66,13 @@ const initialCategories: Category[] = [
},
];
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormBlog() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -82,6 +91,7 @@ export default function FormBlog() {
const [isDraft, setIsDraft] = useState(false);
const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
@ -156,6 +166,8 @@ export default function FormBlog() {
// };
const save = async (data: TaskSchema) => {
loading();
const finalTags = tags.join(", ");
const requestData = {
...data,
title: data.title,
@ -163,7 +175,7 @@ export default function FormBlog() {
categoryId: selectedTarget,
slug: data.slug,
metadata: data.meta,
tags: data.tags,
tags: finalTags,
isDraft,
};
@ -177,19 +189,33 @@ export default function FormBlog() {
}
const blogId = response?.data?.data.id;
if (blogId && thumbnail) {
const formMedia = new FormData();
formMedia.append("file", thumbnail);
if (blogId) {
if (thumbnail) {
const formMedia = new FormData();
formMedia.append("file", thumbnail); // Tambahkan file ke FormData
console.log("FormMedia:", formMedia.get("file"));
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
const responseThumbnail = await uploadThumbnailBlog(blogId, formMedia);
if (responseThumbnail?.error) {
MySwal.fire("Error", responseThumbnail?.message, "error");
return;
if (responseThumbnail?.error) {
MySwal.fire("Error", responseThumbnail?.message, "error");
return;
}
} else {
console.log("No thumbnail to upload");
}
}
close();
MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/en/contributor/blog");
});
};
const onSubmit = (data: TaskSchema) => {
@ -198,8 +224,9 @@ export default function FormBlog() {
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
cancelButtonText: "Batal",
}).then((result) => {
if (result.isConfirmed) {
save(data);
@ -210,11 +237,11 @@ export default function FormBlog() {
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setThumbnail(file);
setThumbnail(file); // Simpan file ke state
setPreview(URL.createObjectURL(file)); // Buat URL untuk preview
console.log("Selected Thumbnail:", file);
}
if (file) {
setPreview(URL.createObjectURL(file));
} else {
console.log("No file selected");
}
};
@ -226,6 +253,19 @@ export default function FormBlog() {
setIsDraft(true);
};
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]); // Add new tag
if (inputRef.current) {
inputRef.current.value = ""; // Clear input field
}
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex lg:flex-row gap-10">
@ -260,12 +300,7 @@ export default function FormBlog() {
control={control}
name="narration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
<CustomEditor onChange={onChange} initialData={value} />
)}
/>
{errors.narration?.message && (
@ -324,10 +359,10 @@ export default function FormBlog() {
</div>
</Card>
<div className="w-4/12">
<Card className=" h-[550px]">
<Card className=" h-[600px]">
<div className="px-3 py-3">
<Label htmlFor="fileInput">Gambar Utama</Label>
<Input id="fileInput" type="file" onChange={handleImageChange} />
<label htmlFor="fileInput">Gambar Utama</label>
<input id="fileInput" type="file" onChange={handleImageChange} />
</div>
{preview && (
<div className="mt-3 px-3">
@ -366,19 +401,30 @@ export default function FormBlog() {
</div>
<div className="px-3 py-3">
<Label>Tags</Label>
<Controller
control={control}
name="tags"
render={({ field }) => (
<Input
size="md"
type="text"
value={field.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 ">
{tags.map((tag, index) => (
<span
key={index}
className=" px-1 py-1 rounded-lg bg-black text-white mr-2 text-sm font-sans"
>
{tag}{" "}
<button
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button"
>
×
</button>
</span>
))}
</div>
{/* <div className="text-sm text-red-500">
{tags.length === 0 && "Please add at least one tag."}
</div>

View File

@ -20,6 +20,7 @@ import {
} from "@/components/ui/select";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import {
getEscalationDiscussion,
getTicketingDetail,
getTicketingInternalDetail,
getTicketingInternalDiscussion,
@ -28,6 +29,8 @@ import {
import { Textarea } from "@/components/ui/textarea";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Link } from "@/i18n/routing";
import { loading } from "@/lib/swal";
import { id } from "date-fns/locale";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -57,9 +60,24 @@ export default function FormDetailEscalation() {
const [detail, setDetail] = useState<any>();
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
const [replyVisible, setReplyVisible] = useState(false);
const [replyMessage, setReplyMessage] = useState("");
const [listDiscussion, setListDiscussion] = useState();
const [message, setMessage] = useState("");
const [selectedPriority, setSelectedPriority] = useState("");
const [selectedStatus, setSelectedStatus] = useState("");
const [replyMessage, setReplyMessage] = useState("");
const [replies, setReplies] = useState([
{
id: 1,
name: "Mabes Polri - Approver",
message: "test",
timestamp: "2024-12-20 00:56:10",
},
{
id: 2,
name: "Mabes Polri - Approver",
message: "balas",
timestamp: "2025-01-18 17:42:48",
},
]);
const {
control,
@ -90,53 +108,32 @@ export default function FormDetailEscalation() {
const handleReply = () => {
setReplyVisible((prev) => !prev); // Toggle visibility
};
// useEffect(() => {
// async function initState() {
// if (id != undefined) {
// loading();
// const responseGet = await getEscalationDiscussion(id);
// close();
const handleSendReply = async () => {
if (replyMessage.trim() === "") {
MySwal.fire({
title: "Error",
text: "Pesan tidak boleh kosong!",
icon: "error",
});
return;
}
// console.log("escal data", responseGet?.data);
// setListDiscussion(responseGet?.data?.data);
// }
// }
// initState();
// }, [id]);
const data = {
ticketId: id,
const handleSendReply = () => {
if (replyMessage.trim() === "") return;
const newReply = {
id: replies.length + 1,
name: "Mabes Polri - Approver", // Sesuaikan dengan data dinamis jika ada
message: replyMessage,
timestamp: new Date().toISOString().slice(0, 19).replace("T", " "),
};
try {
const response = await saveTicketInternalReply(data);
// Tambahkan balasan baru ke daftar balasan
const newReply: replyDetail = {
id: response?.data?.id,
message: replyMessage,
createdAt: response?.data?.createdAt,
messageFrom: response?.data?.messageFrom,
messageTo: response?.data?.messageTo,
};
setTicketReply((prevReplies) => [newReply, ...prevReplies]);
MySwal.fire({
title: "Sukses",
text: "Pesan berhasil dikirim.",
icon: "success",
});
// Reset input dan sembunyikan form balasan
setReplyMessage("");
setReplyVisible(false);
} catch (error) {
MySwal.fire({
title: "Error",
text: "Gagal mengirim balasan.",
icon: "error",
});
console.error("Error sending reply:", error);
}
setReplies([...replies, newReply]);
setReplyMessage("");
};
return (
@ -244,26 +241,46 @@ export default function FormDetailEscalation() {
</p>
)} */}
</div>
<div className="mt-4 px-3 mb-3">
<Label htmlFor="replyMessage">Tulis Pesan</Label>
<div className="mx-3 my-3">
<h3 className="text-gray-700 font-medium">Tanggapan</h3>
<div className="space-y-4">
{replies.map((reply) => (
<div key={reply.id} className="border-b pb-2">
<p className="font-semibold text-gray-800">{reply.name}</p>
<p className="text-gray-600">{reply.message}</p>
<p className="text-sm text-gray-400">{reply.timestamp}</p>
</div>
))}
</div>
</div>
<div className="mx-3">
<label
htmlFor="replyMessage"
className="block text-gray-700 font-medium mb-2"
>
Tulis Tanggapan Anda
</label>
<textarea
id="replyMessage"
className="w-full h-24 border rounded-md p-2"
className="w-full h-24 border border-gray-300 rounded-md p-2"
value={replyMessage}
onChange={(e) => setReplyMessage(e.target.value)}
placeholder="Tulis pesan di sini..."
placeholder="Tulis tanggapan anda di sini..."
/>
<div className="flex justify-end gap-3 mt-2">
<Button
onClick={() => setReplyVisible(false)}
color="default"
variant="outline"
<div className="flex justify-end gap-3 mt-2 mb-3">
<button
onClick={() => setReplyMessage("")}
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-md"
>
Batal
</Button>
<Button onClick={handleSendReply} color="primary">
Kirim
</Button>
</button>
<button
onClick={handleSendReply}
className="px-4 py-2 bg-blue-600 text-white rounded-md"
>
Kirim Pesan
</button>
</div>
</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 { useParams, useRouter } from "next/navigation";
import { useParams } from "next/navigation";
import {
Select,
SelectContent,
@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
import { useRouter } from "@/i18n/routing";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -683,7 +684,7 @@ export default function FormVideoDetail() {
<Icon icon="humbleicons:times" color="red" />
</a>
</div>
{isUserMabesApprover &&
{isUserMabesApprover && (
<div className="flex flex-row gap-2">
<div className="flex items-center space-x-2">
<Checkbox
@ -760,7 +761,7 @@ export default function FormVideoDetail() {
</label>
</div>
</div>
}
)}
</div>
</div>
))

View File

@ -17,7 +17,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { CalendarIcon } from "lucide-react";
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { addDays, format, parseISO, setDate } from "date-fns";
import { DateRange } from "react-day-picker";
@ -28,7 +28,19 @@ import MapHome from "@/components/maps/MapHome";
import { Textarea } from "@/components/ui/textarea";
import { error, loading } from "@/lib/swal";
import Cookies from "js-cookie";
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
import {
detailSchedule,
listScheduleNext,
listScheduleToday,
postSchedule,
} from "@/service/schedule/schedule";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { formatDate } from "@fullcalendar/core/index.js";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -56,6 +68,8 @@ export default function FormEventDetail() {
const [startTime, setStartTime] = useState("08:00");
const [endTime, setEndTime] = useState("09:00");
const [date, setDate] = useState<DateRange | undefined>();
const [todayList, setTodayList] = useState([]);
const [nextDayList, setNextDayList] = useState([]);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
@ -72,6 +86,16 @@ export default function FormEventDetail() {
},
});
async function getDataByDate() {
const resToday = await listScheduleToday();
const today = resToday?.data?.data;
setTodayList(today);
const resNext = await listScheduleNext();
const next = resNext?.data?.data;
setNextDayList(next);
}
useEffect(() => {
async function initState() {
if (id) {
@ -89,6 +113,7 @@ export default function FormEventDetail() {
setStartTime(details.startTime);
setEndTime(details.endTime);
}
getDataByDate();
}
}
initState();
@ -315,7 +340,66 @@ export default function FormEventDetail() {
</div>
</Card>
<Card className="w-full lg:w-3/12">
<div className="px-3 py-3">Jadwal Selanjutnya</div>
<Accordion type="single" collapsible>
{/* Jadwal Hari Ini */}
<AccordionItem value="today">
<AccordionTrigger className="font-semibold">
Jadwal Hari Ini
</AccordionTrigger>
<AccordionContent>
{todayList?.length > 0 ? (
<ul className="list-disc ml-4">
{todayList.map((item: any, index) => (
<li key={index} className="py-1">
{item.name}
</li>
))}
</ul>
) : (
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
)}
</AccordionContent>
</AccordionItem>
{/* Jadwal Selanjutnya */}
<AccordionItem value="next">
<AccordionTrigger className="font-semibold">
Jadwal Selanjutnya
</AccordionTrigger>
<AccordionContent>
{nextDayList?.length > 0 ? (
<div className="list-disc ">
{nextDayList.map((item: any, index) => (
<div key={index} className="">
<li className="text-base font-semibold">{item.title}</li>
<p className="text-sm ml-5 flex my-2">
<CalendarIcon size={20} />
{formatDate(item?.startDate)}-
{formatDate(item?.endDate)}
</p>
<p className="text-sm ml-5 flex my-2">
<Clock1 size={20} />
{item?.startTime}-{item?.endTime}
</p>
<p className="text-sm ml-5 flex items-center my-2">
<MapPin size={50} />
{item?.address}
</p>
<p className="text-sm ml-5">Disampaikan oleh:</p>
<p className="text-sm ml-5 flex my-2">
<User2 />
{item?.speakerTitle} {item?.speakerName}
</p>
</div>
// <p>{item.startDate}</p>
))}
</div>
) : (
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</Card>
</div>
);

View File

@ -9,7 +9,6 @@ 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 {
Select,
SelectContent,
@ -40,6 +39,7 @@ import { Textarea } from "@/components/ui/textarea";
import { error } from "@/lib/swal";
import Cookies from "js-cookie";
import { postSchedule } from "@/service/schedule/schedule";
import { useRouter } from "@/i18n/routing";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),

View File

@ -17,7 +17,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { CalendarIcon } from "lucide-react";
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { addDays, format, parseISO, setDate } from "date-fns";
import { DateRange } from "react-day-picker";
@ -28,7 +28,12 @@ import MapHome from "@/components/maps/MapHome";
import { Textarea } from "@/components/ui/textarea";
import { error, loading } from "@/lib/swal";
import Cookies from "js-cookie";
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
import {
detailSchedule,
listScheduleNext,
listScheduleToday,
postSchedule,
} from "@/service/schedule/schedule";
import {
Select,
SelectContent,
@ -36,6 +41,13 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { formatDate } from "@fullcalendar/core/index.js";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -64,6 +76,8 @@ export default function FormDetailPressRillis() {
const [endTime, setEndTime] = useState("09:00");
const [date, setDate] = useState<DateRange | undefined>();
const [selectedTarget, setSelectedTarget] = useState("all");
const [todayList, setTodayList] = useState([]);
const [nextDayList, setNextDayList] = useState([]);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
@ -80,6 +94,16 @@ export default function FormDetailPressRillis() {
},
});
async function getDataByDate() {
const resToday = await listScheduleToday();
const today = resToday?.data?.data;
setTodayList(today);
const resNext = await listScheduleNext();
const next = resNext?.data?.data;
setNextDayList(next);
}
useEffect(() => {
async function initState() {
if (id) {
@ -97,6 +121,7 @@ export default function FormDetailPressRillis() {
setStartTime(details.startTime);
setEndTime(details.endTime);
}
getDataByDate();
}
}
initState();
@ -340,7 +365,66 @@ export default function FormDetailPressRillis() {
</div>
</Card>
<Card className="w-full lg:w-3/12">
<div className="px-3 py-3">Jadwal Selanjutnya</div>
<Accordion type="single" collapsible>
{/* Jadwal Hari Ini */}
<AccordionItem value="today">
<AccordionTrigger className="font-semibold">
Jadwal Hari Ini
</AccordionTrigger>
<AccordionContent>
{todayList?.length > 0 ? (
<ul className="list-disc ml-4">
{todayList.map((item: any, index) => (
<li key={index} className="py-1">
{item.name}
</li>
))}
</ul>
) : (
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
)}
</AccordionContent>
</AccordionItem>
{/* Jadwal Selanjutnya */}
<AccordionItem value="next">
<AccordionTrigger className="font-semibold">
Jadwal Selanjutnya
</AccordionTrigger>
<AccordionContent>
{nextDayList?.length > 0 ? (
<div className="list-disc ">
{nextDayList.map((item: any, index) => (
<div key={index} className="">
<li className="text-base font-semibold">{item.title}</li>
<p className="text-sm ml-5 flex my-2">
<CalendarIcon size={20} />
{formatDate(item?.startDate)}-
{formatDate(item?.endDate)}
</p>
<p className="text-sm ml-5 flex my-2">
<Clock1 size={20} />
{item?.startTime}-{item?.endTime}
</p>
<p className="text-sm ml-5 flex items-center my-2">
<MapPin size={50} />
{item?.address}
</p>
<p className="text-sm ml-5">Disampaikan oleh:</p>
<p className="text-sm ml-5 flex my-2">
<User2 />
{item?.speakerTitle} {item?.speakerName}
</p>
</div>
// <p>{item.startDate}</p>
))}
</div>
) : (
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</Card>
</div>
);

View File

@ -17,7 +17,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { CalendarIcon } from "lucide-react";
import { CalendarIcon, Clock1, Locate, MapPin, User2 } from "lucide-react";
import { Calendar } from "@/components/ui/calendar";
import { addDays, format, parseISO, setDate } from "date-fns";
import { DateRange } from "react-day-picker";
@ -28,7 +28,20 @@ import MapHome from "@/components/maps/MapHome";
import { Textarea } from "@/components/ui/textarea";
import { error, loading } from "@/lib/swal";
import Cookies from "js-cookie";
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
import {
detailSchedule,
listScheduleNext,
listScheduleToday,
postSchedule,
} from "@/service/schedule/schedule";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { formatDateToIndonesian } from "@/utils/globals";
import { formatDate } from "@fullcalendar/core/index.js";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -56,6 +69,8 @@ export default function FormDetailPressConference() {
const [startTime, setStartTime] = useState("08:00");
const [endTime, setEndTime] = useState("09:00");
const [date, setDate] = useState<DateRange | undefined>();
const [todayList, setTodayList] = useState([]);
const [nextDayList, setNextDayList] = useState([]);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
@ -72,6 +87,16 @@ export default function FormDetailPressConference() {
},
});
async function getDataByDate() {
const resToday = await listScheduleToday();
const today = resToday?.data?.data;
setTodayList(today);
const resNext = await listScheduleNext();
const next = resNext?.data?.data;
setNextDayList(next);
}
useEffect(() => {
async function initState() {
if (id) {
@ -89,6 +114,7 @@ export default function FormDetailPressConference() {
setStartTime(details.startTime);
setEndTime(details.endTime);
}
getDataByDate();
}
}
initState();
@ -315,7 +341,66 @@ export default function FormDetailPressConference() {
</div>
</Card>
<Card className="w-full lg:w-3/12">
<div className="px-3 py-3">Jadwal Selanjutnya</div>
<Accordion type="single" collapsible>
{/* Jadwal Hari Ini */}
<AccordionItem value="today">
<AccordionTrigger className="font-semibold">
Jadwal Hari Ini
</AccordionTrigger>
<AccordionContent>
{todayList?.length > 0 ? (
<ul className="list-disc ml-4">
{todayList.map((item: any, index) => (
<li key={index} className="py-1">
{item.name}
</li>
))}
</ul>
) : (
<p className="text-gray-500">Tidak ada jadwal hari ini</p>
)}
</AccordionContent>
</AccordionItem>
{/* Jadwal Selanjutnya */}
<AccordionItem value="next">
<AccordionTrigger className="font-semibold">
Jadwal Selanjutnya
</AccordionTrigger>
<AccordionContent>
{nextDayList?.length > 0 ? (
<div className="list-disc ">
{nextDayList.map((item: any, index) => (
<div key={index} className="">
<li className="text-base font-semibold">{item.title}</li>
<p className="text-sm ml-5 flex my-2">
<CalendarIcon size={20} />
{formatDate(item?.startDate)}-
{formatDate(item?.endDate)}
</p>
<p className="text-sm ml-5 flex my-2">
<Clock1 size={20} />
{item?.startTime}-{item?.endTime}
</p>
<p className="text-sm ml-5 flex items-center my-2">
<MapPin size={50} />
{item?.address}
</p>
<p className="text-sm ml-5">Disampaikan oleh:</p>
<p className="text-sm ml-5 flex my-2">
<User2 />
{item?.speakerTitle} {item?.speakerName}
</p>
</div>
// <p>{item.startDate}</p>
))}
</div>
) : (
<p className="text-gray-500">Tidak ada jadwal selanjutnya</p>
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</Card>
</div>
);

View File

@ -9,7 +9,6 @@ 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 { Switch } from "@/components/ui/switch";
import {
Popover,
@ -30,6 +29,7 @@ import { Textarea } from "@/components/ui/textarea";
import { error } from "@/lib/swal";
import Cookies from "js-cookie";
import { postSchedule } from "@/service/schedule/schedule";
import { useRouter } from "@/i18n/routing";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),

View File

@ -267,6 +267,7 @@ export default function FormTaskDetail() {
allUnit: false,
mabes: false,
polda: false,
satker: false,
polres: false,
});
@ -389,6 +390,7 @@ export default function FormTaskDetail() {
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
satker: outputSet.has(4),
});
}
}, [detail?.fileTypeOutput]);

View File

@ -147,6 +147,7 @@ export default function FormTaskEdit() {
mabes: false,
polda: false,
polres: false,
satker: false,
});
const [links, setLinks] = useState<string[]>([""]);
@ -265,6 +266,7 @@ export default function FormTaskEdit() {
mabes: outputSet.has(1),
polda: outputSet.has(2),
polres: outputSet.has(3),
satker: outputSet.has(4),
});
}
}, [detail?.fileTypeOutput]);
@ -299,6 +301,7 @@ export default function FormTaskEdit() {
mabes: "1",
polda: "2",
polres: "3",
satker: "4",
};
const assignmentPurposeString = Object.keys(unitSelection)
.filter((key) => unitSelection[key as keyof typeof unitSelection])

View File

@ -123,6 +123,7 @@ export default function FormTask() {
mabes: false,
polda: false,
polres: false,
satker: false,
});
const [links, setLinks] = useState<string[]>([""]);
@ -149,6 +150,7 @@ export default function FormTask() {
try {
const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list);
console.log("polda", response?.data?.data?.list);
const initialExpandedState = response?.data?.data.list.reduce(
(acc: any, polda: any) => {
acc[polda.id] = false;
@ -198,6 +200,7 @@ export default function FormTask() {
mabes: "1",
polda: "2",
polres: "3",
satker: "4",
};
const assignmentPurposeString = Object.keys(unitSelection)
.filter((key) => unitSelection[key as keyof typeof unitSelection])

View File

@ -1,4 +1,8 @@
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service";
import {
httpDeleteInterceptor,
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
export async function paginationBlog(
size: number,
@ -22,5 +26,13 @@ export async function getBlog(id: any) {
export async function uploadThumbnailBlog(id: any, data: any) {
const url = `blog/${id}/thumbnail`;
return httpPostInterceptor(url, data);
const headers = {
"Content-Type": "multipart/form-data",
};
return httpPostInterceptor(url, data, headers);
}
export async function deleteBlog(id: any) {
const url = `blog?id=${id}`;
return httpDeleteInterceptor(url);
}

View File

@ -115,3 +115,8 @@ export async function deleteQuestion(id: string | number) {
const url = `/question?id=${id}`;
return httpDeleteInterceptor(url);
}
export async function getEscalationDiscussion(id: any) {
const url = `ticketing/escalation/discussion?ticketId=${id}`;
return httpGetInterceptor(url);
}

View File

@ -1,4 +1,7 @@
import { httpGetInterceptor, httpPostInterceptor } from "../http-config/http-interceptor-service";
import {
httpGetInterceptor,
httpPostInterceptor,
} from "../http-config/http-interceptor-service";
import { httpGet } from "../http-config/http-base-service";
export async function paginationSchedule(
@ -24,21 +27,30 @@ export async function detailSchedule(id: any) {
export async function listScheduleTodayPublic(group = null) {
const url = `public/schedule/today?group=${group}`;
return httpGet( url, null );
return httpGet(url, null);
}
export async function listScheduleNextPublic(group = null) {
const url = `public/schedule/next-activity?group=${group}`;
return httpGet( url, null );
return httpGet(url, null);
}
export async function listSchedulePrevPublic(group = null) {
const url = `public/schedule/prev-activity?group=${group}`;
return httpGet( url, null );
return httpGet(url, null);
}
export async function listSchedule(group = null) {
const url = `public/schedule/list?group=${group}`;
return httpGet( url, null );
return httpGet(url, null);
}
export async function listScheduleToday() {
const url = "schedule/today";
return httpGetInterceptor(url);
}
export async function listScheduleNext() {
const url = "schedule/next-activity";
return httpGetInterceptor(url);
}