Merge branch 'main' of https://gitlab.com/hanifsalafi/mediahub_redesign into dev-rama
This commit is contained in:
commit
988dc1e3c6
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Eye,
|
||||
|
|
@ -40,6 +41,7 @@ import {
|
|||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
|
|
@ -68,12 +70,16 @@ import SearchDocumentComponent from "@/components/form/media-tracking/search-doc
|
|||
import SearchAudioComponent from "@/components/form/media-tracking/search-audio-card";
|
||||
import TrackingMediaModal from "./modal";
|
||||
import { getMediaTracking } from "@/service/media-tracking/media-tracking";
|
||||
import { group } from "console";
|
||||
import router from "next/router";
|
||||
import { title } from "process";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
const NewsTable = () => {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [search, setSearch] = React.useState("");
|
||||
const [showData, setShowData] = React.useState("10");
|
||||
const [categories, setCategories] = React.useState<any>();
|
||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||
|
|
@ -96,10 +102,11 @@ const NewsTable = () => {
|
|||
const [page, setPage] = React.useState(1);
|
||||
const [imageData, setImageData] = React.useState<any>();
|
||||
const [totalData, setTotalData] = React.useState<number>(1);
|
||||
const [totalPage, setTotalPage] = React.useState<number>(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
const [contentAll, setContentAll] = React.useState([]);
|
||||
const [formatFilter, setFormatFilter] = React.useState<any>([]);
|
||||
const [totalContent, setTotalContent] = React.useState();
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
const group = searchParams?.get("group");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
|
|
@ -144,25 +151,22 @@ const NewsTable = () => {
|
|||
|
||||
React.useEffect(() => {
|
||||
getDataTable();
|
||||
}, []);
|
||||
}, [page, showData, search]);
|
||||
|
||||
const getDataTable = async () => {
|
||||
const res = await getMediaTracking({ page: page - 1, size: 10 });
|
||||
const res = await getMediaTracking(page - 1, search, showData);
|
||||
|
||||
const data = res?.data?.data;
|
||||
console.log;
|
||||
if (data) {
|
||||
const startIndex = 10 * (page - 1);
|
||||
let iterate = 0;
|
||||
const newData = data.content.map((value: any) => {
|
||||
iterate++;
|
||||
value.no = startIndex + iterate;
|
||||
return value;
|
||||
});
|
||||
setDataTable(newData);
|
||||
setTotalData(data.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
setTotalContent(data.totalElements);
|
||||
}
|
||||
const newData = data?.content;
|
||||
|
||||
newData.forEach((item: any, index: number) => {
|
||||
item.no = (page - 1) * Number(showData) + index + 1;
|
||||
});
|
||||
|
||||
setDataTable(newData);
|
||||
setTotalData(data?.totalElements);
|
||||
setTotalPage(data?.totalPages);
|
||||
setTotalContent(data.totalElements);
|
||||
};
|
||||
|
||||
async function getCategories() {
|
||||
|
|
@ -203,33 +207,38 @@ const NewsTable = () => {
|
|||
router.push(`?category=&title=`);
|
||||
};
|
||||
|
||||
async function getData() {
|
||||
if (asPath?.includes("/polda/") == true) {
|
||||
if (asPath?.split("/")[2] !== "[polda_name]") {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
// async function getData() {
|
||||
// if (asPath?.includes("/polda/") == true) {
|
||||
// if (asPath?.split("/")[2] !== "[polda_name]") {
|
||||
// const filter =
|
||||
// categoryFilter?.length > 0
|
||||
// ? categoryFilter?.sort().join(",")
|
||||
// : categorie || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
const filterGroup = group == undefined ? asPath.split("/")[2] : group;
|
||||
loading();
|
||||
const response = await listDataAll("", name, filter, "");
|
||||
close();
|
||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
||||
// setContentImage(response?.data?.data?.content);
|
||||
// setTotalContent(response?.data?.data?.totalElements);
|
||||
const data = response?.data?.data;
|
||||
const contentData = data?.content;
|
||||
setImageData(contentData);
|
||||
setTotalData(data?.totalElements);
|
||||
setContentAll(response?.data?.data?.content);
|
||||
setTotalPage(data?.totalPages);
|
||||
setTotalContent(response?.data?.data?.totalElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
// const name = title == undefined ? "" : title;
|
||||
// const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
// const filterGroup = group == undefined ? asPath.split("/")[2] : group;
|
||||
// loading();
|
||||
// const response = await listDataAll("", name, filter, "");
|
||||
// close();
|
||||
// // setGetTotalPage(response?.data?.data?.totalPages);
|
||||
// // setContentImage(response?.data?.data?.content);
|
||||
// // setTotalContent(response?.data?.data?.totalElements);
|
||||
// const data = response?.data?.data;
|
||||
// const contentData = data?.content;
|
||||
// setImageData(contentData);
|
||||
// setTotalData(data?.totalElements);
|
||||
// setContentAll(response?.data?.data?.content);
|
||||
// setTotalPage(data?.totalPages);
|
||||
// setTotalContent(response?.data?.data?.totalElements);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value);
|
||||
table.getColumn("judul")?.setFilterValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
clearTimeout(typingTimer);
|
||||
|
|
@ -255,50 +264,72 @@ const NewsTable = () => {
|
|||
Tracking Berita hari ini!
|
||||
</p>
|
||||
</div>
|
||||
<TrackingMediaModal triggerFetch={() => getDataTable()} />
|
||||
{/* <Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="bg-blue-600" size="md">
|
||||
Tracking Berita
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="overflow-y-auto h-[500px] min-w-max mx-5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Form Tracking Berita</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 py-4 px-5">
|
||||
<div className="space-y-2 flex flex-col">
|
||||
<Label htmlFor="link" className="text-sm font-medium">
|
||||
Masukkan Link <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
className="pl-4 pr-4 py-1 w-full h-10 text-[15px] border focus:outline-none dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<SearchImageComponent categoryFilter={categoryFilter} />
|
||||
<SearchVideoComponent categoryFilter={categoryFilter} />
|
||||
<SearchDocumentComponent categoryFilter={categoryFilter} />
|
||||
<SearchAudioComponent categoryFilter={categoryFilter} />
|
||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-between sm:items-center md:items-center lg:items-center px-1">
|
||||
<TrackingMediaModal triggerFetch={() => getDataTable()} />
|
||||
<div className=" flex flex-row items-center gap-3">
|
||||
<div className="flex items-center py-2">
|
||||
<div className="mx-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="md" variant="outline">
|
||||
1 - {showData} Data
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56 text-sm">
|
||||
<DropdownMenuRadioGroup
|
||||
value={showData}
|
||||
onValueChange={setShowData}
|
||||
>
|
||||
<DropdownMenuRadioItem value="10">
|
||||
1 - 10 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="50">
|
||||
1 - 50 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="100">
|
||||
1 - 100 Data
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="250">
|
||||
1 - 250 Data
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-2">
|
||||
<Button onClick={cleanCheckbox} variant="outline">
|
||||
Riset Filter
|
||||
</Button>
|
||||
<Button className="bg-blue-600 text-white">Tracking Berita</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog> */}
|
||||
<Table className="overflow-hidden mt-4">
|
||||
<div className="flex items-center py-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto" size="md">
|
||||
Columns <ChevronDown />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{table
|
||||
.getAllColumns()
|
||||
.filter((column) => column.getCanHide())
|
||||
.map((column) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) =>
|
||||
column.toggleVisibility(!!value)
|
||||
}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table className="overflow-hidden">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@ const columns: ColumnDef<any>[] = [
|
|||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "static",
|
||||
accessorKey: "isStaticBanner",
|
||||
header: "Static Banner",
|
||||
cell: ({ row }) => (
|
||||
<StaticToogle
|
||||
id={row.original.id}
|
||||
initChecked={row.original.staticPage}
|
||||
initChecked={row.original.isStaticBanner}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -96,22 +96,9 @@ const BannerListTable = () => {
|
|||
let temp: any;
|
||||
|
||||
const response = await listBanner();
|
||||
temp = response?.data?.data;
|
||||
const response2 = await listStaticBanner();
|
||||
console.log("sadadddd", response2?.data?.data.length);
|
||||
for (let i = 0; i < response2?.data?.data.length; i++) {
|
||||
for (let j = 0; j < temp.length; j++) {
|
||||
console.log("temp", j, temp[j].id);
|
||||
if (response2?.data?.data[i].mediaUploadId === temp[j].id) {
|
||||
temp[j].staticPage = true;
|
||||
} else {
|
||||
temp[j].staticPage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("tesmasdasdasdasd", temp);
|
||||
setGetData(temp);
|
||||
const data = response?.data?.data?.content;
|
||||
console.log("banner", data);
|
||||
setGetData(data);
|
||||
|
||||
close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,19 +45,17 @@ const columns: ColumnDef<any>[] = [
|
|||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "contentType",
|
||||
accessorKey: "mediaTypesString",
|
||||
header: "Tipe Konten",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("contentType")}</span>
|
||||
<span className="normal-case">{row.getValue("mediaTypesString")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "isInternational",
|
||||
accessorKey: "publishedLocation",
|
||||
header: "Wilayah",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">
|
||||
{row.getValue("isInternational") ? "INT" : "ID"}
|
||||
</span>
|
||||
<span className="normal-case">{row.getValue("publishedLocation")}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,619 @@
|
|||
"use client";
|
||||
import React, { ChangeEvent, useEffect, useRef, 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, CardContent } 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, useRouter } from "next/navigation";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import Cookies from "js-cookie";
|
||||
import { postBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
DotSquare,
|
||||
InboxIcon,
|
||||
PaperclipIcon,
|
||||
SmileIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
deleteMediaCurationMessage,
|
||||
detailMedia,
|
||||
getMediaCurationMessage,
|
||||
saveMediaCurationMessage,
|
||||
} from "@/service/curated-content/curated-content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/thumbs";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { listData } from "@/service/landing/landing";
|
||||
import {
|
||||
createAssignmentResponse,
|
||||
deleteAssignmentResponse,
|
||||
getAssignmentResponseList,
|
||||
} from "@/service/task";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, loading } from "@/lib/swal";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { formatDateToIndonesian, htmlToString } from "@/utils/globals";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
|
||||
const detailSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
categoryName: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
meta: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
description: z
|
||||
.string()
|
||||
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
});
|
||||
|
||||
type Category = {
|
||||
id: string;
|
||||
categoryName: string;
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
|
||||
// Pastikan validitas tanggal
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new Error("Invalid date format");
|
||||
}
|
||||
|
||||
// Format tanggal
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const year = date.getFullYear();
|
||||
// const hours = date.getHours().toString().padStart(2, "0");
|
||||
|
||||
// Gabungkan hasil format
|
||||
return `${day}-${month}-${year} `;
|
||||
};
|
||||
|
||||
export type curationDetail = {
|
||||
id: number;
|
||||
title: string;
|
||||
categoryName: string;
|
||||
htmlDescription: string;
|
||||
updatedAt: string;
|
||||
timezone: string;
|
||||
clickCount: string;
|
||||
creatorName: string;
|
||||
uploadedBy: {
|
||||
id: number;
|
||||
fullname: string;
|
||||
username: string | null;
|
||||
email: string;
|
||||
isActive: boolean;
|
||||
isDefault: boolean;
|
||||
isInternational: boolean;
|
||||
userLevel: {
|
||||
id: number;
|
||||
name: string;
|
||||
aliasName: string;
|
||||
userGroupId: number;
|
||||
};
|
||||
};
|
||||
publishedFor: string; // ID for selected radio button
|
||||
publishedForObject: {
|
||||
id: number;
|
||||
name: string;
|
||||
isInternal: boolean;
|
||||
code: string;
|
||||
}[];
|
||||
tags: string;
|
||||
provinceId: string;
|
||||
is_active: string;
|
||||
};
|
||||
|
||||
const initialComments = [
|
||||
{
|
||||
id: 1,
|
||||
username: "Esther Howard",
|
||||
date: "07-04-2023 20:00 WIB",
|
||||
text: "Tolong untuk narasinya mengikuti 5W + 1H!",
|
||||
avatar: "/images/avatar/avatar-3.png", // URL avatar atau path gambar pengguna
|
||||
replies: [], // Komentar balasan
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: "Brooklyn Simmons",
|
||||
date: "07-04-2023 20:00 WIB",
|
||||
text: "Ok Baik, Saya segera melakukan perbaikan. Terima kasih atas masukannya. 🙏",
|
||||
avatar: "/images/avatar/avatar-5.png", // URL avatar atau path gambar pengguna
|
||||
replies: [], // Komentar balasan
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: "Leslie Alexander",
|
||||
date: "07-04-2023 20:00 WIB",
|
||||
text: "Sangat berguna. Terima Kasih!",
|
||||
avatar: "/images/avatar/avatar-7.png", // URL avatar atau path gambar pengguna
|
||||
replies: [], // Komentar balasan
|
||||
},
|
||||
];
|
||||
|
||||
export default function DetailAcceptImage() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const { id } = useParams() as { id: string };
|
||||
const t = useTranslations("LandingPage");
|
||||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type DetailSchema = z.infer<typeof detailSchema>;
|
||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
const scheduleType = Cookies.get("scheduleType");
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [detail, setDetail] = useState<curationDetail>();
|
||||
const [refresh] = useState(false);
|
||||
const [detailThumb, setDetailThumb] = useState<any>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [showInput, setShowInput] = useState<boolean>(false);
|
||||
const [selectedFileId, setSelectedFileId] = useState(null);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<DetailSchema>({
|
||||
resolver: zodResolver(detailSchema),
|
||||
});
|
||||
|
||||
const [commentsData, setCommentsData] = useState(initialComments);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [replyingTo, setReplyingTo] = useState<number | null>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
|
||||
const handleReply = (commentId: number) => {
|
||||
setReplyingTo(commentId);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setMessage(e.target.value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
// loading();
|
||||
const response = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("data", response?.data?.data);
|
||||
console.log("userLvl", userLevelNumber);
|
||||
setListData(response?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
initState();
|
||||
}, [selectedFileId]);
|
||||
|
||||
// const postData = () => {
|
||||
// sendSuggestionParent();
|
||||
// };
|
||||
|
||||
const postData = async () => {
|
||||
if (message?.length > 1 && selectedFileId) {
|
||||
try {
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message,
|
||||
parentId: null,
|
||||
};
|
||||
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log("Komentar terkirim:", response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
setMessage("");
|
||||
} catch (error) {
|
||||
console.error("Error posting comment:", error);
|
||||
}
|
||||
} else {
|
||||
console.log("Pesan atau file ID tidak valid.");
|
||||
}
|
||||
};
|
||||
|
||||
const sendReplyData = async (parentId: number) => {
|
||||
const inputElement = document.querySelector(
|
||||
`#input-comment-${parentId}`
|
||||
) as HTMLTextAreaElement;
|
||||
|
||||
if (inputElement?.value?.length > 1 && selectedFileId) {
|
||||
loading();
|
||||
const data = {
|
||||
mediaUploadFileId: selectedFileId,
|
||||
message: inputElement.value,
|
||||
parentId,
|
||||
};
|
||||
|
||||
console.log("Sending reply:", data);
|
||||
const response = await saveMediaCurationMessage(data);
|
||||
console.log(response);
|
||||
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log("Updated comments:", responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
|
||||
inputElement.value = "";
|
||||
close();
|
||||
setReplyingTo(null);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteDataSuggestion(dataId: any) {
|
||||
loading();
|
||||
const response = await deleteMediaCurationMessage(dataId);
|
||||
console.log(response);
|
||||
const responseGet = await getMediaCurationMessage(selectedFileId);
|
||||
console.log(responseGet?.data?.data);
|
||||
setListData(responseGet?.data?.data);
|
||||
close();
|
||||
}
|
||||
|
||||
const deleteData = (dataId: any) => {
|
||||
deleteDataSuggestion(dataId);
|
||||
console.log(dataId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailMedia(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
setSelectedValue(details?.publishedFor || "");
|
||||
setSelectedFileId(details?.files[0]?.id);
|
||||
const filesData = details.files || [];
|
||||
const fileUrls = filesData.map((file: any) => ({
|
||||
id: file.id,
|
||||
thumbnailFileUrl: file.thumbnailFileUrl || "default-image.jpg",
|
||||
placements: file.placements || "",
|
||||
}));
|
||||
setDetailThumb(fileUrls);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleFileClick = async (fileId: any) => {
|
||||
setSelectedFileId(fileId);
|
||||
try {
|
||||
const response = await getMediaCurationMessage(fileId);
|
||||
console.log("Data komentar:", response?.data?.data);
|
||||
setListData(response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching comments:", error);
|
||||
}
|
||||
};
|
||||
const handleValueChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-10">
|
||||
{detail !== undefined ? (
|
||||
<Card className="w-full ">
|
||||
<div className="px-6 py-6">
|
||||
<p className="text-lg font-semibold mb-3">Kurasi Detail</p>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-5">
|
||||
<div className="w-full px-3 mt-3 rounded-md">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="space-y-2 py-3">
|
||||
<div className="w-full ">
|
||||
<Swiper
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={false}
|
||||
className="w-full"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<div className=" mt-2 ">
|
||||
<Swiper
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={6}
|
||||
spaceBetween={8}
|
||||
pagination={{
|
||||
clickable: true,
|
||||
}}
|
||||
modules={[Pagination, Thumbs]}
|
||||
// className="mySwiper2"
|
||||
>
|
||||
{detailThumb?.map((data: any) => (
|
||||
<SwiperSlide
|
||||
key={data.id}
|
||||
onClick={() => handleFileClick(data.id)}
|
||||
>
|
||||
<img
|
||||
className="object-fill h-full w-full rounded-md"
|
||||
src={data.thumbnailFileUrl}
|
||||
alt={`File ID: ${data.id}`}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4">
|
||||
<div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
|
||||
<p className="text-xs lg:text-sm">
|
||||
{t("by")}
|
||||
<span className="font-semibold text-black dark:text-white">
|
||||
{detail?.uploadedBy?.userLevel?.name}
|
||||
</span>
|
||||
</p>
|
||||
{/* <p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")}
|
||||
{detail?.updatedAt} WIB |
|
||||
</p> */}
|
||||
<p className="text-xs lg:text-sm">
|
||||
| {t("updatedOn")}
|
||||
{formatDateToIndonesian(new Date(detail?.updatedAt))}{" "}
|
||||
{detail?.timezone ? detail?.timezone : "WIB"}
|
||||
|
||||
</p>
|
||||
<p className="text-xs lg:text-sm flex justify-center items-center">
|
||||
|
|
||||
<Icon icon="formkit:eye" width="15" height="15" />
|
||||
{detail?.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="flex text-end text-xs lg:text-sm font-semibold">
|
||||
{t("creator")}
|
||||
{detail?.creatorName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keterangan */}
|
||||
<div className="w-full">
|
||||
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">
|
||||
{detail?.title}
|
||||
</h1>
|
||||
<div
|
||||
className="font-light text-justify mb-5 space-y-4 lg:mb-0"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: detail?.htmlDescription,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent className="p-1">
|
||||
<div className="gap-5 mb-5">
|
||||
<div className="mt-5">
|
||||
<Label className="text-xl text-black">Komentar</Label>
|
||||
<div className="mt-4 border p-4 rounded bg-gray-50">
|
||||
<Textarea
|
||||
placeholder="Tulis tanggapan Anda di sini..."
|
||||
value={message}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<div className="flex justify-end mt-3">
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => postData()}
|
||||
type="button"
|
||||
>
|
||||
Kirim Komentar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{listData?.map((item: any) => (
|
||||
<div key={item.id} className="flex flex-col gap-3 mt-2 ">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${item.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">{item.message}</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(item.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(item.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{replyingTo === item.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${item.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(item.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{item.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col">
|
||||
{item.children.map((child: any) => (
|
||||
<div
|
||||
key={child.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
{/* <div
|
||||
className="flex items-center mt-1 text-blue-500 cursor-pointer"
|
||||
onClick={() => handleReply(child.id)}
|
||||
>
|
||||
<DotSquare className="w-4 h-4" />
|
||||
<span className="ml-1">Balas</span>
|
||||
</div> */}
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() => deleteData(child.id)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{replyingTo === child.id && (
|
||||
<div className="ml-10 mt-2">
|
||||
<textarea
|
||||
id={`input-comment-${child.id}`}
|
||||
className="w-full p-2 border rounded"
|
||||
placeholder="Masukkan tanggapan anda"
|
||||
/>
|
||||
<button
|
||||
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
|
||||
onClick={() => sendReplyData(child.id)}
|
||||
>
|
||||
Kirim
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{child.children?.length > 0 && (
|
||||
<div className="ml-10 mt-2 flex flex-col mb-3">
|
||||
{child.children.map((child2: any) => (
|
||||
<div
|
||||
key={child2.id}
|
||||
className="flex flex-col gap-3 mt-2"
|
||||
>
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<Avatar className="mt-2">
|
||||
<AvatarImage
|
||||
src={"/assets/avatar-profile.png"}
|
||||
alt={`@${child2.username}`}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="flex flex-col bg-slate-200 w-full px-2 py-2 rounded-md">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-700 font-semibold">
|
||||
{item.messageFrom.fullname}
|
||||
</span>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-800 mt-1">
|
||||
{child2.message}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div
|
||||
className="flex items-center mt-1 text-red-500 cursor-pointer"
|
||||
onClick={() =>
|
||||
deleteData(child2.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<span className="ml-1">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -63,7 +63,11 @@ const ImageSliderPage = () => {
|
|||
<div className="p-2">
|
||||
<Card className="shadow-md rounded-lg overflow-hidden">
|
||||
<Link
|
||||
href={`/shared/curated-content/giat-routine/image/detail/${image.id}`}
|
||||
href={
|
||||
roleId === 12
|
||||
? `/shared/curated-content/giat-routine/image/accept-assignment/detail/${image.id}`
|
||||
: `/shared/curated-content/giat-routine/image/detail/${image.id}`
|
||||
}
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
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";
|
||||
|
||||
const EscalationDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormQuestionsForward />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EscalationDetailPage;
|
||||
|
|
@ -136,7 +136,7 @@ const columns: ColumnDef<any>[] = [
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<Link
|
||||
href={`/supervisor/communications/internal/update/${row?.original?.id}`}
|
||||
href={`/supervisor/communications/questions/reply/${row?.original?.id}`}
|
||||
>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none items-center">
|
||||
Jawab
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
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 FormDetailTicketing from "@/components/form/ticketing/ticketing-detail-form";
|
||||
import FormQuestionsReply from "@/components/form/communication/questions-reply-form";
|
||||
|
||||
const QuestionsReplyPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormQuestionsReply />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionsReplyPage;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
'use client'
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
|
|
@ -6,16 +6,25 @@ import {
|
|||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import {Card, CardContent} from "@/components/ui/card";
|
||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import {getKnowledgeBaseCategoryList, getKnowledgeBaseList} from "@/service/master/knowledge-base";
|
||||
import {
|
||||
deleteKnowledgeBase,
|
||||
getKnowledgeBaseCategoryList,
|
||||
getKnowledgeBaseList,
|
||||
} from "@/service/master/knowledge-base";
|
||||
import React from "react";
|
||||
import {Plus, Trash2} from "lucide-react";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import { Plus, Trash, Trash2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import CreateCategory from "./create-category";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { deleteMedia } from "@/service/content/content";
|
||||
import { error } from "@/lib/swal";
|
||||
|
||||
const KnowledgeBase = () => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [categories, setCategories] = React.useState<any>([]);
|
||||
const [questions, setQuestions] = React.useState<any>([]);
|
||||
|
||||
|
|
@ -28,7 +37,7 @@ const KnowledgeBase = () => {
|
|||
const data = response?.data?.data;
|
||||
if (data) {
|
||||
setCategories(data);
|
||||
fetchQuestions(data[0]?.id)
|
||||
fetchQuestions(data[0]?.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,84 +47,135 @@ const KnowledgeBase = () => {
|
|||
if (data) {
|
||||
setQuestions(data);
|
||||
}
|
||||
};
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const response = await deleteKnowledgeBase(id);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteKnowlagde = (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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb/>
|
||||
<Tabs defaultValue={`category-0`}>
|
||||
<div className="grid grid-cols-12 gap-6">
|
||||
<Card className="lg:col-span-3 md:col-span-5 col-span-12 h-max">
|
||||
<CardContent className=" p-6">
|
||||
<TabsList className="md:flex-col gap-2 flex-wrap md:items-start justify-start">
|
||||
<CreateCategory onSuccess={fetchCategoryList} />
|
||||
{categories?.map((category: any, index: number) => (
|
||||
<TabsTrigger
|
||||
key={index}
|
||||
value={`category-${index}`}
|
||||
onClick={() => {
|
||||
fetchQuestions(category?.id);
|
||||
}}
|
||||
className="data-[state=active]:bg-secondary data-[state=active]:text-default rounded-md px-6 py-3 w-full justify-start"
|
||||
>
|
||||
{category?.name}
|
||||
<div
|
||||
className="absolute right-2 top-2 hidden group-hover:inline-flex"
|
||||
// onClick={() => deleteCategory(category?.id)}
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5 me-1" />
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<Tabs defaultValue={`category-0`}>
|
||||
<div className="grid grid-cols-12 gap-6">
|
||||
<Card className="lg:col-span-3 md:col-span-5 col-span-12 h-max">
|
||||
<CardContent className=" p-6">
|
||||
<TabsList className="md:flex-col gap-2 flex-wrap md:items-start justify-start">
|
||||
<CreateCategory onSuccess={fetchCategoryList} />
|
||||
{categories?.map((category: any, index: number) => (
|
||||
<TabsTrigger
|
||||
key={index}
|
||||
value={`category-${index}`}
|
||||
onClick={() => {
|
||||
fetchQuestions(category?.id);
|
||||
}}
|
||||
className="group data-[state=active]:bg-secondary data-[state=active]:text-default rounded-md px-6 py-3 w-full justify-between flex items-center"
|
||||
>
|
||||
{category?.name}
|
||||
<div
|
||||
className="right-2 top-2 hidden group-hover:inline-flex"
|
||||
// onClick={() => deleteCategory(category?.id)}
|
||||
>
|
||||
<Trash size={20} className="text-red-500" />
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="lg:col-span-9 md:col-span-7 col-span-12 mt-4 lg:mt-0">
|
||||
{categories?.map((cateogry: any, index: number) => (
|
||||
<TabsContent
|
||||
key={index}
|
||||
value={`category-${index}`}
|
||||
className="mt-0"
|
||||
>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{questions?.map((question: any) => (
|
||||
<AccordionItem
|
||||
key={question.id}
|
||||
className="dark:bg-secondary bg-white"
|
||||
value={question.id}
|
||||
>
|
||||
<AccordionTrigger className="flex items-center justify-between gap-4 dark:bg-secondary bg-white data-[state=open]:bg-default-200 data-[state=active]:text-default">
|
||||
<div className="flex items-center justify-between w-full gap-4">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: question.question,
|
||||
}}
|
||||
className="text-left"
|
||||
/>
|
||||
<Trash
|
||||
size={20}
|
||||
className="text-red-500 hover:cursor-pointer"
|
||||
onClick={() => handleDeleteKnowlagde(question.id)}
|
||||
/>
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="dark:bg-secondary bg-white">
|
||||
{question.answer}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</TabsList>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="lg:col-span-9 md:col-span-7 col-span-12 mt-4 lg:mt-0">
|
||||
{categories?.map((cateogry: any, index: number) => (
|
||||
<TabsContent key={index} value={`category-${index}`} className="mt-0">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{questions?.map((question: any) => (
|
||||
<AccordionItem
|
||||
key={question.id}
|
||||
className="dark:bg-secondary bg-white"
|
||||
value={question.id}
|
||||
>
|
||||
<AccordionTrigger
|
||||
className="dark:bg-secondary bg-white data-[state=open]:bg-default-200 data-[state=active]:text-default">
|
||||
{question.question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="dark:bg-secondary bg-white">
|
||||
{question.answer}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))
|
||||
}
|
||||
</Accordion>
|
||||
{questions?.length > 0 &&
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
fullWidth
|
||||
size="md"
|
||||
variant="outline"
|
||||
>
|
||||
<Plus className="w-6 h-6 me-1.5"/>
|
||||
Import
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
size="md"
|
||||
>
|
||||
<Plus className="w-6 h-6 me-1.5"/>
|
||||
Add Question
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</TabsContent>
|
||||
))}
|
||||
</div>
|
||||
</Accordion>
|
||||
{questions?.length > 0 && (
|
||||
<div className="flex gap-3">
|
||||
<Button fullWidth size="md" variant="outline">
|
||||
<Plus className="w-6 h-6 me-1.5" />
|
||||
Import
|
||||
</Button>
|
||||
<Button fullWidth size="md">
|
||||
<Plus className="w-6 h-6 me-1.5" />
|
||||
Add Question
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
))}
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import * as React from "react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||
import {
|
||||
CheckSquare2,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -12,6 +18,14 @@ import {
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { formatDateToIndonesian } from "@/utils/globals";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import {
|
||||
closeTicket,
|
||||
deleteTicket,
|
||||
} from "@/service/communication/communication";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { error } from "@/lib/swal";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -90,6 +104,73 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
const response = await deleteTicket(id);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const 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 doClose(id: any) {
|
||||
const response = await closeTicket(id);
|
||||
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
success();
|
||||
}
|
||||
|
||||
const handleClose = (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Ubah status menjadi close?",
|
||||
text: "",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonColor: "#d33",
|
||||
confirmButtonText: "Iya",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doClose(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -102,15 +183,29 @@ const columns: ColumnDef<any>[] = [
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<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" />
|
||||
View
|
||||
<Link href={`/supervisor/ticketing/detail/${row.original.id}`}>
|
||||
<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" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link href={`/supervisor/ticketing/update/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleClose(row.original.id)}
|
||||
className="p-2 border-b text-green-600 bg-green-200 focus:bg-green-400 focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<CheckSquare2 className="w-4 h-4 me-1.5" />
|
||||
Close
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDelete(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
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 FormDetailTicketing from "@/components/form/ticketing/ticketing-detail-form";
|
||||
|
||||
const TicketingDetailPage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormDetailTicketing />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TicketingDetailPage;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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 FormDetailTicketing from "@/components/form/ticketing/ticketing-detail-form";
|
||||
import FormUpdateTicketing from "@/components/form/ticketing/ticketing-update-form";
|
||||
|
||||
const TicketingUpdatePage = async () => {
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<div className="space-y-4">
|
||||
<FormUpdateTicketing />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TicketingUpdatePage;
|
||||
|
|
@ -20,7 +20,7 @@ const Home = ({ params: { locale } }: { params: { locale: string } }) => {
|
|||
<ReactLenis root>
|
||||
<div className="pb-14">
|
||||
<Navbar />
|
||||
<Hero />
|
||||
<Hero group="mabes" />
|
||||
<SearchSection />
|
||||
<NewContent group="mabes" type="latest" />
|
||||
<NewContent group="mabes" type="popular" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,291 @@
|
|||
"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 {
|
||||
getEscalationDiscussion,
|
||||
getQuestionTicket,
|
||||
getTicketingDetail,
|
||||
getTicketingInternalDetail,
|
||||
getTicketingInternalDiscussion,
|
||||
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";
|
||||
|
||||
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 replyDetail = {
|
||||
id: number;
|
||||
message: string;
|
||||
createdAt: string;
|
||||
messageFrom: {
|
||||
id: number;
|
||||
fullname: string;
|
||||
};
|
||||
messageTo: {
|
||||
id: number;
|
||||
fullname: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default function FormQuestionsForward() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const { id } = useParams() as { id: string };
|
||||
|
||||
const [detail, setDetail] = useState<any>();
|
||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||
const [replyVisible, setReplyVisible] = useState(false);
|
||||
const [listDiscussion, setListDiscussion] = useState();
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const [detailTickets, setDetailTickets] = useState<DetailTicket | null>(null);
|
||||
const [selectedPriority, setSelectedPriority] = useState("");
|
||||
const [replyMessage, setReplyMessage] = useState("");
|
||||
const [selectedStatus, setSelectedStatus] = useState("");
|
||||
const [replies, setReplies] = useState([
|
||||
{
|
||||
id: 1,
|
||||
name: "Mabes Polri - Approver",
|
||||
message: "test",
|
||||
timestamp: "2024-12-20 00:56:10",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Mabes Polri - Approver",
|
||||
message: "balas",
|
||||
timestamp: "2025-01-18 17:42:48",
|
||||
},
|
||||
]);
|
||||
|
||||
const {
|
||||
control,
|
||||
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]);
|
||||
|
||||
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("");
|
||||
};
|
||||
|
||||
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>
|
||||
{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"
|
||||
{...field}
|
||||
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>Status</Label>
|
||||
<Select onValueChange={setSelectedStatus} value={selectedStatus}>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">Open</SelectItem>
|
||||
<SelectItem value="2">Close</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</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>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,553 @@
|
|||
"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,
|
||||
getQuestionTicket,
|
||||
getTicketingDetail,
|
||||
getTicketingInternalDetail,
|
||||
getTicketingInternalDiscussion,
|
||||
getTicketingReply,
|
||||
saveTicketing,
|
||||
saveTicketInternalReply,
|
||||
saveTicketReply,
|
||||
saveTicketsQuestion,
|
||||
} from "@/service/communication/communication";
|
||||
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 { useRouter } from "next/navigation";
|
||||
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 } from "@/i18n/routing";
|
||||
|
||||
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;
|
||||
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); // beri tipe number
|
||||
const [replyText, setReplyText] = useState<string>(""); // untuk isi balasan
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
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);
|
||||
|
||||
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); // tutup form setelah kirim
|
||||
getTicketReply(); // refresh balasan
|
||||
} catch (err) {
|
||||
console.error("Gagal kirim balasan:", err);
|
||||
}
|
||||
};
|
||||
|
||||
async function getTicketReply() {
|
||||
const res = await getTicketingReply(id);
|
||||
if (res?.data !== null) {
|
||||
setTicketReply(res?.data?.data);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Tambahkan balasan baru ke daftar balasan
|
||||
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",
|
||||
});
|
||||
|
||||
// Reset input dan sembunyikan form balasan
|
||||
setReplyMessage("");
|
||||
setReplyVisible(false);
|
||||
} catch (error) {
|
||||
MySwal.fire({
|
||||
title: "Error",
|
||||
text: "Gagal mengirim balasan.",
|
||||
icon: "error",
|
||||
});
|
||||
console.error("Error sending reply:", error);
|
||||
}
|
||||
};
|
||||
|
||||
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",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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"
|
||||
{...field}
|
||||
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>Status</Label>
|
||||
<Select
|
||||
onValueChange={setSelectedStatus}
|
||||
value={selectedStatus}
|
||||
>
|
||||
<SelectTrigger size="md">
|
||||
<SelectValue placeholder="Pilih Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">Open</SelectItem>
|
||||
<SelectItem value="2">Close</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ export default function FormAudioUpdate() {
|
|||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
[fileId: number]: string[];
|
||||
}>({});
|
||||
|
||||
const options: Option[] = [
|
||||
|
|
@ -243,6 +243,13 @@ export default function FormAudioUpdate() {
|
|||
|
||||
if (details?.files) {
|
||||
setFiles(details.files);
|
||||
const initialOptions: { [key: number]: string[] } = {};
|
||||
details.files.forEach((file: any) => {
|
||||
if (file.placements) {
|
||||
initialOptions[file.id] = mapPlacementsToOptions(file.placements);
|
||||
}
|
||||
});
|
||||
setSelectedOptions(initialOptions);
|
||||
}
|
||||
|
||||
if (details?.publishedFor) {
|
||||
|
|
@ -274,6 +281,31 @@ export default function FormAudioUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const mapPlacementsToOptions = (placements: string): string[] => {
|
||||
const mapping: Record<string, string> = {
|
||||
all: "all",
|
||||
mabes: "nasional",
|
||||
polda: "wilayah",
|
||||
polres: "internasional",
|
||||
};
|
||||
|
||||
// Jika placements hanya "all", langsung aktifkan semua checkbox
|
||||
if (placements.trim() === "all") {
|
||||
return ["all", "nasional", "wilayah", "internasional"];
|
||||
}
|
||||
|
||||
const options = placements
|
||||
.split(",")
|
||||
.map((p) => mapping[p.trim()])
|
||||
.filter(Boolean);
|
||||
|
||||
const allSelected = ["nasional", "wilayah", "internasional"].every((opt) =>
|
||||
options.includes(opt)
|
||||
);
|
||||
|
||||
return allSelected ? ["all", ...options] : options;
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import { error } from "@/lib/swal";
|
|||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { UnitMapping } from "@/app/[locale]/(protected)/contributor/agenda-setting/unit-mapping";
|
||||
|
||||
const imageSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -142,6 +143,15 @@ export default function FormImageDetail() {
|
|||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
||||
const [wilayahPublish, setWilayahPublish] = React.useState({
|
||||
semua: false,
|
||||
nasional: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
satker: false,
|
||||
international: false,
|
||||
});
|
||||
const [selectedPolda, setSelectedPolda] = React.useState([]);
|
||||
|
||||
let fileTypeId = "1";
|
||||
|
||||
|
|
@ -776,6 +786,15 @@ export default function FormImageDetail() {
|
|||
>
|
||||
Wilayah
|
||||
</label>
|
||||
{wilayahPublish.polda && (
|
||||
<UnitMapping
|
||||
unit="Polda"
|
||||
isDetail={false}
|
||||
sendDataToParent={(data: any) =>
|
||||
setSelectedPolda(data)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export default function FormImageUpdate() {
|
|||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
[fileId: number]: string[];
|
||||
}>({});
|
||||
|
||||
const options: Option[] = [
|
||||
|
|
@ -266,6 +266,14 @@ export default function FormImageUpdate() {
|
|||
|
||||
if (details?.files) {
|
||||
setFiles(details.files);
|
||||
|
||||
const initialOptions: { [key: number]: string[] } = {};
|
||||
details.files.forEach((file: any) => {
|
||||
if (file.placements) {
|
||||
initialOptions[file.id] = mapPlacementsToOptions(file.placements);
|
||||
}
|
||||
});
|
||||
setSelectedOptions(initialOptions);
|
||||
}
|
||||
|
||||
if (details?.publishedFor) {
|
||||
|
|
@ -291,11 +299,30 @@ export default function FormImageUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
// const handleCheckboxChange = (id: number) => {
|
||||
// setSelectedPublishers((prev) =>
|
||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
// );
|
||||
// };
|
||||
const mapPlacementsToOptions = (placements: string): string[] => {
|
||||
const mapping: Record<string, string> = {
|
||||
all: "all",
|
||||
mabes: "nasional",
|
||||
polda: "wilayah",
|
||||
polres: "internasional",
|
||||
};
|
||||
|
||||
// Jika placements hanya "all", langsung aktifkan semua checkbox
|
||||
if (placements.trim() === "all") {
|
||||
return ["all", "nasional", "wilayah", "internasional"];
|
||||
}
|
||||
|
||||
const options = placements
|
||||
.split(",")
|
||||
.map((p) => mapping[p.trim()])
|
||||
.filter(Boolean);
|
||||
|
||||
const allSelected = ["nasional", "wilayah", "internasional"].every((opt) =>
|
||||
options.includes(opt)
|
||||
);
|
||||
|
||||
return allSelected ? ["all", ...options] : options;
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export default function FormTeksUpdate() {
|
|||
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
[fileId: number]: string[];
|
||||
}>({});
|
||||
const [selectedTarget, setSelectedTarget] = useState("");
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
|
|
@ -236,6 +236,13 @@ export default function FormTeksUpdate() {
|
|||
|
||||
if (details?.files) {
|
||||
setFiles(details.files);
|
||||
const initialOptions: { [key: number]: string[] } = {};
|
||||
details.files.forEach((file: any) => {
|
||||
if (file.placements) {
|
||||
initialOptions[file.id] = mapPlacementsToOptions(file.placements);
|
||||
}
|
||||
});
|
||||
setSelectedOptions(initialOptions);
|
||||
}
|
||||
|
||||
if (details?.publishedFor) {
|
||||
|
|
@ -261,6 +268,31 @@ export default function FormTeksUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const mapPlacementsToOptions = (placements: string): string[] => {
|
||||
const mapping: Record<string, string> = {
|
||||
all: "all",
|
||||
mabes: "nasional",
|
||||
polda: "wilayah",
|
||||
polres: "internasional",
|
||||
};
|
||||
|
||||
// Jika placements hanya "all", langsung aktifkan semua checkbox
|
||||
if (placements.trim() === "all") {
|
||||
return ["all", "nasional", "wilayah", "internasional"];
|
||||
}
|
||||
|
||||
const options = placements
|
||||
.split(",")
|
||||
.map((p) => mapping[p.trim()])
|
||||
.filter(Boolean);
|
||||
|
||||
const allSelected = ["nasional", "wilayah", "internasional"].every((opt) =>
|
||||
options.includes(opt)
|
||||
);
|
||||
|
||||
return allSelected ? ["all", ...options] : options;
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ export default function FormVideoUpdate() {
|
|||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedOptions, setSelectedOptions] = useState<{
|
||||
[fileId: number]: string;
|
||||
[fileId: number]: string[];
|
||||
}>({});
|
||||
|
||||
const options: Option[] = [
|
||||
|
|
@ -262,6 +262,13 @@ export default function FormVideoUpdate() {
|
|||
|
||||
if (details?.files) {
|
||||
setFiles(details.files);
|
||||
const initialOptions: { [key: number]: string[] } = {};
|
||||
details.files.forEach((file: any) => {
|
||||
if (file.placements) {
|
||||
initialOptions[file.id] = mapPlacementsToOptions(file.placements);
|
||||
}
|
||||
});
|
||||
setSelectedOptions(initialOptions);
|
||||
}
|
||||
|
||||
if (details?.publishedFor) {
|
||||
|
|
@ -293,6 +300,31 @@ export default function FormVideoUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
const mapPlacementsToOptions = (placements: string): string[] => {
|
||||
const mapping: Record<string, string> = {
|
||||
all: "all",
|
||||
mabes: "nasional",
|
||||
polda: "wilayah",
|
||||
polres: "internasional",
|
||||
};
|
||||
|
||||
// Jika placements hanya "all", langsung aktifkan semua checkbox
|
||||
if (placements.trim() === "all") {
|
||||
return ["all", "nasional", "wilayah", "internasional"];
|
||||
}
|
||||
|
||||
const options = placements
|
||||
.split(",")
|
||||
.map((p) => mapping[p.trim()])
|
||||
.filter(Boolean);
|
||||
|
||||
const allSelected = ["nasional", "wilayah", "internasional"].every((opt) =>
|
||||
options.includes(opt)
|
||||
);
|
||||
|
||||
return allSelected ? ["all", ...options] : options;
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
createTaskTa,
|
||||
getTask,
|
||||
getUserLevelForAssignments,
|
||||
getUserLevelForExpert,
|
||||
} from "@/service/task";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -55,6 +56,7 @@ import TimePicker from "react-time-picker";
|
|||
import "react-time-picker/dist/TimePicker.css";
|
||||
import "react-clock/dist/Clock.css";
|
||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||
import { getListCompetencies } from "@/service/management-user/management-user";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -130,12 +132,17 @@ export default function FormAskExpert() {
|
|||
const [detail, setDetail] = useState<taskDetail>();
|
||||
const [refresh] = useState(false);
|
||||
const [listDest, setListDest] = useState([]);
|
||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||
const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
|
||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
const [userCompetencies, setUserCompetencies] = useState<any[]>([]);
|
||||
const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>(
|
||||
new Set()
|
||||
);
|
||||
const [listExpert, setListExpert] = useState<any[]>([]);
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
|
||||
|
|
@ -151,21 +158,6 @@ export default function FormAskExpert() {
|
|||
from: new Date(2024, 0, 1),
|
||||
});
|
||||
|
||||
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
|
||||
const [unitSelection, setUnitSelection] = useState({
|
||||
semua: false,
|
||||
mabes: false,
|
||||
polda: false,
|
||||
polres: false,
|
||||
satker: false,
|
||||
});
|
||||
const [taskOutput, setTaskOutput] = useState({
|
||||
all: false,
|
||||
video: false,
|
||||
audio: false,
|
||||
image: false,
|
||||
text: false,
|
||||
});
|
||||
const [links, setLinks] = useState<string[]>([""]);
|
||||
|
||||
const {
|
||||
|
|
@ -179,37 +171,68 @@ export default function FormAskExpert() {
|
|||
mode: "all",
|
||||
});
|
||||
|
||||
// const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const selectedValue = Number(event.target.value);
|
||||
// setMainType(selectedValue);
|
||||
useEffect(() => {
|
||||
getDataAdditional();
|
||||
}, []);
|
||||
|
||||
// setPlatformTypeVisible(selectedValue === 2);
|
||||
async function getDataAdditional() {
|
||||
const resCompetencies = await getListCompetencies();
|
||||
console.log("competency", resCompetencies);
|
||||
setUserCompetencies(resCompetencies?.data?.data);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchPoldaPolres() {
|
||||
async function fetchListExpert() {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getUserLevelForAssignments();
|
||||
setListDest(response?.data?.data.list);
|
||||
console.log("polda", response?.data?.data?.list);
|
||||
const initialExpandedState = response?.data?.data.list.reduce(
|
||||
(acc: any, polda: any) => {
|
||||
acc[polda.id] = false;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
setExpandedPolda(initialExpandedState);
|
||||
console.log("polres", initialExpandedState);
|
||||
const response = await getUserLevelForExpert(id);
|
||||
setListExpert(response?.data?.data);
|
||||
console.log("tenaga ahli", response?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching Polda/Polres data:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
fetchPoldaPolres();
|
||||
fetchListExpert();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailMedia(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchExpertsForCompetencies = async () => {
|
||||
const allExperts: any[] = [];
|
||||
|
||||
for (const compId of Array.from(selectedCompetencies)) {
|
||||
const response = await getUserLevelForExpert(compId);
|
||||
const experts = response?.data?.data || [];
|
||||
allExperts.push(...experts);
|
||||
}
|
||||
|
||||
const uniqueExperts = Array.from(
|
||||
new Map(allExperts.map((e) => [e.id, e])).values()
|
||||
);
|
||||
|
||||
setListExpert(uniqueExperts);
|
||||
};
|
||||
|
||||
if (selectedCompetencies.size > 0) {
|
||||
fetchExpertsForCompetencies();
|
||||
} else {
|
||||
setListExpert([]);
|
||||
}
|
||||
}, [selectedCompetencies]);
|
||||
|
||||
// };
|
||||
const handleCheckboxChange = (levelId: number) => {
|
||||
setCheckedLevels((prev) => {
|
||||
|
|
@ -223,217 +246,39 @@ export default function FormAskExpert() {
|
|||
});
|
||||
};
|
||||
|
||||
const handlePoldaPolresChange = () => {
|
||||
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
|
||||
const handleExpertChange = () => {
|
||||
return Array.from(checkedLevels).join(",");
|
||||
};
|
||||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
semua: value,
|
||||
mabes: value,
|
||||
polda: value,
|
||||
polres: value,
|
||||
satker: value,
|
||||
};
|
||||
setUnitSelection(newState);
|
||||
} else {
|
||||
const updatedSelection = {
|
||||
...unitSelection,
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const allChecked = ["mabes", "polda", "polres", "satker"].every(
|
||||
(k) => updatedSelection[k as keyof typeof unitSelection]
|
||||
);
|
||||
|
||||
updatedSelection.semua = allChecked;
|
||||
|
||||
setUnitSelection(updatedSelection);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExpertiseOutputChange = (
|
||||
key: keyof typeof expertise,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
semua: value,
|
||||
komunikasi: value,
|
||||
hukum: value,
|
||||
bahasa: value,
|
||||
ekonomi: value,
|
||||
politik: value,
|
||||
sosiologi: value,
|
||||
ilmuadministrasipemerintah: value,
|
||||
ti: value,
|
||||
};
|
||||
setExpertiseOutput(newState);
|
||||
} else {
|
||||
const updated = {
|
||||
...expertise,
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const allChecked = [
|
||||
"komunikasi",
|
||||
"hukum",
|
||||
"bahasa",
|
||||
"ekonomi",
|
||||
"politik",
|
||||
"sosiologi",
|
||||
"ilmuadministrasipemerintah",
|
||||
"ti",
|
||||
].every((k) => updated[k as keyof typeof expertise]);
|
||||
|
||||
updated.semua = allChecked;
|
||||
setExpertiseOutput(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExpertOutputChange = (
|
||||
key: keyof typeof expert,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
semua: value,
|
||||
};
|
||||
setExpertOutput(newState);
|
||||
} else {
|
||||
const updated = {
|
||||
...expert,
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const allChecked = ["video", "audio", "image", "text"].every(
|
||||
(k) => updated[k as keyof typeof expert]
|
||||
);
|
||||
|
||||
updated.semua = allChecked;
|
||||
setExpertOutput(updated);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await detailMedia(id);
|
||||
const details = response?.data?.data;
|
||||
|
||||
setDetail(details);
|
||||
const handleCompetencyChange = async (competencyId: number) => {
|
||||
setSelectedCompetencies((prev) => {
|
||||
const updated = new Set(prev);
|
||||
if (updated.has(competencyId)) {
|
||||
updated.delete(competencyId);
|
||||
} else {
|
||||
updated.add(competencyId);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleTaskOutputChange = (
|
||||
key: keyof typeof taskOutput,
|
||||
value: boolean
|
||||
) => {
|
||||
if (key === "all") {
|
||||
const newState = {
|
||||
all: value,
|
||||
video: value,
|
||||
audio: value,
|
||||
image: value,
|
||||
text: value,
|
||||
};
|
||||
setTaskOutput(newState);
|
||||
} else {
|
||||
const updated = {
|
||||
...taskOutput,
|
||||
[key]: value,
|
||||
};
|
||||
|
||||
const allChecked = ["video", "audio", "image", "text"].every(
|
||||
(k) => updated[k as keyof typeof taskOutput]
|
||||
);
|
||||
|
||||
updated.all = allChecked;
|
||||
setTaskOutput(updated);
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
const save = async (data: TaskSchema) => {
|
||||
const fileTypeMapping = {
|
||||
all: "1",
|
||||
video: "2",
|
||||
audio: "4",
|
||||
image: "3",
|
||||
text: "5",
|
||||
};
|
||||
|
||||
const areasMapping = {
|
||||
semua: "0",
|
||||
komunikasi: "1",
|
||||
hukum: "2",
|
||||
bahasa: "3",
|
||||
ekonomi: "4",
|
||||
politik: "5",
|
||||
sosiologi: "6",
|
||||
ilmuadministrasipemerintah: "7",
|
||||
ti: "8",
|
||||
};
|
||||
|
||||
const unitMapping = {
|
||||
allUnit: "0",
|
||||
mabes: "1",
|
||||
polda: "2",
|
||||
polres: "3",
|
||||
satker: "4",
|
||||
};
|
||||
const assignmentPurposeString = Object.keys(unitSelection)
|
||||
.filter((key) => unitSelection[key as keyof typeof unitSelection])
|
||||
.map((key) => unitMapping[key as keyof typeof unitMapping])
|
||||
.join(",");
|
||||
|
||||
const selectedOutputs = Object.keys(taskOutput)
|
||||
.filter((key) => taskOutput[key as keyof typeof taskOutput])
|
||||
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping])
|
||||
.join(",");
|
||||
|
||||
const selectedAreaExpert = Object.keys(expertise)
|
||||
.filter((key) => expertise[key as keyof typeof expertise])
|
||||
.map((key) => areasMapping[key as keyof typeof areasMapping])
|
||||
.join(",");
|
||||
|
||||
const requestData: {
|
||||
id?: number;
|
||||
title: string;
|
||||
assignedToLevel: any;
|
||||
assignedToUsers: any;
|
||||
assignmentTypeId: string;
|
||||
fileTypeOutput: string;
|
||||
areasExpertise: string;
|
||||
narration: string;
|
||||
platformType: string | null;
|
||||
assignmentMainTypeId: any;
|
||||
assignmentType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
expertCompetencies: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
// assignmentCategory,
|
||||
assignedToLevel: handlePoldaPolresChange(),
|
||||
assignedToUsers: assignmentPurposeString,
|
||||
assignedToRole: selectedTarget,
|
||||
assignedToUsers: handleExpertChange(),
|
||||
assignmentType: taskType,
|
||||
broadcastType: broadcastType,
|
||||
assignmentMainTypeId: mainType,
|
||||
areasExpertise: selectedAreaExpert,
|
||||
assignmentTypeId: type,
|
||||
fileTypeOutput: selectedOutputs,
|
||||
narration: data.naration,
|
||||
platformType: "",
|
||||
expertCompetencies: "1,2,3",
|
||||
expertCompetencies: Array.from(selectedCompetencies).join(","),
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
};
|
||||
|
|
@ -676,7 +521,6 @@ export default function FormAskExpert() {
|
|||
{detail !== undefined ? (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2">
|
||||
<Label>{t("title")}</Label>
|
||||
<Controller
|
||||
|
|
@ -696,53 +540,6 @@ export default function FormAskExpert() {
|
|||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Label>Ditujukan Kepada :</Label>
|
||||
</div>
|
||||
<div className=" space-y-2">
|
||||
<Label>{t("areas-expertise")}</Label>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{Object.keys(expertise).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={expertise[key as keyof typeof expertise]}
|
||||
onCheckedChange={(value) =>
|
||||
handleExpertiseOutputChange(
|
||||
key as keyof typeof expertise,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("choose-expert")}</Label>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{Object.keys(expert).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={expert[key as keyof typeof expert]}
|
||||
onCheckedChange={(value) =>
|
||||
handleExpertOutputChange(
|
||||
key as keyof typeof expert,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-2 mt-5">
|
||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||
<Popover>
|
||||
|
|
@ -784,27 +581,54 @@ export default function FormAskExpert() {
|
|||
</Popover>
|
||||
</div>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("output-task")}</Label>
|
||||
<Label>{t("areas-expertise")}</Label>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{Object.keys(taskOutput).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
{userCompetencies?.map((item: any) => (
|
||||
<div className="flex items-center gap-2" key={item.id}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={taskOutput[key as keyof typeof taskOutput]}
|
||||
onCheckedChange={(value) =>
|
||||
handleTaskOutputChange(
|
||||
key as keyof typeof taskOutput,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
id={`comp-${item.id}`}
|
||||
checked={selectedCompetencies.has(item.id)}
|
||||
onCheckedChange={() => handleCompetencyChange(item.id)}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
<Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("choose-expert")}</Label>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
[{"Pilih Tenaga Ahli"}]
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Daftar Tenaga Ahli</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||
{listExpert?.map((expert: any) => (
|
||||
<div key={expert.id} className="border p-2">
|
||||
<Label className="flex items-center">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(expert.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(expert.id)
|
||||
}
|
||||
className="mr-3"
|
||||
/>
|
||||
{expert.fullname}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("description")}</Label>
|
||||
<Controller
|
||||
|
|
|
|||
|
|
@ -383,32 +383,22 @@ export default function FormDoItYourself() {
|
|||
const requestData: {
|
||||
id?: number;
|
||||
title: string;
|
||||
assignedToLevel: any;
|
||||
assignedToUsers: any;
|
||||
assignmentTypeId: string;
|
||||
fileTypeOutput: string;
|
||||
narration: string;
|
||||
platformType: string | null;
|
||||
assignmentMainTypeId: any;
|
||||
assignmentType: string;
|
||||
assignedToRole: string;
|
||||
broadcastType: string;
|
||||
expertCompetencies: string;
|
||||
attachmentUrl: string[];
|
||||
} = {
|
||||
...data,
|
||||
// assignmentType,
|
||||
// assignmentCategory,
|
||||
assignedToLevel: handlePoldaPolresChange(),
|
||||
assignedToUsers: assignmentPurposeString,
|
||||
assignedToRole: selectedTarget,
|
||||
assignmentType: taskType,
|
||||
broadcastType: broadcastType,
|
||||
assignmentMainTypeId: mainType,
|
||||
assignmentTypeId: type,
|
||||
fileTypeOutput: selectedOutputs,
|
||||
narration: data.naration,
|
||||
platformType: "",
|
||||
expertCompetencies: "1,2,3",
|
||||
title: data.title,
|
||||
attachmentUrl: links,
|
||||
|
|
@ -695,116 +685,7 @@ export default function FormDoItYourself() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("assignment-selection")}</Label>
|
||||
<div className="flex flex-wrap gap-3 ">
|
||||
{Object.keys(unitSelection).map((key) => (
|
||||
<div className="flex items-center gap-2" key={key}>
|
||||
<Checkbox
|
||||
id={key}
|
||||
checked={
|
||||
unitSelection[key as keyof typeof unitSelection]
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
handleUnitChange(
|
||||
key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
<div className=" lg:pl-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="soft" size="sm" color="primary">
|
||||
[{t("custom")}]
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Daftar Wilayah Polda dan Polres
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||
{listDest.map((polda: any) => (
|
||||
<div key={polda.id} className="border p-2">
|
||||
<Label className="flex items-center">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polda.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polda.id)
|
||||
}
|
||||
className="mr-3"
|
||||
/>
|
||||
{polda.name}
|
||||
<button
|
||||
onClick={() => toggleExpand(polda.id)}
|
||||
className="ml-2 focus:outline-none"
|
||||
>
|
||||
{expandedPolda[polda.id] ? (
|
||||
<ChevronUp size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} />
|
||||
)}
|
||||
</button>
|
||||
</Label>
|
||||
{expandedPolda[polda.id] && (
|
||||
<div className="ml-6 mt-2">
|
||||
<Label className="block">
|
||||
<Checkbox
|
||||
checked={polda?.subDestination?.every(
|
||||
(polres: any) =>
|
||||
checkedLevels.has(polres.id)
|
||||
)}
|
||||
onCheckedChange={(isChecked) => {
|
||||
const updatedLevels = new Set(
|
||||
checkedLevels
|
||||
);
|
||||
polda?.subDestination?.forEach(
|
||||
(polres: any) => {
|
||||
if (isChecked) {
|
||||
updatedLevels.add(polres.id);
|
||||
} else {
|
||||
updatedLevels.delete(polres.id);
|
||||
}
|
||||
}
|
||||
);
|
||||
setCheckedLevels(updatedLevels);
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
Pilih Semua Polres
|
||||
</Label>
|
||||
{polda?.subDestination?.map((polres: any) => (
|
||||
<Label
|
||||
key={polres.id}
|
||||
className="block mt-1"
|
||||
>
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(polres.id)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(polres.id)
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
{polres.name}
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 space-y-2">
|
||||
<Label>{t("type-of-task")}</Label>
|
||||
<RadioGroup
|
||||
|
|
|
|||
|
|
@ -594,6 +594,7 @@ export default function FormTaskTaDetail() {
|
|||
}
|
||||
|
||||
initState();
|
||||
getDataAcceptance();
|
||||
}, []);
|
||||
|
||||
const handleToggleInput = (): void => {
|
||||
|
|
@ -678,10 +679,9 @@ export default function FormTaskTaDetail() {
|
|||
}
|
||||
|
||||
const handleAcceptAcceptance = async () => {
|
||||
const isAccept = true;
|
||||
loading();
|
||||
console.log("Id user :", userId);
|
||||
const response = await acceptAssignment(id, !isAccept);
|
||||
const response = await acceptAssignment(id);
|
||||
|
||||
if (response?.error) {
|
||||
error(response?.message);
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export type taskDetail = {
|
|||
broadcastType: string;
|
||||
narration: string;
|
||||
is_active: string;
|
||||
isAssignmentAccepted: boolean;
|
||||
isDone: any;
|
||||
};
|
||||
|
||||
|
|
@ -398,7 +399,7 @@ export default function FormTaskDetail() {
|
|||
}
|
||||
}
|
||||
initState();
|
||||
// fetchFilteredData();
|
||||
getDataAcceptance();
|
||||
}, [id, refresh]);
|
||||
|
||||
const handleUrlChange = (index: number, newUrl: string) => {
|
||||
|
|
@ -580,16 +581,17 @@ export default function FormTaskDetail() {
|
|||
};
|
||||
|
||||
async function getDataAcceptance() {
|
||||
console.log("Get Acceptance Status >> ");
|
||||
const response = await getAcceptanceAssignmentStatus(id);
|
||||
setStatusAcceptance(response?.data?.data?.isAccept);
|
||||
console.log("Status :", response?.data?.data?.isAccept);
|
||||
console.log("Get Acceptance Status : ", response);
|
||||
}
|
||||
|
||||
const handleAcceptAcceptance = async () => {
|
||||
const isAccept = true;
|
||||
loading();
|
||||
console.log("Id user :", userId);
|
||||
const response = await acceptAssignment(id, !isAccept);
|
||||
const response = await acceptAssignment(id);
|
||||
|
||||
if (response?.error) {
|
||||
error(response?.message);
|
||||
|
|
@ -1413,7 +1415,8 @@ export default function FormTaskDetail() {
|
|||
className="btn btn-primary lg:mx-3"
|
||||
style={
|
||||
statusAcceptance ||
|
||||
detail?.createdBy?.id !== Number(userId)
|
||||
detail?.isAssignmentAccepted == true ||
|
||||
detail?.createdBy?.id == Number(userId)
|
||||
? {
|
||||
display: "none",
|
||||
}
|
||||
|
|
@ -1443,7 +1446,7 @@ export default function FormTaskDetail() {
|
|||
if (!isTableResult) fetchAllData(); // Panggil API saat tombol diklik
|
||||
}}
|
||||
>
|
||||
Hasil Upload {Number(userId)}
|
||||
Hasil Upload
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
export type DetailTicket = {
|
||||
typeId: number;
|
||||
feedId: any;
|
||||
commentId: string;
|
||||
priority: {
|
||||
name: string;
|
||||
};
|
||||
status: {
|
||||
name: string;
|
||||
};
|
||||
emergencyIssue: {
|
||||
date: string;
|
||||
location: string;
|
||||
title: string;
|
||||
urgencyName: string;
|
||||
feedUrl: string;
|
||||
recommendationName: string;
|
||||
link: string;
|
||||
uploadFiles?: string;
|
||||
description: string;
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
// InfoLainnyaModal.tsx
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DetailTicket } from "./info-lainnya-types";
|
||||
|
||||
interface InfoLainnyaModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
data: DetailTicket["emergencyIssue"];
|
||||
}
|
||||
|
||||
export default function InfoLainnyaModal({
|
||||
open,
|
||||
onClose,
|
||||
data,
|
||||
}: InfoLainnyaModalProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent size="md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Info Lainnya</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div className="font-medium">Tanggal</div>
|
||||
<div>:{data?.date}</div>
|
||||
|
||||
<div className="font-medium">Lokasi Kejadian</div>
|
||||
<div>:{data?.location}</div>
|
||||
|
||||
<div className="font-medium">Isu Menonjol</div>
|
||||
<div>:{data?.title}</div>
|
||||
|
||||
<div className="font-medium">Urgensi</div>
|
||||
<div>:{data?.urgencyName}</div>
|
||||
|
||||
<div className="font-medium">Rekomendasi Tindak Lanjut</div>
|
||||
<div>:{data?.recommendationName}</div>
|
||||
|
||||
<div className="font-medium">Link Pendukung</div>
|
||||
<div>
|
||||
:
|
||||
<a
|
||||
href={data?.link}
|
||||
className="text-blue-600"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{data?.link}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{data?.uploadFiles && (
|
||||
<>
|
||||
<div className="font-medium">Lampiran</div>
|
||||
<div>
|
||||
<img
|
||||
src={data?.uploadFiles}
|
||||
alt="Lampiran"
|
||||
className="max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,532 @@
|
|||
"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, parse } from "postcss";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { error } from "@/lib/swal";
|
||||
import { useRouter } from "next/navigation";
|
||||
import InfoLainnyaModal from "./info-lainnya";
|
||||
import { DetailTicket } from "./info-lainnya-types";
|
||||
import { useMediaQuery } from "react-responsive";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
naration: 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;
|
||||
commentFromUserName: string;
|
||||
feedTitle: string;
|
||||
createdBy: {
|
||||
id: number;
|
||||
fullname: string;
|
||||
};
|
||||
sendTo: {
|
||||
id: number;
|
||||
fullname: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default function FormDetailTicketing() {
|
||||
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 [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); // beri tipe number
|
||||
const [replyText, setReplyText] = useState<string>(""); // untuk isi balasan
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(taskSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
setReplyValue(0);
|
||||
const response = await getTicketingDetail(id);
|
||||
setTicketInternal(response?.data?.data || null);
|
||||
setDetail(response?.data?.data);
|
||||
|
||||
if (response?.data !== null) {
|
||||
setDetailTickets(response?.data?.data);
|
||||
}
|
||||
}
|
||||
|
||||
initState();
|
||||
getTicketReply();
|
||||
}, [id]);
|
||||
|
||||
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); // tutup form setelah kirim
|
||||
getTicketReply(); // refresh balasan
|
||||
} catch (err) {
|
||||
console.error("Gagal kirim balasan:", err);
|
||||
}
|
||||
};
|
||||
|
||||
async function getTicketReply() {
|
||||
const res = await getTicketingReply(id);
|
||||
if (res?.data !== null) {
|
||||
setTicketReply(res?.data?.data);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Tambahkan balasan baru ke daftar balasan
|
||||
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",
|
||||
});
|
||||
|
||||
// Reset input dan sembunyikan form balasan
|
||||
setReplyMessage("");
|
||||
setReplyVisible(false);
|
||||
} catch (error) {
|
||||
MySwal.fire({
|
||||
title: "Error",
|
||||
text: "Gagal mengirim balasan.",
|
||||
icon: "error",
|
||||
});
|
||||
console.error("Error sending reply:", error);
|
||||
}
|
||||
};
|
||||
|
||||
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">
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={handleReply} variant="outline">
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
{replyValue === 1 ? "Tutup Balasan" : "Balas"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{replyValue === 1 && (
|
||||
<div className="mt-4 rounded-xl bg-gray-100 p-4">
|
||||
<textarea
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
placeholder="Tulis Pesan"
|
||||
className="w-full resize-none rounded-md border border-gray-300 bg-white p-4 text-sm text-gray-700 focus:outline-none"
|
||||
rows={5}
|
||||
/>
|
||||
<div className="mt-4 flex justify-end gap-3">
|
||||
<Button
|
||||
onClick={() => setReplyValue(0)}
|
||||
variant="outline"
|
||||
className="text-blue-600 border-blue-600"
|
||||
>
|
||||
Batal
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSendReplyData}
|
||||
className="bg-blue-600 text-white hover:bg-blue-700"
|
||||
>
|
||||
Kirim
|
||||
</Button>
|
||||
</div>
|
||||
</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>
|
||||
))}
|
||||
{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?.commentFromUserName}
|
||||
</span>{" "}
|
||||
mengirimkan komentar untuk{" "}
|
||||
<span className="font-bold text-sm">
|
||||
{ticketInternal?.feedTitle}
|
||||
</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>
|
||||
<div className="p-4 bg-white text-sm">
|
||||
<p>{htmlToString(ticketInternal.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>
|
||||
{detail !== undefined && (
|
||||
<div className="gap-5 mb-5 w-[100%] lg:w-[30%] 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"
|
||||
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">
|
||||
<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="space-y-2 px-3 py-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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
"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";
|
||||
|
||||
const taskSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
naration: 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;
|
||||
narration: 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 [replyVisible, setReplyVisible] = useState(false);
|
||||
const [replyMessage, setReplyMessage] = useState("");
|
||||
const [selectedPriority, setSelectedPriority] = useState("");
|
||||
const [selectedStatus, setSelectedStatus] = useState("");
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(taskSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
if (id) {
|
||||
const response = await getTicketingDetail(id);
|
||||
setTicketInternal(response?.data?.data || null);
|
||||
setDetail(response?.data?.data);
|
||||
}
|
||||
}
|
||||
initState();
|
||||
getTicketReply();
|
||||
}, [id]);
|
||||
|
||||
async function getTicketReply() {
|
||||
const res = await getTicketingReply(id);
|
||||
if (res?.data !== null) {
|
||||
setTicketReply(res?.data?.data);
|
||||
}
|
||||
}
|
||||
|
||||
const handleReply = () => {
|
||||
setReplyVisible((prev) => !prev); // Toggle visibility
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Tambahkan balasan baru ke daftar balasan
|
||||
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",
|
||||
});
|
||||
|
||||
// Reset input dan sembunyikan form balasan
|
||||
setReplyMessage("");
|
||||
setReplyVisible(false);
|
||||
} catch (error) {
|
||||
MySwal.fire({
|
||||
title: "Error",
|
||||
text: "Gagal mengirim balasan.",
|
||||
icon: "error",
|
||||
});
|
||||
console.error("Error sending reply:", error);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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">
|
||||
<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={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">
|
||||
<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="space-y-2 px-3 py-3">
|
||||
<Label>Description</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Textarea
|
||||
value={detail?.narration}
|
||||
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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -20,13 +20,13 @@ const AreaCoverageWorkUnits = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (openPolda || openSatker) {
|
||||
document.body.style.overflow = "hidden";
|
||||
document.body.classList.add("overflow-hidden");
|
||||
} else {
|
||||
document.body.style.overflow = "";
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
};
|
||||
}, [openPolda, openSatker]);
|
||||
return (
|
||||
|
|
@ -39,17 +39,25 @@ const AreaCoverageWorkUnits = () => {
|
|||
<div className="flex flex-col justify-center lg:flex-row gap-8 ">
|
||||
{/* POLDA */}
|
||||
<Dialog open={openPolda} onOpenChange={setOpenPolda}>
|
||||
<DialogTrigger className="flex flex-col gap-2 justify-center items-center shadow-lg group rounded-xl py-5 px-24 border-2 border-transparent hover:border-[#bb3523] transition-all duration-300">
|
||||
<Image
|
||||
width={1920}
|
||||
height={1080}
|
||||
alt="indo"
|
||||
src="/assets/indo.png"
|
||||
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] "
|
||||
/>
|
||||
<p className="text-base font-bold">Polda Jajaran</p>
|
||||
<DialogTrigger asChild>
|
||||
<button
|
||||
onClick={() => setOpenPolda(true)}
|
||||
className="flex flex-col gap-2 justify-center items-center shadow-lg group rounded-xl py-5 px-24 border-2 border-transparent hover:border-[#bb3523] transition-all duration-300"
|
||||
>
|
||||
<Image
|
||||
width={1920}
|
||||
height={1080}
|
||||
alt="indo"
|
||||
src="/assets/indo.png"
|
||||
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] "
|
||||
/>
|
||||
<p className="text-base font-bold">Polda Jajaran</p>
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md" className="h-[90vh] overflow-y-auto ">
|
||||
<DialogContent
|
||||
size="md"
|
||||
className="max-h-[90vh] overflow-hidden flex flex-col "
|
||||
>
|
||||
<DialogHeader className="flex flex-col justify-center">
|
||||
<DialogTitle>
|
||||
<p className="text-center">Polda Jajaran</p>
|
||||
|
|
@ -58,11 +66,9 @@ const AreaCoverageWorkUnits = () => {
|
|||
<div className="h-1 w-[150px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="max-h-[70vh] overflow-y-auto">
|
||||
<div className="overflow-y-auto px-1 flex-1">
|
||||
<Coverage />
|
||||
</div>
|
||||
|
||||
<div className="text-right mt-4">
|
||||
<DialogClose asChild>
|
||||
<button className="text-[#bb3523] font-bold">Tutup</button>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
import { getCategoryData, getPublicCategoryData } from "@/service/landing/landing";
|
||||
import {
|
||||
getCategoryData,
|
||||
getPublicCategoryData,
|
||||
} from "@/service/landing/landing";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Reveal } from "./Reveal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "../ui/carousel";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "../ui/carousel";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
|
|
@ -19,14 +28,26 @@ const ContentCategory = (props: { group?: string; type: string }) => {
|
|||
const satkerName = params?.satker_name;
|
||||
const router = useRouter();
|
||||
|
||||
let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/";
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "/";
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
}, []);
|
||||
const initFetch = async () => {
|
||||
const response = await getPublicCategoryData(
|
||||
props.group == "mabes" ? "" : props.group == "polda" && poldaName && String(poldaName)?.length > 1 ? poldaName : props.group == "satker" && satkerName && String(satkerName)?.length > 1 ? "satker-" + satkerName : "",
|
||||
props.group == "mabes"
|
||||
? ""
|
||||
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
|
||||
? poldaName
|
||||
: props.group == "satker" &&
|
||||
satkerName &&
|
||||
String(satkerName)?.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "",
|
||||
"",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
|
|
@ -52,7 +73,10 @@ const ContentCategory = (props: { group?: string; type: string }) => {
|
|||
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||
</svg>`;
|
||||
|
||||
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
|
||||
const toBase64 = (str: string) =>
|
||||
typeof window === "undefined"
|
||||
? Buffer.from(str).toString("base64")
|
||||
: window.btoa(str);
|
||||
|
||||
return (
|
||||
<div className="px-4 lg:px-24 py-10">
|
||||
|
|
@ -60,12 +84,16 @@ const ContentCategory = (props: { group?: string; type: string }) => {
|
|||
<h2 className="text-center text-xl lg:text-3xl font-bold text-[#bb3523] mb-4">
|
||||
{pathname?.split("/")[1] == "in" ? (
|
||||
<>
|
||||
<span className="text-black dark:text-white">{t("category")} </span>
|
||||
<span className="text-black dark:text-white">
|
||||
{t("category")}
|
||||
</span>
|
||||
{t("content")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-black dark:text-white">{t("content")} </span>
|
||||
<span className="text-black dark:text-white">
|
||||
{t("content")}
|
||||
</span>
|
||||
{t("category")}
|
||||
</>
|
||||
)}
|
||||
|
|
@ -73,35 +101,51 @@ const ContentCategory = (props: { group?: string; type: string }) => {
|
|||
<div className="h-1 w-52 bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{(seeAllValue ? categories : categories?.slice(0, 4 ))?.map((category: any) => (
|
||||
<div key={category?.id}>
|
||||
<div onClick={() => router.push(`${prefixPath}all/filter?category=${category?.id}`)} className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg block">
|
||||
{/* Gambar */}
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
|
||||
alt="category"
|
||||
width={2560}
|
||||
height={1440}
|
||||
src={category?.thumbnailLink}
|
||||
className="w-full lg:h-[250px] h-40 object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
/>
|
||||
{(seeAllValue ? categories : categories?.slice(0, 4))?.map(
|
||||
(category: any) => (
|
||||
<div key={category?.id}>
|
||||
<div
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`${prefixPath}all/filter?category=${category?.id}`
|
||||
)
|
||||
}
|
||||
className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg block"
|
||||
>
|
||||
{/* Gambar */}
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
alt="category"
|
||||
width={2560}
|
||||
height={1440}
|
||||
src={category?.thumbnailLink}
|
||||
className="w-full lg:h-[250px] h-40 object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
/>
|
||||
|
||||
{/* Overlay gelap */}
|
||||
<div className="absolute inset-0 bg-black bg-opacity-25 group-hover:bg-opacity-35 transition-all duration-300 rounded-md"></div>
|
||||
{/* Overlay gelap */}
|
||||
<div className="absolute inset-0 bg-black bg-opacity-25 group-hover:bg-opacity-35 transition-all duration-300 rounded-md"></div>
|
||||
|
||||
{/* Judul */}
|
||||
<div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10 group-hover:scale-x-150 origin-left">
|
||||
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
|
||||
{/* Judul */}
|
||||
<div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10 group-hover:scale-x-150 origin-left">
|
||||
<h3 className="text-sm font-semibold truncate">
|
||||
{category?.name}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tombol See More / See Less */}
|
||||
{categories?.length > 8 && (
|
||||
{categories?.length > 10 && (
|
||||
<div className="flex items-center flex-row justify-center mt-6">
|
||||
<Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]">
|
||||
<Button
|
||||
onClick={() => setSeeAllValue(!seeAllValue)}
|
||||
className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]"
|
||||
>
|
||||
{seeAllValue ? t("seeLess") : t("seeMore")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,55 +8,183 @@ import { usePathname } from "next/navigation";
|
|||
import Image from "next/image";
|
||||
|
||||
const regions = [
|
||||
{ name: "Polda Metro Jaya", slug: "metro-jaya", logo: "/logo/polda/polda-metro.png" },
|
||||
{ name: "Polda Jawa Barat", slug: "jawa-barat", logo: "/logo/polda/polda-jawabarat.png" },
|
||||
{ name: "Polda Banten", slug: "banten", logo: "/logo/polda/polda-banten.png" },
|
||||
{ name: "Polda Jawa Tengah", slug: "jawa-tengah", logo: "/logo/polda/polda-jawatengah.png" },
|
||||
{ name: "Polda D.I Yogyakarta", slug: "di-yogyakarta", logo: "/logo/polda/polda-jogja.png" },
|
||||
{ name: "Polda Jawa Timur", slug: "jawa-timur", logo: "/logo/polda/polda-jawatimur.png" },
|
||||
{
|
||||
name: "Polda Metro Jaya",
|
||||
slug: "metro-jaya",
|
||||
logo: "/logo/polda/polda-metro.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Jawa Barat",
|
||||
slug: "jawa-barat",
|
||||
logo: "/logo/polda/polda-jawabarat.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Banten",
|
||||
slug: "banten",
|
||||
logo: "/logo/polda/polda-banten.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Jawa Tengah",
|
||||
slug: "jawa-tengah",
|
||||
logo: "/logo/polda/polda-jawatengah.png",
|
||||
},
|
||||
{
|
||||
name: "Polda D.I Yogyakarta",
|
||||
slug: "di-yogyakarta",
|
||||
logo: "/logo/polda/polda-jogja.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Jawa Timur",
|
||||
slug: "jawa-timur",
|
||||
logo: "/logo/polda/polda-jawatimur.png",
|
||||
},
|
||||
{ name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
{ name: "Polda Sumatera Utara", slug: "sumatera-utara", logo: "/logo/polda/polda-sumut.png" },
|
||||
{ name: "Polda Sumatera Barat", slug: "sumatera-barat", logo: "/logo/polda/polda-sumatera-barat.png" },
|
||||
{
|
||||
name: "Polda Sumatera Utara",
|
||||
slug: "sumatera-utara",
|
||||
logo: "/logo/polda/polda-sumut.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Sumatera Barat",
|
||||
slug: "sumatera-barat",
|
||||
logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
},
|
||||
{ name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
{ name: "Polda Kep. Riau", slug: "kepulauan-riau", logo: "/logo/polda/polda-kepri.png" },
|
||||
{
|
||||
name: "Polda Kep. Riau",
|
||||
slug: "kepulauan-riau",
|
||||
logo: "/logo/polda/polda-kepri.png",
|
||||
},
|
||||
{ name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
{ name: "Polda Sumatera Selatan", slug: "sumatera-selatan", logo: "/logo/polda/polda-sumsel.png" },
|
||||
{ name: "Polda Kep. Bangka Belitung", slug: "bangka-belitung", logo: "/logo/polda/polda-bangkabelitung.png" },
|
||||
{ name: "Polda Bengkulu", slug: "bengkulu", logo: "/logo/polda/polda-bengkulu.png" },
|
||||
{ name: "Polda Lampung", slug: "lampung", logo: "/logo/polda/polda-lampung.png" },
|
||||
{ name: "Polda Nusa Tenggara Barat", slug: "ntb", logo: "/logo/polda/polda-ntb.png" },
|
||||
{ name: "Polda Nusa Tenggara Timur", slug: "ntt", logo: "/logo/polda/polda-ntt.png" },
|
||||
{
|
||||
name: "Polda Sumatera Selatan",
|
||||
slug: "sumatera-selatan",
|
||||
logo: "/logo/polda/polda-sumsel.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kep. Bangka Belitung",
|
||||
slug: "bangka-belitung",
|
||||
logo: "/logo/polda/polda-bangkabelitung.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Bengkulu",
|
||||
slug: "bengkulu",
|
||||
logo: "/logo/polda/polda-bengkulu.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Lampung",
|
||||
slug: "lampung",
|
||||
logo: "/logo/polda/polda-lampung.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Nusa Tenggara Barat",
|
||||
slug: "ntb",
|
||||
logo: "/logo/polda/polda-ntb.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Nusa Tenggara Timur",
|
||||
slug: "ntt",
|
||||
logo: "/logo/polda/polda-ntt.png",
|
||||
},
|
||||
{ name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
{ name: "Polda Kalimantan Barat", slug: "kalimantan-barat", logo: "/logo/polda/polda-kalbar.png" },
|
||||
{ name: "Polda Kalimantan Tengah", slug: "kalimantan-tengah", logo: "/logo/polda/polda-kalteng.png" },
|
||||
{ name: "Polda Kalimantan Selatan", slug: "kalimantan-selatan", logo: "/logo/polda/polda-kalsel.png" },
|
||||
{ name: "Polda Kalimantan Timur", slug: "kalimantan-timur", logo: "/logo/polda/polda-kaltim.png" },
|
||||
{ name: "Polda Kalimantan Utara", slug: "kaltara", logo: "/logo/polda/polda-kaltara.png" },
|
||||
{ name: "Polda Sulawesi Tengah", slug: "sulawesi-tengah", logo: "/logo/polda/polda-sulawesi-tengah.png" },
|
||||
{ name: "Polda Sulawesi Utara", slug: "sulawesi-utara", logo: "/logo/polda/polda-sulawesi-utara.png" },
|
||||
{ name: "Polda Gorontalo", slug: "gorontalo", logo: "/logo/polda/polda-gorontalo.png" },
|
||||
{ name: "Polda Sulawesi Barat", slug: "sulawesi-barat", logo: "/logo/polda/polda-sulbar.png" },
|
||||
{ name: "Polda Sulawesi Selatan", slug: "sulawesi-selatan", logo: "/logo/polda/polda-sulsel.png" },
|
||||
{ name: "Polda Sulawesi Tenggara", slug: "sulawesi-tenggara", logo: "/logo/polda/polda-sulawesi-tenggara.png" },
|
||||
{ name: "Polda Maluku Utara", slug: "maluku-utara", logo: "/logo/polda/polda-maluku-utara.png" },
|
||||
{ name: "Polda Maluku", slug: "maluku", logo: "/logo/polda/polda-maluku.png" },
|
||||
{ name: "Polda Papua Barat", slug: "papua-barat", logo: "/logo/polda/polda-papua-barat.png" },
|
||||
{
|
||||
name: "Polda Kalimantan Barat",
|
||||
slug: "kalimantan-barat",
|
||||
logo: "/logo/polda/polda-kalbar.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Tengah",
|
||||
slug: "kalimantan-tengah",
|
||||
logo: "/logo/polda/polda-kalteng.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Selatan",
|
||||
slug: "kalimantan-selatan",
|
||||
logo: "/logo/polda/polda-kalsel.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Timur",
|
||||
slug: "kalimantan-timur",
|
||||
logo: "/logo/polda/polda-kaltim.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Kalimantan Utara",
|
||||
slug: "kaltara",
|
||||
logo: "/logo/polda/polda-kaltara.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Sulawesi Tengah",
|
||||
slug: "sulawesi-tengah",
|
||||
logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Sulawesi Utara",
|
||||
slug: "sulawesi-utara",
|
||||
logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Gorontalo",
|
||||
slug: "gorontalo",
|
||||
logo: "/logo/polda/polda-gorontalo.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Sulawesi Barat",
|
||||
slug: "sulawesi-barat",
|
||||
logo: "/logo/polda/polda-sulbar.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Sulawesi Selatan",
|
||||
slug: "sulawesi-selatan",
|
||||
logo: "/logo/polda/polda-sulsel.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Sulawesi Tenggara",
|
||||
slug: "sulawesi-tenggara",
|
||||
logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Maluku Utara",
|
||||
slug: "maluku-utara",
|
||||
logo: "/logo/polda/polda-maluku-utara.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Maluku",
|
||||
slug: "maluku",
|
||||
logo: "/logo/polda/polda-maluku.png",
|
||||
},
|
||||
{
|
||||
name: "Polda Papua Barat",
|
||||
slug: "papua-barat",
|
||||
logo: "/logo/polda/polda-papua-barat.png",
|
||||
},
|
||||
{ name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
{ name: "Satuan Kerja POLRI", slug: "satker-polri", logo: "/logo/satker/SATUAN-KERJA-POLRI.png" },
|
||||
{ name: "Internasional", slug: "internasional", logo: "/assets/polda/internasional.png" },
|
||||
{
|
||||
name: "Satuan Kerja POLRI",
|
||||
slug: "satker-polri",
|
||||
logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
},
|
||||
{
|
||||
name: "Internasional",
|
||||
slug: "internasional",
|
||||
logo: "/assets/polda/internasional.png",
|
||||
},
|
||||
];
|
||||
|
||||
const Coverage: React.FC = () => {
|
||||
const [seeAllValue, setSeeAllValue] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions);
|
||||
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(
|
||||
regions
|
||||
);
|
||||
const pathname = usePathname();
|
||||
const t = useTranslations("LandingPage");
|
||||
|
||||
const handleSearch = () => {
|
||||
const value = searchTerm.toLowerCase();
|
||||
|
||||
const filtered = regions.filter((polda) => polda.name.toLowerCase().includes(value));
|
||||
const filtered = regions.filter((polda) =>
|
||||
polda.name.toLowerCase().includes(value)
|
||||
);
|
||||
setFilteredList(filtered);
|
||||
};
|
||||
|
||||
|
|
@ -74,58 +202,38 @@ const Coverage: React.FC = () => {
|
|||
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||
</svg>`;
|
||||
|
||||
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
|
||||
const toBase64 = (str: string) =>
|
||||
typeof window === "undefined"
|
||||
? Buffer.from(str).toString("base64")
|
||||
: window.btoa(str);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Header */}
|
||||
{/* <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||
{pathname?.split("/")[1] == "in" ? (
|
||||
<>
|
||||
{t("coverageOnly")} <span className="text-[#bb3523]">{t("area")}</span>{" "}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t("area")}
|
||||
<span className="text-[#bb3523]">{t("coverageOnly")}</span>
|
||||
</>
|
||||
)}
|
||||
</h2> */}
|
||||
|
||||
{/* <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||
Liputan <span className="text-[#bb3523]">Wilayah</span>
|
||||
</h2> */}
|
||||
|
||||
{/* <div className="h-1 w-48 bg-[#bb3523] mx-auto mb-6 rounded"></div> */}
|
||||
|
||||
{/* Pencarian */}
|
||||
{/* <div className="flex items-center justify-center gap-4 mb-6">
|
||||
<input onChange={(e) => setSearchTerm(e.target.value)} type="text" placeholder={t("search")} className="w-4/5 px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-[#bb3523] focus:outline-none" />
|
||||
<button onClick={handleSearch} className="px-2 w-1/5 lg:px-4 py-2 bg-[#bb3523] text-xs lg:text-base text-white flex justify-center items-center gap-2 rounded-md hover:bg-red-700">
|
||||
{t("searchRegional")}
|
||||
<Icon icon="ri:arrow-right-s-line" fontSize={20} />
|
||||
</button>
|
||||
</div> */}
|
||||
|
||||
{/* Grid Wilayah */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 overflow-y-auto px-4 max-h-[60vh]">
|
||||
{filteredList?.map((region: any) => (
|
||||
<Link key={region.slug} href={`/polda/${region.slug}`} className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all">
|
||||
<div className="mb-1 flex items-center justify-center">
|
||||
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={region.logo} alt={region.name} className="w-14 h-14 object-contain" />
|
||||
</div>
|
||||
<p className="text-xs font-semibold">{region.name}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* {filteredList && filteredList.length > 9 && (
|
||||
<div className="flex justify-center py-5">
|
||||
<Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]">
|
||||
{seeAllValue ? t("seeLess") : t("seeMore")}
|
||||
</Button>
|
||||
<div className="max-h-[60vh] overflow-y-auto px-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 px-4 max-h-[60vh]">
|
||||
{filteredList?.map((region: any) => (
|
||||
<Link
|
||||
key={region.slug}
|
||||
href={`/polda/${region.slug}`}
|
||||
className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all"
|
||||
>
|
||||
<div className="mb-1 flex items-center justify-center">
|
||||
<Image
|
||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||
shimmer(700, 475)
|
||||
)}`}
|
||||
width={1920}
|
||||
height={1080}
|
||||
src={region.logo}
|
||||
alt={region.name}
|
||||
className="w-14 h-14 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs font-semibold">{region.name}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import { listData } from "@/service/landing/landing";
|
||||
import { listData, listStaticBanner } from "@/service/landing/landing";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||
|
|
@ -28,8 +28,17 @@ const HeaderBannerSatker = () => {
|
|||
|
||||
async function fetchData() {
|
||||
const res = await listData("1", "", "", 5, 0, "createdAt", "", "", "satker-" + satkerName);
|
||||
let data = res?.data?.data?.content;
|
||||
setContent(data);
|
||||
var data = res?.data?.data?.content;
|
||||
if (data) {
|
||||
const resStatic = await listStaticBanner(satkerName, false);
|
||||
for (let i = 0; i < resStatic?.data?.data?.length; i++) {
|
||||
const media = resStatic?.data.data[i]?.mediaUpload;
|
||||
media.fileTypeId = media.fileType?.id;
|
||||
data = data.filter((item: any) => item.id != media.id);
|
||||
data.splice(0, 0, media);
|
||||
}
|
||||
setContent(data);
|
||||
}
|
||||
setCenterPadding(`${Math.trunc(Number(window.innerWidth) / 10 + 40)}px`);
|
||||
setIsBannerLoading(false);
|
||||
console.log("Done");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import { listData } from "@/service/landing/landing";
|
||||
import { listData, listStaticBanner } from "@/service/landing/landing";
|
||||
import { useParams } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||
|
|
@ -29,8 +29,17 @@ const HeaderBanner = () => {
|
|||
|
||||
async function fetchData() {
|
||||
const res = await listData("1", "", "", 5, 0, "createdAt", "", "", poldaName);
|
||||
let data = res?.data?.data?.content;
|
||||
setContent(data);
|
||||
var data = res?.data?.data?.content;
|
||||
if (data) {
|
||||
const resStatic = await listStaticBanner(poldaName, false);
|
||||
for (let i = 0; i < resStatic?.data?.data?.length; i++) {
|
||||
const media = resStatic?.data.data[i]?.mediaUpload;
|
||||
media.fileTypeId = media.fileType?.id;
|
||||
data = data.filter((item: any) => item.id != media.id);
|
||||
data.splice(0, 0, media);
|
||||
}
|
||||
setContent(data);
|
||||
}
|
||||
setCenterPadding(`${Math.trunc(Number(window.innerWidth) / 10 + 40)}px`);
|
||||
setIsBannerLoading(false);
|
||||
console.log("Done");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { formatDateToIndonesian, shimmer, toBase64 } from "@/utils/globals";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import "swiper/css/bundle";
|
||||
import "swiper/css/navigation";
|
||||
import { getHeroData } from "@/service/landing/landing";
|
||||
import { getHeroData, listStaticBanner } from "@/service/landing/landing";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||
import {
|
||||
|
|
@ -81,8 +81,20 @@ const HeroModal = ({ onClose }: { onClose: () => void }) => {
|
|||
const initFetch = async () => {
|
||||
const response = await getHeroData();
|
||||
console.log(response);
|
||||
setHeroData(response?.data?.data?.content);
|
||||
let data = response?.data?.data?.content;
|
||||
if (data) {
|
||||
const resStatic = await listStaticBanner();
|
||||
for (let i = 0; i < resStatic?.data?.data?.length; i++) {
|
||||
const media = resStatic?.data.data[i]?.mediaUpload;
|
||||
media.fileTypeId = media.fileType?.id;
|
||||
data = data.filter((item: any) => item.id != media.id);
|
||||
data.splice(0, 0, media);
|
||||
}
|
||||
setHeroData(data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center backdrop-brightness-50 z-50 ">
|
||||
<div className="relative dark:bg-gray-900 rounded-lg w-[90%] md:w-[600px] p-4 shadow-none">
|
||||
|
|
@ -190,16 +202,19 @@ const SurveyIntroModal = ({ onNext }: { onNext: () => void }) => {
|
|||
|
||||
const ONE_MONTH = 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const Hero: React.FC = () => {
|
||||
const Hero = (props: { group?: string }) => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const params = useParams();
|
||||
const locale = params?.locale;
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
const [heroData, setHeroData] = useState<any>();
|
||||
const [content, setContent] = useState<any>();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [showSurveyModal, setShowSurveyModal] = useState(false);
|
||||
const [showFormModal, setShowFormModal] = useState(false);
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
|
|
@ -274,7 +289,28 @@ const Hero: React.FC = () => {
|
|||
const initFetch = async () => {
|
||||
const response = await getHeroData();
|
||||
console.log(response);
|
||||
let data = response?.data?.data?.content;
|
||||
setHeroData(response?.data?.data?.content);
|
||||
if (data) {
|
||||
const resStatic = await listStaticBanner(
|
||||
props.group == "mabes"
|
||||
? ""
|
||||
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
|
||||
? poldaName
|
||||
: props.group == "satker" &&
|
||||
satkerName &&
|
||||
String(satkerName)?.length > 1
|
||||
? "satker-" + satkerName
|
||||
: ""
|
||||
);
|
||||
for (let i = 0; i < resStatic?.data?.data?.length; i++) {
|
||||
const media = resStatic?.data.data[i]?.mediaUpload;
|
||||
media.fileTypeId = media.fileType?.id;
|
||||
data = data.filter((item: any) => item.id != media.id);
|
||||
data.splice(0, 0, media);
|
||||
}
|
||||
setContent(data);
|
||||
}
|
||||
};
|
||||
|
||||
const shimmer = (w: number, h: number) => `
|
||||
|
|
@ -322,7 +358,7 @@ const Hero: React.FC = () => {
|
|||
) : (
|
||||
<Carousel className="lg:w-2/3 lg:h-full ">
|
||||
<CarouselContent>
|
||||
{heroData?.map((list: any) => (
|
||||
{content?.map((list: any) => (
|
||||
<CarouselItem key={list?.id}>
|
||||
<div className="relative h-[310px] lg:h-[460px] mt-1">
|
||||
<Image
|
||||
|
|
@ -438,7 +474,7 @@ const Hero: React.FC = () => {
|
|||
className="w-full h-[73px] object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<div className="w-[280px] lg:w-[200px]">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||
{item?.categoryName}
|
||||
</span>
|
||||
|
|
@ -487,7 +523,7 @@ const Hero: React.FC = () => {
|
|||
className="w-full h-[73px] object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<div className="w-[280px] lg:w-[200px]">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||
{item?.categoryName}
|
||||
</span>
|
||||
|
|
@ -534,7 +570,7 @@ const Hero: React.FC = () => {
|
|||
className="w-full h-[73px] object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[280px] lg:w-auto">
|
||||
<div className="w-[280px] lg:w-[200px]">
|
||||
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||
{item?.categoryName}
|
||||
</span>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,7 +15,10 @@ import { Button } from "./ui/button";
|
|||
import { Label } from "./ui/label";
|
||||
import { Input } from "./ui/input";
|
||||
import pdfGenerator from "@/utils/pdf-generator";
|
||||
import { tableauSignin, tableauViewImage } from "@/service/tableau/tableau-service";
|
||||
import {
|
||||
tableauSignin,
|
||||
tableauViewImage,
|
||||
} from "@/service/tableau/tableau-service";
|
||||
|
||||
const PageTitle = ({
|
||||
title,
|
||||
|
|
@ -24,7 +27,6 @@ const PageTitle = ({
|
|||
title?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
|
||||
const [reportDate, setReportDate] = useState<any>();
|
||||
const pathname = usePathname();
|
||||
const name = pathname?.split("/").slice(1).join(" ");
|
||||
|
|
@ -113,19 +115,45 @@ const PageTitle = ({
|
|||
"516c8790-ccd5-44dc-bb66-b0e146d7168b",
|
||||
reportDate
|
||||
);
|
||||
const blobFrontCover = new Blob([resFrontCover.data], { type: "image/png" });
|
||||
const blobAssignmentDetail = new Blob([resAssignmentDetail.data], { type: "image/png" });
|
||||
const blobFrontCover = new Blob([resFrontCover.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
const blobAssignmentDetail = new Blob([resAssignmentDetail.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
// const blobMonitoringContent = new Blob([resMonitoringContent.data], { type: "image/png" });
|
||||
// const blobMonitoringArticle = new Blob([resMonitoringArticle.data], { type: "image/png" });
|
||||
const blobInteractionContent = new Blob([resInteractionContent.data], { type: "image/png" });
|
||||
const blobTotalInteractionContent = new Blob([resTotalInteractionContent.data], { type: "image/png" });
|
||||
const blobInteractionContentCategory1 = new Blob([resInteractionContentCategory1.data], { type: "image/png" });
|
||||
const blobInteractionContentCategory2 = new Blob([resInteractionContentCategory2.data], { type: "image/png" });
|
||||
const blobInteractionContentCategory3 = new Blob([resInteractionContentCategory3.data], { type: "image/png" });
|
||||
const blobInteractionDistribution = new Blob([resInteractionDistribution.data], { type: "image/png" });
|
||||
const blobAssignments = new Blob([resAssignments.data], { type: "image/png" });
|
||||
const blobInteractionContent = new Blob([resInteractionContent.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
const blobTotalInteractionContent = new Blob(
|
||||
[resTotalInteractionContent.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionContentCategory1 = new Blob(
|
||||
[resInteractionContentCategory1.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionContentCategory2 = new Blob(
|
||||
[resInteractionContentCategory2.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionContentCategory3 = new Blob(
|
||||
[resInteractionContentCategory3.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobInteractionDistribution = new Blob(
|
||||
[resInteractionDistribution.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobAssignments = new Blob([resAssignments.data], {
|
||||
type: "image/png",
|
||||
});
|
||||
const blobUserCount = new Blob([resUserCount.data], { type: "image/png" });
|
||||
const blobUserCountDistribution = new Blob([resUserCountDistribution.data], { type: "image/png" });
|
||||
const blobUserCountDistribution = new Blob(
|
||||
[resUserCountDistribution.data],
|
||||
{ type: "image/png" }
|
||||
);
|
||||
const blobBackCover = new Blob([resBackCover.data], { type: "image/png" });
|
||||
|
||||
console.log(blobFrontCover);
|
||||
|
|
@ -144,14 +172,14 @@ const PageTitle = ({
|
|||
blobAssignments,
|
||||
blobUserCount,
|
||||
blobUserCountDistribution,
|
||||
blobBackCover
|
||||
blobBackCover,
|
||||
]);
|
||||
};
|
||||
|
||||
async function mergeImagesToCanvas(blobs: Blob[]) {
|
||||
try {
|
||||
const images = await Promise.all(
|
||||
blobs.map(blob => {
|
||||
blobs.map((blob) => {
|
||||
return new Promise<HTMLImageElement>((resolve) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
|
|
@ -163,38 +191,37 @@ const PageTitle = ({
|
|||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
// Hitung total tinggi dari semua gambar (stacked vertically)
|
||||
const width = Math.max(...images.map(img => img.width));
|
||||
const width = Math.max(...images.map((img) => img.width));
|
||||
const height = images.reduce((sum, img) => sum + img.height, 0);
|
||||
|
||||
const canvas = document.getElementById('pdf-canvas') as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
|
||||
const canvas = document.getElementById("pdf-canvas") as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
|
||||
let y = 0;
|
||||
for (const img of images) {
|
||||
ctx.drawImage(img, 0, y, img.width, img.height);
|
||||
y += img.height;
|
||||
}
|
||||
|
||||
|
||||
// Simpan hasil sebagai gambar (PNG) atau bisa print
|
||||
const dataURL = canvas.toDataURL('image/png');
|
||||
|
||||
const dataURL = canvas.toDataURL("image/png");
|
||||
|
||||
// Simpan atau cetak (print sebagai PDF)
|
||||
const link = document.createElement('a');
|
||||
const link = document.createElement("a");
|
||||
link.href = dataURL;
|
||||
link.download = 'merged-image.png';
|
||||
link.download = "merged-image.png";
|
||||
link.click();
|
||||
|
||||
|
||||
// Atau tampilkan dan user bisa "Print to PDF"
|
||||
window.open(dataURL, '_blank');
|
||||
window.open(dataURL, "_blank");
|
||||
} catch (error) {
|
||||
console.log("Error :::", error)
|
||||
console.log("Error :::", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Number(roleId) == 2 || Number(roleId) == 11 || Number(roleId) == 12 ? (
|
||||
""
|
||||
|
|
@ -208,7 +235,7 @@ const PageTitle = ({
|
|||
<div className="text-2xl font-medium text-default-800 capitalize">
|
||||
Dashboard
|
||||
</div>
|
||||
<Dialog>
|
||||
{/* <Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Download Report</Button>
|
||||
</DialogTrigger>
|
||||
|
|
@ -229,15 +256,12 @@ const PageTitle = ({
|
|||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={downloadReport}
|
||||
>
|
||||
<Button type="submit" onClick={downloadReport}>
|
||||
Download
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Dialog> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
138
lib/menus.ts
138
lib/menus.ts
|
|
@ -82,7 +82,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
{
|
||||
href: "/contributor/content/teks",
|
||||
label: t("text"),
|
||||
active: pathname.includes("/content/text"),
|
||||
active: pathname.includes("/content/teks"),
|
||||
icon: "heroicons:document",
|
||||
children: [],
|
||||
},
|
||||
|
|
@ -1492,7 +1492,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
{
|
||||
href: "/contributor/content/teks",
|
||||
label: t("text"),
|
||||
active: pathname.includes("/content/text"),
|
||||
active: pathname.includes("/content/teks"),
|
||||
icon: "heroicons:document",
|
||||
children: [],
|
||||
},
|
||||
|
|
@ -1719,7 +1719,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
{
|
||||
href: "/contributor/content/teks",
|
||||
label: t("text"),
|
||||
active: pathname.includes("/content/text"),
|
||||
active: pathname.includes("/content/teks"),
|
||||
icon: "heroicons:document",
|
||||
children: [],
|
||||
},
|
||||
|
|
@ -2421,7 +2421,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
{
|
||||
href: "/contributor/content/teks",
|
||||
label: t("text"),
|
||||
active: pathname.includes("/content/text"),
|
||||
active: pathname.includes("/content/teks"),
|
||||
icon: "heroicons:document",
|
||||
children: [],
|
||||
},
|
||||
|
|
@ -2703,7 +2703,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
id: "faq",
|
||||
href: "/supervisor/faq",
|
||||
label: t("faq"),
|
||||
active: pathname.includes("/frequently-asked-question"),
|
||||
active: pathname.includes("/faq"),
|
||||
icon: "wpf:faq",
|
||||
submenus: [],
|
||||
},
|
||||
|
|
@ -3470,70 +3470,70 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
|||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// groupLabel: "",
|
||||
// id: "settings",
|
||||
// menus: [
|
||||
// {
|
||||
// id: "settings",
|
||||
// href: "/admin/settings",
|
||||
// label: t("settings"),
|
||||
// active: pathname.includes("/settinng"),
|
||||
// icon: "material-symbols:settings",
|
||||
// submenus: [
|
||||
// {
|
||||
// href: "/admin/settings/category",
|
||||
// label: t("category"),
|
||||
// active: pathname === "/admin/settings/category",
|
||||
// icon: "heroicons:arrow-trending-up",
|
||||
// children: [],
|
||||
// },
|
||||
// {
|
||||
// href: "/admin/settings/tag",
|
||||
// label: "Tag",
|
||||
// active: pathname === "/admin/settings/tag",
|
||||
// icon: "heroicons:arrow-trending-up",
|
||||
// children: [],
|
||||
// },
|
||||
// {
|
||||
// href: "/admin/settings/banner",
|
||||
// label: "Banner",
|
||||
// active: pathname === "/admin/settings/banner",
|
||||
// icon: "heroicons:arrow-trending-up",
|
||||
// children: [],
|
||||
// },
|
||||
// {
|
||||
// href: "/admin/settings/feedback",
|
||||
// label: "Feedback",
|
||||
// active: pathname === "/admin/settings/feedback",
|
||||
// icon: "heroicons:arrow-trending-up",
|
||||
// children: [],
|
||||
// },
|
||||
// {
|
||||
// href: "/admin/settings/faq",
|
||||
// label: "FAQ",
|
||||
// active: pathname === "/admin/settings/faq",
|
||||
// icon: "heroicons:arrow-trending-up",
|
||||
// children: [],
|
||||
// },
|
||||
// {
|
||||
// href: "https://nat-mediahub.polri.go.id/",
|
||||
// label: "Mediahub 2022",
|
||||
// active: pathname === "/admin/settings/mediahub-2022",
|
||||
// icon: "heroicons:arrow-trending-up",
|
||||
// children: [],
|
||||
// },
|
||||
// {
|
||||
// href: "/admin/settings/privacy",
|
||||
// label: t("privacy"),
|
||||
// active: pathname === "/admin/settings/privacy",
|
||||
// icon: "heroicons:arrow-trending-up",
|
||||
// children: [],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
groupLabel: "",
|
||||
id: "settings",
|
||||
menus: [
|
||||
{
|
||||
id: "settings",
|
||||
href: "/admin/settings",
|
||||
label: t("settings"),
|
||||
active: pathname.includes("/settinng"),
|
||||
icon: "material-symbols:settings",
|
||||
submenus: [
|
||||
{
|
||||
href: "/admin/settings/category",
|
||||
label: t("category"),
|
||||
active: pathname === "/admin/settings/category",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
href: "/admin/settings/tag",
|
||||
label: "Tag",
|
||||
active: pathname === "/admin/settings/tag",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
href: "/admin/settings/banner",
|
||||
label: "Banner",
|
||||
active: pathname === "/admin/settings/banner",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
href: "/admin/settings/feedback",
|
||||
label: "Feedback",
|
||||
active: pathname === "/admin/settings/feedback",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
href: "/admin/settings/faq",
|
||||
label: "FAQ",
|
||||
active: pathname === "/admin/settings/faq",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
href: "https://nat-mediahub.polri.go.id/",
|
||||
label: "Mediahub 2022",
|
||||
active: pathname === "/admin/settings/mediahub-2022",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
href: "/admin/settings/privacy",
|
||||
label: t("privacy"),
|
||||
active: pathname === "/admin/settings/privacy",
|
||||
icon: "heroicons:arrow-trending-up",
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
} else {
|
||||
menusSelected = [
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@
|
|||
"react-player": "^2.16.0",
|
||||
"react-quill": "^0.0.2",
|
||||
"react-resizable-panels": "^2.0.19",
|
||||
"react-responsive": "^10.0.0",
|
||||
"react-responsive": "^10.0.1",
|
||||
"react-select": "^5.8.3",
|
||||
"react-slick": "^0.30.2",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
|
|
@ -15275,9 +15275,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-responsive": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.0.tgz",
|
||||
"integrity": "sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==",
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz",
|
||||
"integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==",
|
||||
"dependencies": {
|
||||
"hyphenate-style-name": "^1.0.0",
|
||||
"matchmediaquery": "^0.4.2",
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@
|
|||
"react-player": "^2.16.0",
|
||||
"react-quill": "^0.0.2",
|
||||
"react-resizable-panels": "^2.0.19",
|
||||
"react-responsive": "^10.0.0",
|
||||
"react-responsive": "^10.0.1",
|
||||
"react-select": "^5.8.3",
|
||||
"react-slick": "^0.30.2",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export async function listTicketingInternal(
|
|||
page: number,
|
||||
size: any,
|
||||
title: string = "",
|
||||
category: any = "",
|
||||
category: any = ""
|
||||
) {
|
||||
return await httpGetInterceptor(
|
||||
`ticketing/internal/pagination?enablePage=1&size=${size}&page=${page}&title=${title}&category=${category}`
|
||||
|
|
@ -54,6 +54,21 @@ export async function saveTicketing(data: any) {
|
|||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
|
||||
export async function saveTicketReply(data: any) {
|
||||
const url = "ticketing/reply";
|
||||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
|
||||
export async function deleteTicket(id: any) {
|
||||
const url = `ticketing?id=${id}`;
|
||||
return httpDeleteInterceptor(url, id);
|
||||
}
|
||||
|
||||
export async function closeTicket(id: any) {
|
||||
const url = `ticketing/close?id=${id}`;
|
||||
return httpPostInterceptor(url, id);
|
||||
}
|
||||
|
||||
export async function getTicketingInternalDetail(id: any) {
|
||||
const url = `ticketing/internal?id=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
|
|
@ -74,6 +89,11 @@ export async function getTicketingDetail(id: any) {
|
|||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getTicketingReply(id: any) {
|
||||
const url = `ticketing/reply?ticketId=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getTicketCollaborationTeams(id: string | number) {
|
||||
const url = `ticketing/collaboration/teams?ticketId=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
|
|
@ -121,3 +141,13 @@ export async function getEscalationDiscussion(id: any) {
|
|||
const url = `ticketing/escalation/discussion?ticketId=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getQuestionTicket(id: any) {
|
||||
const url = `question?id=${id}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function saveTicketsQuestion(data: any) {
|
||||
const url = "ticketing/convert-question";
|
||||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ export async function getHeroData() {
|
|||
);
|
||||
}
|
||||
|
||||
export async function listStaticBanner(
|
||||
group: any = "",
|
||||
isInt: Boolean = false
|
||||
) {
|
||||
const url = `media/static-banner?group=${group}&isInt=${isInt}`;
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function getPublicCategoryData(
|
||||
group: any = "",
|
||||
type: string = "",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
httpDeleteInterceptor,
|
||||
httpGetInterceptor,
|
||||
httpPostInterceptor,
|
||||
} from "../http-config/http-interceptor-service";
|
||||
|
|
@ -14,6 +15,11 @@ export async function getKnowledgeBaseList(id: number) {
|
|||
}
|
||||
|
||||
export async function saveKnowledgeBaseCategory(data: any) {
|
||||
const url = 'knowledge-base/category';
|
||||
const url = "knowledge-base/category";
|
||||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteKnowledgeBase(id: any) {
|
||||
const url = `knowledge-base?id=${id}`;
|
||||
return httpDeleteInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,24 @@ export async function mediaTrackingSave(data: any) {
|
|||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
|
||||
export async function getMediaTracking(data: any) {
|
||||
// const url = `/media/tracking/monitoring/pagination`;
|
||||
const url = `/media/tracking/monitoring/pagination?enablePagination=1&page=${
|
||||
data.page || 0
|
||||
}&size=${data?.size || 10}`;
|
||||
return httpGetInterceptor(url);
|
||||
export async function getMediaTracking(
|
||||
page: any,
|
||||
title: string = "",
|
||||
size: any
|
||||
) {
|
||||
return httpGetInterceptor(
|
||||
`/media/tracking/monitoring/pagination?enablePagination=1&size=${size}&page=${page}&title=${title}`
|
||||
);
|
||||
}
|
||||
|
||||
// export async function getMediaTracking(data: any) {
|
||||
// // const url = `/media/tracking/monitoring/pagination`;
|
||||
// const url = `/media/tracking/monitoring/pagination?enablePagination=1&page=${
|
||||
// data.page || 0
|
||||
// }&size=${data?.size || 10}`;
|
||||
// return httpGetInterceptor(url);
|
||||
// }
|
||||
|
||||
export async function getMediaTrackingResult(data: any) {
|
||||
// const url = `/media/tracking/monitoring/pagination`;
|
||||
const url = `/media/tracking/monitoring/results/pagination?trackingId=${
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export async function getAcceptance(id: any, isAccept: any) {
|
|||
return httpGetInterceptor(url);
|
||||
}
|
||||
|
||||
export async function acceptAssignment(id: any, isAccept: any) {
|
||||
export async function acceptAssignment(id: any) {
|
||||
const url = `assignment/acceptance?id=${id}`;
|
||||
return httpPostInterceptor(url, id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const config = {
|
|||
extend: {
|
||||
screens: {
|
||||
"custom-lg": "1090px",
|
||||
"custom-lg-button": "1030px",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["DM Sans", "sans-serif"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue