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";
|
} from "@/components/ui/table";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Eye,
|
Eye,
|
||||||
|
|
@ -40,6 +41,7 @@ import {
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuRadioGroup,
|
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 SearchAudioComponent from "@/components/form/media-tracking/search-audio-card";
|
||||||
import TrackingMediaModal from "./modal";
|
import TrackingMediaModal from "./modal";
|
||||||
import { getMediaTracking } from "@/service/media-tracking/media-tracking";
|
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 NewsTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const asPath = usePathname();
|
const asPath = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [search, setSearch] = React.useState("");
|
|
||||||
const [showData, setShowData] = React.useState("10");
|
const [showData, setShowData] = React.useState("10");
|
||||||
const [categories, setCategories] = React.useState<any>();
|
const [categories, setCategories] = React.useState<any>();
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
|
|
@ -96,10 +102,11 @@ const NewsTable = () => {
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [imageData, setImageData] = React.useState<any>();
|
const [imageData, setImageData] = React.useState<any>();
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
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 [contentAll, setContentAll] = React.useState([]);
|
||||||
const [formatFilter, setFormatFilter] = React.useState<any>([]);
|
const [formatFilter, setFormatFilter] = React.useState<any>([]);
|
||||||
const [totalContent, setTotalContent] = React.useState();
|
const [totalContent, setTotalContent] = React.useState();
|
||||||
|
const [search, setSearch] = React.useState<string>("");
|
||||||
const group = searchParams?.get("group");
|
const group = searchParams?.get("group");
|
||||||
const title = searchParams?.get("title");
|
const title = searchParams?.get("title");
|
||||||
const categorie = searchParams?.get("category");
|
const categorie = searchParams?.get("category");
|
||||||
|
|
@ -144,25 +151,22 @@ const NewsTable = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
getDataTable();
|
getDataTable();
|
||||||
}, []);
|
}, [page, showData, search]);
|
||||||
|
|
||||||
const getDataTable = async () => {
|
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;
|
const data = res?.data?.data;
|
||||||
console.log;
|
const newData = data?.content;
|
||||||
if (data) {
|
|
||||||
const startIndex = 10 * (page - 1);
|
newData.forEach((item: any, index: number) => {
|
||||||
let iterate = 0;
|
item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
const newData = data.content.map((value: any) => {
|
|
||||||
iterate++;
|
|
||||||
value.no = startIndex + iterate;
|
|
||||||
return value;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setDataTable(newData);
|
setDataTable(newData);
|
||||||
setTotalData(data.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
setTotalContent(data.totalElements);
|
setTotalContent(data.totalElements);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getCategories() {
|
async function getCategories() {
|
||||||
|
|
@ -203,33 +207,38 @@ const NewsTable = () => {
|
||||||
router.push(`?category=&title=`);
|
router.push(`?category=&title=`);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getData() {
|
// async function getData() {
|
||||||
if (asPath?.includes("/polda/") == true) {
|
// if (asPath?.includes("/polda/") == true) {
|
||||||
if (asPath?.split("/")[2] !== "[polda_name]") {
|
// if (asPath?.split("/")[2] !== "[polda_name]") {
|
||||||
const filter =
|
// const filter =
|
||||||
categoryFilter?.length > 0
|
// categoryFilter?.length > 0
|
||||||
? categoryFilter?.sort().join(",")
|
// ? categoryFilter?.sort().join(",")
|
||||||
: categorie || "";
|
// : categorie || "";
|
||||||
|
|
||||||
const name = title == undefined ? "" : title;
|
// const name = title == undefined ? "" : title;
|
||||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
// const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||||
const filterGroup = group == undefined ? asPath.split("/")[2] : group;
|
// const filterGroup = group == undefined ? asPath.split("/")[2] : group;
|
||||||
loading();
|
// loading();
|
||||||
const response = await listDataAll("", name, filter, "");
|
// const response = await listDataAll("", name, filter, "");
|
||||||
close();
|
// close();
|
||||||
// setGetTotalPage(response?.data?.data?.totalPages);
|
// // setGetTotalPage(response?.data?.data?.totalPages);
|
||||||
// setContentImage(response?.data?.data?.content);
|
// // 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);
|
// setTotalContent(response?.data?.data?.totalElements);
|
||||||
const data = response?.data?.data;
|
// }
|
||||||
const contentData = data?.content;
|
// }
|
||||||
setImageData(contentData);
|
// }
|
||||||
setTotalData(data?.totalElements);
|
|
||||||
setContentAll(response?.data?.data?.content);
|
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setTotalPage(data?.totalPages);
|
setSearch(e.target.value);
|
||||||
setTotalContent(response?.data?.data?.totalElements);
|
table.getColumn("judul")?.setFilterValue(e.target.value);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
const handleKeyUp = () => {
|
||||||
clearTimeout(typingTimer);
|
clearTimeout(typingTimer);
|
||||||
|
|
@ -255,50 +264,72 @@ const NewsTable = () => {
|
||||||
Tracking Berita hari ini!
|
Tracking Berita hari ini!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<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()} />
|
<TrackingMediaModal triggerFetch={() => getDataTable()} />
|
||||||
{/* <Dialog>
|
<div className=" flex flex-row items-center gap-3">
|
||||||
<DialogTrigger asChild>
|
<div className="flex items-center py-2">
|
||||||
<Button className="bg-blue-600" size="md">
|
<div className="mx-3">
|
||||||
Tracking Berita
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button size="md" variant="outline">
|
||||||
|
1 - {showData} Data
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DialogContent className="overflow-y-auto h-[500px] min-w-max mx-5">
|
<DropdownMenuContent className="w-56 text-sm">
|
||||||
<DialogHeader>
|
<DropdownMenuRadioGroup
|
||||||
<DialogTitle>Form Tracking Berita</DialogTitle>
|
value={showData}
|
||||||
</DialogHeader>
|
onValueChange={setShowData}
|
||||||
|
>
|
||||||
<div className="grid gap-4 py-4 px-5">
|
<DropdownMenuRadioItem value="10">
|
||||||
<div className="space-y-2 flex flex-col">
|
1 - 10 Data
|
||||||
<Label htmlFor="link" className="text-sm font-medium">
|
</DropdownMenuRadioItem>
|
||||||
Masukkan Link <span className="text-red-500">*</span>
|
<DropdownMenuRadioItem value="50">
|
||||||
</Label>
|
1 - 50 Data
|
||||||
<input
|
</DropdownMenuRadioItem>
|
||||||
value={searchTitle}
|
<DropdownMenuRadioItem value="100">
|
||||||
onChange={(e) => setSearchTitle(e.target.value)}
|
1 - 100 Data
|
||||||
onKeyUp={handleKeyUp}
|
</DropdownMenuRadioItem>
|
||||||
onKeyDown={handleKeyDown}
|
<DropdownMenuRadioItem value="250">
|
||||||
type="text"
|
1 - 250 Data
|
||||||
placeholder="Search..."
|
</DropdownMenuRadioItem>
|
||||||
className="pl-4 pr-4 py-1 w-full h-10 text-[15px] border focus:outline-none dark:text-white"
|
</DropdownMenuRadioGroup>
|
||||||
/>
|
</DropdownMenuContent>
|
||||||
</div>
|
</DropdownMenu>
|
||||||
<div className="flex flex-col gap-2 w-full">
|
|
||||||
<SearchImageComponent categoryFilter={categoryFilter} />
|
|
||||||
<SearchVideoComponent categoryFilter={categoryFilter} />
|
|
||||||
<SearchDocumentComponent categoryFilter={categoryFilter} />
|
|
||||||
<SearchAudioComponent categoryFilter={categoryFilter} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex justify-end gap-2">
|
<div className="flex items-center py-4">
|
||||||
<Button onClick={cleanCheckbox} variant="outline">
|
<DropdownMenu>
|
||||||
Riset Filter
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
|
Columns <ChevronDown />
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="bg-blue-600 text-white">Tracking Berita</Button>
|
</DropdownMenuTrigger>
|
||||||
</DialogFooter>
|
<DropdownMenuContent align="end">
|
||||||
</DialogContent>
|
{table
|
||||||
</Dialog> */}
|
.getAllColumns()
|
||||||
<Table className="overflow-hidden mt-4">
|
.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>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,12 @@ const columns: ColumnDef<any>[] = [
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "static",
|
accessorKey: "isStaticBanner",
|
||||||
header: "Static Banner",
|
header: "Static Banner",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<StaticToogle
|
<StaticToogle
|
||||||
id={row.original.id}
|
id={row.original.id}
|
||||||
initChecked={row.original.staticPage}
|
initChecked={row.original.isStaticBanner}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -96,22 +96,9 @@ const BannerListTable = () => {
|
||||||
let temp: any;
|
let temp: any;
|
||||||
|
|
||||||
const response = await listBanner();
|
const response = await listBanner();
|
||||||
temp = response?.data?.data;
|
const data = response?.data?.data?.content;
|
||||||
const response2 = await listStaticBanner();
|
console.log("banner", data);
|
||||||
console.log("sadadddd", response2?.data?.data.length);
|
setGetData(data);
|
||||||
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);
|
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,19 +45,17 @@ const columns: ColumnDef<any>[] = [
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "contentType",
|
accessorKey: "mediaTypesString",
|
||||||
header: "Tipe Konten",
|
header: "Tipe Konten",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.getValue("contentType")}</span>
|
<span className="normal-case">{row.getValue("mediaTypesString")}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "isInternational",
|
accessorKey: "publishedLocation",
|
||||||
header: "Wilayah",
|
header: "Wilayah",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">
|
<span className="normal-case">{row.getValue("publishedLocation")}</span>
|
||||||
{row.getValue("isInternational") ? "INT" : "ID"}
|
|
||||||
</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">
|
<div className="p-2">
|
||||||
<Card className="shadow-md rounded-lg overflow-hidden">
|
<Card className="shadow-md rounded-lg overflow-hidden">
|
||||||
<Link
|
<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">
|
<CardContent className="p-0">
|
||||||
<img
|
<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>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
<Link
|
<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">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none items-center">
|
||||||
Jawab
|
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 {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
|
|
@ -9,13 +9,22 @@ import {
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
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 React from "react";
|
||||||
import {Plus, Trash2} from "lucide-react";
|
import { Plus, Trash, Trash2 } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import CreateCategory from "./create-category";
|
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 KnowledgeBase = () => {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
const [categories, setCategories] = React.useState<any>([]);
|
const [categories, setCategories] = React.useState<any>([]);
|
||||||
const [questions, setQuestions] = React.useState<any>([]);
|
const [questions, setQuestions] = React.useState<any>([]);
|
||||||
|
|
||||||
|
|
@ -28,7 +37,7 @@ const KnowledgeBase = () => {
|
||||||
const data = response?.data?.data;
|
const data = response?.data?.data;
|
||||||
if (data) {
|
if (data) {
|
||||||
setCategories(data);
|
setCategories(data);
|
||||||
fetchQuestions(data[0]?.id)
|
fetchQuestions(data[0]?.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +47,51 @@ const KnowledgeBase = () => {
|
||||||
if (data) {
|
if (data) {
|
||||||
setQuestions(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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -56,14 +109,14 @@ const KnowledgeBase = () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
fetchQuestions(category?.id);
|
fetchQuestions(category?.id);
|
||||||
}}
|
}}
|
||||||
className="data-[state=active]:bg-secondary data-[state=active]:text-default rounded-md px-6 py-3 w-full justify-start"
|
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}
|
{category?.name}
|
||||||
<div
|
<div
|
||||||
className="absolute right-2 top-2 hidden group-hover:inline-flex"
|
className="right-2 top-2 hidden group-hover:inline-flex"
|
||||||
// onClick={() => deleteCategory(category?.id)}
|
// onClick={() => deleteCategory(category?.id)}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-3.5 h-3.5 me-1" />
|
<Trash size={20} className="text-red-500" />
|
||||||
</div>
|
</div>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
|
|
@ -72,7 +125,11 @@ const KnowledgeBase = () => {
|
||||||
</Card>
|
</Card>
|
||||||
<div className="lg:col-span-9 md:col-span-7 col-span-12 mt-4 lg:mt-0">
|
<div className="lg:col-span-9 md:col-span-7 col-span-12 mt-4 lg:mt-0">
|
||||||
{categories?.map((cateogry: any, index: number) => (
|
{categories?.map((cateogry: any, index: number) => (
|
||||||
<TabsContent key={index} value={`category-${index}`} className="mt-0">
|
<TabsContent
|
||||||
|
key={index}
|
||||||
|
value={`category-${index}`}
|
||||||
|
className="mt-0"
|
||||||
|
>
|
||||||
<Accordion type="single" collapsible className="w-full">
|
<Accordion type="single" collapsible className="w-full">
|
||||||
{questions?.map((question: any) => (
|
{questions?.map((question: any) => (
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
|
|
@ -80,36 +137,39 @@ const KnowledgeBase = () => {
|
||||||
className="dark:bg-secondary bg-white"
|
className="dark:bg-secondary bg-white"
|
||||||
value={question.id}
|
value={question.id}
|
||||||
>
|
>
|
||||||
<AccordionTrigger
|
<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">
|
||||||
className="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">
|
||||||
{question.question}
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: question.question,
|
||||||
|
}}
|
||||||
|
className="text-left"
|
||||||
|
/>
|
||||||
|
<Trash
|
||||||
|
size={20}
|
||||||
|
className="text-red-500 hover:cursor-pointer"
|
||||||
|
onClick={() => handleDeleteKnowlagde(question.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="dark:bg-secondary bg-white">
|
<AccordionContent className="dark:bg-secondary bg-white">
|
||||||
{question.answer}
|
{question.answer}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
{questions?.length > 0 &&
|
{questions?.length > 0 && (
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button fullWidth size="md" variant="outline">
|
||||||
fullWidth
|
|
||||||
size="md"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<Plus className="w-6 h-6 me-1.5" />
|
<Plus className="w-6 h-6 me-1.5" />
|
||||||
Import
|
Import
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button fullWidth size="md">
|
||||||
fullWidth
|
|
||||||
size="md"
|
|
||||||
>
|
|
||||||
<Plus className="w-6 h-6 me-1.5" />
|
<Plus className="w-6 h-6 me-1.5" />
|
||||||
Add Question
|
Add Question
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
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 { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -12,6 +18,14 @@ import {
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { formatDateToIndonesian } from "@/utils/globals";
|
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>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -90,6 +104,73 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
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 (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|
@ -102,15 +183,29 @@ const columns: ColumnDef<any>[] = [
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
|
<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">
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
<Eye className="w-4 h-4 me-1.5" />
|
||||||
View
|
View
|
||||||
</DropdownMenuItem>
|
</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">
|
<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" />
|
<SquarePen className="w-4 h-4 me-1.5" />
|
||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
</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
|
||||||
|
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" />
|
<Trash2 className="w-4 h-4 me-1.5" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</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>
|
<ReactLenis root>
|
||||||
<div className="pb-14">
|
<div className="pb-14">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Hero />
|
<Hero group="mabes" />
|
||||||
<SearchSection />
|
<SearchSection />
|
||||||
<NewContent group="mabes" type="latest" />
|
<NewContent group="mabes" type="latest" />
|
||||||
<NewContent group="mabes" type="popular" />
|
<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 [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedOptions, setSelectedOptions] = useState<{
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
[fileId: number]: string;
|
[fileId: number]: string[];
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
const options: Option[] = [
|
const options: Option[] = [
|
||||||
|
|
@ -243,6 +243,13 @@ export default function FormAudioUpdate() {
|
||||||
|
|
||||||
if (details?.files) {
|
if (details?.files) {
|
||||||
setFiles(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) {
|
if (details?.publishedFor) {
|
||||||
|
|
@ -274,6 +281,31 @@ export default function FormAudioUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [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) => {
|
const handleCheckboxChange = (id: string) => {
|
||||||
if (id === "all") {
|
if (id === "all") {
|
||||||
// Select all options except "all"
|
// Select all options except "all"
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ import { error } from "@/lib/swal";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useRouter } from "@/i18n/routing";
|
import { useRouter } from "@/i18n/routing";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { UnitMapping } from "@/app/[locale]/(protected)/contributor/agenda-setting/unit-mapping";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -142,6 +143,15 @@ export default function FormImageDetail() {
|
||||||
const [selectedTarget, setSelectedTarget] = useState("");
|
const [selectedTarget, setSelectedTarget] = useState("");
|
||||||
const [files, setFiles] = useState<FileType[]>([]);
|
const [files, setFiles] = useState<FileType[]>([]);
|
||||||
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
|
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";
|
let fileTypeId = "1";
|
||||||
|
|
||||||
|
|
@ -776,6 +786,15 @@ export default function FormImageDetail() {
|
||||||
>
|
>
|
||||||
Wilayah
|
Wilayah
|
||||||
</label>
|
</label>
|
||||||
|
{wilayahPublish.polda && (
|
||||||
|
<UnitMapping
|
||||||
|
unit="Polda"
|
||||||
|
isDetail={false}
|
||||||
|
sendDataToParent={(data: any) =>
|
||||||
|
setSelectedPolda(data)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ export default function FormImageUpdate() {
|
||||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedOptions, setSelectedOptions] = useState<{
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
[fileId: number]: string;
|
[fileId: number]: string[];
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
const options: Option[] = [
|
const options: Option[] = [
|
||||||
|
|
@ -266,6 +266,14 @@ export default function FormImageUpdate() {
|
||||||
|
|
||||||
if (details?.files) {
|
if (details?.files) {
|
||||||
setFiles(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) {
|
if (details?.publishedFor) {
|
||||||
|
|
@ -291,11 +299,30 @@ export default function FormImageUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [refresh, setValue]);
|
||||||
|
|
||||||
// const handleCheckboxChange = (id: number) => {
|
const mapPlacementsToOptions = (placements: string): string[] => {
|
||||||
// setSelectedPublishers((prev) =>
|
const mapping: Record<string, string> = {
|
||||||
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
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) => {
|
const handleCheckboxChange = (id: string) => {
|
||||||
if (id === "all") {
|
if (id === "all") {
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ export default function FormTeksUpdate() {
|
||||||
|
|
||||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||||
const [selectedOptions, setSelectedOptions] = useState<{
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
[fileId: number]: string;
|
[fileId: number]: string[];
|
||||||
}>({});
|
}>({});
|
||||||
const [selectedTarget, setSelectedTarget] = useState("");
|
const [selectedTarget, setSelectedTarget] = useState("");
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
|
|
@ -236,6 +236,13 @@ export default function FormTeksUpdate() {
|
||||||
|
|
||||||
if (details?.files) {
|
if (details?.files) {
|
||||||
setFiles(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) {
|
if (details?.publishedFor) {
|
||||||
|
|
@ -261,6 +268,31 @@ export default function FormTeksUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [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) => {
|
const handleCheckboxChange = (id: string) => {
|
||||||
if (id === "all") {
|
if (id === "all") {
|
||||||
// Select all options except "all"
|
// Select all options except "all"
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ export default function FormVideoUpdate() {
|
||||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedOptions, setSelectedOptions] = useState<{
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
[fileId: number]: string;
|
[fileId: number]: string[];
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
const options: Option[] = [
|
const options: Option[] = [
|
||||||
|
|
@ -262,6 +262,13 @@ export default function FormVideoUpdate() {
|
||||||
|
|
||||||
if (details?.files) {
|
if (details?.files) {
|
||||||
setFiles(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) {
|
if (details?.publishedFor) {
|
||||||
|
|
@ -293,6 +300,31 @@ export default function FormVideoUpdate() {
|
||||||
initState();
|
initState();
|
||||||
}, [refresh, setValue]);
|
}, [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) => {
|
const handleCheckboxChange = (id: string) => {
|
||||||
if (id === "all") {
|
if (id === "all") {
|
||||||
// Select all options except "all"
|
// Select all options except "all"
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {
|
||||||
createTaskTa,
|
createTaskTa,
|
||||||
getTask,
|
getTask,
|
||||||
getUserLevelForAssignments,
|
getUserLevelForAssignments,
|
||||||
|
getUserLevelForExpert,
|
||||||
} from "@/service/task";
|
} from "@/service/task";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
|
@ -55,6 +56,7 @@ import TimePicker from "react-time-picker";
|
||||||
import "react-time-picker/dist/TimePicker.css";
|
import "react-time-picker/dist/TimePicker.css";
|
||||||
import "react-clock/dist/Clock.css";
|
import "react-clock/dist/Clock.css";
|
||||||
import { detailMedia } from "@/service/curated-content/curated-content";
|
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||||
|
import { getListCompetencies } from "@/service/management-user/management-user";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -130,12 +132,17 @@ export default function FormAskExpert() {
|
||||||
const [detail, setDetail] = useState<taskDetail>();
|
const [detail, setDetail] = useState<taskDetail>();
|
||||||
const [refresh] = useState(false);
|
const [refresh] = useState(false);
|
||||||
const [listDest, setListDest] = useState([]);
|
const [listDest, setListDest] = useState([]);
|
||||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
|
||||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
const [timer, setTimer] = useState<number>(120);
|
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 t = useTranslations("Form");
|
||||||
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
|
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
|
||||||
|
|
@ -151,21 +158,6 @@ export default function FormAskExpert() {
|
||||||
from: new Date(2024, 0, 1),
|
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 [links, setLinks] = useState<string[]>([""]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -179,37 +171,68 @@ export default function FormAskExpert() {
|
||||||
mode: "all",
|
mode: "all",
|
||||||
});
|
});
|
||||||
|
|
||||||
// const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
useEffect(() => {
|
||||||
// const selectedValue = Number(event.target.value);
|
getDataAdditional();
|
||||||
// setMainType(selectedValue);
|
}, []);
|
||||||
|
|
||||||
// setPlatformTypeVisible(selectedValue === 2);
|
async function getDataAdditional() {
|
||||||
|
const resCompetencies = await getListCompetencies();
|
||||||
|
console.log("competency", resCompetencies);
|
||||||
|
setUserCompetencies(resCompetencies?.data?.data);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchPoldaPolres() {
|
async function fetchListExpert() {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await getUserLevelForAssignments();
|
const response = await getUserLevelForExpert(id);
|
||||||
setListDest(response?.data?.data.list);
|
setListExpert(response?.data?.data);
|
||||||
console.log("polda", response?.data?.data?.list);
|
console.log("tenaga ahli", response?.data?.data);
|
||||||
const initialExpandedState = response?.data?.data.list.reduce(
|
|
||||||
(acc: any, polda: any) => {
|
|
||||||
acc[polda.id] = false;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
setExpandedPolda(initialExpandedState);
|
|
||||||
console.log("polres", initialExpandedState);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching Polda/Polres data:", error);
|
console.error("Error fetching Polda/Polres data:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
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) => {
|
const handleCheckboxChange = (levelId: number) => {
|
||||||
setCheckedLevels((prev) => {
|
setCheckedLevels((prev) => {
|
||||||
|
|
@ -223,217 +246,39 @@ export default function FormAskExpert() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePoldaPolresChange = () => {
|
const handleExpertChange = () => {
|
||||||
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
|
return Array.from(checkedLevels).join(",");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnitChange = (
|
const handleCompetencyChange = async (competencyId: number) => {
|
||||||
key: keyof typeof unitSelection,
|
setSelectedCompetencies((prev) => {
|
||||||
value: boolean
|
const updated = new Set(prev);
|
||||||
) => {
|
if (updated.has(competencyId)) {
|
||||||
if (key === "semua") {
|
updated.delete(competencyId);
|
||||||
const newState = {
|
|
||||||
semua: value,
|
|
||||||
mabes: value,
|
|
||||||
polda: value,
|
|
||||||
polres: value,
|
|
||||||
satker: value,
|
|
||||||
};
|
|
||||||
setUnitSelection(newState);
|
|
||||||
} else {
|
} else {
|
||||||
const updatedSelection = {
|
updated.add(competencyId);
|
||||||
...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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 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: {
|
const requestData: {
|
||||||
id?: number;
|
id?: number;
|
||||||
title: string;
|
title: string;
|
||||||
assignedToLevel: any;
|
|
||||||
assignedToUsers: any;
|
assignedToUsers: any;
|
||||||
assignmentTypeId: string;
|
assignmentTypeId: string;
|
||||||
fileTypeOutput: string;
|
|
||||||
areasExpertise: string;
|
|
||||||
narration: string;
|
narration: string;
|
||||||
platformType: string | null;
|
|
||||||
assignmentMainTypeId: any;
|
|
||||||
assignmentType: string;
|
assignmentType: string;
|
||||||
assignedToRole: string;
|
|
||||||
broadcastType: string;
|
|
||||||
expertCompetencies: string;
|
expertCompetencies: string;
|
||||||
attachmentUrl: string[];
|
attachmentUrl: string[];
|
||||||
} = {
|
} = {
|
||||||
...data,
|
...data,
|
||||||
// assignmentType,
|
assignedToUsers: handleExpertChange(),
|
||||||
// assignmentCategory,
|
|
||||||
assignedToLevel: handlePoldaPolresChange(),
|
|
||||||
assignedToUsers: assignmentPurposeString,
|
|
||||||
assignedToRole: selectedTarget,
|
|
||||||
assignmentType: taskType,
|
assignmentType: taskType,
|
||||||
broadcastType: broadcastType,
|
|
||||||
assignmentMainTypeId: mainType,
|
|
||||||
areasExpertise: selectedAreaExpert,
|
|
||||||
assignmentTypeId: type,
|
assignmentTypeId: type,
|
||||||
fileTypeOutput: selectedOutputs,
|
|
||||||
narration: data.naration,
|
narration: data.naration,
|
||||||
platformType: "",
|
expertCompetencies: Array.from(selectedCompetencies).join(","),
|
||||||
expertCompetencies: "1,2,3",
|
|
||||||
title: data.title,
|
title: data.title,
|
||||||
attachmentUrl: links,
|
attachmentUrl: links,
|
||||||
};
|
};
|
||||||
|
|
@ -676,7 +521,6 @@ export default function FormAskExpert() {
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="gap-5 mb-5">
|
<div className="gap-5 mb-5">
|
||||||
{/* Input Title */}
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t("title")}</Label>
|
<Label>{t("title")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -696,53 +540,6 @@ export default function FormAskExpert() {
|
||||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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">
|
<div className="flex flex-col space-y-2 mt-5">
|
||||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||||
<Popover>
|
<Popover>
|
||||||
|
|
@ -784,27 +581,54 @@ export default function FormAskExpert() {
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 space-y-2">
|
<div className="mt-5 space-y-2">
|
||||||
<Label>{t("output-task")}</Label>
|
<Label>{t("areas-expertise")}</Label>
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{Object.keys(taskOutput).map((key) => (
|
{userCompetencies?.map((item: any) => (
|
||||||
<div className="flex items-center gap-2" key={key}>
|
<div className="flex items-center gap-2" key={item.id}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={key}
|
id={`comp-${item.id}`}
|
||||||
checked={taskOutput[key as keyof typeof taskOutput]}
|
checked={selectedCompetencies.has(item.id)}
|
||||||
onCheckedChange={(value) =>
|
onCheckedChange={() => handleCompetencyChange(item.id)}
|
||||||
handleTaskOutputChange(
|
|
||||||
key as keyof typeof taskOutput,
|
|
||||||
value as boolean
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={key}>
|
<Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
|
||||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<div className="mt-5 space-y-2">
|
||||||
<Label>{t("description")}</Label>
|
<Label>{t("description")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
|
||||||
|
|
@ -383,32 +383,22 @@ export default function FormDoItYourself() {
|
||||||
const requestData: {
|
const requestData: {
|
||||||
id?: number;
|
id?: number;
|
||||||
title: string;
|
title: string;
|
||||||
assignedToLevel: any;
|
|
||||||
assignedToUsers: any;
|
assignedToUsers: any;
|
||||||
assignmentTypeId: string;
|
assignmentTypeId: string;
|
||||||
fileTypeOutput: string;
|
fileTypeOutput: string;
|
||||||
narration: string;
|
narration: string;
|
||||||
platformType: string | null;
|
|
||||||
assignmentMainTypeId: any;
|
|
||||||
assignmentType: string;
|
assignmentType: string;
|
||||||
assignedToRole: string;
|
|
||||||
broadcastType: string;
|
|
||||||
expertCompetencies: string;
|
expertCompetencies: string;
|
||||||
attachmentUrl: string[];
|
attachmentUrl: string[];
|
||||||
} = {
|
} = {
|
||||||
...data,
|
...data,
|
||||||
// assignmentType,
|
// assignmentType,
|
||||||
// assignmentCategory,
|
// assignmentCategory,
|
||||||
assignedToLevel: handlePoldaPolresChange(),
|
|
||||||
assignedToUsers: assignmentPurposeString,
|
assignedToUsers: assignmentPurposeString,
|
||||||
assignedToRole: selectedTarget,
|
|
||||||
assignmentType: taskType,
|
assignmentType: taskType,
|
||||||
broadcastType: broadcastType,
|
|
||||||
assignmentMainTypeId: mainType,
|
|
||||||
assignmentTypeId: type,
|
assignmentTypeId: type,
|
||||||
fileTypeOutput: selectedOutputs,
|
fileTypeOutput: selectedOutputs,
|
||||||
narration: data.naration,
|
narration: data.naration,
|
||||||
platformType: "",
|
|
||||||
expertCompetencies: "1,2,3",
|
expertCompetencies: "1,2,3",
|
||||||
title: data.title,
|
title: data.title,
|
||||||
attachmentUrl: links,
|
attachmentUrl: links,
|
||||||
|
|
@ -695,116 +685,7 @@ export default function FormDoItYourself() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="mt-5 space-y-2">
|
||||||
<Label>{t("type-of-task")}</Label>
|
<Label>{t("type-of-task")}</Label>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
|
|
|
||||||
|
|
@ -594,6 +594,7 @@ export default function FormTaskTaDetail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
initState();
|
initState();
|
||||||
|
getDataAcceptance();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggleInput = (): void => {
|
const handleToggleInput = (): void => {
|
||||||
|
|
@ -678,10 +679,9 @@ export default function FormTaskTaDetail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAcceptAcceptance = async () => {
|
const handleAcceptAcceptance = async () => {
|
||||||
const isAccept = true;
|
|
||||||
loading();
|
loading();
|
||||||
console.log("Id user :", userId);
|
console.log("Id user :", userId);
|
||||||
const response = await acceptAssignment(id, !isAccept);
|
const response = await acceptAssignment(id);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response?.message);
|
error(response?.message);
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ export type taskDetail = {
|
||||||
broadcastType: string;
|
broadcastType: string;
|
||||||
narration: string;
|
narration: string;
|
||||||
is_active: string;
|
is_active: string;
|
||||||
|
isAssignmentAccepted: boolean;
|
||||||
isDone: any;
|
isDone: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -398,7 +399,7 @@ export default function FormTaskDetail() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
// fetchFilteredData();
|
getDataAcceptance();
|
||||||
}, [id, refresh]);
|
}, [id, refresh]);
|
||||||
|
|
||||||
const handleUrlChange = (index: number, newUrl: string) => {
|
const handleUrlChange = (index: number, newUrl: string) => {
|
||||||
|
|
@ -580,16 +581,17 @@ export default function FormTaskDetail() {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getDataAcceptance() {
|
async function getDataAcceptance() {
|
||||||
|
console.log("Get Acceptance Status >> ");
|
||||||
const response = await getAcceptanceAssignmentStatus(id);
|
const response = await getAcceptanceAssignmentStatus(id);
|
||||||
setStatusAcceptance(response?.data?.data?.isAccept);
|
setStatusAcceptance(response?.data?.data?.isAccept);
|
||||||
console.log("Status :", response?.data?.data?.isAccept);
|
console.log("Get Acceptance Status : ", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAcceptAcceptance = async () => {
|
const handleAcceptAcceptance = async () => {
|
||||||
const isAccept = true;
|
const isAccept = true;
|
||||||
loading();
|
loading();
|
||||||
console.log("Id user :", userId);
|
console.log("Id user :", userId);
|
||||||
const response = await acceptAssignment(id, !isAccept);
|
const response = await acceptAssignment(id);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response?.message);
|
error(response?.message);
|
||||||
|
|
@ -1413,7 +1415,8 @@ export default function FormTaskDetail() {
|
||||||
className="btn btn-primary lg:mx-3"
|
className="btn btn-primary lg:mx-3"
|
||||||
style={
|
style={
|
||||||
statusAcceptance ||
|
statusAcceptance ||
|
||||||
detail?.createdBy?.id !== Number(userId)
|
detail?.isAssignmentAccepted == true ||
|
||||||
|
detail?.createdBy?.id == Number(userId)
|
||||||
? {
|
? {
|
||||||
display: "none",
|
display: "none",
|
||||||
}
|
}
|
||||||
|
|
@ -1443,7 +1446,7 @@ export default function FormTaskDetail() {
|
||||||
if (!isTableResult) fetchAllData(); // Panggil API saat tombol diklik
|
if (!isTableResult) fetchAllData(); // Panggil API saat tombol diklik
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Hasil Upload {Number(userId)}
|
Hasil Upload
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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(() => {
|
useEffect(() => {
|
||||||
if (openPolda || openSatker) {
|
if (openPolda || openSatker) {
|
||||||
document.body.style.overflow = "hidden";
|
document.body.classList.add("overflow-hidden");
|
||||||
} else {
|
} else {
|
||||||
document.body.style.overflow = "";
|
document.body.classList.remove("overflow-hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.body.style.overflow = "";
|
document.body.classList.remove("overflow-hidden");
|
||||||
};
|
};
|
||||||
}, [openPolda, openSatker]);
|
}, [openPolda, openSatker]);
|
||||||
return (
|
return (
|
||||||
|
|
@ -39,7 +39,11 @@ const AreaCoverageWorkUnits = () => {
|
||||||
<div className="flex flex-col justify-center lg:flex-row gap-8 ">
|
<div className="flex flex-col justify-center lg:flex-row gap-8 ">
|
||||||
{/* POLDA */}
|
{/* POLDA */}
|
||||||
<Dialog open={openPolda} onOpenChange={setOpenPolda}>
|
<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">
|
<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
|
<Image
|
||||||
width={1920}
|
width={1920}
|
||||||
height={1080}
|
height={1080}
|
||||||
|
|
@ -48,8 +52,12 @@ const AreaCoverageWorkUnits = () => {
|
||||||
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] "
|
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] "
|
||||||
/>
|
/>
|
||||||
<p className="text-base font-bold">Polda Jajaran</p>
|
<p className="text-base font-bold">Polda Jajaran</p>
|
||||||
|
</button>
|
||||||
</DialogTrigger>
|
</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">
|
<DialogHeader className="flex flex-col justify-center">
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<p className="text-center">Polda Jajaran</p>
|
<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>
|
<div className="h-1 w-[150px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
<div className="overflow-y-auto px-1 flex-1">
|
||||||
<div className="max-h-[70vh] overflow-y-auto">
|
|
||||||
<Coverage />
|
<Coverage />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-right mt-4">
|
<div className="text-right mt-4">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<button className="text-[#bb3523] font-bold">Tutup</button>
|
<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 React, { useEffect, useState } from "react";
|
||||||
import { Reveal } from "./Reveal";
|
import { Reveal } from "./Reveal";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import Image from "next/image";
|
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 { useRouter } from "@/i18n/routing";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
|
@ -19,14 +28,26 @@ const ContentCategory = (props: { group?: string; type: string }) => {
|
||||||
const satkerName = params?.satker_name;
|
const satkerName = params?.satker_name;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/";
|
let prefixPath = poldaName
|
||||||
|
? `/polda/${poldaName}`
|
||||||
|
: satkerName
|
||||||
|
? `/satker/${satkerName}`
|
||||||
|
: "/";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initFetch();
|
initFetch();
|
||||||
}, []);
|
}, []);
|
||||||
const initFetch = async () => {
|
const initFetch = async () => {
|
||||||
const response = await getPublicCategoryData(
|
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
|
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" />
|
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||||
</svg>`;
|
</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 (
|
return (
|
||||||
<div className="px-4 lg:px-24 py-10">
|
<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">
|
<h2 className="text-center text-xl lg:text-3xl font-bold text-[#bb3523] mb-4">
|
||||||
{pathname?.split("/")[1] == "in" ? (
|
{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")}
|
{t("content")}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className="text-black dark:text-white">{t("content")} </span>
|
<span className="text-black dark:text-white">
|
||||||
|
{t("content")}
|
||||||
|
</span>
|
||||||
{t("category")}
|
{t("category")}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -73,12 +101,22 @@ const ContentCategory = (props: { group?: string; type: string }) => {
|
||||||
<div className="h-1 w-52 bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
<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">
|
<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) => (
|
{(seeAllValue ? categories : categories?.slice(0, 4))?.map(
|
||||||
|
(category: any) => (
|
||||||
<div key={category?.id}>
|
<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">
|
<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 */}
|
{/* Gambar */}
|
||||||
<Image
|
<Image
|
||||||
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
|
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||||
|
shimmer(700, 475)
|
||||||
|
)}`}
|
||||||
alt="category"
|
alt="category"
|
||||||
width={2560}
|
width={2560}
|
||||||
height={1440}
|
height={1440}
|
||||||
|
|
@ -91,17 +129,23 @@ const ContentCategory = (props: { group?: string; type: string }) => {
|
||||||
|
|
||||||
{/* Judul */}
|
{/* 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">
|
<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>
|
<h3 className="text-sm font-semibold truncate">
|
||||||
|
{category?.name}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tombol See More / See Less */}
|
{/* Tombol See More / See Less */}
|
||||||
{categories?.length > 8 && (
|
{categories?.length > 10 && (
|
||||||
<div className="flex items-center flex-row justify-center mt-6">
|
<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")}
|
{seeAllValue ? t("seeLess") : t("seeMore")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,55 +8,183 @@ import { usePathname } from "next/navigation";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
const regions = [
|
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 Metro Jaya",
|
||||||
{ name: "Polda Banten", slug: "banten", logo: "/logo/polda/polda-banten.png" },
|
slug: "metro-jaya",
|
||||||
{ name: "Polda Jawa Tengah", slug: "jawa-tengah", logo: "/logo/polda/polda-jawatengah.png" },
|
logo: "/logo/polda/polda-metro.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 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 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 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 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 Sumatera Selatan",
|
||||||
{ name: "Polda Bengkulu", slug: "bengkulu", logo: "/logo/polda/polda-bengkulu.png" },
|
slug: "sumatera-selatan",
|
||||||
{ name: "Polda Lampung", slug: "lampung", logo: "/logo/polda/polda-lampung.png" },
|
logo: "/logo/polda/polda-sumsel.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 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 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 Barat",
|
||||||
{ name: "Polda Kalimantan Selatan", slug: "kalimantan-selatan", logo: "/logo/polda/polda-kalsel.png" },
|
slug: "kalimantan-barat",
|
||||||
{ name: "Polda Kalimantan Timur", slug: "kalimantan-timur", logo: "/logo/polda/polda-kaltim.png" },
|
logo: "/logo/polda/polda-kalbar.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 Kalimantan Tengah",
|
||||||
{ name: "Polda Gorontalo", slug: "gorontalo", logo: "/logo/polda/polda-gorontalo.png" },
|
slug: "kalimantan-tengah",
|
||||||
{ name: "Polda Sulawesi Barat", slug: "sulawesi-barat", logo: "/logo/polda/polda-sulbar.png" },
|
logo: "/logo/polda/polda-kalteng.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 Kalimantan Selatan",
|
||||||
{ name: "Polda Maluku", slug: "maluku", logo: "/logo/polda/polda-maluku.png" },
|
slug: "kalimantan-selatan",
|
||||||
{ name: "Polda Papua Barat", slug: "papua-barat", logo: "/logo/polda/polda-papua-barat.png" },
|
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: "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 Coverage: React.FC = () => {
|
||||||
const [seeAllValue, setSeeAllValue] = useState(false);
|
const [seeAllValue, setSeeAllValue] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(regions);
|
const [filteredList, setFilteredList] = useState<typeof regions | undefined>(
|
||||||
|
regions
|
||||||
|
);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const t = useTranslations("LandingPage");
|
const t = useTranslations("LandingPage");
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
const value = searchTerm.toLowerCase();
|
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);
|
setFilteredList(filtered);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -74,58 +202,38 @@ const Coverage: React.FC = () => {
|
||||||
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||||
</svg>`;
|
</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 (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{/* Header */}
|
<div className="max-h-[60vh] overflow-y-auto px-4">
|
||||||
{/* <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-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]">
|
||||||
{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) => (
|
{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">
|
<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">
|
<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" />
|
<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>
|
</div>
|
||||||
<p className="text-xs font-semibold">{region.name}</p>
|
<p className="text-xs font-semibold">{region.name}</p>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)} */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { listData } from "@/service/landing/landing";
|
import { listData, listStaticBanner } from "@/service/landing/landing";
|
||||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||||
|
|
@ -28,8 +28,17 @@ const HeaderBannerSatker = () => {
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const res = await listData("1", "", "", 5, 0, "createdAt", "", "", "satker-" + satkerName);
|
const res = await listData("1", "", "", 5, 0, "createdAt", "", "", "satker-" + satkerName);
|
||||||
let data = res?.data?.data?.content;
|
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);
|
setContent(data);
|
||||||
|
}
|
||||||
setCenterPadding(`${Math.trunc(Number(window.innerWidth) / 10 + 40)}px`);
|
setCenterPadding(`${Math.trunc(Number(window.innerWidth) / 10 + 40)}px`);
|
||||||
setIsBannerLoading(false);
|
setIsBannerLoading(false);
|
||||||
console.log("Done");
|
console.log("Done");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { listData } from "@/service/landing/landing";
|
import { listData, listStaticBanner } from "@/service/landing/landing";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||||
|
|
@ -29,8 +29,17 @@ const HeaderBanner = () => {
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const res = await listData("1", "", "", 5, 0, "createdAt", "", "", poldaName);
|
const res = await listData("1", "", "", 5, 0, "createdAt", "", "", poldaName);
|
||||||
let data = res?.data?.data?.content;
|
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);
|
setContent(data);
|
||||||
|
}
|
||||||
setCenterPadding(`${Math.trunc(Number(window.innerWidth) / 10 + 40)}px`);
|
setCenterPadding(`${Math.trunc(Number(window.innerWidth) / 10 + 40)}px`);
|
||||||
setIsBannerLoading(false);
|
setIsBannerLoading(false);
|
||||||
console.log("Done");
|
console.log("Done");
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { formatDateToIndonesian, shimmer, toBase64 } from "@/utils/globals";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import "swiper/css/bundle";
|
import "swiper/css/bundle";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import { getHeroData } from "@/service/landing/landing";
|
import { getHeroData, listStaticBanner } from "@/service/landing/landing";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams, usePathname, useRouter } from "next/navigation";
|
import { useParams, usePathname, useRouter } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
|
|
@ -81,8 +81,20 @@ const HeroModal = ({ onClose }: { onClose: () => void }) => {
|
||||||
const initFetch = async () => {
|
const initFetch = async () => {
|
||||||
const response = await getHeroData();
|
const response = await getHeroData();
|
||||||
console.log(response);
|
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 (
|
return (
|
||||||
<div className="fixed inset-0 flex items-center justify-center backdrop-brightness-50 z-50 ">
|
<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">
|
<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 ONE_MONTH = 30 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
const Hero: React.FC = () => {
|
const Hero = (props: { group?: string }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const locale = params?.locale;
|
const locale = params?.locale;
|
||||||
const [isLoading, setIsLoading] = useState<any>(true);
|
const [isLoading, setIsLoading] = useState<any>(true);
|
||||||
const [heroData, setHeroData] = useState<any>();
|
const [heroData, setHeroData] = useState<any>();
|
||||||
|
const [content, setContent] = useState<any>();
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [showSurveyModal, setShowSurveyModal] = useState(false);
|
const [showSurveyModal, setShowSurveyModal] = useState(false);
|
||||||
const [showFormModal, setShowFormModal] = useState(false);
|
const [showFormModal, setShowFormModal] = useState(false);
|
||||||
|
const poldaName = params?.polda_name;
|
||||||
|
const satkerName = params?.satker_name;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
|
|
@ -274,7 +289,28 @@ const Hero: React.FC = () => {
|
||||||
const initFetch = async () => {
|
const initFetch = async () => {
|
||||||
const response = await getHeroData();
|
const response = await getHeroData();
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
let data = response?.data?.data?.content;
|
||||||
setHeroData(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) => `
|
const shimmer = (w: number, h: number) => `
|
||||||
|
|
@ -322,7 +358,7 @@ const Hero: React.FC = () => {
|
||||||
) : (
|
) : (
|
||||||
<Carousel className="lg:w-2/3 lg:h-full ">
|
<Carousel className="lg:w-2/3 lg:h-full ">
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{heroData?.map((list: any) => (
|
{content?.map((list: any) => (
|
||||||
<CarouselItem key={list?.id}>
|
<CarouselItem key={list?.id}>
|
||||||
<div className="relative h-[310px] lg:h-[460px] mt-1">
|
<div className="relative h-[310px] lg:h-[460px] mt-1">
|
||||||
<Image
|
<Image
|
||||||
|
|
@ -438,7 +474,7 @@ const Hero: React.FC = () => {
|
||||||
className="w-full h-[73px] object-cover rounded-lg"
|
className="w-full h-[73px] object-cover rounded-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||||
{item?.categoryName}
|
{item?.categoryName}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -487,7 +523,7 @@ const Hero: React.FC = () => {
|
||||||
className="w-full h-[73px] object-cover rounded-lg"
|
className="w-full h-[73px] object-cover rounded-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||||
{item?.categoryName}
|
{item?.categoryName}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -534,7 +570,7 @@ const Hero: React.FC = () => {
|
||||||
className="w-full h-[73px] object-cover rounded-lg"
|
className="w-full h-[73px] object-cover rounded-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
|
||||||
{item?.categoryName}
|
{item?.categoryName}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -187,15 +187,19 @@ const Navbar = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50">
|
<div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50">
|
||||||
<div className="flex items-center justify-between px-4 lg:px-20 py-0 gap-3">
|
<div className="flex items-center justify-between px-4 lg:px-16 py-2 gap-3">
|
||||||
<div className="flex flex-row gap-8">
|
<div className="flex flex-row gap-2">
|
||||||
{/* Logo */}
|
|
||||||
<Link href={prefixPath} className="flex items-center">
|
<Link href={prefixPath} className="flex items-center">
|
||||||
<Image src="/assets/mediahub-logo.gif" alt="Media Hub Logo" width={2560} height={1440} className="w-[125px] lg:w-[250px] h-[115px] lg:h-full object-contain" />
|
<Image
|
||||||
|
src="/assets/mediahub-logo.gif"
|
||||||
|
alt="Media Hub Logo"
|
||||||
|
width={2560}
|
||||||
|
height={1440}
|
||||||
|
className=" w-[200px] h-full shrink-0 object-contain"
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Nav Menu */}
|
{/* Nav Menu */}
|
||||||
<div className="hidden lg:flex items-center gap-5">
|
<div className="hidden custom-lg-button:flex items-center gap-5">
|
||||||
<NavigationMenu>
|
<NavigationMenu>
|
||||||
<NavigationMenuList>
|
<NavigationMenuList>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
|
|
@ -271,9 +275,7 @@ const Navbar = () => {
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<Link href={prefixPath + "/schedule"} legacyBehavior passHref>
|
<Link href={prefixPath + "/schedule"} legacyBehavior passHref>
|
||||||
<NavigationMenuLink
|
<NavigationMenuLink className="group inline-flex h-10 w-max items-center justify-center rounded-md bg-[#f7f7f7] dark:bg-black xl:px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50">
|
||||||
className={navigationMenuTriggerStyle()}
|
|
||||||
>
|
|
||||||
<span>
|
<span>
|
||||||
<svg
|
<svg
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|
@ -295,9 +297,7 @@ const Navbar = () => {
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<Link href={prefixPath + "/indeks"} legacyBehavior passHref>
|
<Link href={prefixPath + "/indeks"} legacyBehavior passHref>
|
||||||
<NavigationMenuLink
|
<NavigationMenuLink className="group inline-flex h-10 w-max items-center justify-center rounded-md bg-[#f7f7f7] dark:bg-black xl:px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50">
|
||||||
className={navigationMenuTriggerStyle()}
|
|
||||||
>
|
|
||||||
<span>
|
<span>
|
||||||
<svg
|
<svg
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|
@ -325,39 +325,9 @@ const Navbar = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Menu Toggle */}
|
{/* Mobile Menu Toggle */}
|
||||||
<button
|
|
||||||
className="text-black dark:text-white right-0 size-20 h-10 w-10 lg:hidden"
|
|
||||||
onClick={() => setMenuOpen(!menuOpen)}
|
|
||||||
>
|
|
||||||
{menuOpen ? (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="m13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29l-4.3 4.29a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l4.29-4.3l4.29 4.3a1 1 0 0 0 1.42 0a1 1 0 0 0 0-1.42Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m1 5a1 1 0 1 0 0 2h14a1 1 0 1 0 0-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Desktop Navigation */}
|
{/* Desktop Navigation */}
|
||||||
<div className="hidden lg:flex items-center gap-5">
|
<div className="hidden custom-lg-button:flex lg:flex items-center xl:gap-5">
|
||||||
{roleId == undefined ? (
|
{roleId == undefined ? (
|
||||||
""
|
""
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -398,7 +368,7 @@ const Navbar = () => {
|
||||||
<span className="w-2 h-2 bg-red-500 rounded-full"></span>
|
<span className="w-2 h-2 bg-red-500 rounded-full"></span>
|
||||||
<span className="font-medium">{t("live")}</span>
|
<span className="font-medium">{t("live")}</span>
|
||||||
</Link> */}
|
</Link> */}
|
||||||
<div className="flex items-center w-[100px] h-[120px]">
|
<div className="hidden custom-lg-button:flex items-center w-[100px] h-[120px]">
|
||||||
<a href="https://tvradio.polri.go.id/">
|
<a href="https://tvradio.polri.go.id/">
|
||||||
<Image
|
<Image
|
||||||
src="/assets/polriTv.png"
|
src="/assets/polriTv.png"
|
||||||
|
|
@ -411,14 +381,16 @@ const Navbar = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Languange */}
|
{/* Languange */}
|
||||||
<div className="relative inline-block text-left">
|
<div className="hidden custom-lg-button:flex relative text-left">
|
||||||
<LocalSwitcher />
|
<LocalSwitcher />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dark Mode */}
|
{/* Dark Mode */}
|
||||||
|
<div className="hidden custom-lg-button:flex text-left">
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="relative text-gray-600 dark:text-white hidden custom-lg:block">
|
<div className="hidden custom-lg-button:relative text-gray-600 dark:text-white ">
|
||||||
{/* <input
|
{/* <input
|
||||||
value={onSearch}
|
value={onSearch}
|
||||||
onChange={(e) => setOnSearch(e.target.value)}
|
onChange={(e) => setOnSearch(e.target.value)}
|
||||||
|
|
@ -952,7 +924,7 @@ const Navbar = () => {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
// Masuk and Daftar buttons for roleId === null
|
// Masuk and Daftar buttons for roleId === null
|
||||||
<div className="flex flex-wrap justify-center items-center gap-5">
|
<div className="hidden custom-lg-button:flex flex-row justify-center items-center gap-5">
|
||||||
<Link
|
<Link
|
||||||
href="/auth"
|
href="/auth"
|
||||||
className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"
|
className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"
|
||||||
|
|
@ -1005,11 +977,42 @@ const Navbar = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className="text-black dark:text-white right-0 size-20 h-10 w-10 custom-lg-button:hidden flex justify-end"
|
||||||
|
onClick={() => setMenuOpen(!menuOpen)}
|
||||||
|
>
|
||||||
|
{menuOpen ? (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29l-4.3 4.29a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l4.29-4.3l4.29 4.3a1 1 0 0 0 1.42 0a1 1 0 0 0 0-1.42Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m0 6a1 1 0 0 1 1-1h14a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1m1 5a1 1 0 1 0 0 2h14a1 1 0 1 0 0-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Menu */}
|
{/* Mobile Menu */}
|
||||||
{menuOpen && (
|
{menuOpen && (
|
||||||
<div className="lg:hidden absolute bg-[#f7f7f7] dark:bg-slate-600 px-4 py-3 w-full space-y-3 z-50">
|
<div className="custom-lg-button:hidden absolute bg-[#f7f7f7] dark:bg-slate-600 px-4 py-3 w-full space-y-3 z-50 ">
|
||||||
|
<div className="flex flex-row pl-5 ">
|
||||||
<NavigationMenu>
|
<NavigationMenu>
|
||||||
<NavigationMenuList>
|
<NavigationMenuList>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
|
|
@ -1062,7 +1065,9 @@ const Navbar = () => {
|
||||||
)}
|
)}
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
<NavigationMenuLink
|
<NavigationMenuLink
|
||||||
onClick={() => router.push(prefixPath + "/document/filter")}
|
onClick={() =>
|
||||||
|
router.push(prefixPath + "/document/filter")
|
||||||
|
}
|
||||||
className="flex place-items-start gap-1.5 p-2"
|
className="flex place-items-start gap-1.5 p-2"
|
||||||
>
|
>
|
||||||
<p className="text-slate-600 text-left dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center py-2 cursor-pointer">
|
<p className="text-slate-600 text-left dark:text-white hover:text-[#bb3523] flex flex-row justify-center items-center py-2 cursor-pointer">
|
||||||
|
|
@ -1083,7 +1088,9 @@ const Navbar = () => {
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<Link href={prefixPath + "/schedule"} legacyBehavior passHref>
|
<Link href={prefixPath + "/schedule"} legacyBehavior passHref>
|
||||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
<NavigationMenuLink
|
||||||
|
className={navigationMenuTriggerStyle()}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<svg
|
<svg
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|
@ -1105,7 +1112,9 @@ const Navbar = () => {
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<Link href={prefixPath + "/indeks"} legacyBehavior passHref>
|
<Link href={prefixPath + "/indeks"} legacyBehavior passHref>
|
||||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
<NavigationMenuLink
|
||||||
|
className={navigationMenuTriggerStyle()}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<svg
|
<svg
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|
@ -1129,7 +1138,6 @@ const Navbar = () => {
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
</NavigationMenu>
|
</NavigationMenu>
|
||||||
|
|
||||||
<div className="flex flex-row justify-between mx-3">
|
<div className="flex flex-row justify-between mx-3">
|
||||||
{roleId == undefined ? (
|
{roleId == undefined ? (
|
||||||
""
|
""
|
||||||
|
|
@ -1225,6 +1233,7 @@ const Navbar = () => {
|
||||||
className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white"
|
className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white"
|
||||||
/>{" "}
|
/>{" "}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-center items-center mx-3 gap-5">
|
<div className="flex justify-center items-center mx-3 gap-5">
|
||||||
{/* {fullName ? (
|
{/* {fullName ? (
|
||||||
<>
|
<>
|
||||||
|
|
@ -1427,7 +1436,9 @@ const Navbar = () => {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<small>
|
<small>
|
||||||
{`${new Date(list?.createdAt).getDate()}/${
|
{`${new Date(
|
||||||
|
list?.createdAt
|
||||||
|
).getDate()}/${
|
||||||
new Date(list?.createdAt).getMonth() + 1
|
new Date(list?.createdAt).getMonth() + 1
|
||||||
}/${new Date(
|
}/${new Date(
|
||||||
list?.createdAt
|
list?.createdAt
|
||||||
|
|
@ -1516,7 +1527,10 @@ const Navbar = () => {
|
||||||
className="w-full flex items-center gap-2"
|
className="w-full flex items-center gap-2"
|
||||||
onClick={onLogout}
|
onClick={onLogout}
|
||||||
>
|
>
|
||||||
<Icon icon="heroicons:power" className="w-4 h-4" />
|
<Icon
|
||||||
|
icon="heroicons:power"
|
||||||
|
className="w-4 h-4"
|
||||||
|
/>
|
||||||
{t("logOut")}
|
{t("logOut")}
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -1744,7 +1758,10 @@ const Navbar = () => {
|
||||||
className="w-full flex items-center gap-2"
|
className="w-full flex items-center gap-2"
|
||||||
onClick={onLogout}
|
onClick={onLogout}
|
||||||
>
|
>
|
||||||
<Icon icon="heroicons:power" className="w-4 h-4" />
|
<Icon
|
||||||
|
icon="heroicons:power"
|
||||||
|
className="w-4 h-4"
|
||||||
|
/>
|
||||||
{t("logOut")}
|
{t("logOut")}
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -1755,7 +1772,7 @@ const Navbar = () => {
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
// Masuk and Daftar buttons for roleId === null
|
// Masuk and Daftar buttons for roleId === null
|
||||||
<div className="flex justify-center items-center mx-3 gap-5">
|
<div className="flex justify-center items-center mx-3 gap-1 lg:gap-1 xl:gap-5">
|
||||||
<Link
|
<Link
|
||||||
href="/auth"
|
href="/auth"
|
||||||
className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"
|
className="w-full lg:w-max px-4 py-1 bg-[#bb3523] text-white font-semibold rounded-md hover:bg-red-700 text-center"
|
||||||
|
|
@ -1764,7 +1781,10 @@ const Navbar = () => {
|
||||||
</Link>
|
</Link>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button className="w-full lg:w-fit px-4 h-8 border bg-white border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] hover:text-white">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="w-full lg:w-fit px-4 h-8 border bg-white border-[#bb3523] text-[#bb3523] font-semibold rounded-md hover:bg-[#bb3523] hover:text-white"
|
||||||
|
>
|
||||||
{t("register")}
|
{t("register")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
@ -1773,7 +1793,9 @@ const Navbar = () => {
|
||||||
<p className="text-lg font-semibold text-center">
|
<p className="text-lg font-semibold text-center">
|
||||||
{t("categoryReg")}
|
{t("categoryReg")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-base text-center">{t("selectOne")}</p>
|
<p className="text-base text-center">
|
||||||
|
{t("selectOne")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{role?.map((row: any) => (
|
{role?.map((row: any) => (
|
||||||
|
|
@ -1789,7 +1811,10 @@ const Navbar = () => {
|
||||||
setCategory(event.target.value)
|
setCategory(event.target.value)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<label className="ml-2" htmlFor={`category${row.id}`}>
|
<label
|
||||||
|
className="ml-2"
|
||||||
|
htmlFor={`category${row.id}`}
|
||||||
|
>
|
||||||
{row.name}
|
{row.name}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1811,6 +1836,7 @@ const Navbar = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ import { Button } from "./ui/button";
|
||||||
import { Label } from "./ui/label";
|
import { Label } from "./ui/label";
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import pdfGenerator from "@/utils/pdf-generator";
|
import pdfGenerator from "@/utils/pdf-generator";
|
||||||
import { tableauSignin, tableauViewImage } from "@/service/tableau/tableau-service";
|
import {
|
||||||
|
tableauSignin,
|
||||||
|
tableauViewImage,
|
||||||
|
} from "@/service/tableau/tableau-service";
|
||||||
|
|
||||||
const PageTitle = ({
|
const PageTitle = ({
|
||||||
title,
|
title,
|
||||||
|
|
@ -24,7 +27,6 @@ const PageTitle = ({
|
||||||
title?: string;
|
title?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const [reportDate, setReportDate] = useState<any>();
|
const [reportDate, setReportDate] = useState<any>();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const name = pathname?.split("/").slice(1).join(" ");
|
const name = pathname?.split("/").slice(1).join(" ");
|
||||||
|
|
@ -113,19 +115,45 @@ const PageTitle = ({
|
||||||
"516c8790-ccd5-44dc-bb66-b0e146d7168b",
|
"516c8790-ccd5-44dc-bb66-b0e146d7168b",
|
||||||
reportDate
|
reportDate
|
||||||
);
|
);
|
||||||
const blobFrontCover = new Blob([resFrontCover.data], { type: "image/png" });
|
const blobFrontCover = new Blob([resFrontCover.data], {
|
||||||
const blobAssignmentDetail = new Blob([resAssignmentDetail.data], { type: "image/png" });
|
type: "image/png",
|
||||||
|
});
|
||||||
|
const blobAssignmentDetail = new Blob([resAssignmentDetail.data], {
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
// const blobMonitoringContent = new Blob([resMonitoringContent.data], { type: "image/png" });
|
// const blobMonitoringContent = new Blob([resMonitoringContent.data], { type: "image/png" });
|
||||||
// const blobMonitoringArticle = new Blob([resMonitoringArticle.data], { type: "image/png" });
|
// const blobMonitoringArticle = new Blob([resMonitoringArticle.data], { type: "image/png" });
|
||||||
const blobInteractionContent = new Blob([resInteractionContent.data], { type: "image/png" });
|
const blobInteractionContent = new Blob([resInteractionContent.data], {
|
||||||
const blobTotalInteractionContent = new Blob([resTotalInteractionContent.data], { type: "image/png" });
|
type: "image/png",
|
||||||
const blobInteractionContentCategory1 = new Blob([resInteractionContentCategory1.data], { type: "image/png" });
|
});
|
||||||
const blobInteractionContentCategory2 = new Blob([resInteractionContentCategory2.data], { type: "image/png" });
|
const blobTotalInteractionContent = new Blob(
|
||||||
const blobInteractionContentCategory3 = new Blob([resInteractionContentCategory3.data], { type: "image/png" });
|
[resTotalInteractionContent.data],
|
||||||
const blobInteractionDistribution = new Blob([resInteractionDistribution.data], { type: "image/png" });
|
{ type: "image/png" }
|
||||||
const blobAssignments = new Blob([resAssignments.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 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" });
|
const blobBackCover = new Blob([resBackCover.data], { type: "image/png" });
|
||||||
|
|
||||||
console.log(blobFrontCover);
|
console.log(blobFrontCover);
|
||||||
|
|
@ -144,14 +172,14 @@ const PageTitle = ({
|
||||||
blobAssignments,
|
blobAssignments,
|
||||||
blobUserCount,
|
blobUserCount,
|
||||||
blobUserCountDistribution,
|
blobUserCountDistribution,
|
||||||
blobBackCover
|
blobBackCover,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function mergeImagesToCanvas(blobs: Blob[]) {
|
async function mergeImagesToCanvas(blobs: Blob[]) {
|
||||||
try {
|
try {
|
||||||
const images = await Promise.all(
|
const images = await Promise.all(
|
||||||
blobs.map(blob => {
|
blobs.map((blob) => {
|
||||||
return new Promise<HTMLImageElement>((resolve) => {
|
return new Promise<HTMLImageElement>((resolve) => {
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
|
|
@ -165,11 +193,11 @@ const PageTitle = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hitung total tinggi dari semua gambar (stacked vertically)
|
// 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 height = images.reduce((sum, img) => sum + img.height, 0);
|
||||||
|
|
||||||
const canvas = document.getElementById('pdf-canvas') as HTMLCanvasElement;
|
const canvas = document.getElementById("pdf-canvas") as HTMLCanvasElement;
|
||||||
const ctx = canvas.getContext('2d')!;
|
const ctx = canvas.getContext("2d")!;
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
|
|
||||||
|
|
@ -180,22 +208,21 @@ const PageTitle = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simpan hasil sebagai gambar (PNG) atau bisa print
|
// 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)
|
// Simpan atau cetak (print sebagai PDF)
|
||||||
const link = document.createElement('a');
|
const link = document.createElement("a");
|
||||||
link.href = dataURL;
|
link.href = dataURL;
|
||||||
link.download = 'merged-image.png';
|
link.download = "merged-image.png";
|
||||||
link.click();
|
link.click();
|
||||||
|
|
||||||
// Atau tampilkan dan user bisa "Print to PDF"
|
// Atau tampilkan dan user bisa "Print to PDF"
|
||||||
window.open(dataURL, '_blank');
|
window.open(dataURL, "_blank");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error :::", error)
|
console.log("Error :::", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Number(roleId) == 2 || Number(roleId) == 11 || Number(roleId) == 12 ? (
|
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">
|
<div className="text-2xl font-medium text-default-800 capitalize">
|
||||||
Dashboard
|
Dashboard
|
||||||
</div>
|
</div>
|
||||||
<Dialog>
|
{/* <Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline">Download Report</Button>
|
<Button variant="outline">Download Report</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
@ -229,15 +256,12 @@ const PageTitle = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button type="submit" onClick={downloadReport}>
|
||||||
type="submit"
|
|
||||||
onClick={downloadReport}
|
|
||||||
>
|
|
||||||
Download
|
Download
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog> */}
|
||||||
</div>
|
</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",
|
href: "/contributor/content/teks",
|
||||||
label: t("text"),
|
label: t("text"),
|
||||||
active: pathname.includes("/content/text"),
|
active: pathname.includes("/content/teks"),
|
||||||
icon: "heroicons:document",
|
icon: "heroicons:document",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
@ -1492,7 +1492,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
href: "/contributor/content/teks",
|
href: "/contributor/content/teks",
|
||||||
label: t("text"),
|
label: t("text"),
|
||||||
active: pathname.includes("/content/text"),
|
active: pathname.includes("/content/teks"),
|
||||||
icon: "heroicons:document",
|
icon: "heroicons:document",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
@ -1719,7 +1719,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
href: "/contributor/content/teks",
|
href: "/contributor/content/teks",
|
||||||
label: t("text"),
|
label: t("text"),
|
||||||
active: pathname.includes("/content/text"),
|
active: pathname.includes("/content/teks"),
|
||||||
icon: "heroicons:document",
|
icon: "heroicons:document",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
@ -2421,7 +2421,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
href: "/contributor/content/teks",
|
href: "/contributor/content/teks",
|
||||||
label: t("text"),
|
label: t("text"),
|
||||||
active: pathname.includes("/content/text"),
|
active: pathname.includes("/content/teks"),
|
||||||
icon: "heroicons:document",
|
icon: "heroicons:document",
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
|
@ -2703,7 +2703,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
id: "faq",
|
id: "faq",
|
||||||
href: "/supervisor/faq",
|
href: "/supervisor/faq",
|
||||||
label: t("faq"),
|
label: t("faq"),
|
||||||
active: pathname.includes("/frequently-asked-question"),
|
active: pathname.includes("/faq"),
|
||||||
icon: "wpf:faq",
|
icon: "wpf:faq",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
},
|
},
|
||||||
|
|
@ -3470,70 +3470,70 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// groupLabel: "",
|
groupLabel: "",
|
||||||
// id: "settings",
|
id: "settings",
|
||||||
// menus: [
|
menus: [
|
||||||
// {
|
{
|
||||||
// id: "settings",
|
id: "settings",
|
||||||
// href: "/admin/settings",
|
href: "/admin/settings",
|
||||||
// label: t("settings"),
|
label: t("settings"),
|
||||||
// active: pathname.includes("/settinng"),
|
active: pathname.includes("/settinng"),
|
||||||
// icon: "material-symbols:settings",
|
icon: "material-symbols:settings",
|
||||||
// submenus: [
|
submenus: [
|
||||||
// {
|
{
|
||||||
// href: "/admin/settings/category",
|
href: "/admin/settings/category",
|
||||||
// label: t("category"),
|
label: t("category"),
|
||||||
// active: pathname === "/admin/settings/category",
|
active: pathname === "/admin/settings/category",
|
||||||
// icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
// children: [],
|
children: [],
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// href: "/admin/settings/tag",
|
href: "/admin/settings/tag",
|
||||||
// label: "Tag",
|
label: "Tag",
|
||||||
// active: pathname === "/admin/settings/tag",
|
active: pathname === "/admin/settings/tag",
|
||||||
// icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
// children: [],
|
children: [],
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// href: "/admin/settings/banner",
|
href: "/admin/settings/banner",
|
||||||
// label: "Banner",
|
label: "Banner",
|
||||||
// active: pathname === "/admin/settings/banner",
|
active: pathname === "/admin/settings/banner",
|
||||||
// icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
// children: [],
|
children: [],
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// href: "/admin/settings/feedback",
|
href: "/admin/settings/feedback",
|
||||||
// label: "Feedback",
|
label: "Feedback",
|
||||||
// active: pathname === "/admin/settings/feedback",
|
active: pathname === "/admin/settings/feedback",
|
||||||
// icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
// children: [],
|
children: [],
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// href: "/admin/settings/faq",
|
href: "/admin/settings/faq",
|
||||||
// label: "FAQ",
|
label: "FAQ",
|
||||||
// active: pathname === "/admin/settings/faq",
|
active: pathname === "/admin/settings/faq",
|
||||||
// icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
// children: [],
|
children: [],
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// href: "https://nat-mediahub.polri.go.id/",
|
href: "https://nat-mediahub.polri.go.id/",
|
||||||
// label: "Mediahub 2022",
|
label: "Mediahub 2022",
|
||||||
// active: pathname === "/admin/settings/mediahub-2022",
|
active: pathname === "/admin/settings/mediahub-2022",
|
||||||
// icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
// children: [],
|
children: [],
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// href: "/admin/settings/privacy",
|
href: "/admin/settings/privacy",
|
||||||
// label: t("privacy"),
|
label: t("privacy"),
|
||||||
// active: pathname === "/admin/settings/privacy",
|
active: pathname === "/admin/settings/privacy",
|
||||||
// icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
// children: [],
|
children: [],
|
||||||
// },
|
},
|
||||||
// ],
|
],
|
||||||
// },
|
},
|
||||||
// ],
|
],
|
||||||
// },
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
menusSelected = [
|
menusSelected = [
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
"react-player": "^2.16.0",
|
"react-player": "^2.16.0",
|
||||||
"react-quill": "^0.0.2",
|
"react-quill": "^0.0.2",
|
||||||
"react-resizable-panels": "^2.0.19",
|
"react-resizable-panels": "^2.0.19",
|
||||||
"react-responsive": "^10.0.0",
|
"react-responsive": "^10.0.1",
|
||||||
"react-select": "^5.8.3",
|
"react-select": "^5.8.3",
|
||||||
"react-slick": "^0.30.2",
|
"react-slick": "^0.30.2",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
|
@ -15275,9 +15275,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-responsive": {
|
"node_modules/react-responsive": {
|
||||||
"version": "10.0.0",
|
"version": "10.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz",
|
||||||
"integrity": "sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==",
|
"integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hyphenate-style-name": "^1.0.0",
|
"hyphenate-style-name": "^1.0.0",
|
||||||
"matchmediaquery": "^0.4.2",
|
"matchmediaquery": "^0.4.2",
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
"react-player": "^2.16.0",
|
"react-player": "^2.16.0",
|
||||||
"react-quill": "^0.0.2",
|
"react-quill": "^0.0.2",
|
||||||
"react-resizable-panels": "^2.0.19",
|
"react-resizable-panels": "^2.0.19",
|
||||||
"react-responsive": "^10.0.0",
|
"react-responsive": "^10.0.1",
|
||||||
"react-select": "^5.8.3",
|
"react-select": "^5.8.3",
|
||||||
"react-slick": "^0.30.2",
|
"react-slick": "^0.30.2",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export async function listTicketingInternal(
|
||||||
page: number,
|
page: number,
|
||||||
size: any,
|
size: any,
|
||||||
title: string = "",
|
title: string = "",
|
||||||
category: any = "",
|
category: any = ""
|
||||||
) {
|
) {
|
||||||
return await httpGetInterceptor(
|
return await httpGetInterceptor(
|
||||||
`ticketing/internal/pagination?enablePage=1&size=${size}&page=${page}&title=${title}&category=${category}`
|
`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);
|
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) {
|
export async function getTicketingInternalDetail(id: any) {
|
||||||
const url = `ticketing/internal?id=${id}`;
|
const url = `ticketing/internal?id=${id}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
|
|
@ -74,6 +89,11 @@ export async function getTicketingDetail(id: any) {
|
||||||
return httpGetInterceptor(url);
|
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) {
|
export async function getTicketCollaborationTeams(id: string | number) {
|
||||||
const url = `ticketing/collaboration/teams?ticketId=${id}`;
|
const url = `ticketing/collaboration/teams?ticketId=${id}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
|
|
@ -121,3 +141,13 @@ export async function getEscalationDiscussion(id: any) {
|
||||||
const url = `ticketing/escalation/discussion?ticketId=${id}`;
|
const url = `ticketing/escalation/discussion?ticketId=${id}`;
|
||||||
return httpGetInterceptor(url);
|
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(
|
export async function getPublicCategoryData(
|
||||||
group: any = "",
|
group: any = "",
|
||||||
type: string = "",
|
type: string = "",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
httpDeleteInterceptor,
|
||||||
httpGetInterceptor,
|
httpGetInterceptor,
|
||||||
httpPostInterceptor,
|
httpPostInterceptor,
|
||||||
} from "../http-config/http-interceptor-service";
|
} from "../http-config/http-interceptor-service";
|
||||||
|
|
@ -14,6 +15,11 @@ export async function getKnowledgeBaseList(id: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveKnowledgeBaseCategory(data: any) {
|
export async function saveKnowledgeBaseCategory(data: any) {
|
||||||
const url = 'knowledge-base/category';
|
const url = "knowledge-base/category";
|
||||||
return httpPostInterceptor(url, data);
|
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);
|
return httpPostInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMediaTracking(data: any) {
|
export async function getMediaTracking(
|
||||||
// const url = `/media/tracking/monitoring/pagination`;
|
page: any,
|
||||||
const url = `/media/tracking/monitoring/pagination?enablePagination=1&page=${
|
title: string = "",
|
||||||
data.page || 0
|
size: any
|
||||||
}&size=${data?.size || 10}`;
|
) {
|
||||||
return httpGetInterceptor(url);
|
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) {
|
export async function getMediaTrackingResult(data: any) {
|
||||||
// const url = `/media/tracking/monitoring/pagination`;
|
// const url = `/media/tracking/monitoring/pagination`;
|
||||||
const url = `/media/tracking/monitoring/results/pagination?trackingId=${
|
const url = `/media/tracking/monitoring/results/pagination?trackingId=${
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ export async function getAcceptance(id: any, isAccept: any) {
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function acceptAssignment(id: any, isAccept: any) {
|
export async function acceptAssignment(id: any) {
|
||||||
const url = `assignment/acceptance?id=${id}`;
|
const url = `assignment/acceptance?id=${id}`;
|
||||||
return httpPostInterceptor(url, id);
|
return httpPostInterceptor(url, id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const config = {
|
||||||
extend: {
|
extend: {
|
||||||
screens: {
|
screens: {
|
||||||
"custom-lg": "1090px",
|
"custom-lg": "1090px",
|
||||||
|
"custom-lg-button": "1030px",
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["DM Sans", "sans-serif"],
|
sans: ["DM Sans", "sans-serif"],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue