[QUDO-98] feat:emergency issue

This commit is contained in:
Anang Yusman 2025-05-26 18:48:12 +08:00
parent 706246b013
commit 78b633d337
12 changed files with 1393 additions and 232 deletions

View File

@ -0,0 +1,21 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTask from "@/components/form/task/task-form";
import FormTaskDetail from "@/components/form/task/task-detail-form";
import FormDetailInternal from "@/components/form/communication/internal-detail-form";
import FormDetailEscalation from "@/components/form/communication/escalation-detail-form";
import FormQuestionsForward from "@/components/form/communication/escalation-forward-form";
import FormQuestionsDetail from "@/components/form/communication/escalation-detail-new-form";
const EscalationDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormQuestionsDetail />
</div>
</div>
);
};
export default EscalationDetailPage;

View File

@ -6,7 +6,7 @@ import FormDetailInternal from "@/components/form/communication/internal-detail-
import FormDetailEscalation from "@/components/form/communication/escalation-detail-form"; import FormDetailEscalation from "@/components/form/communication/escalation-detail-form";
import FormQuestionsForward from "@/components/form/communication/escalation-forward-form"; import FormQuestionsForward from "@/components/form/communication/escalation-forward-form";
const EscalationDetailPage = async () => { const EscalationForwardPage = async () => {
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
@ -17,4 +17,4 @@ const EscalationDetailPage = async () => {
); );
}; };
export default EscalationDetailPage; export default EscalationForwardPage;

View File

@ -93,7 +93,7 @@ const columns: ColumnDef<any>[] = [
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link <Link
href={`/supervisor/communications/escalation/forward/${row.original.id}`} href={`/supervisor/communications/escalation/detail/${row.original.id}`}
> >
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"> <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" /> <Eye className="w-4 h-4 me-1.5" />

View File

@ -143,10 +143,10 @@ const columns: ColumnDef<any>[] = [
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<Link <Link
href={`/supervisor/communications/forward/detail/${row?.original?.id}`} href={`/supervisor/communications/escalation/forward/${row?.original?.id}`}
> >
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none items-center"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none items-center">
Eskalasi Bantuan
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<a onClick={() => deleteValidation(row?.original?.id)}> <a onClick={() => deleteValidation(row?.original?.id)}>

View File

@ -57,7 +57,6 @@ export default function FormCollaboration() {
const editor = useRef(null); const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>; type TaskSchema = z.infer<typeof taskSchema>;
// State for various form fields
const [taskOutput, setTaskOutput] = useState({ const [taskOutput, setTaskOutput] = useState({
all: false, all: false,
video: false, video: false,
@ -68,7 +67,7 @@ export default function FormCollaboration() {
const [assignmentType, setAssignmentType] = useState("mediahub"); const [assignmentType, setAssignmentType] = useState("mediahub");
const [assignmentCategory, setAssignmentCategory] = useState("publication"); const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<number>(1); // untuk Tipe Penugasan const [mainType, setMainType] = useState<number>(1);
const [type, setType] = useState<string>("1"); const [type, setType] = useState<string>("1");
const [options, setOptions] = useState<Option[]>([]); const [options, setOptions] = useState<Option[]>([]);
const [ticketPriority, setTicketPriority] = useState< const [ticketPriority, setTicketPriority] = useState<
@ -81,7 +80,6 @@ export default function FormCollaboration() {
const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [selectedTarget, setSelectedTarget] = useState<number | null>(null); const [selectedTarget, setSelectedTarget] = useState<number | null>(null);
// Opsi untuk dropdown
const priority = [ const priority = [
{ value: "low", label: "Low" }, { value: "low", label: "Low" },
{ value: "medium", label: "Medium" }, { value: "medium", label: "Medium" },
@ -134,13 +132,12 @@ export default function FormCollaboration() {
if (res?.data !== null) { if (res?.data !== null) {
const rawData = res?.data?.data; const rawData = res?.data?.data;
// Ubah ke format Select { value: number, label: string }
const priorityOptions = rawData.map((item: any) => ({ const priorityOptions = rawData.map((item: any) => ({
value: item.id, // value dikirim ke API (number) value: item.id,
label: item.name, // label yang ditampilkan ke user label: item.name,
})); }));
setTicketPriority(priorityOptions); // ← ini akan dipakai di komponen Select setTicketPriority(priorityOptions);
} }
} }
@ -151,7 +148,6 @@ export default function FormCollaboration() {
const rawUser = res?.data?.data?.content; const rawUser = res?.data?.data?.content;
console.log("raw user", rawUser); console.log("raw user", rawUser);
// Tentukan tipe array sebagai Option[]
const optionArr: Option[] = rawUser.map((option: any) => ({ const optionArr: Option[] = rawUser.map((option: any) => ({
id: option?.id, id: option?.id,
label: option?.username + option?.fullname + option?.userLevel?.name, label: option?.username + option?.fullname + option?.userLevel?.name,
@ -175,7 +171,7 @@ export default function FormCollaboration() {
isCollaboration: true, isCollaboration: true,
isEscalation: true, isEscalation: true,
isCollaborationWithNoneTicket: true, isCollaborationWithNoneTicket: true,
operatorTeam: selectedOption?.id, // This should work now without the error operatorTeam: selectedOption?.id,
}; };
const response = await saveTicketing(requestData); const response = await saveTicketing(requestData);

View File

@ -0,0 +1,647 @@
"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";
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 [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,
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);
// Ambil escalationTeams seperti ":891:"
if (detail?.escalationTeams) {
const teamIds = detail.escalationTeams
.split(":")
.filter((id: string) => id); // hapus string kosong
setSelectedOperatorEscalation(teamIds); // set ke state
}
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-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 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-col 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>
);
}

View File

@ -12,7 +12,6 @@ import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { import {
Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
@ -20,6 +19,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Avatar, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { import {
getCuratorUser,
getEscalationDiscussion, getEscalationDiscussion,
getQuestionTicket, getQuestionTicket,
getTicketingDetail, getTicketingDetail,
@ -35,6 +35,24 @@ import { loading } from "@/lib/swal";
import { id } from "date-fns/locale"; import { id } from "date-fns/locale";
import { DetailTicket } from "../ticketing/info-lainnya-types"; import { DetailTicket } from "../ticketing/info-lainnya-types";
import { Description } from "@radix-ui/react-toast"; 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 makeAnimated from "react-select/animated";
import Select from "react-select";
interface Option {
id: string;
label: string;
value: string;
fullname: string;
userLevel: string;
userLevelId: string;
}
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -57,6 +75,17 @@ export type replyDetail = {
}; };
}; };
const optionsData = [
{ value: "1", label: "Low" },
{ value: "2", label: "Medium" },
{ value: "3", label: "High" },
];
type OptionType = {
value: string;
label: string;
};
export default function FormQuestionsForward() { export default function FormQuestionsForward() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
@ -68,9 +97,21 @@ export default function FormQuestionsForward() {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [detailTickets, setDetailTickets] = useState<DetailTicket | null>(null); const [detailTickets, setDetailTickets] = useState<DetailTicket | null>(null);
const [selectedPriority, setSelectedPriority] = useState(""); // const [selectedPriority, setSelectedPriority] = useState("");
const [selectedPriority, setSelectedPriority] = useState<OptionType | null>(
null
);
const [replyMessage, setReplyMessage] = useState(""); const [replyMessage, setReplyMessage] = useState("");
const [selectedStatus, setSelectedStatus] = useState(""); const [selectedStatus, setSelectedStatus] = useState("");
const [operatorOpt, setOperatorOpt] = useState<
{ id: string; label: string; value: string }[]
>([]);
const [selectedOperator, setSelectedOperator] = useState<string[]>([]);
const [options, setOptions] = useState<Option[]>([]);
const animatedComponent = makeAnimated();
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
undefined
);
const [replies, setReplies] = useState([ const [replies, setReplies] = useState([
{ {
id: 1, id: 1,
@ -95,38 +136,45 @@ export default function FormQuestionsForward() {
resolver: zodResolver(taskSchema), 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(() => { useEffect(() => {
async function initState() { async function initState() {
if (id) { const response = await getQuestionTicket(id);
const response = await getTicketingDetail(id); setDetail(response?.data?.data);
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(); initState();
getTicketReply(); getTicketReply();
}, [id]); getUser();
}, [id, reset]);
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]);
async function getTicketReply() { async function getTicketReply() {
const res = await getTicketingInternalDiscussion(id); const res = await getTicketingInternalDiscussion(id);
@ -141,10 +189,13 @@ export default function FormQuestionsForward() {
id, id,
title: data.title, title: data.title,
description: data.description, description: data.description,
priorityId: selectedPriority, priorityId: selectedPriority?.value,
statusId: selectedStatus, statusId: selectedStatus,
typeId: detailTickets?.typeId, typeId: detailTickets?.typeId,
parentCommentId: detailTickets?.feedId, parentCommentId: detailTickets?.feedId,
operatorTeam: selectedOperator.join(","),
isEscalation: true,
communicationTeam: selectedOption?.id,
}; };
const response = await saveTicketsQuestion(payload); const response = await saveTicketsQuestion(payload);
@ -167,6 +218,46 @@ export default function FormQuestionsForward() {
} }
}; };
const handleChange = (e: any) => {
const selected = e;
setSelectedOption(selected);
};
const formatOptionLabel = (option: Option) => (
<>
<div className="row">
<div className="col">
{option.value} | {option.fullname}
</div>
</div>
<div className="row">
<div className="col">
<b>{option.userLevel}</b>
</div>
</div>
</>
);
async function getUser() {
const res = await getCuratorUser();
if (res?.data !== null) {
const rawUser = res?.data?.data?.content;
console.log("raw user", rawUser);
const optionArr: Option[] = rawUser.map((option: any) => ({
id: option?.id,
label: option?.username + option?.fullname + option?.userLevel?.name,
value: option?.username,
fullname: option?.fullname,
userLevel: option?.userLevel?.name,
userLevelId: option?.userLevel?.id,
}));
setOptions(optionArr);
}
}
const handleSendReply = () => { const handleSendReply = () => {
if (replyMessage.trim() === "") return; if (replyMessage.trim() === "") return;
@ -228,107 +319,142 @@ export default function FormQuestionsForward() {
)} )}
</div> </div>
</div> </div>
{detail !== undefined && ( {detailTickets && (
<div className="gap-5 mb-5 w-full border mt-3 rounded-md bg-white"> <form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-2 px-3 mt-3"> <div className="gap-5 mb-5 w-[100%] lg:w-auto border bg-white rounded-md">
<Label>Judul</Label> <p className="mx-3 mt-3">Properties</p>
<Controller <div className="space-y-2 px-3">
control={control} <Label>Judul</Label>
name="title" <Controller
render={({ field }) => ( control={control}
<Input name="title"
size="md" render={({ field }) => (
type="text" <Input
value={detail?.title} size="md"
onChange={field.onChange} defaultValue={detailTickets?.message}
placeholder="Enter Title" type="text"
/> {...field}
)} placeholder="Masukkan judul"
/> />
{/* {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>
<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"
/>
)}
/>
{/* {errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)} */}
</div>
<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>
<div className="mx-3"> <div className="mt-5 px-3 space-y-3">
<label <Label>Prioritas</Label>
htmlFor="replyMessage" <Select
className="block text-gray-700 font-medium mb-2" options={optionsData}
> value={selectedPriority}
Tulis Tanggapan Anda onChange={(option) => setSelectedPriority(option)}
</label> placeholder="Pilih Prioritas"
<textarea />
id="replyMessage" </div>
className="w-full h-24 border border-gray-300 rounded-md p-2"
value={replyMessage} <div className="mt-5 px-3 mb-3 flex flex-col gap-y-3">
onChange={(e) => setReplyMessage(e.target.value)} <Label>Operator</Label>
placeholder="Tulis tanggapan anda di sini..."
/> {/* Tag yang ditampilkan secara kolom */}
<div className="flex justify-end gap-3 mt-2 mb-3"> {selectedOperator.length > 0 && (
<button <div className="flex flex-col gap-2 mb-2">
onClick={() => setReplyMessage("")} {selectedOperator.map((id) => {
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-md" const label = operatorOpt.find(
> (op: any) => op.value === id
Batal )?.label;
</button> return (
<button <div
onClick={handleSendReply} key={id}
className="px-4 py-2 bg-blue-600 text-white rounded-md" className="flex items-center justify-between bg-gray-200 px-3 py-1 rounded"
> >
Kirim Pesan <span>{label}</span>
</button> <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-3/12 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 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">
<div className="">
<Label>
Eskalasi Untuk <span className="text-red-500">*</span>
</Label>
<Select
options={options}
className="w-100"
closeMenuOnSelect={false}
components={animatedComponent}
onChange={handleChange}
formatOptionLabel={formatOptionLabel}
isMulti={false}
/>
</div>
</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">
Simpan
</Button>
</div> </div>
</div> </div>
</div> </form>
)} )}
</div> </div>
); );

View File

@ -42,6 +42,13 @@ import { DetailTicket } from "../ticketing/info-lainnya-types";
import InfoLainnyaModal from "../ticketing/info-lainnya"; import InfoLainnyaModal from "../ticketing/info-lainnya";
import { Description } from "@radix-ui/react-toast"; import { Description } from "@radix-ui/react-toast";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ChevronDownIcon } from "lucide-react";
import { getOperatorUser } from "@/service/management-user/management-user";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -125,8 +132,12 @@ export default function FormQuestionsReply() {
const [selectedPriority, setSelectedPriority] = useState(""); const [selectedPriority, setSelectedPriority] = useState("");
const [selectedStatus, setSelectedStatus] = useState(""); const [selectedStatus, setSelectedStatus] = useState("");
const [openEmergencyModal, setOpenEmergencyModal] = useState(false); const [openEmergencyModal, setOpenEmergencyModal] = useState(false);
const [replyValue, setReplyValue] = useState<number>(0); // beri tipe number const [replyValue, setReplyValue] = useState<number>(0);
const [replyText, setReplyText] = useState<string>(""); // untuk isi balasan const [replyText, setReplyText] = useState<string>("");
const [operatorOpt, setOperatorOpt] = useState<
{ id: string; label: string; value: string }[]
>([]);
const [selectedOperator, setSelectedOperator] = useState<string[]>([]);
const { const {
control, control,
handleSubmit, handleSubmit,
@ -196,6 +207,23 @@ export default function FormQuestionsReply() {
} }
} }
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 () => { const handleSendReply = async () => {
if (replyMessage.trim() === "") { if (replyMessage.trim() === "") {
MySwal.fire({ MySwal.fire({
@ -254,6 +282,7 @@ export default function FormQuestionsReply() {
statusId: selectedStatus, statusId: selectedStatus,
typeId: detailTickets?.typeId, typeId: detailTickets?.typeId,
parentCommentId: detailTickets?.feedId, parentCommentId: detailTickets?.feedId,
operatorTeam: selectedOperator.join(","),
}; };
const response = await saveTicketsQuestion(payload); const response = await saveTicketsQuestion(payload);
@ -488,6 +517,7 @@ export default function FormQuestionsReply() {
<Input <Input
size="md" size="md"
type="text" type="text"
defaultValue={detailTickets?.message}
{...field} {...field}
placeholder="Masukkan judul" placeholder="Masukkan judul"
/> />
@ -513,19 +543,78 @@ export default function FormQuestionsReply() {
</div> </div>
<div className="mt-5 px-3 mb-3"> <div className="mt-5 px-3 mb-3">
<Label>Status</Label> <Label>Operator</Label>
<Select
onValueChange={setSelectedStatus} {/* Tag yang ditampilkan secara kolom */}
value={selectedStatus} {selectedOperator.length > 0 && (
> <div className="flex flex-col gap-2 mb-2">
<SelectTrigger size="md"> {selectedOperator.map((id) => {
<SelectValue placeholder="Pilih Status" /> const label = operatorOpt.find(
</SelectTrigger> (op: any) => op.value === id
<SelectContent> )?.label;
<SelectItem value="1">Open</SelectItem> return (
<SelectItem value="2">Close</SelectItem> <div
</SelectContent> key={id}
</Select> 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>
<div className="space-y-2 px-3 py-3"> <div className="space-y-2 px-3 py-3">

View File

@ -1,4 +1,7 @@
export type DetailTicket = { export type DetailTicket = {
commentFromUserId: string;
assignedTeams: string;
message: string;
typeId: number; typeId: number;
feedId: any; feedId: any;
commentId: string; commentId: string;

View File

@ -38,7 +38,13 @@ import { useRouter } from "next/navigation";
import InfoLainnyaModal from "./info-lainnya"; import InfoLainnyaModal from "./info-lainnya";
import { DetailTicket } from "./info-lainnya-types"; import { DetailTicket } from "./info-lainnya-types";
import { useMediaQuery } from "react-responsive"; import { useMediaQuery } from "react-responsive";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft, ChevronDownIcon } from "lucide-react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { getOperatorUser } from "@/service/management-user/management-user";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -120,8 +126,12 @@ export default function FormDetailTicketing() {
const [selectedPriority, setSelectedPriority] = useState(""); const [selectedPriority, setSelectedPriority] = useState("");
const [selectedStatus, setSelectedStatus] = useState(""); const [selectedStatus, setSelectedStatus] = useState("");
const [openEmergencyModal, setOpenEmergencyModal] = useState(false); const [openEmergencyModal, setOpenEmergencyModal] = useState(false);
const [replyValue, setReplyValue] = useState<number>(0); // beri tipe number const [replyValue, setReplyValue] = useState<number>(0);
const [replyText, setReplyText] = useState<string>(""); // untuk isi balasan const [replyText, setReplyText] = useState<string>("");
const [operatorOpt, setOperatorOpt] = useState<
{ id: string; label: string; value: string }[]
>([]);
const [selectedOperator, setSelectedOperator] = useState<string[]>([]);
const { const {
control, control,
handleSubmit, handleSubmit,
@ -146,6 +156,31 @@ export default function FormDetailTicketing() {
getTicketReply(); getTicketReply();
}, [id]); }, [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]);
const handleReply = () => { const handleReply = () => {
setReplyValue((prev) => (prev === 1 ? 0 : 1)); setReplyValue((prev) => (prev === 1 ? 0 : 1));
}; };
@ -505,6 +540,77 @@ export default function FormDetailTicketing() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </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"> <div className="space-y-2 px-3 py-3">
<Label>Description</Label> <Label>Description</Label>
<Controller <Controller

View File

@ -35,10 +35,19 @@ import { htmlToString } from "@/utils/globals";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import { useRouter } from "next/navigation"; 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({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, { description: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.", message: "Narasi Penugasan harus lebih dari 2 karakter.",
}), }),
}); });
@ -65,7 +74,7 @@ export type taskDetail = {
name: string; name: string;
}; };
broadcastType: string; broadcastType: string;
narration: string; description: string;
is_active: string; is_active: string;
}; };
@ -106,6 +115,13 @@ export default function FormUpdateTicketing() {
const [ticketInternal, setTicketInternal] = useState<internalDetail | null>( const [ticketInternal, setTicketInternal] = useState<internalDetail | null>(
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 [replyVisible, setReplyVisible] = useState(false);
const [replyMessage, setReplyMessage] = useState(""); const [replyMessage, setReplyMessage] = useState("");
const [selectedPriority, setSelectedPriority] = useState(""); const [selectedPriority, setSelectedPriority] = useState("");
@ -114,23 +130,67 @@ export default function FormUpdateTicketing() {
const { const {
control, control,
handleSubmit, handleSubmit,
setValue,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
resolver: zodResolver(taskSchema), resolver: zodResolver(taskSchema),
defaultValues: {
title: "",
description: "",
priority: "",
status: "",
},
}); });
useEffect(() => { useEffect(() => {
async function initState() { async function initState() {
if (id) { setReplyValue(0);
const response = await getTicketingDetail(id); const response = await getTicketingDetail(id);
setTicketInternal(response?.data?.data || null); setTicketInternal(response?.data?.data || null);
setDetail(response?.data?.data); 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(); initState();
getTicketReply(); getTicketReply();
}, [id]); }, [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]);
async function getTicketReply() { async function getTicketReply() {
const res = await getTicketingReply(id); const res = await getTicketingReply(id);
if (res?.data !== null) { if (res?.data !== null) {
@ -227,6 +287,39 @@ export default function FormUpdateTicketing() {
}); });
}; };
async function save(data: any) {
const reqData: any = {
title: data.title,
description: data.description,
priority: data.priority,
status: data.status,
operatorTeam: selectedOperator.join(","), // Gunakan selectedOperator yang sudah ada
};
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 ( return (
<div className="py-5"> <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">
@ -331,83 +424,158 @@ export default function FormUpdateTicketing() {
</div> </div>
{detail !== undefined && ( {detail !== undefined && (
<div className="gap-5 mb-5 w-[100%] lg:w-[30%] border bg-white rounded-md"> <div className="gap-5 mb-5 w-[100%] lg:w-[30%] border bg-white rounded-md">
<p className="mx-3 mt-3">Properties</p> <form onSubmit={handleSubmit(save)}>
<div className="space-y-2 px-3"> <p className="mx-3 mt-3">Properties</p>
<Label>Judul</Label> <div className="space-y-2 px-3">
<Controller <Label>Judul</Label>
control={control} <Controller
name="title" control={control}
render={({ field }) => ( name="title"
<Input render={({ field }) => (
size="md" <Input
type="text" {...field}
defaultValue={detail?.title} size="md"
onChange={field.onChange} type="text"
placeholder="Enter Title" placeholder="Enter Title"
/> />
)} )}
/> />
{/* {errors.title?.message && ( {/* {errors.title?.message && (
<p className="text-red-400 text-sm"> <p className="text-red-400 text-sm">
{errors.title.message} {errors.title.message}
</p> </p>
)} */} )} */}
</div> </div>
<div className="mt-5 px-3"> <div className="mt-5 px-3">
<Label>Prioritas</Label> <Label>Prioritas</Label>
<Select <Select
onValueChange={setSelectedPriority} onValueChange={setSelectedPriority}
value={detail?.priority?.name} value={detail?.priority?.name}
> >
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="Low">Low</SelectItem> <SelectItem value="Low">Low</SelectItem>
<SelectItem value="Medium">Medium</SelectItem> <SelectItem value="Medium">Medium</SelectItem>
<SelectItem value="High">High</SelectItem> <SelectItem value="High">High</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="mt-5 px-3 mb-3"> <div className="mt-5 px-3 mb-3">
<Label>Status</Label> <Label>Status</Label>
<Select <Select
onValueChange={setSelectedStatus} onValueChange={setSelectedStatus}
value={detail?.status?.name} value={detail?.status?.name}
> >
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="Open">Open</SelectItem> <SelectItem value="Open">Open</SelectItem>
<SelectItem value="Close">Close</SelectItem> <SelectItem value="Close">Close</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2 px-3 py-3"> <div className="mt-5 px-3 mb-3">
<Label>Description</Label> <Label>Operator</Label>
<Controller
control={control} {/* Tag yang ditampilkan secara kolom */}
name="title" {selectedOperator.length > 0 && (
render={({ field }) => ( <div className="flex flex-col gap-2 mb-2">
<Textarea {selectedOperator.map((id) => {
value={detail?.narration} const label = operatorOpt.find(
onChange={field.onChange} (op: any) => op.value === id
placeholder="Enter Title" )?.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>
)} )}
/>
{/* {errors.title?.message && ( {/* 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"> <p className="text-red-400 text-sm">
{errors.title.message} {errors.title.message}
</p> </p>
)} */} )} */}
</div> </div>
<div className="flex justify-end mt-3 mr-3 py-3"> <div className="flex justify-end mt-3 mr-3 py-3">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Update Update
</Button> </Button>
</div> </div>
</form>
</div> </div>
)} )}
</div> </div>

View File

@ -70,3 +70,8 @@ export async function deleteUser(userId: number | string) {
const url = `users/delete-user?userId=${userId}`; const url = `users/delete-user?userId=${userId}`;
return httpDeleteInterceptor(url); return httpDeleteInterceptor(url);
} }
export async function getOperatorUser(typeId: any) {
const url = `users/search-operator-user?code=opt-id&typeId=${typeId}`;
return httpGetInterceptor(url);
}