kontenhumas-fe/components/form/communication/questions-reply-form.tsx

671 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 { Icon } from "@iconify/react/dist/iconify.js";
import { list, parse } from "postcss";
import { htmlToString } from "@/utils/globals";
import { Textarea } from "@/components/ui/textarea";
import { error } from "@/lib/swal";
import { useMediaQuery } from "react-responsive";
import { DetailTicket } from "../ticketing/info-lainnya-types";
import InfoLainnyaModal from "../ticketing/info-lainnya";
import { Description } from "@radix-ui/react-toast";
import { Link, useRouter } from "@/i18n/routing";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ChevronDownIcon } from "lucide-react";
import { deleteTicket, getQuestionTicket, getTicketingReply, saveTicketReply, saveTicketsQuestion } from "@/service/service/communication/communication";
import { getOperatorUser } from "@/service/service/management-user/management-user";
const taskSchema = z.object({
title: z.string().optional(),
description: z.string().optional(),
});
export type taskDetail = {
id: number;
title: string;
createdAt: string;
referenceNumber: string | number;
createdBy: {
id: number;
fullname: string;
};
sendTo: {
id: number;
fullname: string;
};
status: {
id: number;
name: string;
};
priority: {
id: number;
name: string;
};
broadcastType: string;
description: string;
is_active: string;
};
export type replyDetail = {
id: number;
comments: string;
createdAt: string;
user: {
id: number;
fullname: string;
};
messageTo: {
id: number;
fullname: string;
};
};
export type internalDetail = {
id: number;
message: string;
createdAt: string;
commentFromUserName: string;
feedTitle: string;
createdBy: {
id: number;
fullname: string;
};
sendTo: {
id: number;
fullname: string;
};
};
export default function FormQuestionsReply() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
const router = useRouter();
const isMobile = useMediaQuery({
maxWidth: 768,
});
const [detail, setDetail] = useState<taskDetail>();
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
const [ticketInternal, setTicketInternal] = useState<internalDetail | null>(
null
);
const [detailTicketsQuestions, setDetailTicketsQuestions] =
useState<internalDetail | null>(null);
const [detailTickets, setDetailTickets] = useState<DetailTicket | null>(null);
const [replyVisible, setReplyVisible] = useState(false);
const [replyMessage, setReplyMessage] = useState("");
const [selectedPriority, setSelectedPriority] = useState("");
const [selectedStatus, setSelectedStatus] = useState("");
const [openEmergencyModal, setOpenEmergencyModal] = useState(false);
const [replyValue, setReplyValue] = useState<number>(0);
const [replyText, setReplyText] = useState<string>("");
const [operatorOpt, setOperatorOpt] = useState<
{ id: string; label: string; value: string }[]
>([]);
const [selectedOperator, setSelectedOperator] = useState<string[]>([]);
const {
control,
handleSubmit,
reset,
setValue,
formState: { errors },
} = useForm({
resolver: zodResolver(taskSchema),
});
useEffect(() => {
async function initState() {
setReplyValue(0);
const response = await getQuestionTicket(id);
setDetailTicketsQuestions(response?.data?.data || null);
setTicketInternal(response?.data?.data || null);
setDetail(response?.data?.data);
setValue('title', response?.data?.data?.message)
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]);
const handleReply = () => {
setReplyValue((prev) => (prev === 1 ? 0 : 1));
};
const handleSendReplyData = async () => {
if (!replyText.trim()) {
console.warn("Balasan kosong!");
return;
}
try {
const res = await saveTicketReply({
ticketId: id,
comment: replyText,
parentCommentId: detailTickets?.commentId,
isFromInternal: true,
});
console.log("Berhasil kirim balasan:", res?.data);
setReplyText("");
setReplyValue(0);
getTicketReply();
} catch (err) {
console.error("Gagal kirim balasan:", err);
}
};
async function getTicketReply() {
const res = await getTicketingReply(id);
if (res?.data !== null) {
setTicketReply(res?.data?.data);
}
}
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,
}));
setOperatorOpt(optionArr);
}
}
getOperator();
}, [detailTickets]);
const handleSendReply = async () => {
if (replyMessage.trim() === "") {
MySwal.fire({
title: "Error",
text: "Pesan tidak boleh kosong!",
icon: "error",
});
return;
}
const data = {
ticketId: id,
comment: replyMessage,
};
try {
const response = await saveTicketReply(data);
const newReply: replyDetail = {
id: response?.data?.id,
comments: replyMessage,
createdAt: response?.data?.createdAt,
user: response?.data?.messageFrom,
messageTo: response?.data?.messageTo,
};
setTicketReply((prevReplies) => [newReply, ...prevReplies]);
MySwal.fire({
title: "Sukses",
text: "Pesan berhasil dikirim.",
icon: "success",
});
setReplyMessage("");
setReplyVisible(false);
} catch (error) {
MySwal.fire({
title: "Error",
text: "Gagal mengirim balasan.",
icon: "error",
});
console.error("Error sending reply:", error);
}
};
// const onSubmit = async (data: any) => {
// try {
// const payload = {
// id,
// title: data.title,
// description: data.description,
// priorityId: selectedPriority,
// statusId: 1,
// typeId: detailTickets?.typeId,
// parentCommentId: detailTickets?.feedId,
// operatorTeam: selectedOperator.join(","),
// };
// const response = await saveTicketsQuestion(payload);
// MySwal.fire({
// title: "Sukses",
// text: "Data berhasil diperbarui.",
// icon: "success",
// });
// getTicketReply();
// } catch (error) {
// console.error("Gagal update:", error);
// MySwal.fire({
// title: "Error",
// text: "Terjadi kesalahan saat memperbarui.",
// icon: "error",
// });
// }
// };
const onSubmit = async (data: any) => {
try {
MySwal.fire({
title: "Menyimpan...",
text: "Mohon tunggu sebentar",
allowOutsideClick: false,
didOpen: () => {
MySwal.showLoading();
},
});
const payload = {
id,
title: data.title,
description: data.description,
priorityId: selectedPriority,
statusId: 1,
typeId: detailTickets?.typeId,
parentCommentId: detailTickets?.feedId,
operatorTeam: selectedOperator.join(","),
};
await saveTicketsQuestion(payload);
MySwal.fire({
title: "Sukses",
text: "Data berhasil diperbarui.",
icon: "success",
confirmButtonText: "OK",
}).then(() => {
router.push("/supervisor/ticketing");
});
} catch (error) {
MySwal.fire({
title: "Error",
text: "Terjadi kesalahan saat memperbarui.",
icon: "error",
});
}
};
async function doDelete(id: any) {
const response = await deleteTicket(id);
if (response?.error) {
error(response.message);
return false;
}
success("/in/supervisor/ticketing");
}
function success(redirect: string) {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
}
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
const openEmergencyIssueDetail = () => {
setOpenEmergencyModal(true);
};
return (
<div className="py-5">
<div className="mt-4 flex flex-row items-center gap-3">
<div className="mt-4 flex flex-row items-center gap-3">
<Link href={`/supervisor/communications/escalation/forward/${id}`}>
<Button color="default" variant={"outline"}>
Eskalasi
</Button>
</Link>
</div>
</div>
<div className="flex flex-col md:flex-row lg:flex-row gap-5 mt-5">
<div className="flex flex-col w-[100%] lg:w-[70%]">
{replyVisible && (
<div className="">
<textarea
id="replyMessage"
className="w-full h-24 border rounded-md p-2"
value={replyMessage}
onChange={(e) => setReplyMessage(e.target.value)}
placeholder="Tulis pesan di sini..."
/>
<div className="flex justify-end gap-3 my-2">
<Button
onClick={() => setReplyVisible(false)}
color="default"
variant="outline"
>
Batal
</Button>
<Button onClick={handleSendReply} color="primary">
Kirim
</Button>
</div>
</div>
)}
<div className="border rounded-t-xl">
<p className="p-4 bg-slate-300 rounded-t-xl text-lg font-semibold">
Ticket #{detail?.referenceNumber}
</p>
{ticketReply?.map((list) => (
<div key={list.id} className="flex flex-col mb-4">
{isMobile ? (
<div className="flex gap-3 bg-sky-100 p-3 items-center">
<Icon icon="qlementine-icons:user-16" width={36} />
<div>
<p className="text-sm">
<span className="font-bold">
{list?.user?.fullname}
</span>{" "}
mengirimkan balasan{" "}
<span className="font-bold">
{list?.messageTo?.fullname}
</span>
</p>
<p className="text-xs">
{new Date(list.createdAt).toLocaleString("id-ID", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</p>
</div>
</div>
) : (
<div className="flex gap-4 bg-sky-100 p-4 items-center">
<div className="text-center">
<Icon icon="qlementine-icons:user-16" width={50} />
</div>
<div>
<p className="text-sm">
<span className="font-bold">
{list?.user?.fullname}
</span>{" "}
mengirimkan balasan{" "}
<span className="font-bold">
{list?.messageTo?.fullname}
</span>
</p>
<p className="text-xs">
{new Date(list.createdAt).toLocaleString("id-ID", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
})}
</p>
</div>
</div>
)}
<div className="bg-white text-sm p-4">{list.comments}</div>
</div>
))}
{detailTicketsQuestions && (
<div key={detailTicketsQuestions.id} className="flex flex-col">
<div className="flex flex-row gap-3 bg-sky-100 p-4 items-center">
<Icon icon="qlementine-icons:user-16" width={36} />
<div>
<p>
<span className="font-bold text-sm">
{detailTicketsQuestions?.commentFromUserName}
</span>{" "}
mengirimkan komentar untuk{" "}
<span className="font-bold text-sm">
{detailTicketsQuestions?.feedTitle}
</span>
</p>
<p className="text-xs">
{`${new Date(
detailTicketsQuestions?.createdAt
).getDate()}-${
new Date(detailTicketsQuestions?.createdAt).getMonth() +
1
}-${new Date(
detailTicketsQuestions?.createdAt
).getFullYear()} ${new Date(
detailTicketsQuestions?.createdAt
).getHours()}:${new Date(
detailTicketsQuestions?.createdAt
).getMinutes()}`}
</p>
</div>
</div>
<div className="p-4 bg-white text-sm">
<p>{htmlToString(detailTicketsQuestions.message)}</p>
{detailTickets?.typeId === 6 &&
detailTickets?.emergencyIssue ? (
<div className="row mx-0 mb-3 emergency-attachments">
<div className="mt-3 mr-4">
<Button
color="primary"
size="md"
onClick={openEmergencyIssueDetail}
>
Info Lainnya
</Button>
</div>
</div>
) : null}
{detailTickets?.emergencyIssue && (
<InfoLainnyaModal
open={openEmergencyModal}
onClose={() => setOpenEmergencyModal(false)}
data={detailTickets.emergencyIssue}
/>
)}
</div>
</div>
)}
</div>
</div>
{detailTickets && (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5 w-[100%] lg:w-auto border bg-white rounded-md">
<p className="mx-3 mt-3">Properties</p>
<div className="space-y-2 px-3">
<Label>Judul</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
// defaultValue={detailTickets?.message}
value={field.value}
// {...field}
onChange={field.onChange}
placeholder="Masukkan judul"
/>
)}
/>
</div>
<div className="mt-5 px-3">
<Label>Prioritas</Label>
<Select
onValueChange={setSelectedPriority}
value={selectedPriority}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih Prioritas" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Low</SelectItem>
<SelectItem value="2">Medium</SelectItem>
<SelectItem value="3">High</SelectItem>
</SelectContent>
</Select>
</div>
<div className="mt-5 px-3 mb-3">
<Label>Operator</Label>
{/* Tag yang ditampilkan secara kolom */}
{selectedOperator.length > 0 && (
<div className="flex flex-col 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
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 py-3">
<Label>Deskripsi</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea {...field} placeholder="Masukkan description" />
)}
/>
</div>
<div className="flex justify-end mt-3 mr-3 py-3">
<Button type="submit" color="primary">
Buat Tiket
</Button>
</div>
</div>
</form>
)}
</div>
</div>
);
}