kontenhumas-fe/components/form/ticketing/ticketing-update-form.tsx

582 lines
18 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 {
deleteTicket,
getTicketingDetail,
getTicketingInternalDetail,
getTicketingInternalDiscussion,
getTicketingReply,
saveTicketing,
saveTicketInternalReply,
saveTicketReply,
} from "@/service/communication/communication";
import { Icon } from "@iconify/react/dist/iconify.js";
import { list } from "postcss";
import { htmlToString } from "@/utils/globals";
import { Textarea } from "@/components/ui/textarea";
import { error } from "@/lib/swal";
import { useRouter } from "next/navigation";
import { getOperatorUser } from "@/service/management-user/management-user";
import { DetailTicket } from "./info-lainnya-types";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ChevronDownIcon } from "lucide-react";
import { Description } from "@radix-ui/react-toast";
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.",
}),
});
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;
createdBy: {
id: number;
fullname: string;
};
sendTo: {
id: number;
fullname: string;
};
};
export default function FormUpdateTicketing() {
const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string };
const router = useRouter();
const [detail, setDetail] = useState<taskDetail>();
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
const [ticketInternal, setTicketInternal] = useState<internalDetail | null>(
null
);
const [detailTickets, setDetailTickets] = useState<DetailTicket | null>(null);
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 [replyVisible, setReplyVisible] = useState(false);
const [replyMessage, setReplyMessage] = useState("");
const [selectedPriority, setSelectedPriority] = useState("");
const [selectedStatus, setSelectedStatus] = useState("");
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm({
resolver: zodResolver(taskSchema),
defaultValues: {
title: "",
description: "",
priority: "",
status: "",
},
});
useEffect(() => {
async function initState() {
setReplyValue(0);
const response = await getTicketingDetail(id);
setTicketInternal(response?.data?.data || null);
setDetail(response?.data?.data);
if (response?.data?.data) {
const detailData = response.data.data;
setValue("title", detailData.title || "");
setValue("description", detailData.description || "");
setSelectedPriority(detailData.priority?.name || "");
setSelectedStatus(detailData.status?.name || "");
}
if (response?.data !== null) {
setDetailTickets(response?.data?.data);
}
}
initState();
getTicketReply();
}, [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(),
}));
setOperatorOpt(optionArr);
if (detailTickets?.assignedTeams) {
const assigned = detailTickets.assignedTeams
.split(":")
.filter((id: string) => id);
setSelectedOperator(assigned);
}
}
}
getOperator();
}, [detailTickets]);
async function getTicketReply() {
const res = await getTicketingReply(id);
if (res?.data !== null) {
setTicketReply(res?.data?.data);
}
}
const handleReply = () => {
setReplyVisible((prev) => !prev);
};
const handleSendReply = async () => {
if (replyMessage.trim() === "") {
MySwal.fire({
title: "Error",
text: "Pesan tidak boleh kosong!",
icon: "error",
});
return;
}
const data = {
ticketId: id,
message: 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);
}
};
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);
}
});
};
async function save(data: any) {
const reqData: any = {
title: data.title,
description: data.description,
priority: data.priority,
status: data.status,
operatorTeam: selectedOperator.join(","),
};
if (id) {
reqData.id = id;
}
const response = await saveTicketing(reqData);
if (response?.error) {
MySwal.fire({
title: "Error",
text: response.message,
icon: "error",
});
return false;
}
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonText: "OK",
}).then(() => {
router.push(`/in/supervisor/ticketing`);
});
}
return (
<div className="py-5">
<div className="mt-4 flex flex-row items-center gap-3">
<Button onClick={handleReply} color="default" variant={"outline"}>
Balas
</Button>
{/* <Button onClick={handleDelete} color="default" variant={"outline"}>
Hapus
</Button> */}
</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">
<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">
{list?.user?.fullname}
</span>{" "}
mengirimkan pesan untuk{" "}
<span className="font-bold text-sm">
{list?.messageTo?.fullname}
</span>
</p>
<p className="text-xs">
{`${new Date(list?.createdAt).getDate()}-${
new Date(list?.createdAt).getMonth() + 1
}-${new Date(list?.createdAt).getFullYear()} ${new Date(
list?.createdAt
).getHours()}:${new Date(list?.createdAt).getMinutes()}`}
</p>
</div>
</div>
<p className="p-4 bg-white text-sm">{list.comments}</p>
</div>
))}
{ticketInternal && (
<div key={ticketInternal.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">
{ticketInternal?.createdBy?.fullname}
</span>{" "}
mengirimkan pesan untuk{" "}
<span className="font-bold text-sm">
{ticketInternal?.sendTo?.fullname}
</span>
</p>
<p className="text-xs">
{`${new Date(ticketInternal?.createdAt).getDate()}-${
new Date(ticketInternal?.createdAt).getMonth() + 1
}-${new Date(
ticketInternal?.createdAt
).getFullYear()} ${new Date(
ticketInternal?.createdAt
).getHours()}:${new Date(
ticketInternal?.createdAt
).getMinutes()}`}
</p>
</div>
</div>
<p className="p-4 bg-white text-sm">
{htmlToString(ticketInternal.message)}
</p>
</div>
)}
</div>
</div>
{detail !== undefined && (
<div className="gap-5 mb-5 w-[100%] lg:w-[30%] border bg-white rounded-md">
<form onSubmit={handleSubmit(save)}>
<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
{...field}
size="md"
type="text"
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">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Low">Low</SelectItem>
<SelectItem value="Medium">Medium</SelectItem>
<SelectItem value="High">High</SelectItem>
</SelectContent>
</Select>
</div>
<div className="mt-5 px-3 mb-3">
<Label>Status</Label>
<Select
onValueChange={setSelectedStatus}
value={detail?.status?.name}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Open">Open</SelectItem>
<SelectItem value="Close">Close</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>Description</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<Textarea
defaultValue={detail?.description}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{/* {errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)} */}
</div>
<div className="flex justify-end mt-3 mr-3 py-3">
<Button type="submit" color="primary">
Update
</Button>
</div>
</form>
</div>
)}
</div>
</div>
);
}