mediahub-fe/components/form/communication/escalation-detail-new-form.tsx

636 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
"use client";
import React, { useEffect, 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 } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import {
getCuratorUser,
getEscalationDiscussion,
getQuestionTicket,
getTicketingDetail,
getTicketingInternalDetail,
getTicketingInternalDiscussion,
saveEscalationDiscussion,
saveTicketInternalReply,
saveTicketsQuestion,
} from "@/service/communication/communication";
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";
import { DetailTicket } from "../ticketing/info-lainnya-types";
import { Description } from "@radix-ui/react-toast";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ChevronDownIcon } from "lucide-react";
import { getOperatorUser } from "@/service/management-user/management-user";
import { getCookiesDecrypt } from "@/lib/utils";
import { htmlToString } from "@/utils/globals";
import InfoLainnyaModal from "../ticketing/info-lainnya";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
});
type DiscussionItem = {
id: string;
username: string;
message: string;
createdAt: string;
parentId?: string | null;
};
export type replyDetail = {
id: number;
message: string;
createdAt: string;
messageFrom: {
id: number;
fullname: string;
};
messageTo: {
id: number;
fullname: string;
};
};
export default function FormQuestionsDetail() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
const roleId = getCookiesDecrypt("urie");
const [detail, setDetail] = useState<any>();
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
const [replyVisible, setReplyVisible] = useState(false);
const [listDiscussion, setListDiscussion] = useState<DiscussionItem[]>([]);
const [message, setMessage] = useState<string>("");
const [replyMessage, setReplyMessage] = useState<Record<string, string>>({});
const [detailTickets, setDetailTickets] = useState<DetailTicket | null>(null);
const [selectedPriority, setSelectedPriority] = useState("");
const [selectedStatus, setSelectedStatus] = useState("");
const [operatorOpt, setOperatorOpt] = useState<
{ id: string; label: string; value: string }[]
>([]);
const [curatorOpt, setCuratorOpt] = useState<
{ id: string; label: string; value: string; badge: string }[]
>([]);
const [selectedOperator, setSelectedOperator] = useState<string[]>([]);
const [selectedOperatorEscalation, setSelectedOperatorEscalation] = useState<
string[]
>([]);
const {
control,
handleSubmit,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(taskSchema),
});
// useEffect(() => {
// async function initState() {
// const response = await getQuestionTicket(id);
// setDetail(response?.data?.data);
// if (response?.data !== null) {
// setDetailTickets(response?.data?.data);
// }
// if (detailTickets?.emergencyIssue) {
// reset({
// title: detailTickets.emergencyIssue.title || "",
// description: detailTickets.emergencyIssue.description || "",
// });
// // setSelectedPriority(String(detailTickets.emergencyIssue.urgencyId));
// // setSelectedStatus(String(detailTickets.statusId)); // jika ada
// }
// }
// initState();
// getTicketReply();
// }, [id, reset]);
useEffect(() => {
async function initState() {
const response = await getTicketingDetail(id);
const detail = response?.data?.data;
setDetail(detail);
setDetailTickets(detail);
if (detail?.escalationTeams) {
const teamIds = detail.escalationTeams
.split(":")
.filter((id: string) => id);
setSelectedOperatorEscalation(teamIds);
}
getTicketReply();
}
initState();
}, [id]);
useEffect(() => {
async function getOperator() {
const res = await getOperatorUser(detailTickets?.typeId);
if (res?.data !== null) {
const rawUser = res?.data?.data;
const optionArr = rawUser?.map((option: any) => ({
id: option.id,
label: option.fullName,
value: option.id.toString(), // pastikan string
}));
setOperatorOpt(optionArr);
// 👇 Parse `assignedTeams` ke dalam array string
if (detailTickets?.assignedTeams) {
const assigned = detailTickets.assignedTeams
.split(":")
.filter((id: string) => id); // hapus string kosong
setSelectedOperator(assigned);
}
}
}
getOperator();
}, [detailTickets]);
useEffect(() => {
async function getCurator() {
const res = await getCuratorUser();
const rawUser = res?.data?.data?.content ?? []; // ✅ langsung ambil dari content
const optionArr = rawUser?.map((option: any) => ({
id: option.id,
label: `${option.username} | ${option.fullname}`,
value: option.id.toString(),
badge: option.userLevel?.name ?? "", // optional jika ingin tampilkan badge/tingkatan
}));
setCuratorOpt(optionArr);
}
getCurator();
}, []);
async function getTicketReply() {
const res = await getTicketingInternalDiscussion(id);
if (res?.data !== null) {
setTicketReply(res?.data?.data);
}
}
const onSubmit = async (data: any) => {
try {
const payload = {
id,
title: data.title,
description: data.description,
priorityId: selectedPriority,
statusId: selectedStatus,
typeId: detailTickets?.typeId,
parentCommentId: detailTickets?.feedId,
};
const response = await saveTicketsQuestion(payload);
MySwal.fire({
title: "Sukses",
text: "Data berhasil diperbarui.",
icon: "success",
});
// Refresh data jika perlu
getTicketReply();
} catch (error) {
console.error("Gagal update:", error);
MySwal.fire({
title: "Error",
text: "Terjadi kesalahan saat memperbarui.",
icon: "error",
});
}
};
// 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", " "),
// };
// setReplies([...replies, newReply]);
// setReplyMessage("");
// };
useEffect(() => {
fetchDiscussions();
}, [id]);
const fetchDiscussions = async () => {
try {
const response = await getEscalationDiscussion(id);
setListDiscussion(response?.data?.data || []);
} catch (error) {
console.error("Gagal mengambil diskusi", error);
}
};
const sendDiscussionParent = async () => {
if (message?.trim().length === 0) return;
const data = {
ticketId: id,
message,
parentId: null,
};
try {
await saveEscalationDiscussion(data);
setMessage("");
const response = await getEscalationDiscussion(id);
setListDiscussion(response?.data?.data || []);
} catch (error) {
console.error("Gagal kirim tanggapan", error);
}
};
const sendDiscussionChild = async (parentId: string) => {
const inputMsg = replyMessage[parentId];
if (!inputMsg || inputMsg.trim().length === 0) return;
const data = {
ticketId: id,
message: inputMsg,
parentMessageId: parentId,
};
try {
await saveEscalationDiscussion(data);
const response = await getEscalationDiscussion(id);
setListDiscussion(response?.data?.data || []);
setReplyMessage((prev) => ({ ...prev, [parentId]: "" }));
} catch (error) {
console.error("Gagal kirim balasan", error);
}
};
return (
<div>
<div className="flex">
<div className="flex flex-col mt-6 w-full mb-3">
{detail !== undefined && (
<div key={detail?.id} className="bg-slate-300 rounded-md">
<p className="p-5 bg-slate-300 rounded-md text-lg font-semibold">
Ticket #{detail.id}
</p>
<div className="flex flex-row gap-3 bg-sky-100 p-5 items-center">
<Icon icon="qlementine-icons:user-16" width={36} />
<div>
<p>
<span className="font-bold">
{detail?.commentFromUserName}
</span>
{` `}
mengirimkan pesan untuk{` `}
<Link
href={
detail?.feed
? detail?.feed?.permalink_url == undefined
? detail?.feedUrl
: detail?.feed?.permalink_url
: ""
}
target="_blank"
className="font-bold"
>
{detail?.message}
</Link>
</p>
<p className="text-xs">
{`${new Date(detail?.createdAt).getDate()}-${
new Date(detail?.createdAt).getMonth() + 1
}-${new Date(detail?.createdAt).getFullYear()} ${new Date(
detail?.createdAt
).getHours()}:${new Date(detail?.createdAt).getMinutes()}`}
</p>
</div>
</div>
<p className="p-5 bg-white">{detail.message}</p>
</div>
)}
</div>
</div>
{detail !== undefined && (
<div className="gap-5 mb-5 w-full border mt-3 rounded-md bg-white">
<div className="space-y-2 px-3 mt-3">
<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 px-3">
<Label>Prioritas</Label>
<Select
onValueChange={setSelectedPriority}
value={detail?.priority?.name}
>
<SelectTrigger size="md" className="w-3/12">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Low">Low</SelectItem>
<SelectItem value="Medium">Medium</SelectItem>
<SelectItem value="High">High</SelectItem>
</SelectContent>
</Select>
</div>
{(roleId === "9" || roleId === "10") && (
<div className="mt-5 px-3 mb-3">
<Label>Operator</Label>
{/* Tag yang ditampilkan secara kolom */}
{selectedOperator.length > 0 && (
<div className="flex flex-row gap-2 mb-2">
{selectedOperator.map((id) => {
const label = operatorOpt.find(
(op: any) => op.value === id
)?.label;
return (
<div
key={id}
className="flex items-center justify-between bg-gray-200 px-3 py-1 rounded"
>
<span>{label}</span>
<button
type="button"
onClick={() =>
setSelectedOperator((prev) =>
prev.filter((val) => val !== id)
)
}
className="ml-2 text-gray-500 hover:text-red-600"
>
×
</button>
</div>
);
})}
</div>
)}
{/* Popover Checkbox Dropdown */}
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-full justify-between">
Pilih Operator
<ChevronDownIcon className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full max-h-60 overflow-auto">
<div className="flex flex-col gap-1">
{operatorOpt.map((op: any) => (
<label
key={op.id}
className="flex items-center space-x-2 cursor-pointer px-2 py-1 hover:bg-gray-100 rounded"
>
<input
disabled
type="checkbox"
checked={selectedOperator.includes(op.value)}
onChange={(e) => {
if (e.target.checked) {
setSelectedOperator((prev) => [
...prev,
op.value,
]);
} else {
setSelectedOperator((prev) =>
prev.filter((val) => val !== op.value)
);
}
}}
/>
<span>{op.label}</span>
</label>
))}
</div>
</PopoverContent>
</Popover>
</div>
)}
<div className="space-y-2 px-3 mt-3">
<Label>Description</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Textarea
value={detail?.description}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
</div>
{(roleId === "9" || roleId === "10") && (
<div className="mt-5 px-3 mb-3">
<Label>Eskalasi Untuk</Label>
{selectedOperatorEscalation.length > 0 && (
<div className="flex flex-row gap-2 mb-2">
{selectedOperatorEscalation.map((id) => {
const operator = curatorOpt.find(
(op: any) => op.value === id
);
if (!operator) return null;
return (
<div
key={id}
className="flex items-center justify-between bg-gray-200 px-3 py-1 rounded"
>
<div>
<div className="font-semibold">{operator.label}</div>
{operator.badge && (
<div className="text-xs text-gray-500">
{operator.badge}
</div>
)}
</div>
<button
type="button"
onClick={() =>
setSelectedOperatorEscalation((prev) =>
prev.filter((val) => val !== id)
)
}
className="ml-2 text-gray-500 hover:text-red-600"
>
×
</button>
</div>
);
})}
</div>
)}
</div>
)}
<div className="mx-3 my-3">
<h3 className="text-gray-700 font-medium">Tanggapan</h3>
{listDiscussion.map((item: any) => (
<div key={item.id} className="mb-4 border-b pb-2">
<p className="font-semibold">{item.messageFrom?.fullname}</p>
<p>{item.message}</p>
<small className="text-gray-500">{item.createdAt}</small>
<div className="text-sm mt-1">
<button
onClick={() =>
setReplyMessage((prev) => ({
...prev,
[item.id]:
prev[item.id] !== undefined
? ""
: `@${item.messageFrom?.fullname} `,
}))
}
className="text-blue-600"
>
Balas
</button>
</div>
{/* Form Balas */}
{replyMessage[item.id] !== undefined && (
<div className="mt-2">
<textarea
id={`input-comment-${item.id}`}
value={replyMessage[item.id]}
onChange={(e) =>
setReplyMessage((prev) => ({
...prev,
[item.id]: e.target.value,
}))
}
className="w-full h-20 border border-gray-300 rounded-md p-2"
placeholder={`@${item.messageFrom?.fullname}`}
/>
<div className="flex gap-3 mt-1">
<button
onClick={() => sendDiscussionChild(item.id)}
className="text-blue-600"
>
Kirim Pesan
</button>
<button
onClick={() =>
setReplyMessage((prev) => {
const newState = { ...prev };
delete newState[item.id];
return newState;
})
}
className="text-red-500"
>
Cancel
</button>
</div>
</div>
)}
{/* List Balasan */}
{item.children?.map((child: any) => (
<div key={child.id} className="ml-4 mt-2 border-l pl-4">
<p className="font-semibold">
{child.messageFrom?.fullname}
</p>
<p>{child.message}</p>
<small className="text-gray-500">{child.createdAt}</small>
</div>
))}
</div>
))}
{/* Form Tanggapan Baru */}
<div className="mt-4">
<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 border-gray-300 rounded-md p-2"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Tulis tanggapan anda di sini..."
/>
<div className="flex justify-end gap-3 mt-2 mb-3">
<button
onClick={() => setMessage("")}
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-md"
>
Batal
</button>
<button
onClick={sendDiscussionParent}
className="px-4 py-2 bg-blue-600 text-white rounded-md"
>
Kirim Pesan
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
}