Merge branch 'main' of https://gitlab.com/hanifsalafi/mediahub_redesign into prod
This commit is contained in:
commit
76ab479e2d
|
|
@ -201,7 +201,7 @@ const AddExpertTable = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">Tenaga Ahli</p>
|
<p className="text-xl font-medium text-default-900">Tenaga Ahli</p>
|
||||||
<Link href="/admin/add-experts/create">
|
<Link href="/admin/add-experts/create">
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ export default function AddExpertForm() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white rounded-sm p-4"
|
className="space-y-3 bg-white dark:bg-black rounded-sm p-4"
|
||||||
>
|
>
|
||||||
<p className="fonnt-semibold">Campaign</p>
|
<p className="fonnt-semibold">Campaign</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,8 @@ export default function ContentManagement() {
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<Accordion id="polri" type="single" collapsible className="w-full">
|
<Accordion id="polri" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
KONTEN YANG DISIMPAN OLEH PENGGUNA POLRI INDONESIA
|
KONTEN YANG DISIMPAN OLEH PENGGUNA POLRI INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -161,8 +161,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="2" type="single" collapsible className="w-full">
|
<Accordion id="2" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-2" className="bg-white w-full">
|
<AccordionItem value="item-2" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -189,8 +189,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="3" type="single" collapsible className="w-full">
|
<Accordion id="3" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
KONTEN YANG DISIMPAN OLEH PENGGUNA JURNALIS INTERNASIONAL
|
KONTEN YANG DISIMPAN OLEH PENGGUNA JURNALIS INTERNASIONAL
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -217,8 +217,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="4" type="single" collapsible className="w-full">
|
<Accordion id="4" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA POLRI INDONESIA
|
PENAMBAHAN JUMLAH PENGGUNA POLRI INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -245,8 +245,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="5" type="single" collapsible className="w-full">
|
<Accordion id="5" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INDONESIA
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -273,8 +273,8 @@ export default function ContentManagement() {
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion id="6" type="single" collapsible className="w-full">
|
<Accordion id="6" type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INTERNASIONAL
|
PENAMBAHAN JUMLAH PENGGUNA JURNALIS INTERNASIONAL
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@ export default function EmergencyIssue() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
ANALISA BERKAITAN DENGAN AKUN PELAPOR{" "}
|
ANALISA BERKAITAN DENGAN AKUN PELAPOR{" "}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,8 @@ export default function FeedbackCenter() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
TICKET PADA FEEDBACK CENTER{" "}
|
TICKET PADA FEEDBACK CENTER{" "}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
PUBLISH JADWAL PRESS CONFERENCE TERBANYAK
|
PUBLISH JADWAL PRESS CONFERENCE TERBANYAK
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -147,8 +147,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
JUMLAH PRODUKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
JUMLAH PRODUKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -180,8 +180,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
TINGKAT INTERAKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
TINGKAT INTERAKSI KONTEN UNTUK KATEGORI PRESS CONFERENCE
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
@ -213,8 +213,8 @@ export default function ContentManagement() {
|
||||||
collapsible
|
collapsible
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<AccordionItem value="item-1" className="bg-white w-full">
|
<AccordionItem value="item-1" className="bg-white dark:bg-black w-full">
|
||||||
<AccordionTrigger className="bg-white">
|
<AccordionTrigger className="bg-white dark:bg-black">
|
||||||
AKTIFITAS MEDIA BERKAITAN DENGAN PERS RILIS
|
AKTIFITAS MEDIA BERKAITAN DENGAN PERS RILIS
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
|
|
|
||||||
|
|
@ -30,34 +30,27 @@ const columns: ColumnDef<any>[] = [
|
||||||
accessorKey: "accountName",
|
accessorKey: "accountName",
|
||||||
header: "Nama",
|
header: "Nama",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.getValue("accountName")}</span>
|
<span className="normal-case">{row.original.mediaBlastAccountName}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "accountType",
|
accessorKey: "accountType",
|
||||||
header: "Tipe Akun",
|
header: "Tipe Akun",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.getValue("accountType")}</span>
|
<span className="normal-case">{row.original.mediaBlastAccountType}</span>
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "accountCategory",
|
|
||||||
header: "Kategory",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<span className="uppercase">{row.getValue("accountCategory")}</span>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "emailAddress",
|
accessorKey: "emailAddress",
|
||||||
header: "Email",
|
header: "Email",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="normal-case">{row.getValue("emailAddress")}</span>
|
<span className="normal-case">{row.original.mediaBlastAccountEmail}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "whatsappNumber",
|
accessorKey: "whatsappNumber",
|
||||||
header: "Whatsapp",
|
header: "Whatsapp",
|
||||||
cell: ({ row }) => <span>{row.getValue("whatsappNumber")}</span>,
|
cell: ({ row }) => <span>{row.original.mediaBlastAccountPhone}</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
SortingState,
|
SortingState,
|
||||||
|
|
@ -15,7 +14,6 @@ import {
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
|
@ -24,25 +22,60 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { UserIcon } from "lucide-react";
|
import {
|
||||||
|
Dialog,
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
DialogContent,
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
DialogFooter,
|
||||||
import columns from "./column";
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogClose,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Select as UISelect,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { getMediaBlastAccountPage } from "@/service/broadcast/broadcast";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { Icon } from "@iconify/react";
|
||||||
import { Link } from "@/i18n/routing";
|
import { useParams, useSearchParams } from "next/navigation";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import ReactSelect from "react-select";
|
||||||
|
|
||||||
|
import columns from "./column";
|
||||||
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
|
import {
|
||||||
|
getMediaBlastCampaignAccountList,
|
||||||
|
deleteMediaBlastCampaignAccount,
|
||||||
|
saveMediaBlastCampaignAccountBulk,
|
||||||
|
} from "@/service/broadcast/broadcast";
|
||||||
|
import { AdministrationUserList } from "@/service/management-user/management-user";
|
||||||
|
import { close, loading, error, success, successCallback } from "@/config/swal";
|
||||||
|
|
||||||
|
// Mock data for available accounts - replace with actual API call
|
||||||
|
const availableAccounts = [
|
||||||
|
{ id: "1", accountName: "Account 1", category: "polri" },
|
||||||
|
{ id: "2", accountName: "Account 2", category: "jurnalis" },
|
||||||
|
{ id: "3", accountName: "Account 3", category: "umum" },
|
||||||
|
{ id: "4", accountName: "Account 4", category: "ksp" },
|
||||||
|
{ id: "5", accountName: "Account 5", category: "polri" },
|
||||||
|
];
|
||||||
|
|
||||||
const AccountListTable = () => {
|
const AccountListTable = () => {
|
||||||
const router = useRouter();
|
const params = useParams();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const campaignId = params?.id as string;
|
||||||
|
|
||||||
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
const [totalData, setTotalData] = React.useState<number>(1);
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
|
@ -56,10 +89,18 @@ const AccountListTable = () => {
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [totalPage, setTotalPage] = React.useState(1);
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
const [filtered, setFiltered] = React.useState<string[]>([]);
|
const [filtered, setFiltered] = React.useState<string[]>([]);
|
||||||
|
|
||||||
|
// --- state utk Dialog Pilih Akun ---
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
||||||
|
const [accountCategory, setAccountCategory] = React.useState<string>("");
|
||||||
|
const [selectedAccount, setSelectedAccount] = React.useState<any[]>([]);
|
||||||
|
const [selectedCategory, setSelectedCategory] = React.useState<string>("");
|
||||||
|
const [availableAccountsList, setAvailableAccountsList] = React.useState<any[]>(availableAccounts);
|
||||||
|
const [usersList, setUsersList] = React.useState<any[]>([]);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataTable,
|
data: dataTable,
|
||||||
columns,
|
columns,
|
||||||
|
|
@ -83,24 +124,24 @@ const AccountListTable = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const pageFromUrl = searchParams?.get("page");
|
const pageFromUrl = searchParams?.get("page");
|
||||||
if (pageFromUrl) {
|
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||||
setPage(Number(pageFromUrl));
|
|
||||||
}
|
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page]);
|
}, [page, filtered]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
loading();
|
loading();
|
||||||
const res = await getMediaBlastAccountPage(
|
const res = await getMediaBlastCampaignAccountList(
|
||||||
page - 1,
|
page - 1,
|
||||||
filtered ? filtered.join(",") : ""
|
filtered ? filtered.join(",") : "",
|
||||||
|
campaignId
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content || [];
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * 10 + index + 1;
|
item.no = (page - 1) * 10 + index + 1;
|
||||||
});
|
});
|
||||||
|
|
@ -109,41 +150,304 @@ const AccountListTable = () => {
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
close();
|
close();
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Error fetching tasks:", error);
|
console.error("Error fetching tasks:", err);
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- API helpers ---
|
||||||
|
async function doDeleteAccount(id: string) {
|
||||||
|
loading();
|
||||||
|
const response = await deleteMediaBlastCampaignAccount(id);
|
||||||
|
close();
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCampaignAccount() {
|
||||||
|
try {
|
||||||
|
loading();
|
||||||
|
|
||||||
|
if (accountCategory === "all-account") {
|
||||||
|
// Handle all accounts - send only campaignId and category "all"
|
||||||
|
const request = {
|
||||||
|
mediaBlastCampaignId: campaignId,
|
||||||
|
mediaBlastAccountCategory: "all",
|
||||||
|
};
|
||||||
|
const response = await saveMediaBlastCampaignAccountBulk(request);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (accountCategory === "kategori" && selectedCategory) {
|
||||||
|
// Handle category selection - send campaignId and role-based category
|
||||||
|
let roleId = "";
|
||||||
|
switch (selectedCategory) {
|
||||||
|
case "umum":
|
||||||
|
roleId = "5";
|
||||||
|
break;
|
||||||
|
case "jurnalis":
|
||||||
|
roleId = "6";
|
||||||
|
break;
|
||||||
|
case "polri":
|
||||||
|
roleId = "7";
|
||||||
|
break;
|
||||||
|
case "ksp":
|
||||||
|
roleId = "8";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
roleId = "5";
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
mediaBlastCampaignId: campaignId,
|
||||||
|
mediaBlastAccountCategory: `role-${roleId}`,
|
||||||
|
};
|
||||||
|
const response = await saveMediaBlastCampaignAccountBulk(request);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (accountCategory === "custom") {
|
||||||
|
// Handle custom selection - send campaignId and selected user IDs
|
||||||
|
const request = {
|
||||||
|
mediaBlastCampaignId: campaignId,
|
||||||
|
mediaBlastAccountIds: selectedAccount.map(acc => acc.id),
|
||||||
|
};
|
||||||
|
const response = await saveMediaBlastCampaignAccountBulk(request);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
successCallback("Akun berhasil ditambahkan ke campaign!");
|
||||||
|
resetDialogState();
|
||||||
|
fetchData();
|
||||||
|
} catch (err) {
|
||||||
|
close();
|
||||||
|
error("Terjadi kesalahan saat menyimpan akun");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetDialogState = () => {
|
||||||
|
setAccountCategory("");
|
||||||
|
setSelectedAccount([]);
|
||||||
|
setSelectedCategory("");
|
||||||
|
setUsersList([]);
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchUsersList = async () => {
|
||||||
|
try {
|
||||||
|
loading();
|
||||||
|
const response = await AdministrationUserList(
|
||||||
|
"1", // levelId
|
||||||
|
0, // page
|
||||||
|
"", // name
|
||||||
|
"100", // size
|
||||||
|
"1", // featureId
|
||||||
|
"" // role
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response?.data?.data?.content) {
|
||||||
|
setUsersList(response.data.data.content);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
} catch (err) {
|
||||||
|
close();
|
||||||
|
error("Terjadi kesalahan saat mengambil daftar user");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleFilter = (id: string, checked: boolean) => {
|
const handleFilter = (id: string, checked: boolean) => {
|
||||||
let temp = [...filtered];
|
let temp = [...filtered];
|
||||||
if (checked) {
|
if (checked) temp = [...temp, id];
|
||||||
temp = [...temp, id];
|
else temp = temp.filter((a) => a !== id);
|
||||||
} else {
|
|
||||||
temp = temp.filter((a) => a !== id);
|
|
||||||
}
|
|
||||||
setFiltered(temp);
|
setFiltered(temp);
|
||||||
// console.log("sss", temp);
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const removeSelectedAccount = (accountId: string) => {
|
||||||
|
setSelectedAccount(selectedAccount.filter(acc => acc.id !== accountId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilteredAccounts = () => {
|
||||||
|
if (accountCategory === "kategori" && selectedCategory) {
|
||||||
|
return availableAccountsList.filter(acc => acc.category === selectedCategory);
|
||||||
|
}
|
||||||
|
return availableAccountsList;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-3 items-center">
|
<div className="flex justify-between mb-3 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">Daftar Akun</p>
|
<p className="text-xl font-medium text-default-900">Daftar Akun</p>
|
||||||
<div className="flex flex-row gap-3">
|
<div className="flex flex-row gap-3">
|
||||||
<Link href="/admin/broadcast/campaign-list/account-list/create">
|
{/* === Dialog Pilih Akun === */}
|
||||||
<Button color="primary" size="md" className="text-sm">
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
<Icon icon="tdesign:user-add-filled" />
|
<DialogTrigger asChild>
|
||||||
Tambah Akun
|
<Button size="sm" className="text-sm">
|
||||||
</Button>
|
<Icon icon="tdesign:user-add-filled" className="mr-2" />
|
||||||
</Link>
|
Pilih Akun
|
||||||
{/* <Link href="/admin/broadcast/campaign-list/import">
|
</Button>
|
||||||
<Button color="success" size="md" className="text-sm">
|
</DialogTrigger>
|
||||||
<UserIcon />
|
<DialogContent size="md" className="max-w-xl max-h-[80vh] overflow-y-auto">
|
||||||
Import Akun
|
<DialogHeader>
|
||||||
</Button>
|
<DialogTitle>Pilih Akun Untuk Campaign Ini</DialogTitle>
|
||||||
</Link> */}
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4 my-3">
|
||||||
|
<RadioGroup
|
||||||
|
value={accountCategory}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
setAccountCategory(val);
|
||||||
|
setSelectedAccount([]);
|
||||||
|
setSelectedCategory("");
|
||||||
|
if (val === "custom") {
|
||||||
|
fetchUsersList();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex space-x-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="all-account" id="all-account" />
|
||||||
|
<Label htmlFor="all-account">Semua Akun</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="kategori" id="kategori" />
|
||||||
|
<Label htmlFor="kategori">Kategori</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="custom" id="custom" />
|
||||||
|
<Label htmlFor="custom">Kustom</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
{/* Category Selection */}
|
||||||
|
{accountCategory === "kategori" && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Pilih Kategori:</Label>
|
||||||
|
<UISelect onValueChange={(val) => setSelectedCategory(val)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih kategori" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="umum">Umum</SelectItem>
|
||||||
|
<SelectItem value="polri">Polri</SelectItem>
|
||||||
|
<SelectItem value="ksp">KSP</SelectItem>
|
||||||
|
<SelectItem value="jurnalis">Jurnalis</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</UISelect>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Custom Account Selection */}
|
||||||
|
{accountCategory === "custom" && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Pilih User:</Label>
|
||||||
|
<ReactSelect
|
||||||
|
isMulti
|
||||||
|
options={usersList.map((user: any) => ({
|
||||||
|
value: user.id,
|
||||||
|
label: `${user.fullname} (${user.role?.name})`,
|
||||||
|
user: user
|
||||||
|
}))}
|
||||||
|
value={selectedAccount.map((acc: any) => ({
|
||||||
|
value: acc.id,
|
||||||
|
label: `${acc.fullname} (${acc.role?.name})`,
|
||||||
|
user: acc
|
||||||
|
}))}
|
||||||
|
onChange={(selectedOptions: any) => {
|
||||||
|
const selectedUsers = selectedOptions ? selectedOptions.map((option: any) => option.user) : [];
|
||||||
|
setSelectedAccount(selectedUsers);
|
||||||
|
}}
|
||||||
|
placeholder="Cari dan pilih user..."
|
||||||
|
noOptionsMessage={() => "Tidak ada user ditemukan"}
|
||||||
|
loadingMessage={() => "Memuat..."}
|
||||||
|
isSearchable={true}
|
||||||
|
isClearable={true}
|
||||||
|
className="react-select"
|
||||||
|
classNamePrefix="select"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Selected Accounts Display */}
|
||||||
|
{selectedAccount.length > 0 && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>User Terpilih ({selectedAccount.length}):</Label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{selectedAccount.map((acc) => (
|
||||||
|
<Badge key={acc.id} className="flex items-center gap-1">
|
||||||
|
{acc.fullname}
|
||||||
|
<X
|
||||||
|
className="h-3 w-3 cursor-pointer"
|
||||||
|
onClick={() => removeSelectedAccount(acc.id)}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* All Accounts Info */}
|
||||||
|
{accountCategory === "all-account" && (
|
||||||
|
<div className="p-3 bg-blue-50 rounded-md">
|
||||||
|
<p className="text-sm text-blue-700">
|
||||||
|
Semua akun akan ditambahkan ke campaign ini.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Category Accounts Info */}
|
||||||
|
{accountCategory === "kategori" && selectedCategory && (
|
||||||
|
<div className="p-3 bg-green-50 rounded-md">
|
||||||
|
<p className="text-sm text-green-700">
|
||||||
|
Semua akun dengan role "{selectedCategory.toUpperCase()}" akan ditambahkan.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Custom Selection Info */}
|
||||||
|
{accountCategory === "custom" && (
|
||||||
|
<div className="p-3 bg-purple-50 rounded-md">
|
||||||
|
<p className="text-sm text-purple-700">
|
||||||
|
{selectedAccount.length} user terpilih akan ditambahkan ke campaign ini.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
onClick={saveCampaignAccount}
|
||||||
|
disabled={
|
||||||
|
!accountCategory ||
|
||||||
|
(accountCategory === "custom" && selectedAccount.length < 1) ||
|
||||||
|
(accountCategory === "kategori" && !selectedCategory)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button variant="outline" onClick={resetDialogState}>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* === Filter Akun === */}
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
|
|
@ -163,65 +467,28 @@ const AccountListTable = () => {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
||||||
<div className="flex items-center space-x-2">
|
{["polri", "jurnalis", "umum", "ksp"].map((cat) => (
|
||||||
<Checkbox
|
<div key={cat} className="flex items-center space-x-2">
|
||||||
id="accepted"
|
<Checkbox
|
||||||
checked={filtered.includes("polri")}
|
id={cat}
|
||||||
onCheckedChange={(e) => handleFilter("polri", Boolean(e))}
|
checked={filtered.includes(cat)}
|
||||||
/>
|
onCheckedChange={(e) => handleFilter(cat, Boolean(e))}
|
||||||
<label
|
/>
|
||||||
htmlFor="accepted"
|
<label
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
htmlFor={cat}
|
||||||
>
|
className="text-xs font-medium leading-none"
|
||||||
POLRI
|
>
|
||||||
</label>
|
{cat.toUpperCase()}
|
||||||
</div>
|
</label>
|
||||||
<div className="flex items-center space-x-2">
|
</div>
|
||||||
<Checkbox
|
))}
|
||||||
id="accepted"
|
|
||||||
checked={filtered.includes("jurnalis")}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleFilter("jurnalis", Boolean(e))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
JURNALIS
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={filtered.includes("umum")}
|
|
||||||
onCheckedChange={(e) => handleFilter("umum", Boolean(e))}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
UMUM
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="accepted"
|
|
||||||
checked={filtered.includes("ksp")}
|
|
||||||
onCheckedChange={(e) => handleFilter("ksp", Boolean(e))}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="accepted"
|
|
||||||
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
KSP
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* === Table Data === */}
|
||||||
<Table className="overflow-hidden">
|
<Table className="overflow-hidden">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
|
@ -263,6 +530,7 @@ const AccountListTable = () => {
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<TablePagination
|
<TablePagination
|
||||||
table={table}
|
table={table}
|
||||||
totalData={totalData}
|
totalData={totalData}
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ const CampaignListTable = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">Daftar Campaign</p>
|
<p className="text-xl font-medium text-default-900">Daftar Campaign</p>
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ export default function CreateCampaign() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white rounded-sm p-4"
|
className="space-y-3 bg-white dark:bg-black rounded-sm p-4"
|
||||||
>
|
>
|
||||||
<p className="fonnt-semibold">Campaign</p>
|
<p className="fonnt-semibold">Campaign</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,6 @@ export default function BroadcastCampaignDetail({
|
||||||
"sent" | "schedule" | "account-list"
|
"sent" | "schedule" | "account-list"
|
||||||
>("sent");
|
>("sent");
|
||||||
const { page, size } = searchParams;
|
const { page, size } = searchParams;
|
||||||
|
|
||||||
const [calenderState, setCalenderState] = useState<boolean>(false);
|
const [calenderState, setCalenderState] = useState<boolean>(false);
|
||||||
const [typeFilter, setTypeFilter] = useState<string>("email");
|
const [typeFilter, setTypeFilter] = useState<string>("email");
|
||||||
const [dateRange, setDateRange] = useState<[Date, Date]>([
|
const [dateRange, setDateRange] = useState<[Date, Date]>([
|
||||||
|
|
@ -91,10 +90,8 @@ export default function BroadcastCampaignDetail({
|
||||||
new Date(),
|
new Date(),
|
||||||
]);
|
]);
|
||||||
const [startDate, endDate] = dateRange;
|
const [startDate, endDate] = dateRange;
|
||||||
|
|
||||||
const [startDateString, setStartDateString] = useState<string | undefined>();
|
const [startDateString, setStartDateString] = useState<string | undefined>();
|
||||||
const [endDateString, setEndDateString] = useState<string | undefined>();
|
const [endDateString, setEndDateString] = useState<string | undefined>();
|
||||||
|
|
||||||
// Table state
|
// Table state
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||||
|
|
@ -104,7 +101,6 @@ export default function BroadcastCampaignDetail({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: parseInt(size || "10"),
|
pageSize: parseInt(size || "10"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const pages = page ? parseInt(page) - 1 : 0;
|
const pages = page ? parseInt(page) - 1 : 0;
|
||||||
const currentPage = page ? parseInt(page) : 1;
|
const currentPage = page ? parseInt(page) : 1;
|
||||||
const pageSize = parseInt(size || "10");
|
const pageSize = parseInt(size || "10");
|
||||||
|
|
@ -264,7 +260,7 @@ export default function BroadcastCampaignDetail({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white container-fluid rounded ">
|
<div className="bg-white dark:bg-black container-fluid rounded ">
|
||||||
<div className="mt-1 p-4">
|
<div className="mt-1 p-4">
|
||||||
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-4">
|
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-4">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ export default function EditCampaign() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white rounded-sm p-4"
|
className="space-y-3 bg-white dark:bg-black rounded-sm p-4"
|
||||||
>
|
>
|
||||||
<p className="fonnt-semibold">Campaign</p>
|
<p className="fonnt-semibold">Campaign</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@ const BroadcastEmailTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between ">
|
<div className="flex justify-between ">
|
||||||
<Link href="/admin/broadcast/campaign-list" className="mr-3">
|
<Link href="/admin/broadcast/campaign-list" className="mr-3">
|
||||||
<Button color="primary" size="md" className="text-sm">
|
<Button color="primary" size="md" className="text-sm">
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default function AdminBroadcast() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
{tab === "Email Blast" && <BroadcastEmailTable />}
|
{tab === "Email Blast" && <BroadcastEmailTable />}
|
||||||
{tab === "WhatsApp Blast" && <BroadcastWhatsAppTable />}
|
{tab === "WhatsApp Blast" && <BroadcastWhatsAppTable />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ export default function CreateUserForm() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-6 bg-white p-10 w-full"
|
className="space-y-6 bg-white dark:bg-black p-10 w-full"
|
||||||
>
|
>
|
||||||
<p className="text-xl">Data Pengelola Media Hub</p>
|
<p className="text-xl">Data Pengelola Media Hub</p>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default function ManagementUser() {
|
||||||
<ManagementUserVisualization />
|
<ManagementUserVisualization />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5">
|
<section className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3 mt-5">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<p className="text-lg">
|
<p className="text-lg">
|
||||||
Data User {isInternal ? "Internal" : "Eksternal"}
|
Data User {isInternal ? "Internal" : "Eksternal"}
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ const ResultTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3 border ">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3 border ">
|
||||||
<div className="flex flex-col sm:flex-row lg:flex-row justify-end sm:items-center md:items-center lg:items-center">
|
<div className="flex flex-col sm:flex-row lg:flex-row justify-end sm:items-center md:items-center lg:items-center">
|
||||||
<div className=" flex flex-row justify-end items-center gap-3">
|
<div className=" flex flex-row justify-end items-center gap-3">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ const columns: ColumnDef<any>[] = [
|
||||||
router.push("/admin/settings/category?dataChange=true");
|
router.push("/admin/settings/category?dataChange=true");
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Menubar className="border-none">
|
<Menubar className="border-none dark:bg-black">
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger>
|
<MenubarTrigger>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ export default function CreateCategoryModal() {
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-3 bg-white rounded-sm"
|
className="space-y-3 bg-white dark:bg-[#1f2937] rounded-sm"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ const AdminCategoryTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<p className="text-xl font-medium text-default-900">{t("category", { defaultValue: "Category" })}</p>
|
<p className="text-xl font-medium text-default-900">{t("category", { defaultValue: "Category" })}</p>
|
||||||
<CreateCategoryModal />
|
<CreateCategoryModal />
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ const AdminSettingTrackingTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex items-end justify-between">
|
<div className="flex items-end justify-between">
|
||||||
{/* <CreateSettingTracking /> */}
|
{/* <CreateSettingTracking /> */}
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ const SurveyListTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex-1 text-xl font-medium text-default-900">Survey</div>
|
<div className="flex-1 text-xl font-medium text-default-900">Survey</div>
|
||||||
<div className="flex flex-row gap-2 items-center justify-between">
|
<div className="flex flex-row gap-2 items-center justify-between">
|
||||||
<div className="w-full md:w-[200px] lg:w-[300px] px-2">
|
<div className="w-full md:w-[200px] lg:w-[300px] px-2">
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export default function AdminSurvey() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<SurveyListTable />
|
<SurveyListTable />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
const firstType = typeSplit[0] as EventType;
|
const firstType = typeSplit[0] as EventType;
|
||||||
|
|
||||||
const colors: Record<EventType, string> = {
|
const colors: Record<EventType, string> = {
|
||||||
"0": "bg-black",
|
"0": "bg-gray-500",
|
||||||
"1": "bg-yellow-500",
|
"1": "bg-yellow-500",
|
||||||
"2": "bg-blue-400",
|
"2": "bg-blue-400",
|
||||||
"3": "bg-slate-400",
|
"3": "bg-slate-400",
|
||||||
|
|
@ -408,13 +408,13 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
const hasMoreEvents = events.length > 3;
|
const hasMoreEvents = events.length > 3;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 bg-white rounded-lg shadow-sm border border-gray-200 pb-3 mr-1">
|
<div className="flex-1 bg-white dark:bg-black rounded-lg shadow-sm border border-gray-200 pb-3 mr-1">
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
<h4 className="font-bold text-center">{label}</h4>
|
<h4 className="font-bold text-center">{label}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
{events.length === 0 ? (
|
{events.length === 0 ? (
|
||||||
<div className="mt-1 py-2 rounded-lg bg-white border border-black">
|
<div className="mt-1 py-2 rounded-lg bg-white dark:bg-black border border-black dark:border-gray-500">
|
||||||
<p className="text-center">
|
<p className="text-center">
|
||||||
{t("no-data-yet", { defaultValue: "No Data Yet" })}
|
{t("no-data-yet", { defaultValue: "No Data Yet" })}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -226,12 +226,12 @@ const LiveReportTable = () => {
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-black rounded-md w-full md:w-fit">
|
<div className="border dark:bg-transparent border-black dark:border dark:border-white rounded-md w-full md:w-fit">
|
||||||
<Select
|
<Select
|
||||||
value={selectedType}
|
value={selectedType}
|
||||||
onValueChange={(value) => setSelectedType(value)}
|
onValueChange={(value) => setSelectedType(value)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full md:w-[150px] text-black">
|
<SelectTrigger className="w-full md:w-[150px] text-black dark:text-white">
|
||||||
<SelectValue placeholder="Tipe" />
|
<SelectValue placeholder="Tipe" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import Swal from "sweetalert2";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
const useTableColumns = () => {
|
const useTableColumns = () => {
|
||||||
const t = useTranslations("Table"); // Panggil di dalam hook
|
const t = useTranslations("Table");
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "no",
|
accessorKey: "no",
|
||||||
|
|
@ -50,6 +50,12 @@ const useTableColumns = () => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "uniqueCode",
|
||||||
|
header: t("code", { defaultValue: "Code" }),
|
||||||
|
cell: ({ row }) => <span>{row.getValue("uniqueCode")}</span>,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "createdAt",
|
accessorKey: "createdAt",
|
||||||
header: t("upload-date", { defaultValue: "Upload Date" }),
|
header: t("upload-date", { defaultValue: "Upload Date" }),
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ import { Badge } from "@/components/ui/badge";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
import columns from "./columns";
|
import columns from "./columns";
|
||||||
import { listTask, listTaskTa } from "@/service/task";
|
import { listTask, listTaskMabesForTa, listTaskTa } from "@/service/task";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
@ -78,6 +78,9 @@ const TaskTaTable = () => {
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: Number(showData),
|
pageSize: Number(showData),
|
||||||
});
|
});
|
||||||
|
const [activeTab, setActiveTab] = React.useState<"ta" | "daily" | "special">(
|
||||||
|
"ta"
|
||||||
|
);
|
||||||
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
const [dateFilter, setDateFilter] = React.useState("");
|
const [dateFilter, setDateFilter] = React.useState("");
|
||||||
const [endDate, setEndDate] = React.useState("");
|
const [endDate, setEndDate] = React.useState("");
|
||||||
|
|
@ -126,41 +129,56 @@ const TaskTaTable = () => {
|
||||||
dateFilter,
|
dateFilter,
|
||||||
filterByCode,
|
filterByCode,
|
||||||
statusFilter,
|
statusFilter,
|
||||||
|
activeTab,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
const formattedStartDate = dateFilter
|
const formattedStartDate = dateFilter
|
||||||
? format(new Date(dateFilter), "yyyy-MM-dd")
|
? format(new Date(dateFilter), "yyyy-MM-dd")
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await listTaskTa(
|
let res;
|
||||||
page - 1,
|
|
||||||
search,
|
if (activeTab === "ta") {
|
||||||
showData,
|
res = await listTaskTa(
|
||||||
filterByCode,
|
page - 1,
|
||||||
formattedStartDate,
|
search,
|
||||||
isSpecificAttention ? "atensi-khusus" : "tugas-harian",
|
showData,
|
||||||
statusFilter
|
filterByCode,
|
||||||
);
|
formattedStartDate,
|
||||||
|
"atensi-khusus",
|
||||||
|
statusFilter
|
||||||
|
);
|
||||||
|
} else if (activeTab === "daily") {
|
||||||
|
res = await listTaskTa(
|
||||||
|
page - 1,
|
||||||
|
search,
|
||||||
|
showData,
|
||||||
|
filterByCode,
|
||||||
|
formattedStartDate,
|
||||||
|
"tugas-harian",
|
||||||
|
statusFilter
|
||||||
|
);
|
||||||
|
} else if (activeTab === "special") {
|
||||||
|
res = await listTask(
|
||||||
|
page - 1,
|
||||||
|
search,
|
||||||
|
showData,
|
||||||
|
filterByCode,
|
||||||
|
formattedStartDate,
|
||||||
|
"atensi-khusus",
|
||||||
|
statusFilter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const data = res?.data?.data;
|
const data = res?.data?.data;
|
||||||
const contentData = data?.content;
|
const contentData = data?.content || [];
|
||||||
|
|
||||||
// let contentDataFilter = res?.data?.data?.content || [];
|
|
||||||
|
|
||||||
// Filter berdasarkan status
|
|
||||||
// contentDataFilter = contentDataFilter.filter((item: any) => {
|
|
||||||
// const isSelesai = statusFilter.includes(1) ? item.isDone : true;
|
|
||||||
// const isAktif = statusFilter.includes(2) ? item.isActive : true;
|
|
||||||
// return isSelesai && isAktif;
|
|
||||||
// });
|
|
||||||
|
|
||||||
contentData.forEach((item: any, index: number) => {
|
contentData.forEach((item: any, index: number) => {
|
||||||
item.no = (page - 1) * Number(showData) + index + 1;
|
item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log("contentData : ", contentData);
|
|
||||||
|
|
||||||
setDataTable(contentData);
|
setDataTable(contentData);
|
||||||
setTotalData(data?.totalElements);
|
setTotalData(data?.totalElements);
|
||||||
setTotalPage(data?.totalPages);
|
setTotalPage(data?.totalPages);
|
||||||
|
|
@ -169,6 +187,57 @@ const TaskTaTable = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async function fetchData() {
|
||||||
|
// const formattedStartDate = dateFilter
|
||||||
|
// ? format(new Date(dateFilter), "yyyy-MM-dd")
|
||||||
|
// : "";
|
||||||
|
// try {
|
||||||
|
// const res = isSpecificAttention
|
||||||
|
// ? await listTaskTa(
|
||||||
|
// page - 1,
|
||||||
|
// search,
|
||||||
|
// showData,
|
||||||
|
// filterByCode,
|
||||||
|
// formattedStartDate,
|
||||||
|
// "atensi-khusus",
|
||||||
|
// statusFilter
|
||||||
|
// )
|
||||||
|
// : await listTask(
|
||||||
|
// page - 1,
|
||||||
|
// search,
|
||||||
|
// showData,
|
||||||
|
// filterByCode,
|
||||||
|
// formattedStartDate,
|
||||||
|
// "atensi-khusus",
|
||||||
|
// statusFilter
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const data = res?.data?.data;
|
||||||
|
// const contentData = data?.content;
|
||||||
|
|
||||||
|
// // let contentDataFilter = res?.data?.data?.content || [];
|
||||||
|
|
||||||
|
// // Filter berdasarkan status
|
||||||
|
// // contentDataFilter = contentDataFilter.filter((item: any) => {
|
||||||
|
// // const isSelesai = statusFilter.includes(1) ? item.isDone : true;
|
||||||
|
// // const isAktif = statusFilter.includes(2) ? item.isActive : true;
|
||||||
|
// // return isSelesai && isAktif;
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// contentData.forEach((item: any, index: number) => {
|
||||||
|
// item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log("contentData : ", contentData);
|
||||||
|
|
||||||
|
// setDataTable(contentData);
|
||||||
|
// setTotalData(data?.totalElements);
|
||||||
|
// setTotalPage(data?.totalPages);
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error("Error fetching tasks:", error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setFilterByCode(e.target.value);
|
setFilterByCode(e.target.value);
|
||||||
setSearch(e.target.value);
|
setSearch(e.target.value);
|
||||||
|
|
@ -202,7 +271,42 @@ const TaskTaTable = () => {
|
||||||
onChange={() => setIsSpecificAttention(!isSpecificAttention)}
|
onChange={() => setIsSpecificAttention(!isSpecificAttention)}
|
||||||
hidden
|
hidden
|
||||||
/>
|
/>
|
||||||
<span
|
<div className="flex mb-6">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("special")}
|
||||||
|
className={`px-4 py-1 rounded transition ${
|
||||||
|
activeTab === "special"
|
||||||
|
? "bg-default-900 text-white dark:text-black"
|
||||||
|
: "border dark:text-default-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* {t("special-attention", {defaultValue: "Special Attention", })} */}
|
||||||
|
Atensi Khusus Mabes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("ta")}
|
||||||
|
className={`px-4 py-1 rounded transition ${
|
||||||
|
activeTab === "ta"
|
||||||
|
? "bg-default-900 text-white dark:text-black"
|
||||||
|
: "border dark:text-default-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Atensi Khusus TA
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("daily")}
|
||||||
|
className={`px-4 py-1 rounded transition ${
|
||||||
|
activeTab === "daily"
|
||||||
|
? "bg-default-900 text-white dark:text-black"
|
||||||
|
: "border dark:text-default-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t("daily-tasks", { defaultValue: "Daily Tasks" })}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <span
|
||||||
className={` ${
|
className={` ${
|
||||||
isSpecificAttention
|
isSpecificAttention
|
||||||
? "bg-default-900 text-white"
|
? "bg-default-900 text-white"
|
||||||
|
|
@ -210,7 +314,19 @@ const TaskTaTable = () => {
|
||||||
}
|
}
|
||||||
px-[18px] py-1 transition duration-100 rounded`}
|
px-[18px] py-1 transition duration-100 rounded`}
|
||||||
>
|
>
|
||||||
{t("special-attention", { defaultValue: "Special Attention" })}
|
Atensi Khusus TA{" "}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
${
|
||||||
|
!isSpecificAttention
|
||||||
|
? "bg-default-900 text-white dark:text-black"
|
||||||
|
: " dark:text-default-700 border-2 dark:border dark:border-gray-500"
|
||||||
|
}
|
||||||
|
px-[18px] py-1 transition duration-100 rounded
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{t("daily-tasks", { defaultValue: "Daily Tasks" })}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`
|
className={`
|
||||||
|
|
@ -222,8 +338,10 @@ const TaskTaTable = () => {
|
||||||
px-[18px] py-1 transition duration-100 rounded
|
px-[18px] py-1 transition duration-100 rounded
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{t("daily-tasks", { defaultValue: "Daily Tasks" })}
|
{t("special-attention", {
|
||||||
</span>
|
defaultValue: "Special Attention",
|
||||||
|
})}{" "}
|
||||||
|
</span> */}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ const TaskTable = () => {
|
||||||
className={` ${
|
className={` ${
|
||||||
isSpecificAttention
|
isSpecificAttention
|
||||||
? "bg-default-900 text-white dark:text-black"
|
? "bg-default-900 text-white dark:text-black"
|
||||||
: "dark:text-default-700 border-2"
|
: "dark:text-default-700 border-2 dark:border dark:border-gray-500"
|
||||||
}
|
}
|
||||||
px-[18px] py-1 transition duration-100 rounded`}
|
px-[18px] py-1 transition duration-100 rounded`}
|
||||||
>
|
>
|
||||||
|
|
@ -218,7 +218,7 @@ const TaskTable = () => {
|
||||||
${
|
${
|
||||||
!isSpecificAttention
|
!isSpecificAttention
|
||||||
? "bg-default-900 text-white dark:text-black"
|
? "bg-default-900 text-white dark:text-black"
|
||||||
: " dark:text-default-700 border-2"
|
: " dark:text-default-700 border-2 dark:border dark:border-gray-500"
|
||||||
}
|
}
|
||||||
px-[18px] py-1 transition duration-100 rounded
|
px-[18px] py-1 transition duration-100 rounded
|
||||||
`}
|
`}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export default function UserFeedback() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="flex flex-col gap-2 bg-white p-4">
|
<div className="flex flex-col gap-2 bg-white dark:bg-black p-4">
|
||||||
<p className="text-lg">Hasil Feedback</p>
|
<p className="text-lg">Hasil Feedback</p>
|
||||||
<div className="grid grid-cols-2 gap-5">
|
<div className="grid grid-cols-2 gap-5">
|
||||||
{listData?.map(
|
{listData?.map(
|
||||||
|
|
@ -43,7 +43,7 @@ export default function UserFeedback() {
|
||||||
list?.avgScore !== "NaN" && (
|
list?.avgScore !== "NaN" && (
|
||||||
<div
|
<div
|
||||||
key={list?.id}
|
key={list?.id}
|
||||||
className="flex flex-col gap-2 bg-gray-100 rounded-md p-5"
|
className="flex flex-col gap-2 bg-gray-100 dark:bg-blue-950 rounded-md p-5"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row gap-3 items-center">
|
<div className="flex flex-row gap-3 items-center">
|
||||||
<p className="text-3xl">{parseInt(list?.avgScore)}</p>
|
<p className="text-3xl">{parseInt(list?.avgScore)}</p>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const CommunicationPage = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between py-3">
|
<div className="flex justify-between py-3">
|
||||||
<p className="text-lg">{tab}</p>
|
<p className="text-lg">{tab}</p>
|
||||||
{tab === "Pertanyaan Internal" && (
|
{tab === "Pertanyaan Internal" && (
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,19 +1,19 @@
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
// import { Card, CardContent } from "@/components/ui/card";
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
// import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import FormTask from "@/components/form/task/task-form";
|
// import FormTask from "@/components/form/task/task-form";
|
||||||
import FormTaskDetail from "@/components/form/task/task-detail-form";
|
// import FormTaskDetail from "@/components/form/task/task-detail-form";
|
||||||
import FormDetailInternal from "@/components/form/communication/internal-detail-form";
|
// import FormDetailInternal from "@/components/form/communication/internal-detail-form";
|
||||||
import FormDetailTicketing from "@/components/form/ticketing/ticketing-detail-form";
|
// import FormDetailTicketing from "@/components/form/ticketing/ticketing-detail-form";
|
||||||
|
|
||||||
const TicketingDetailPage = async () => {
|
// const TicketingDetailPage = async () => {
|
||||||
return (
|
// return (
|
||||||
<div>
|
// <div>
|
||||||
<SiteBreadcrumb />
|
// <SiteBreadcrumb />
|
||||||
<div className="space-y-4">
|
// <div className="space-y-4">
|
||||||
<FormDetailTicketing />
|
// <FormDetailTicketing />
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
export default TicketingDetailPage;
|
// export default TicketingDetailPage;
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ const TicketingPage = async () => {
|
||||||
|
|
||||||
<section
|
<section
|
||||||
id="table"
|
id="table"
|
||||||
className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5"
|
className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3 mt-5"
|
||||||
>
|
>
|
||||||
<div className="flex justify-between py-3">
|
{/* <div className="flex justify-between py-3">
|
||||||
<p className="text-lg">Semua Ticket</p>
|
<p className="text-lg">Semua Ticket : 0</p>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<TicketingTable />
|
<TicketingTable />
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,73 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { Reveal } from "@/components/landing-page/Reveal";
|
import { Reveal } from "@/components/landing-page/Reveal";
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
|
||||||
import * as Yup from "yup";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { getInfoProfile, getProfile, getSubjects } from "@/service/auth";
|
import { getInfoProfile, getSubjects } from "@/service/auth";
|
||||||
import { close, error, loading, successCallback } from "@/config/swal";
|
import { close, error, loading, successCallback } from "@/config/swal";
|
||||||
import { sendMessage } from "@/service/landing/landing";
|
import { sendMessage } from "@/service/landing/landing";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
|
interface IFormInput {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
phone?: string | undefined;
|
||||||
|
subjects: string;
|
||||||
|
othersubject?: string | undefined;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
const ContactForm = () => {
|
const ContactForm = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userId = getCookiesDecrypt("uie");
|
const userId = getCookiesDecrypt("uie");
|
||||||
const [subjects, setSubjects] = useState<any>();
|
const [subjects, setSubjects] = useState<any[]>([]);
|
||||||
const [isOtherActive, setIsOtherActive] = useState(false);
|
const [isOtherActive, setIsOtherActive] = useState(false);
|
||||||
const t = useTranslations("LandingPage");
|
const t = useTranslations("LandingPage");
|
||||||
|
|
||||||
const form = document.getElementById("form") as HTMLFormElement;
|
const validationSchema = z.object({
|
||||||
|
name: z.string().min(1, "Nama tidak boleh kosong"),
|
||||||
// Form Handling
|
email: z.string().email("Email tidak valid"),
|
||||||
const validationSchema = Yup.object().shape({
|
phone: z.string().optional(),
|
||||||
name: Yup.string().required("Nama tidak boleh kosong"),
|
subjects: z.string().min(1, "Subjek tidak boleh kosong"),
|
||||||
email: Yup.string().required("Email tidak boleh kosong"),
|
othersubject: z.string().optional(),
|
||||||
subjects: Yup.string().required("Subjek tidak boleh kosong"),
|
message: z.string().min(1, "Pesan tidak boleh kosong"),
|
||||||
message: Yup.string().required("Pesan tidak boleh kosong"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const formOptions = {
|
type IFormInput = z.infer<typeof validationSchema>;
|
||||||
resolver: yupResolver(validationSchema),
|
|
||||||
};
|
|
||||||
|
|
||||||
const { register, handleSubmit, formState, setValue } = useForm(formOptions);
|
const {
|
||||||
|
register,
|
||||||
const { errors } = formState;
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
setValue,
|
||||||
|
reset,
|
||||||
|
} = useForm<IFormInput>({
|
||||||
|
resolver: zodResolver(validationSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function initState() {
|
async function initState() {
|
||||||
const response = await getInfoProfile();
|
const response = await getInfoProfile();
|
||||||
const responseSubject = await getSubjects();
|
const responseSubject = await getSubjects();
|
||||||
const profile = response?.data?.data;
|
const profile = response?.data?.data;
|
||||||
|
|
||||||
setSubjects(responseSubject?.data?.data);
|
setSubjects(responseSubject?.data?.data || []);
|
||||||
// console.log(response);
|
if (profile) {
|
||||||
setValue("name", profile?.fullname);
|
setValue("name", profile?.fullname || "");
|
||||||
setValue("email", profile?.email); // setValue('name', profile?.fullname);
|
setValue("email", profile?.email || "");
|
||||||
// setValue('name', profile?.fullname);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initState();
|
initState();
|
||||||
}, []);
|
}, [setValue]);
|
||||||
|
|
||||||
async function save(data: any) {
|
async function save(data: IFormInput) {
|
||||||
loading();
|
loading();
|
||||||
|
|
||||||
const finalData = {
|
const finalData = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
|
|
@ -65,29 +79,24 @@ const ContactForm = () => {
|
||||||
const response = await sendMessage(finalData);
|
const response = await sendMessage(finalData);
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
error(response?.message);
|
error(response?.message);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
close();
|
close();
|
||||||
successCallback("Terima kasih, pesan Anda telah terkirim");
|
successCallback("Terima kasih, pesan Anda telah terkirim");
|
||||||
// $("#form")[0].onreset();
|
reset();
|
||||||
if (form) {
|
|
||||||
form.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSubmit(data: any) {
|
async function onSubmit(data: IFormInput) {
|
||||||
if (userId == undefined) {
|
if (userId == undefined) {
|
||||||
router.push("/auth/login");
|
router.push("/auth");
|
||||||
} else {
|
} else {
|
||||||
save(data);
|
save(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubjects = (e: any) => {
|
const handleSubjects = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const id = e.target.value;
|
if (e.target.value === "Lainnya") {
|
||||||
|
|
||||||
if (id == "Lainnya") {
|
|
||||||
setIsOtherActive(true);
|
setIsOtherActive(true);
|
||||||
} else {
|
} else {
|
||||||
setIsOtherActive(false);
|
setIsOtherActive(false);
|
||||||
|
|
@ -95,63 +104,163 @@ const ContactForm = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form method="POST" id="form" onSubmit={handleSubmit(onSubmit)} className="max-w-2xl mx-auto bg-white dark:bg-black p-6">
|
<form
|
||||||
|
method="POST"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
className="max-w-2xl mx-auto bg-white dark:bg-black p-6"
|
||||||
|
>
|
||||||
<Reveal>
|
<Reveal>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-center mb-6">
|
<div className="flex items-center justify-center mb-6">
|
||||||
<img src="/assets/icons-contact.png" alt="contact" />
|
<img src="/assets/icons-contact.png" alt="contact" />
|
||||||
<h2 className="ml-4 text-2xl font-bold">{t("contactUs", { defaultValue: "Contact Us" })}</h2>
|
<h2 className="ml-4 text-2xl font-bold">
|
||||||
|
{t("contactUs", { defaultValue: "Contact Us" })}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-1">{t("writeMessage", { defaultValue: "Write Message" })}</h3>
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-1">
|
||||||
<p className="text-sm text-gray-600 dark:text-white mb-6">{t("leaveMessage", { defaultValue: "Leave Message" })}</p>
|
{t("writeMessage", { defaultValue: "Write Message" })}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-white mb-6">
|
||||||
|
{t("leaveMessage", { defaultValue: "Leave Message" })}
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* Form */}
|
{/* Form */}
|
||||||
<form>
|
<div>
|
||||||
|
{/* Name */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">{t("name", { defaultValue: "Name" })}</label>
|
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">
|
||||||
<input type="text" placeholder={t("enterName", { defaultValue: "Enter Name" })} className={`w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.name ? "block" : ""}`} {...register("name")} required />
|
{t("name", { defaultValue: "Name" })}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t("enterName", { defaultValue: "Enter Name" })}
|
||||||
|
className={`w-full p-2 border rounded-md focus:outline-none focus:ring-2 ${
|
||||||
|
errors.name
|
||||||
|
? "border-red-500 focus:ring-red-500"
|
||||||
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
|
}`}
|
||||||
|
{...register("name")}
|
||||||
|
/>
|
||||||
|
{errors.name && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.name.message}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Email */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">Email</label>
|
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">
|
||||||
<input type="email" placeholder="name@mail.com" className={`w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.email ? "block" : ""}`} {...register("email")} required />
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="name@mail.com"
|
||||||
|
className={`w-full p-2 border rounded-md focus:outline-none focus:ring-2 ${
|
||||||
|
errors.email
|
||||||
|
? "border-red-500 focus:ring-red-500"
|
||||||
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
|
}`}
|
||||||
|
{...register("email")}
|
||||||
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.email.message}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Phone */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">{t("number", { defaultValue: "Number" })} (Optional)</label>
|
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">
|
||||||
<input type="text" placeholder={t("enterNumber", { defaultValue: "Enter Number" })} className="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
{t("number", { defaultValue: "Number" })} (Optional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t("enterNumber", { defaultValue: "Enter Number" })}
|
||||||
|
className="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
{...register("phone")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Subjects */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">{t("subject", { defaultValue: "Subject" })}</label>
|
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">
|
||||||
|
{t("subject", { defaultValue: "Subject" })}
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
className={`w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${errors.subjects ? "block" : ""}`}
|
className={`w-full p-2 border rounded-md focus:outline-none focus:ring-2 ${
|
||||||
|
errors.subjects
|
||||||
|
? "border-red-500 focus:ring-red-500"
|
||||||
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
|
}`}
|
||||||
{...register("subjects", { onChange: (e) => handleSubjects(e) })}
|
{...register("subjects", { onChange: (e) => handleSubjects(e) })}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
>
|
>
|
||||||
<option value="" disabled>
|
<option value="" disabled>
|
||||||
{t("selectSubject", { defaultValue: "Select Subject" })}
|
{t("selectSubject", { defaultValue: "Select Subject" })}
|
||||||
</option>
|
</option>
|
||||||
{/* <option value="1">{t("question", { defaultValue: "Question" })}</option>
|
|
||||||
<option value="2">{t("criticism", { defaultValue: "Criticism" })}</option>
|
|
||||||
<option value="3">{t("suggestion", { defaultValue: "Suggestion" })}</option> */}
|
|
||||||
{subjects?.map((list: any) => (
|
{subjects?.map((list: any) => (
|
||||||
<option key={list.id} value={list.title}>
|
<option key={list.id} value={list.title}>
|
||||||
{list.title}
|
{list.title}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
<option value="Lainnya">Lainnya</option>
|
||||||
</select>
|
</select>
|
||||||
|
{errors.subjects && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.subjects.message}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Other Subject */}
|
||||||
|
{isOtherActive && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">
|
||||||
|
Subjek Lainnya
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan subjek lainnya"
|
||||||
|
className={`w-full p-2 border rounded-md focus:outline-none focus:ring-2 ${
|
||||||
|
errors.othersubject
|
||||||
|
? "border-red-500 focus:ring-red-500"
|
||||||
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
|
}`}
|
||||||
|
{...register("othersubject")}
|
||||||
|
/>
|
||||||
|
{errors.othersubject && (
|
||||||
|
<p className="text-red-500 text-sm">
|
||||||
|
{errors.othersubject.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Message */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">{t("messages", { defaultValue: "Messages" })}</label>
|
<label className="block text-sm font-medium text-gray-700 dark:text-white mb-1">
|
||||||
<textarea placeholder={t("writeYourMessage", { defaultValue: "Write Your Message" })} rows={4} className="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
{t("messages", { defaultValue: "Messages" })}
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
placeholder={t("writeYourMessage", {
|
||||||
|
defaultValue: "Write Your Message",
|
||||||
|
})}
|
||||||
|
rows={4}
|
||||||
|
className={`w-full p-2 border rounded-md focus:outline-none focus:ring-2 ${
|
||||||
|
errors.message
|
||||||
|
? "border-red-500 focus:ring-red-500"
|
||||||
|
: "border-gray-300 focus:ring-blue-500"
|
||||||
|
}`}
|
||||||
|
{...register("message")}
|
||||||
|
></textarea>
|
||||||
|
{errors.message && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.message.message}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" className="w-fit bg-blue-500 flex justify-self-end text-white p-2 px-8 rounded-md hover:bg-blue-600 transition">
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-fit bg-blue-500 flex justify-self-end text-white p-2 px-8 rounded-md hover:bg-blue-600 transition"
|
||||||
|
>
|
||||||
{t("send", { defaultValue: "Send" })}
|
{t("send", { defaultValue: "Send" })}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -575,10 +575,108 @@ html[dir="rtl"] .react-select .select__loading-indicator {
|
||||||
background: #9ca3af;
|
background: #9ca3af;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* CKEditor Styling */
|
||||||
.ck-editor__editable_inline {
|
.ck-editor__editable_inline {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Main CKEditor content area styling */
|
||||||
|
.ck.ck-editor__editable {
|
||||||
|
padding: 1.5em 2em !important;
|
||||||
|
min-height: 400px;
|
||||||
|
max-height: 600px;
|
||||||
|
line-height: 1.6;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CKEditor content styling */
|
||||||
|
.ck.ck-editor__editable .ck-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CKEditor scrollbar styling */
|
||||||
|
.ck.ck-editor__editable::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable::-webkit-scrollbar-track {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CKEditor editable area focus state */
|
||||||
|
.ck.ck-editor__editable.ck-focused {
|
||||||
|
border-color: #1a9aef;
|
||||||
|
box-shadow: 0 0 0 2px rgba(26, 154, 239, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CKEditor toolbar styling */
|
||||||
|
.ck.ck-toolbar {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CKEditor editable border styling */
|
||||||
|
.ck.ck-editor__editable {
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CKEditor content typography */
|
||||||
|
.ck.ck-editor__editable p {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View Editor specific styling (read-only mode) */
|
||||||
|
.ckeditor-view-wrapper .ck.ck-editor__editable {
|
||||||
|
background-color: #f8fafc !important;
|
||||||
|
color: #4b5563 !important;
|
||||||
|
cursor: default !important;
|
||||||
|
border: 1px solid #d1d5db !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-view-wrapper .ck.ck-editor__editable.ck-focused {
|
||||||
|
border-color: #d1d5db !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-view-wrapper .ck.ck-toolbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable h1,
|
||||||
|
.ck.ck-editor__editable h2,
|
||||||
|
.ck.ck-editor__editable h3,
|
||||||
|
.ck.ck-editor__editable h4,
|
||||||
|
.ck.ck-editor__editable h5,
|
||||||
|
.ck.ck-editor__editable h6 {
|
||||||
|
margin: 1em 0 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable ul,
|
||||||
|
.ck.ck-editor__editable ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable blockquote {
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border-left: 4px solid #d1d5db;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide FullCalendar grid elements */
|
/* Hide FullCalendar grid elements */
|
||||||
.fc-view-harness:has(.hide-calendar-grid) {
|
.fc-view-harness:has(.hide-calendar-grid) {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import "./theme.css";
|
import "./theme.css";
|
||||||
|
import "../../style/ckeditor.css";
|
||||||
import { ThemeProvider } from "@/providers/theme-provider";
|
import { ThemeProvider } from "@/providers/theme-provider";
|
||||||
import MountedProvider from "@/providers/mounted.provider";
|
import MountedProvider from "@/providers/mounted.provider";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
|
|
|
||||||
|
|
@ -5,79 +5,166 @@ import { CKEditor } from "@ckeditor/ckeditor5-react";
|
||||||
import Editor from "ckeditor5-custom-build";
|
import Editor from "ckeditor5-custom-build";
|
||||||
|
|
||||||
function CustomEditor(props) {
|
function CustomEditor(props) {
|
||||||
const editorRef = useRef(null);
|
const maxHeight = props.maxHeight || 600;
|
||||||
const [isEditorReady, setIsEditorReady] = useState(false);
|
|
||||||
const [currentContent, setCurrentContent] = useState(props.initialData || "");
|
|
||||||
|
|
||||||
// Handle editor initialization
|
|
||||||
const handleInit = useCallback((evt, editor) => {
|
|
||||||
editorRef.current = editor;
|
|
||||||
setIsEditorReady(true);
|
|
||||||
|
|
||||||
// Set initial content immediately when editor is ready
|
|
||||||
if (currentContent) {
|
|
||||||
editor.setContent(currentContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple onChange handler
|
|
||||||
editor.on('change', () => {
|
|
||||||
const content = editor.getContent();
|
|
||||||
setCurrentContent(content);
|
|
||||||
if (props.onChange) {
|
|
||||||
props.onChange(content);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [currentContent, props.onChange]);
|
|
||||||
|
|
||||||
// Watch for changes in initialData prop
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.initialData !== currentContent) {
|
|
||||||
setCurrentContent(props.initialData || "");
|
|
||||||
|
|
||||||
// Update editor content if editor is ready
|
|
||||||
if (editorRef.current && isEditorReady) {
|
|
||||||
editorRef.current.setContent(props.initialData || "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [props.initialData, currentContent, isEditorReady]);
|
|
||||||
|
|
||||||
// Handle initial data when editor becomes ready
|
|
||||||
useEffect(() => {
|
|
||||||
if (isEditorReady && currentContent && editorRef.current) {
|
|
||||||
editorRef.current.setContent(currentContent);
|
|
||||||
}
|
|
||||||
}, [isEditorReady, currentContent]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CKEditor
|
<div className="ckeditor-wrapper">
|
||||||
editor={Editor}
|
<CKEditor
|
||||||
data={props.initialData}
|
editor={Editor}
|
||||||
onChange={(event, editor) => {
|
data={props.initialData}
|
||||||
const data = editor.getData();
|
onChange={(event, editor) => {
|
||||||
// console.log({ event, editor, data });
|
const data = editor.getData();
|
||||||
props.onChange(data);
|
console.log({ event, editor, data });
|
||||||
}}
|
props.onChange(data);
|
||||||
config={{
|
}}
|
||||||
toolbar: [
|
config={{
|
||||||
"heading",
|
toolbar: [
|
||||||
"fontsize",
|
"heading",
|
||||||
"bold",
|
"fontsize",
|
||||||
"italic",
|
"bold",
|
||||||
"link",
|
"italic",
|
||||||
"numberedList",
|
"link",
|
||||||
"bulletedList",
|
"numberedList",
|
||||||
"undo",
|
"bulletedList",
|
||||||
"redo",
|
"undo",
|
||||||
"alignment",
|
"redo",
|
||||||
"outdent",
|
"alignment",
|
||||||
"indent",
|
"outdent",
|
||||||
"blockQuote",
|
"indent",
|
||||||
"insertTable",
|
"blockQuote",
|
||||||
"codeBlock",
|
"insertTable",
|
||||||
"sourceEditing",
|
"codeBlock",
|
||||||
],
|
"sourceEditing",
|
||||||
}}
|
],
|
||||||
/>
|
content_style: `
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #111 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 1em 0 0.5em 0;
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
ul, ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border-left: 4px solid #d1d5db;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
height: props.height || 400,
|
||||||
|
removePlugins: ["Title"],
|
||||||
|
mobile: {
|
||||||
|
theme: "silver",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<style jsx>{`
|
||||||
|
.ckeditor-wrapper {
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
// box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||||
|
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper :global(.ck.ck-editor__main) {
|
||||||
|
min-height: ${props.height || 300}px;
|
||||||
|
max-height: ${maxHeight}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper :global(.ck.ck-editor__editable) {
|
||||||
|
min-height: ${(props.height || 400) - 50}px;
|
||||||
|
max-height: ${maxHeight - 50}px;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||||
|
background: #fff !important;
|
||||||
|
color: #111 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode support */
|
||||||
|
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable) {
|
||||||
|
background: #111 !important;
|
||||||
|
color: #f9fafb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h1),
|
||||||
|
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h2),
|
||||||
|
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h3),
|
||||||
|
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h4),
|
||||||
|
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h5),
|
||||||
|
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h6) {
|
||||||
|
color: #f9fafb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark)
|
||||||
|
.ckeditor-wrapper
|
||||||
|
:global(.ck.ck-editor__editable blockquote) {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
border-left-color: #374151 !important;
|
||||||
|
color: #f3f4f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar styling for webkit browsers */
|
||||||
|
.ckeditor-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar) {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode scrollbar */
|
||||||
|
:global(.dark)
|
||||||
|
.ckeditor-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
||||||
|
background: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark)
|
||||||
|
.ckeditor-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
||||||
|
background: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark)
|
||||||
|
.ckeditor-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
||||||
|
background: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure content doesn't overflow */
|
||||||
|
.ckeditor-wrapper :global(.ck.ck-editor__editable .ck-content) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,261 @@ import { CKEditor } from "@ckeditor/ckeditor5-react";
|
||||||
import Editor from "ckeditor5-custom-build";
|
import Editor from "ckeditor5-custom-build";
|
||||||
|
|
||||||
function ViewEditor(props) {
|
function ViewEditor(props) {
|
||||||
|
const maxHeight = props.maxHeight || 600; // Default max height 600px
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CKEditor
|
<div className="ckeditor-view-wrapper">
|
||||||
editor={Editor}
|
<CKEditor
|
||||||
data={props.initialData}
|
editor={Editor}
|
||||||
disabled={true}
|
data={props.initialData}
|
||||||
config={{
|
disabled={true}
|
||||||
// toolbar: [],
|
config={{
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
}}
|
content_style: `
|
||||||
/>
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #111;
|
||||||
|
background: #fff;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin: 1em 0 0.5em 0;
|
||||||
|
}
|
||||||
|
ul, ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border-left: 4px solid #d1d5db;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
height: props.height || 400,
|
||||||
|
removePlugins: ["Title"],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<style jsx>{`
|
||||||
|
.ckeditor-view-wrapper {
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
||||||
|
0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-view-wrapper :global(.ck.ck-editor__main) {
|
||||||
|
min-height: ${props.height || 400}px;
|
||||||
|
max-height: ${maxHeight}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-view-wrapper :global(.ck.ck-editor__editable) {
|
||||||
|
min-height: ${(props.height || 400) - 50}px;
|
||||||
|
max-height: ${maxHeight - 50}px;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 🌙 Dark mode support */
|
||||||
|
:global(.dark) .ckeditor-view-wrapper :global(.ck.ck-editor__editable) {
|
||||||
|
background-color: #111 !important;
|
||||||
|
color: #f9fafb !important;
|
||||||
|
border-color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .ckeditor-view-wrapper h1,
|
||||||
|
:global(.dark) .ckeditor-view-wrapper h2,
|
||||||
|
:global(.dark) .ckeditor-view-wrapper h3,
|
||||||
|
:global(.dark) .ckeditor-view-wrapper h4,
|
||||||
|
:global(.dark) .ckeditor-view-wrapper h5,
|
||||||
|
:global(.dark) .ckeditor-view-wrapper h6 {
|
||||||
|
color: #f9fafb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .ckeditor-view-wrapper blockquote {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
border-left: 4px solid #374151 !important;
|
||||||
|
color: #f3f4f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar styling */
|
||||||
|
.ckeditor-view-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar) {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-view-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-view-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ckeditor-view-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 🌙 Dark mode scrollbar */
|
||||||
|
:global(.dark)
|
||||||
|
.ckeditor-view-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
||||||
|
background: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark)
|
||||||
|
.ckeditor-view-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
||||||
|
background: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark)
|
||||||
|
.ckeditor-view-wrapper
|
||||||
|
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
||||||
|
background: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read-only specific styling */
|
||||||
|
.ckeditor-view-wrapper :global(.ck.ck-editor__editable.ck-read-only) {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide toolbar */
|
||||||
|
.ckeditor-view-wrapper :global(.ck.ck-toolbar) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ViewEditor;
|
export default ViewEditor;
|
||||||
|
|
||||||
|
// import React from "react";
|
||||||
|
// import { CKEditor } from "@ckeditor/ckeditor5-react";
|
||||||
|
// import Editor from "ckeditor5-custom-build";
|
||||||
|
|
||||||
|
// function ViewEditor(props) {
|
||||||
|
// const maxHeight = props.maxHeight || 600;
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div className="ckeditor-view-wrapper">
|
||||||
|
// <CKEditor
|
||||||
|
// editor={Editor}
|
||||||
|
// data={props.initialData}
|
||||||
|
// disabled={true}
|
||||||
|
// config={{
|
||||||
|
// // toolbar: [],
|
||||||
|
// isReadOnly: true,
|
||||||
|
// // Add content styling configuration for read-only mode
|
||||||
|
// content_style: `
|
||||||
|
// body {
|
||||||
|
// font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
// font-size: 14px;
|
||||||
|
// line-height: 1.6;
|
||||||
|
// color: #333;
|
||||||
|
// margin: 0;
|
||||||
|
// padding: 0;
|
||||||
|
// }
|
||||||
|
// p {
|
||||||
|
// margin: 0.5em 0;
|
||||||
|
// }
|
||||||
|
// h1, h2, h3, h4, h5, h6 {
|
||||||
|
// margin: 1em 0 0.5em 0;
|
||||||
|
// }
|
||||||
|
// ul, ol {
|
||||||
|
// margin: 0.5em 0;
|
||||||
|
// padding-left: 2em;
|
||||||
|
// }
|
||||||
|
// blockquote {
|
||||||
|
// margin: 1em 0;
|
||||||
|
// padding: 0.5em 1em;
|
||||||
|
// border-left: 4px solid #d1d5db;
|
||||||
|
// background-color: #f9fafb;
|
||||||
|
// }
|
||||||
|
// `,
|
||||||
|
// // Editor appearance settings
|
||||||
|
// height: props.height || 400,
|
||||||
|
// removePlugins: ['Title'],
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// <style jsx>{`
|
||||||
|
// .ckeditor-view-wrapper {
|
||||||
|
// border-radius: 6px;
|
||||||
|
// overflow: hidden;
|
||||||
|
// box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__main) {
|
||||||
|
// min-height: ${props.height || 400}px;
|
||||||
|
// max-height: ${maxHeight}px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__editable) {
|
||||||
|
// min-height: ${(props.height || 400) - 50}px;
|
||||||
|
// max-height: ${maxHeight - 50}px;
|
||||||
|
// overflow-y: auto !important;
|
||||||
|
// scrollbar-width: thin;
|
||||||
|
// scrollbar-color: #cbd5e1 #f1f5f9;
|
||||||
|
// background-color:rgb(253, 253, 253);
|
||||||
|
// border: 1px solid #d1d5db;
|
||||||
|
// border-radius: 6px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Custom scrollbar styling for webkit browsers */
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar) {
|
||||||
|
// width: 8px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
|
||||||
|
// background: #f1f5f9;
|
||||||
|
// border-radius: 4px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
|
||||||
|
// background: #cbd5e1;
|
||||||
|
// border-radius: 4px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
|
||||||
|
// background: #94a3b8;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Ensure content doesn't overflow */
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__editable .ck-content) {
|
||||||
|
// overflow: hidden;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Read-only specific styling */
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-editor__editable.ck-read-only) {
|
||||||
|
// background-color: #f8fafc;
|
||||||
|
// color: #4b5563;
|
||||||
|
// cursor: default;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /* Hide toolbar for view-only mode */
|
||||||
|
// .ckeditor-view-wrapper :global(.ck.ck-toolbar) {
|
||||||
|
// display: none !important;
|
||||||
|
// }
|
||||||
|
// `}</style>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default ViewEditor;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { postBlog, uploadThumbnailBlog } from "@/service/blog/blog";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { error } from "console";
|
import { error } from "console";
|
||||||
import { loading } from "@/lib/swal";
|
import { loading } from "@/lib/swal";
|
||||||
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -92,6 +93,7 @@ export default function FormBlog() {
|
||||||
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
const [thumbnail, setThumbnail] = useState<File | null>(null);
|
||||||
const [preview, setPreview] = useState<string | null>(null);
|
const [preview, setPreview] = useState<string | null>(null);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const roleName = getCookiesDecrypt("urne");
|
||||||
|
|
||||||
const [unitSelection, setUnitSelection] = useState({
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
allUnit: false,
|
allUnit: false,
|
||||||
|
|
@ -180,6 +182,7 @@ export default function FormBlog() {
|
||||||
|
|
||||||
const save = async (data: TaskSchema) => {
|
const save = async (data: TaskSchema) => {
|
||||||
loading();
|
loading();
|
||||||
|
console.log("roleName", roleName);
|
||||||
const finalTags = tags.join(", ");
|
const finalTags = tags.join(", ");
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...data,
|
...data,
|
||||||
|
|
@ -190,6 +193,7 @@ export default function FormBlog() {
|
||||||
metadata: data.meta,
|
metadata: data.meta,
|
||||||
tags: finalTags,
|
tags: finalTags,
|
||||||
isDraft,
|
isDraft,
|
||||||
|
isInternational: roleName?.includes("INT") ? true : false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await postBlog(requestData);
|
const response = await postBlog(requestData);
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,7 @@ export default function ContentBlast(props: { type: string }) {
|
||||||
<Dialog open={openModal}>
|
<Dialog open={openModal}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Email Terkirim</DialogTitle>
|
<DialogTitle>Terkirim !!</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex flex-col justify-center items-center gap-3 mb-3 text-sm">
|
<div className="flex flex-col justify-center items-center gap-3 mb-3 text-sm">
|
||||||
<img
|
<img
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,78 @@ export default function FormCollaboration() {
|
||||||
Priority<span className="text-red-500">*</span>
|
Priority<span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
|
id="target-select"
|
||||||
|
options={ticketPriority}
|
||||||
|
onChange={(selectedOption) =>
|
||||||
|
setSelectedTarget(Number(selectedOption?.value))
|
||||||
|
}
|
||||||
|
placeholder="Pilih"
|
||||||
|
styles={{
|
||||||
|
control: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
minHeight: "40px",
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937" // gray-800
|
||||||
|
: "#ffffff",
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb" // gray-100
|
||||||
|
: "#111827", // gray-900
|
||||||
|
borderColor: state.isFocused
|
||||||
|
? "#2563eb"
|
||||||
|
: base.borderColor, // biru-600
|
||||||
|
boxShadow: state.isFocused
|
||||||
|
? "0 0 0 1px #2563eb"
|
||||||
|
: base.boxShadow,
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "#2563eb",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
menu: (base) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
}),
|
||||||
|
option: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor: state.isSelected
|
||||||
|
? "#2563eb" // biru solid kalau dipilih
|
||||||
|
: state.isFocused
|
||||||
|
? "#2563eb33" // biru transparan kalau hover
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: state.isSelected
|
||||||
|
? "#ffffff"
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
cursor: "pointer",
|
||||||
|
}),
|
||||||
|
singleValue: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
}),
|
||||||
|
placeholder: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#9ca3af"
|
||||||
|
: "#6b7280",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="mt-5">
|
||||||
|
<Label>
|
||||||
|
Priority<span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
className="bg-white dark:bg-black"
|
||||||
id="target-select"
|
id="target-select"
|
||||||
options={ticketPriority}
|
options={ticketPriority}
|
||||||
onChange={(selectedOption) =>
|
onChange={(selectedOption) =>
|
||||||
|
|
@ -268,7 +340,7 @@ export default function FormCollaboration() {
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
|
|
@ -283,8 +355,82 @@ export default function FormCollaboration() {
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
formatOptionLabel={formatOptionLabel}
|
formatOptionLabel={formatOptionLabel}
|
||||||
isMulti={false}
|
isMulti={false}
|
||||||
|
styles={{
|
||||||
|
control: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937" // bg-gray-800
|
||||||
|
: "#ffffff", // bg-white
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb" // text-gray-100
|
||||||
|
: "#111827", // text-gray-900
|
||||||
|
borderColor: state.isFocused
|
||||||
|
? "#2563eb"
|
||||||
|
: base.borderColor,
|
||||||
|
boxShadow: state.isFocused
|
||||||
|
? "0 0 0 1px #2563eb"
|
||||||
|
: base.boxShadow,
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "#2563eb",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
menu: (base) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
}),
|
||||||
|
option: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor: state.isSelected
|
||||||
|
? "#2563eb"
|
||||||
|
: state.isFocused
|
||||||
|
? "#2563eb33"
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: state.isSelected
|
||||||
|
? "#ffffff"
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
cursor: "pointer",
|
||||||
|
}),
|
||||||
|
singleValue: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
}),
|
||||||
|
placeholder: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#9ca3af"
|
||||||
|
: "#6b7280",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="mt-5">
|
||||||
|
<Label>
|
||||||
|
Eskalasi Untuk <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
options={options}
|
||||||
|
className="w-100"
|
||||||
|
closeMenuOnSelect={false}
|
||||||
|
components={animatedComponent}
|
||||||
|
onChange={handleChange}
|
||||||
|
formatOptionLabel={formatOptionLabel}
|
||||||
|
isMulti={false}
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<Label>Narasi Penugasan</Label>
|
<Label>Narasi Penugasan</Label>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ export type replyDetail = {
|
||||||
export default function FormDetailEscalation() {
|
export default function FormDetailEscalation() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
|
|
||||||
const [detail, setDetail] = useState<any>();
|
const [detail, setDetail] = useState<any>();
|
||||||
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
const [ticketReply, setTicketReply] = useState<replyDetail[]>([]);
|
||||||
const [replyVisible, setReplyVisible] = useState(false);
|
const [replyVisible, setReplyVisible] = useState(false);
|
||||||
|
|
@ -173,11 +172,11 @@ export default function FormDetailEscalation() {
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex flex-col mt-6 w-full mb-3">
|
<div className="flex flex-col mt-6 w-full mb-3">
|
||||||
{detail !== undefined && (
|
{detail !== undefined && (
|
||||||
<div key={detail?.id} className="bg-slate-300 rounded-md">
|
<div key={detail?.id} className="bg-slate-300 dark:bg-black rounded-md">
|
||||||
<p className="p-5 bg-slate-300 rounded-md text-lg font-semibold">
|
<p className="p-5 bg-slate-300 dark:bg-black rounded-md text-lg font-semibold">
|
||||||
Ticket #{detail.id}
|
Ticket #{detail.id}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-row gap-3 bg-sky-100 p-5 items-center">
|
<div className="flex flex-row gap-3 bg-sky-100 dark:bg-sky-900 p-5 items-center">
|
||||||
<Icon icon="qlementine-icons:user-16" width={36} />
|
<Icon icon="qlementine-icons:user-16" width={36} />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -210,8 +209,8 @@ export default function FormDetailEscalation() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="p-5 bg-white">{detail.message}</p>
|
<p className="p-5 bg-white dark:bg-black">{detail.message}</p>
|
||||||
<div className="px-4 py-1 bg-white text-sm">
|
<div className="px-4 py-1 bg-white dark:bg-black text-sm">
|
||||||
{detail?.typeId === 6 && detail?.emergencyIssue ? (
|
{detail?.typeId === 6 && detail?.emergencyIssue ? (
|
||||||
<div className="row mx-0 mb-3 emergency-attachments">
|
<div className="row mx-0 mb-3 emergency-attachments">
|
||||||
<div className=" mr-4">
|
<div className=" mr-4">
|
||||||
|
|
@ -239,7 +238,7 @@ export default function FormDetailEscalation() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{detail !== undefined && (
|
{detail !== undefined && (
|
||||||
<div className="gap-5 mb-5 w-full border mt-3 rounded-md bg-white">
|
<div className="gap-5 mb-5 w-full border mt-3 rounded-md bg-white dark:bg-black">
|
||||||
<div className="space-y-2 px-3 mt-3">
|
<div className="space-y-2 px-3 mt-3">
|
||||||
<Label>Judul</Label>
|
<Label>Judul</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -297,16 +296,16 @@ export default function FormDetailEscalation() {
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-3 my-3">
|
<div className="mx-3 my-3">
|
||||||
<h3 className="text-gray-700 font-medium">Tanggapan</h3>
|
<h3 className="text-gray-700 dark:text-white font-medium">Tanggapan</h3>
|
||||||
<div className="space-y-4 ml-5 mt-5">
|
<div className="space-y-4 ml-5 mt-5">
|
||||||
{listDiscussion.map((parent: any) => (
|
{listDiscussion.map((parent: any) => (
|
||||||
<div key={parent.id} className="border-b pb-4">
|
<div key={parent.id} className="border-b pb-4">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-gray-800">
|
<p className="font-semibold text-gray-800 dark:text-white">
|
||||||
{parent.messageFrom?.fullname}
|
{parent.messageFrom?.fullname}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-600">{parent.message}</p>
|
<p className="text-gray-600 dark:text-white">{parent.message}</p>
|
||||||
<p className="text-sm text-gray-400">
|
<p className="text-sm text-gray-400">
|
||||||
{parent.createdAt}
|
{parent.createdAt}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -338,7 +337,7 @@ export default function FormDetailEscalation() {
|
||||||
<div className="flex justify-end gap-2 mt-2">
|
<div className="flex justify-end gap-2 mt-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveReplyId(null)}
|
onClick={() => setActiveReplyId(null)}
|
||||||
className="px-4 py-1 text-sm bg-gray-200 text-gray-800 rounded-md"
|
className="px-4 py-1 text-sm bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-white rounded-md"
|
||||||
>
|
>
|
||||||
Batal
|
Batal
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -363,10 +362,10 @@ export default function FormDetailEscalation() {
|
||||||
className="flex justify-between items-start"
|
className="flex justify-between items-start"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-gray-800">
|
<p className="font-semibold text-gray-800 dark:text-white">
|
||||||
{child.messageFrom?.fullname}
|
{child.messageFrom?.fullname}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-600">{child.message}</p>
|
<p className="text-gray-600 dark:text-white">{child.message}</p>
|
||||||
<p className="text-sm text-gray-400">
|
<p className="text-sm text-gray-400">
|
||||||
{child.createdAt}
|
{child.createdAt}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -386,7 +385,7 @@ export default function FormDetailEscalation() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<label className="block text-gray-700 font-medium mb-2">
|
<label className="block text-gray-700 dark:text-white font-medium mb-2">
|
||||||
Tulis Tanggapan Anda
|
Tulis Tanggapan Anda
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
|
|
||||||
|
|
@ -201,10 +201,80 @@ export default function FormInternal() {
|
||||||
setSelectedTarget(selectedOption?.value ?? null)
|
setSelectedTarget(selectedOption?.value ?? null)
|
||||||
}
|
}
|
||||||
placeholder="Pilih"
|
placeholder="Pilih"
|
||||||
styles={{ control: (base) => ({ ...base, minHeight: "40px" }) }}
|
styles={{
|
||||||
|
control: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
minHeight: "40px",
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937" // bg-gray-800
|
||||||
|
: "#ffffff", // bg-white
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb" // text-gray-100
|
||||||
|
: "#111827", // text-gray-900
|
||||||
|
borderColor: state.isFocused ? "#2563eb" : base.borderColor,
|
||||||
|
boxShadow: state.isFocused
|
||||||
|
? "0 0 0 1px #2563eb"
|
||||||
|
: base.boxShadow,
|
||||||
|
"&:hover": { borderColor: "#2563eb" },
|
||||||
|
}),
|
||||||
|
menu: (base) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
}),
|
||||||
|
option: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor: state.isSelected
|
||||||
|
? "#2563eb"
|
||||||
|
: state.isFocused
|
||||||
|
? "#2563eb33"
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: state.isSelected
|
||||||
|
? "#ffffff"
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
cursor: "pointer",
|
||||||
|
}),
|
||||||
|
singleValue: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
}),
|
||||||
|
placeholder: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#9ca3af"
|
||||||
|
: "#6b7280",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="w-6/12">
|
||||||
|
<Label>
|
||||||
|
Priority <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
id="target-select"
|
||||||
|
options={priority}
|
||||||
|
onChange={(selectedOption) =>
|
||||||
|
setSelectedTarget(selectedOption?.value ?? null)
|
||||||
|
}
|
||||||
|
placeholder="Pilih"
|
||||||
|
styles={{ control: (base) => ({ ...base, minHeight: "40px" }) }}
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
<div className="w-6/12">
|
<div className="w-6/12">
|
||||||
<Label>Ditunjukan Untuk</Label>
|
<Label>Ditunjukan Untuk</Label>
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -214,9 +284,77 @@ export default function FormInternal() {
|
||||||
components={animatedComponent}
|
components={animatedComponent}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
isMulti={false}
|
isMulti={false}
|
||||||
|
styles={{
|
||||||
|
control: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
minHeight: "40px",
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
borderColor: state.isFocused ? "#2563eb" : base.borderColor,
|
||||||
|
boxShadow: state.isFocused
|
||||||
|
? "0 0 0 1px #2563eb"
|
||||||
|
: base.boxShadow,
|
||||||
|
"&:hover": { borderColor: "#2563eb" },
|
||||||
|
}),
|
||||||
|
menu: (base) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor:
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
}),
|
||||||
|
option: (base, state) => ({
|
||||||
|
...base,
|
||||||
|
backgroundColor: state.isSelected
|
||||||
|
? "#2563eb"
|
||||||
|
: state.isFocused
|
||||||
|
? "#2563eb33"
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#1f2937"
|
||||||
|
: "#ffffff",
|
||||||
|
color: state.isSelected
|
||||||
|
? "#ffffff"
|
||||||
|
: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
cursor: "pointer",
|
||||||
|
}),
|
||||||
|
singleValue: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#f9fafb"
|
||||||
|
: "#111827",
|
||||||
|
}),
|
||||||
|
placeholder: (base) => ({
|
||||||
|
...base,
|
||||||
|
color: document.documentElement.classList.contains("dark")
|
||||||
|
? "#9ca3af"
|
||||||
|
: "#6b7280",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="w-6/12">
|
||||||
|
<Label>Ditunjukan Untuk</Label>
|
||||||
|
<Select
|
||||||
|
options={options}
|
||||||
|
className="w-100"
|
||||||
|
closeMenuOnSelect={false}
|
||||||
|
components={animatedComponent}
|
||||||
|
onChange={handleChange}
|
||||||
|
isMulti={false}
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
<div className="w-6/12 mt-5">
|
<div className="w-6/12 mt-5">
|
||||||
<Label>Narasi Penugasan</Label>
|
<Label>Narasi Penugasan</Label>
|
||||||
<Controller
|
<Controller
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,9 @@ export default function FormAudioDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[fileIndex] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[fileIndex] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
|
|
@ -721,7 +723,9 @@ export default function FormAudioDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
listDest.forEach((item: any) => {
|
listDest.forEach((item: any) => {
|
||||||
|
|
@ -784,7 +788,9 @@ export default function FormAudioDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Unchecklist semua item di modal
|
// Unchecklist semua item di modal
|
||||||
currentFileLevels.clear();
|
currentFileLevels.clear();
|
||||||
|
|
@ -868,8 +874,6 @@ export default function FormAudioDetail() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const updateModalChecklistLevels = (
|
const updateModalChecklistLevels = (
|
||||||
fileIndex: number,
|
fileIndex: number,
|
||||||
placement: string,
|
placement: string,
|
||||||
|
|
@ -1193,7 +1197,10 @@ export default function FormAudioDetail() {
|
||||||
// setSelectedTarget(id);
|
// setSelectedTarget(id);
|
||||||
// }}
|
// }}
|
||||||
>
|
>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger
|
||||||
|
size="md"
|
||||||
|
className="border border-gray-300 dark:border-gray-600"
|
||||||
|
>
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,8 @@ export default function FormImageDetail() {
|
||||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
const [listDest, setListDest] = useState<Destination[]>([]);
|
const [listDest, setListDest] = useState<Destination[]>([]);
|
||||||
console.log("LALALALA", userLevelName);
|
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
// console.log(id);
|
|
||||||
const editor = useRef(null);
|
const editor = useRef(null);
|
||||||
type ImageSchema = z.infer<typeof imageSchema>;
|
type ImageSchema = z.infer<typeof imageSchema>;
|
||||||
const t = useTranslations("Form");
|
const t = useTranslations("Form");
|
||||||
|
|
@ -487,7 +485,9 @@ export default function FormImageDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[fileIndex] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[fileIndex] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
|
|
@ -951,8 +951,8 @@ export default function FormImageDetail() {
|
||||||
setCheckedLevels(levels);
|
setCheckedLevels(levels);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject?.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
@ -1015,18 +1015,20 @@ export default function FormImageDetail() {
|
||||||
const temp = [];
|
const temp = [];
|
||||||
for (let i = 0; i < filePlacements?.length; i++) {
|
for (let i = 0; i < filePlacements?.length; i++) {
|
||||||
if (filePlacements[i]?.length !== 0) {
|
if (filePlacements[i]?.length !== 0) {
|
||||||
const now = filePlacements[i]
|
const now = filePlacements[i];
|
||||||
let nowArr = now?.join(",")?.replaceAll("wilayah", "polda");
|
let nowArr = now?.join(",")?.replaceAll("wilayah", "polda");
|
||||||
nowArr = nowArr?.replaceAll("nasional", "mabes");
|
nowArr = nowArr?.replaceAll("nasional", "mabes");
|
||||||
nowArr = nowArr?.replaceAll("semua", "all");
|
nowArr = nowArr?.replaceAll("semua", "all");
|
||||||
|
|
||||||
// Dapatkan checked levels untuk file ini
|
// Dapatkan checked levels untuk file ini
|
||||||
const currentFileCheckedLevels = fileCheckedLevels[i] ? Array.from(fileCheckedLevels[i]) : [];
|
const currentFileCheckedLevels = fileCheckedLevels[i]
|
||||||
|
? Array.from(fileCheckedLevels[i])
|
||||||
|
: [];
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
mediaFileId: files[i]?.id,
|
mediaFileId: files[i]?.id,
|
||||||
placements: nowArr,
|
placements: nowArr,
|
||||||
customLocationPlacements: currentFileCheckedLevels.join(",")
|
customLocationPlacements: currentFileCheckedLevels.join(","),
|
||||||
};
|
};
|
||||||
temp.push(data);
|
temp.push(data);
|
||||||
}
|
}
|
||||||
|
|
@ -1059,15 +1061,17 @@ export default function FormImageDetail() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataReject = {
|
if (rejectedFiles) {
|
||||||
listFiles: rejectedFiles,
|
const dataReject = {
|
||||||
};
|
listFiles: rejectedFiles,
|
||||||
|
};
|
||||||
|
|
||||||
const resReject = await rejectFiles(dataReject);
|
const resReject = await rejectFiles(dataReject);
|
||||||
|
|
||||||
if (resReject?.error) {
|
if (resReject?.error) {
|
||||||
error(resReject.message);
|
error(resReject.message);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close();
|
close();
|
||||||
|
|
@ -1089,7 +1093,9 @@ export default function FormImageDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
listDest.forEach((item: any) => {
|
listDest.forEach((item: any) => {
|
||||||
|
|
@ -1152,7 +1158,9 @@ export default function FormImageDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Unchecklist semua item di modal
|
// Unchecklist semua item di modal
|
||||||
currentFileLevels.clear();
|
currentFileLevels.clear();
|
||||||
|
|
@ -1385,7 +1393,6 @@ export default function FormImageDetail() {
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="py-3 w-full space-y-2">
|
<div className="py-3 w-full space-y-2">
|
||||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
disabled
|
disabled
|
||||||
value={String(detail?.category?.id)}
|
value={String(detail?.category?.id)}
|
||||||
|
|
@ -1394,11 +1401,13 @@ export default function FormImageDetail() {
|
||||||
// setSelectedTarget(id);
|
// setSelectedTarget(id);
|
||||||
// }}
|
// }}
|
||||||
>
|
>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger
|
||||||
|
size="md"
|
||||||
|
className="border border-gray-300 dark:border-gray-600"
|
||||||
|
>
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{/* Show the category from details if it doesn't exist in categories list */}
|
|
||||||
{detail &&
|
{detail &&
|
||||||
!categories.find(
|
!categories.find(
|
||||||
(cat) =>
|
(cat) =>
|
||||||
|
|
@ -1999,9 +2008,7 @@ export default function FormImageDetail() {
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button>
|
<Button>Simpan</Button>
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import {
|
||||||
getDetailArticle,
|
getDetailArticle,
|
||||||
getGenerateKeywords,
|
getGenerateKeywords,
|
||||||
getGenerateTitle,
|
getGenerateTitle,
|
||||||
|
translateText,
|
||||||
} from "@/service/content/ai";
|
} from "@/service/content/ai";
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
|
|
@ -109,7 +110,8 @@ export default function FormImage() {
|
||||||
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
|
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
|
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
|
||||||
|
const [localContent, setLocalContent] = useState("");
|
||||||
|
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
|
||||||
const [articleIds, setArticleIds] = useState<string[]>([]);
|
const [articleIds, setArticleIds] = useState<string[]>([]);
|
||||||
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
|
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
|
||||||
const [articleBody, setArticleBody] = useState<string>("");
|
const [articleBody, setArticleBody] = useState<string>("");
|
||||||
|
|
@ -408,6 +410,17 @@ export default function FormImage() {
|
||||||
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
|
Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function doTranslate() {
|
||||||
|
const data = {
|
||||||
|
text: "Hello World",
|
||||||
|
sourceLang: "EN",
|
||||||
|
targetLang: "ID",
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await translateText(data);
|
||||||
|
console.log("Hasil Translate:", result);
|
||||||
|
}
|
||||||
|
|
||||||
const handleArticleIdClick = async (id: string) => {
|
const handleArticleIdClick = async (id: string) => {
|
||||||
setIsLoadingData(true);
|
setIsLoadingData(true);
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
|
|
@ -1162,18 +1175,22 @@ export default function FormImage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
|
{" "}
|
||||||
<Label>
|
<Label>
|
||||||
{t("description", { defaultValue: "Description" })}
|
{" "}
|
||||||
</Label>
|
{t("description", { defaultValue: "Description" })}{" "}
|
||||||
|
</Label>{" "}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="description"
|
||||||
render={({ field: { onChange, value } }) =>
|
render={({ field: { onChange, value } }) =>
|
||||||
isLoadingData ? (
|
isLoadingData ? (
|
||||||
<div className="flex justify-center items-center h-40">
|
<div className="flex justify-center items-center h-40">
|
||||||
|
{" "}
|
||||||
<p className="text-gray-500 dark:text-black">
|
<p className="text-gray-500 dark:text-black">
|
||||||
Loading Proses Data...
|
{" "}
|
||||||
</p>
|
Loading Proses Data...{" "}
|
||||||
|
</p>{" "}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<CustomEditor
|
<CustomEditor
|
||||||
|
|
@ -1186,12 +1203,13 @@ export default function FormImage() {
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>{" "}
|
||||||
{errors.description?.message && (
|
{errors.description?.message && (
|
||||||
<p className="text-red-400 text-sm">
|
<p className="text-red-400 text-sm">
|
||||||
{errors.description.message}
|
{" "}
|
||||||
|
{errors.description.message}{" "}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}{" "}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1208,7 +1226,69 @@ export default function FormImage() {
|
||||||
Select Original Description
|
Select Original Description
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-3 space-y-2">
|
<div className="py-3 space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Label>
|
||||||
|
{t("description", { defaultValue: "Description" })}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
setIsLoadingTranslate(true);
|
||||||
|
const res = await translateText({
|
||||||
|
text: localContent,
|
||||||
|
sourceLang: "ID",
|
||||||
|
targetLang: "EN",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.error && res.data?.translatedText) {
|
||||||
|
setLocalContent(res.data.translatedText);
|
||||||
|
setValue(
|
||||||
|
"descriptionOri",
|
||||||
|
res.data.translatedText
|
||||||
|
);
|
||||||
|
setEditorContent(res.data.translatedText);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Translate gagal:", err);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingTranslate(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
{isLoadingTranslate
|
||||||
|
? "Translating..."
|
||||||
|
: "Translate to English"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Editor */}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="descriptionOri"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={(val: any) => {
|
||||||
|
onChange(val);
|
||||||
|
setLocalContent(val);
|
||||||
|
setEditorContent(val);
|
||||||
|
}}
|
||||||
|
initialData={localContent || value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* <div className="py-3 space-y-2">
|
||||||
<Label>
|
<Label>
|
||||||
{t("description", { defaultValue: "Description" })}
|
{t("description", { defaultValue: "Description" })}
|
||||||
</Label>
|
</Label>
|
||||||
|
|
@ -1230,7 +1310,7 @@ export default function FormImage() {
|
||||||
{errors.description.message}
|
{errors.description.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<p className="text-sm font-semibold">Content Rewrite</p>
|
<p className="text-sm font-semibold">Content Rewrite</p>
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
|
|
|
||||||
|
|
@ -731,7 +731,7 @@ export default function FormConvertSPIT() {
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="lg:col-span-2 space-y-6">
|
<div className="lg:col-span-3 space-y-6">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
@ -799,138 +799,40 @@ export default function FormConvertSPIT() {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-8">
|
<CardContent className="space-y-8">
|
||||||
{/* Original Content */}
|
{/* Pilih Upload Type */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-lg text-black">Original Content</Label>
|
<Label>Upload Type</Label>
|
||||||
<Controller
|
<RadioGroup
|
||||||
control={control}
|
value={selectedFileType}
|
||||||
name="contentDescription"
|
onValueChange={(value: "original" | "rewrite") =>
|
||||||
render={({ field }) => (
|
setSelectedFileType(value)
|
||||||
<CustomEditor
|
}
|
||||||
onChange={field.onChange}
|
className="grid grid-cols-2 gap-4"
|
||||||
initialData={field.value}
|
>
|
||||||
/>
|
<div className="flex items-center space-x-2">
|
||||||
)}
|
<RadioGroupItem value="original" id="original" />
|
||||||
/>
|
<Label htmlFor="original">Original Content</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="rewrite" id="rewrite" />
|
||||||
|
<Label htmlFor="rewrite">Rewritten Content</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Rewrite */}
|
{/* Tampilkan keduanya berdampingan */}
|
||||||
<div className="space-y-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<Label className="text-lg text-black">
|
{/* Original Content */}
|
||||||
Rewritten Content
|
<div
|
||||||
</Label>
|
className={`space-y-2 p-4 rounded-lg border ${
|
||||||
|
selectedFileType === "original"
|
||||||
<div className="flex items-center justify-between">
|
? "border-blue-600"
|
||||||
<div className="space-y-2">
|
: "border-gray-300"
|
||||||
<Label>Writing Style</Label>
|
}`}
|
||||||
<Select
|
>
|
||||||
value={selectedWritingStyle}
|
<Label className="text-lg text-black">
|
||||||
onValueChange={setSelectedWritingStyle}
|
Original Content
|
||||||
>
|
</Label>
|
||||||
<SelectTrigger className="w-48">
|
|
||||||
<SelectValue placeholder="Select style" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{WRITING_STYLES.map((style) => (
|
|
||||||
<SelectItem key={style.value} value={style.value}>
|
|
||||||
{style.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={handleRewriteClick}
|
|
||||||
disabled={
|
|
||||||
isGeneratingRewrite || !detail?.contentDescription
|
|
||||||
}
|
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
{isGeneratingRewrite ? (
|
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Edit3 className="h-4 w-4 mr-2" />
|
|
||||||
)}
|
|
||||||
Generate Rewrite
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showRewriteEditor && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{articleIds.length > 0 && (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{articleIds.map((articleId, index) => (
|
|
||||||
<Button
|
|
||||||
key={articleId}
|
|
||||||
type="button"
|
|
||||||
variant={
|
|
||||||
selectedArticleId === articleId
|
|
||||||
? "default"
|
|
||||||
: "outline"
|
|
||||||
}
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleArticleSelect(articleId)}
|
|
||||||
disabled={isLoadingRewrite}
|
|
||||||
>
|
|
||||||
{isLoadingRewrite &&
|
|
||||||
selectedArticleId === articleId && (
|
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
)}
|
|
||||||
Narrative {index + 1}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Rewritten Content</Label>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="contentRewriteDescription"
|
|
||||||
render={({ field }) => (
|
|
||||||
<CustomEditor
|
|
||||||
onChange={field.onChange}
|
|
||||||
initialData={articleBody || field.value}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* <Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Edit3 className="h-5 w-5" />
|
|
||||||
Content Editor
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<RadioGroup
|
|
||||||
value={selectedFileType}
|
|
||||||
onValueChange={(value: "original" | "rewrite") =>
|
|
||||||
setSelectedFileType(value)
|
|
||||||
}
|
|
||||||
className="grid grid-cols-2 gap-4"
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="original" id="original" />
|
|
||||||
<Label htmlFor="original">Original Content</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="rewrite" id="rewrite" />
|
|
||||||
<Label htmlFor="rewrite">Rewritten Content</Label>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
{/* Original Content */}
|
|
||||||
{/* {selectedFileType === "original" && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Content Description</Label>
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="contentDescription"
|
name="contentDescription"
|
||||||
|
|
@ -942,11 +844,19 @@ export default function FormConvertSPIT() {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
|
||||||
|
|
||||||
{/* Content Rewrite */}
|
{/* Rewrite Content */}
|
||||||
{/* {selectedFileType === "rewrite" && (
|
<div
|
||||||
<div className="space-y-4">
|
className={`space-y-4 p-4 rounded-lg border ${
|
||||||
|
selectedFileType === "rewrite"
|
||||||
|
? "border-blue-600"
|
||||||
|
: "border-gray-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Label className="text-lg text-black">
|
||||||
|
Rewritten Content
|
||||||
|
</Label>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Writing Style</Label>
|
<Label>Writing Style</Label>
|
||||||
|
|
@ -955,7 +865,7 @@ export default function FormConvertSPIT() {
|
||||||
onValueChange={setSelectedWritingStyle}
|
onValueChange={setSelectedWritingStyle}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-48">
|
<SelectTrigger className="w-48">
|
||||||
<SelectValue />
|
<SelectValue placeholder="Select style" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{WRITING_STYLES.map((style) => (
|
{WRITING_STYLES.map((style) => (
|
||||||
|
|
@ -1026,359 +936,367 @@ export default function FormConvertSPIT() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card> */}
|
</Card>
|
||||||
|
|
||||||
{/* Media Files */}
|
{/* Media Files */}
|
||||||
{detailThumb.length > 0 && (
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
<Card>
|
<div className="flex-1 space-y-6">
|
||||||
<CardHeader>
|
{detailThumb.length > 0 && (
|
||||||
<CardTitle className="flex items-center gap-2">
|
<Card>
|
||||||
<Image className="h-5 w-5" />
|
<CardHeader>
|
||||||
Media Files
|
<CardTitle className="flex items-center gap-2">
|
||||||
</CardTitle>
|
<Image className="h-5 w-5" />
|
||||||
</CardHeader>
|
Media Files
|
||||||
<CardContent className="space-y-4">
|
</CardTitle>
|
||||||
<div className="space-y-4">
|
</CardHeader>
|
||||||
<Swiper
|
<CardContent className="space-y-4">
|
||||||
thumbs={{ swiper: thumbsSwiper }}
|
<div className="space-y-4">
|
||||||
modules={[FreeMode, Navigation, Thumbs]}
|
<Swiper
|
||||||
navigation={true}
|
thumbs={{ swiper: thumbsSwiper }}
|
||||||
className="w-full h-96"
|
modules={[FreeMode, Navigation, Thumbs]}
|
||||||
>
|
navigation={true}
|
||||||
{detailThumb.map((item) => (
|
className="w-full h-96"
|
||||||
<SwiperSlide key={item.contentId}>
|
>
|
||||||
{item.contentType === "VIDEO" ? (
|
{detailThumb.map((item) => (
|
||||||
<div className="relative max-h-screen overflow-hidden">
|
<SwiperSlide key={item.contentId}>
|
||||||
<div className="w-full max-h-screen aspect-video">
|
{item.contentType === "VIDEO" ? (
|
||||||
<div className="w-full h-full object-contain">
|
<div className="relative max-h-screen overflow-hidden">
|
||||||
{/* main video player */}
|
<div className="w-full max-h-screen aspect-video">
|
||||||
<video
|
<div className="w-full h-full object-contain">
|
||||||
className="object-contain h-full w-full rounded-lg"
|
{/* main video player */}
|
||||||
src={item.contentFile}
|
<video
|
||||||
controls
|
className="object-contain h-full w-full rounded-lg"
|
||||||
// playsInline to better on mobile
|
src={item.contentFile}
|
||||||
playsInline
|
controls
|
||||||
// you can set poster if available: poster={item.thumbnailFileUrl}
|
// playsInline to better on mobile
|
||||||
title={`Video ${item.contentId}`}
|
playsInline
|
||||||
/>
|
// you can set poster if available: poster={item.thumbnailFileUrl}
|
||||||
|
title={`Video ${item.contentId}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
</div>
|
<img
|
||||||
) : (
|
src={item.contentFile}
|
||||||
<img
|
alt={`Media ${item.contentId}`}
|
||||||
src={item.contentFile}
|
className="w-full h-full object-cover rounded-lg"
|
||||||
alt={`Media ${item.contentId}`}
|
/>
|
||||||
className="w-full h-full object-cover rounded-lg"
|
)}
|
||||||
/>
|
</SwiperSlide>
|
||||||
)}
|
))}
|
||||||
</SwiperSlide>
|
</Swiper>
|
||||||
))}
|
|
||||||
</Swiper>
|
|
||||||
|
|
||||||
<Swiper
|
<Swiper
|
||||||
onSwiper={setThumbsSwiper}
|
onSwiper={setThumbsSwiper}
|
||||||
slidesPerView={8}
|
slidesPerView={8}
|
||||||
spaceBetween={8}
|
spaceBetween={8}
|
||||||
modules={[Pagination, Thumbs]}
|
modules={[Pagination, Thumbs]}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{detailThumb.map((item) => (
|
{detailThumb.map((item) => (
|
||||||
<SwiperSlide key={`thumb-${item.contentId}`}>
|
<SwiperSlide key={`thumb-${item.contentId}`}>
|
||||||
{item.contentType === "VIDEO" ? (
|
{item.contentType === "VIDEO" ? (
|
||||||
<div className="relative w-full h-16 rounded cursor-pointer overflow-hidden">
|
<div className="relative w-full h-16 rounded cursor-pointer overflow-hidden">
|
||||||
{/* use preload metadata so browser doesn't download full video */}
|
{/* use preload metadata so browser doesn't download full video */}
|
||||||
|
<video
|
||||||
|
src={item.contentFile}
|
||||||
|
className="w-full h-16 object-cover"
|
||||||
|
muted
|
||||||
|
preload="metadata"
|
||||||
|
playsInline
|
||||||
|
// no controls in thumbnail
|
||||||
|
tabIndex={-1}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center bg-black/30 pointer-events-none">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="w-6 h-6 text-white"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path d="M8 5v14l11-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={item.contentFile}
|
||||||
|
alt={`Thumbnail ${item.contentId}`}
|
||||||
|
className="w-full h-16 object-cover rounded cursor-pointer"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* File Placement */}
|
||||||
|
{files.length > 0 && isUserMabesApprover && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Globe className="h-5 w-5" />
|
||||||
|
File Placement
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{files.length > 1 && (
|
||||||
|
<div className="flex flex-wrap gap-4 p-4 bg-muted/50 rounded-lg">
|
||||||
|
{PLACEMENT_OPTIONS.map((option) => (
|
||||||
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`select-all-${option.value}`}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleSelectAllPlacements(
|
||||||
|
option.value,
|
||||||
|
Boolean(checked)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`select-all-${option.value}`}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
All {option.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{files.map((file, index) => (
|
||||||
|
<div
|
||||||
|
key={file.contentId}
|
||||||
|
className="flex gap-4 p-4 border rounded-lg"
|
||||||
|
>
|
||||||
|
{/* show thumbnail or video preview */}
|
||||||
|
{file.contentType === "VIDEO" ? (
|
||||||
<video
|
<video
|
||||||
src={item.contentFile}
|
src={file.contentFile}
|
||||||
className="w-full h-16 object-cover"
|
className="w-32 h-24 object-cover rounded"
|
||||||
muted
|
muted
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
playsInline
|
playsInline
|
||||||
// no controls in thumbnail
|
|
||||||
tabIndex={-1}
|
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/30 pointer-events-none">
|
) : (
|
||||||
<svg
|
<img
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
src={file.contentFile}
|
||||||
className="w-6 h-6 text-white"
|
alt={file.fileName || `file-${file.contentId}`}
|
||||||
fill="currentColor"
|
className="w-32 h-24 object-cover rounded"
|
||||||
viewBox="0 0 24 24"
|
/>
|
||||||
>
|
)}
|
||||||
<path d="M8 5v14l11-7z" />
|
|
||||||
</svg>
|
<div className="flex-1 space-y-3">
|
||||||
|
<p className="font-medium text-sm">
|
||||||
|
{file.fileName ||
|
||||||
|
file.contentFileName ||
|
||||||
|
`File ${file.contentId}`}
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{PLACEMENT_OPTIONS.map((option) => (
|
||||||
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`${file.contentId}-${option.value}`}
|
||||||
|
checked={filePlacements[index]?.includes(
|
||||||
|
option.value
|
||||||
|
)}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
handleFilePlacementChange(
|
||||||
|
index,
|
||||||
|
option.value,
|
||||||
|
Boolean(checked)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`${file.contentId}-${option.value}`}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<img
|
))}
|
||||||
src={item.contentFile}
|
</div>
|
||||||
alt={`Thumbnail ${item.contentId}`}
|
</CardContent>
|
||||||
className="w-full h-16 object-cover rounded cursor-pointer"
|
</Card>
|
||||||
/>
|
)}
|
||||||
)}
|
</div>
|
||||||
</SwiperSlide>
|
|
||||||
))}
|
|
||||||
</Swiper>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* File Placement */}
|
{/* Sidebar */}
|
||||||
{files.length > 0 && isUserMabesApprover && (
|
<div className="w-full lg:w-[30%] space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Globe className="h-5 w-5" />
|
<Users className="h-5 w-5" />
|
||||||
File Placement
|
Creator Information
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{files.length > 1 && (
|
<div className="space-y-2">
|
||||||
<div className="flex flex-wrap gap-4 p-4 bg-muted/50 rounded-lg">
|
<Label htmlFor="creator">Creator *</Label>
|
||||||
{PLACEMENT_OPTIONS.map((option) => (
|
<Controller
|
||||||
<div
|
control={control}
|
||||||
key={option.value}
|
name="contentCreator"
|
||||||
className="flex items-center space-x-2"
|
render={({ field }) => (
|
||||||
>
|
<Input
|
||||||
<Checkbox
|
id="creator"
|
||||||
id={`select-all-${option.value}`}
|
placeholder="Enter creator name"
|
||||||
onCheckedChange={(checked) =>
|
{...field}
|
||||||
handleSelectAllPlacements(
|
|
||||||
option.value,
|
|
||||||
Boolean(checked)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Label
|
|
||||||
htmlFor={`select-all-${option.value}`}
|
|
||||||
className="text-sm"
|
|
||||||
>
|
|
||||||
All {option.label}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{files.map((file, index) => (
|
|
||||||
<div
|
|
||||||
key={file.contentId}
|
|
||||||
className="flex gap-4 p-4 border rounded-lg"
|
|
||||||
>
|
|
||||||
{/* show thumbnail or video preview */}
|
|
||||||
{file.contentType === "VIDEO" ? (
|
|
||||||
<video
|
|
||||||
src={file.contentFile}
|
|
||||||
className="w-32 h-24 object-cover rounded"
|
|
||||||
muted
|
|
||||||
preload="metadata"
|
|
||||||
playsInline
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
src={file.contentFile}
|
|
||||||
alt={file.fileName || `file-${file.contentId}`}
|
|
||||||
className="w-32 h-24 object-cover rounded"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
/>
|
||||||
|
{errors.contentCreator && (
|
||||||
|
<Alert variant="soft">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
{errors.contentCreator.message}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<div className="flex-1 space-y-3">
|
{/* Preview */}
|
||||||
<p className="font-medium text-sm">
|
{detail?.contentThumbnail && (
|
||||||
{file.fileName ||
|
<Card>
|
||||||
file.contentFileName ||
|
<CardHeader>
|
||||||
`File ${file.contentId}`}
|
<CardTitle className="flex items-center gap-2">
|
||||||
</p>
|
<Eye className="h-5 w-5" />
|
||||||
<div className="flex flex-wrap gap-3">
|
Preview
|
||||||
{PLACEMENT_OPTIONS.map((option) => (
|
</CardTitle>
|
||||||
<div
|
</CardHeader>
|
||||||
key={option.value}
|
<CardContent>
|
||||||
className="flex items-center space-x-2"
|
<img
|
||||||
>
|
src={detail.contentThumbnail}
|
||||||
<Checkbox
|
alt="Content thumbnail"
|
||||||
id={`${file.contentId}-${option.value}`}
|
className="w-full h-auto rounded-lg"
|
||||||
checked={filePlacements[index]?.includes(
|
/>
|
||||||
option.value
|
</CardContent>
|
||||||
)}
|
</Card>
|
||||||
onCheckedChange={(checked) =>
|
)}
|
||||||
handleFilePlacementChange(
|
|
||||||
index,
|
{/* Tags */}
|
||||||
option.value,
|
<Card>
|
||||||
Boolean(checked)
|
<CardHeader>
|
||||||
)
|
<CardTitle className="flex items-center gap-2">
|
||||||
}
|
<Tag className="h-5 w-5" />
|
||||||
/>
|
Tags
|
||||||
<Label
|
</CardTitle>
|
||||||
htmlFor={`${file.contentId}-${option.value}`}
|
</CardHeader>
|
||||||
className="text-sm"
|
<CardContent className="space-y-4">
|
||||||
>
|
<div className="space-y-2">
|
||||||
{option.label}
|
<Label htmlFor="tag-input">Add Tags</Label>
|
||||||
</Label>
|
<Input
|
||||||
</div>
|
id="tag-input"
|
||||||
))}
|
placeholder="Type a tag and press Enter"
|
||||||
</div>
|
onKeyDown={handleAddTag}
|
||||||
</div>
|
ref={inputRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{tags.map((tag, index) => (
|
||||||
|
<Badge
|
||||||
|
key={index}
|
||||||
|
className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
|
||||||
|
onClick={() => handleRemoveTag(index)}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
<XCircle className="h-3 w-3 ml-1" />
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Publish Targets */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Settings className="h-5 w-5" />
|
||||||
|
Publish Targets
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{PUBLISH_OPTIONS.map((option) => (
|
||||||
|
<div
|
||||||
|
key={option.id}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={option.id}
|
||||||
|
checked={
|
||||||
|
option.id === "all"
|
||||||
|
? publishedFor.length ===
|
||||||
|
PUBLISH_OPTIONS.filter(
|
||||||
|
(opt) => opt.id !== "all"
|
||||||
|
).length
|
||||||
|
: publishedFor.includes(option.id)
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handlePublishTargetChange(option.id)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={option.id} className="text-sm">
|
||||||
|
{option.label}
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</CardContent>
|
||||||
</CardContent>
|
</Card>
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Submit Button */}
|
||||||
<div className="space-y-6">
|
<Card>
|
||||||
{/* Creator Information */}
|
<CardContent className="pt-6">
|
||||||
<Card>
|
<Button
|
||||||
<CardHeader>
|
type="submit"
|
||||||
<CardTitle className="flex items-center gap-2">
|
className="w-full mb-4"
|
||||||
<Users className="h-5 w-5" />
|
disabled={isSubmitting || isSaving || isAlreadySaved}
|
||||||
Creator Information
|
>
|
||||||
</CardTitle>
|
{isSubmitting || isSaving ? (
|
||||||
</CardHeader>
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
<CardContent className="space-y-4">
|
) : (
|
||||||
<div className="space-y-2">
|
<Save className="h-4 w-4 mr-2" />
|
||||||
<Label htmlFor="creator">Creator *</Label>
|
)}
|
||||||
<Controller
|
{isAlreadySaved
|
||||||
control={control}
|
? "Already Saved"
|
||||||
name="contentCreator"
|
: isSubmitting || isSaving
|
||||||
render={({ field }) => (
|
? "Saving..."
|
||||||
<Input
|
: "Save Changes"}
|
||||||
id="creator"
|
</Button>
|
||||||
placeholder="Enter creator name"
|
|
||||||
{...field}
|
{isAlreadySaved && (
|
||||||
/>
|
<Alert variant="soft">
|
||||||
|
<CheckCircle className="h-4 w-4 text-red-500" />
|
||||||
|
<AlertDescription className="text-red-500">
|
||||||
|
Konten sudah disimpan. Anda tidak dapat menyimpan
|
||||||
|
ulang.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
)}
|
)}
|
||||||
/>
|
</CardContent>
|
||||||
{errors.contentCreator && (
|
</Card>
|
||||||
<Alert variant="soft">
|
</div>
|
||||||
<AlertCircle className="h-4 w-4" />
|
</div>
|
||||||
<AlertDescription>
|
|
||||||
{errors.contentCreator.message}
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Preview */}
|
|
||||||
{detail?.contentThumbnail && (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Eye className="h-5 w-5" />
|
|
||||||
Preview
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<img
|
|
||||||
src={detail.contentThumbnail}
|
|
||||||
alt="Content thumbnail"
|
|
||||||
className="w-full h-auto rounded-lg"
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Tags */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Tag className="h-5 w-5" />
|
|
||||||
Tags
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="tag-input">Add Tags</Label>
|
|
||||||
<Input
|
|
||||||
id="tag-input"
|
|
||||||
placeholder="Type a tag and press Enter"
|
|
||||||
onKeyDown={handleAddTag}
|
|
||||||
ref={inputRef}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{tags.length > 0 && (
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{tags.map((tag, index) => (
|
|
||||||
<Badge
|
|
||||||
key={index}
|
|
||||||
className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
|
|
||||||
onClick={() => handleRemoveTag(index)}
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
<XCircle className="h-3 w-3 ml-1" />
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Publish Targets */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Settings className="h-5 w-5" />
|
|
||||||
Publish Targets
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
|
||||||
{PUBLISH_OPTIONS.map((option) => (
|
|
||||||
<div key={option.id} className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id={option.id}
|
|
||||||
checked={
|
|
||||||
option.id === "all"
|
|
||||||
? publishedFor.length ===
|
|
||||||
PUBLISH_OPTIONS.filter((opt) => opt.id !== "all")
|
|
||||||
.length
|
|
||||||
: publishedFor.includes(option.id)
|
|
||||||
}
|
|
||||||
onCheckedChange={() =>
|
|
||||||
handlePublishTargetChange(option.id)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Label htmlFor={option.id} className="text-sm">
|
|
||||||
{option.label}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Submit Button */}
|
|
||||||
<Card>
|
|
||||||
<CardContent className="pt-6">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className="w-full mb-4"
|
|
||||||
disabled={isSubmitting || isSaving || isAlreadySaved}
|
|
||||||
>
|
|
||||||
{isSubmitting || isSaving ? (
|
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Save className="h-4 w-4 mr-2" />
|
|
||||||
)}
|
|
||||||
{isAlreadySaved
|
|
||||||
? "Already Saved"
|
|
||||||
: isSubmitting || isSaving
|
|
||||||
? "Saving..."
|
|
||||||
: "Save Changes"}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{isAlreadySaved && (
|
|
||||||
<Alert variant="soft">
|
|
||||||
<CheckCircle className="h-4 w-4 text-red-500" />
|
|
||||||
<AlertDescription className="text-red-500">
|
|
||||||
Konten sudah disimpan. Anda tidak dapat menyimpan ulang.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -261,8 +261,8 @@ export default function FormAudioTaDetail() {
|
||||||
});
|
});
|
||||||
setupPlacementCheck(details?.files?.length);
|
setupPlacementCheck(details?.files?.length);
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject?.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
|
||||||
|
|
@ -247,8 +247,8 @@ export default function FormImageTaDetail() {
|
||||||
});
|
});
|
||||||
setupPlacementCheck(details?.files?.length);
|
setupPlacementCheck(details?.files?.length);
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject?.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
|
||||||
|
|
@ -246,8 +246,8 @@ export default function FormTeksTaDetail() {
|
||||||
format: details?.files[0]?.format,
|
format: details?.files[0]?.format,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject?.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
|
||||||
|
|
@ -237,8 +237,8 @@ export default function FormVideoTaDetail() {
|
||||||
format: details?.files[0]?.format,
|
format: details?.files[0]?.format,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject?.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,9 @@ export default function FormTeksDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[fileIndex] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[fileIndex] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
|
|
@ -517,8 +519,8 @@ export default function FormTeksDetail() {
|
||||||
setCheckedLevels(levels);
|
setCheckedLevels(levels);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject?.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
@ -589,7 +591,7 @@ export default function FormTeksDetail() {
|
||||||
nowArr = nowArr?.replaceAll("nasional", "mabes");
|
nowArr = nowArr?.replaceAll("nasional", "mabes");
|
||||||
nowArr = nowArr?.replaceAll("semua", "all");
|
nowArr = nowArr?.replaceAll("semua", "all");
|
||||||
|
|
||||||
// Dapatkan checked levels untuk file ini
|
// Dapatkan checked levels untuk file ini
|
||||||
const currentFileCheckedLevels = fileCheckedLevels[i]
|
const currentFileCheckedLevels = fileCheckedLevels[i]
|
||||||
? Array.from(fileCheckedLevels[i])
|
? Array.from(fileCheckedLevels[i])
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -728,7 +730,9 @@ export default function FormTeksDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
listDest.forEach((item: any) => {
|
listDest.forEach((item: any) => {
|
||||||
|
|
@ -791,7 +795,9 @@ export default function FormTeksDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Unchecklist semua item di modal
|
// Unchecklist semua item di modal
|
||||||
currentFileLevels.clear();
|
currentFileLevels.clear();
|
||||||
|
|
@ -1162,7 +1168,10 @@ export default function FormTeksDetail() {
|
||||||
// setSelectedTarget(id);
|
// setSelectedTarget(id);
|
||||||
// }}
|
// }}
|
||||||
>
|
>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger
|
||||||
|
size="md"
|
||||||
|
className="border border-gray-300 dark:border-gray-600"
|
||||||
|
>
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,9 @@ export default function FormVideoDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[fileIndex] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[fileIndex] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
|
|
@ -494,7 +496,7 @@ export default function FormVideoDetail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details?.publishedForObject) {
|
if (details?.publishedForObject) {
|
||||||
const publisherIds = details.publishedForObject.map(
|
const publisherIds = details?.publishedForObject?.map(
|
||||||
(obj: any) => obj.id
|
(obj: any) => obj.id
|
||||||
);
|
);
|
||||||
setSelectedPublishers(publisherIds);
|
setSelectedPublishers(publisherIds);
|
||||||
|
|
@ -693,7 +695,9 @@ export default function FormVideoDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Checklist semua item di modal
|
// Checklist semua item di modal
|
||||||
listDest.forEach((item: any) => {
|
listDest.forEach((item: any) => {
|
||||||
|
|
@ -756,7 +760,9 @@ export default function FormVideoDetail() {
|
||||||
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
|
||||||
setFileCheckedLevels((prevLevels) => {
|
setFileCheckedLevels((prevLevels) => {
|
||||||
const newArray = [...prevLevels];
|
const newArray = [...prevLevels];
|
||||||
const currentFileLevels = new Set<number>(newArray[index] || new Set());
|
const currentFileLevels = new Set<number>(
|
||||||
|
newArray[index] || new Set()
|
||||||
|
);
|
||||||
|
|
||||||
// Unchecklist semua item di modal
|
// Unchecklist semua item di modal
|
||||||
currentFileLevels.clear();
|
currentFileLevels.clear();
|
||||||
|
|
@ -1164,7 +1170,10 @@ export default function FormVideoDetail() {
|
||||||
// setSelectedTarget(id);
|
// setSelectedTarget(id);
|
||||||
// }}
|
// }}
|
||||||
>
|
>
|
||||||
<SelectTrigger size="md">
|
<SelectTrigger
|
||||||
|
size="md"
|
||||||
|
className="border border-gray-300 dark:border-gray-600"
|
||||||
|
>
|
||||||
<SelectValue placeholder="Pilih" />
|
<SelectValue placeholder="Pilih" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -87,13 +87,13 @@ export default function PublishMediahub() {
|
||||||
const t = useTranslations("Form");
|
const t = useTranslations("Form");
|
||||||
const [mainType, setMainType] = useState<number>(1);
|
const [mainType, setMainType] = useState<number>(1);
|
||||||
const [taskType, setTaskType] = useState<string>("atensi-khusus");
|
const [taskType, setTaskType] = useState<string>("atensi-khusus");
|
||||||
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
|
const [broadcastType, setBroadcastType] = useState<string>("all");
|
||||||
const [type, setType] = useState<string>("1");
|
const [type, setType] = useState<string>("1");
|
||||||
const [selectedTarget, setSelectedTarget] = useState("all");
|
const [selectedTarget, setSelectedTarget] = useState("all");
|
||||||
const [startDate, setStartDate] = useState<Date>(new Date());
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
const [detail, setDetail] = useState<mediahubDetail>();
|
const [detail, setDetail] = useState<mediahubDetail>();
|
||||||
const [refresh] = useState(false);
|
const [refresh] = useState(false);
|
||||||
const [listDest, setListDest] = useState([]); // Data Polda dan Polres
|
const [listDest, setListDest] = useState([]);
|
||||||
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
const [checkedLevels, setCheckedLevels] = useState(new Set());
|
||||||
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
const [expandedPolda, setExpandedPolda] = useState([{}]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
@ -267,7 +267,7 @@ export default function PublishMediahub() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
requestData.id = parseInt(id, 10); // Ensure id is a number
|
requestData.id = parseInt(id, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Form Data Submitted:", requestData);
|
console.log("Form Data Submitted:", requestData);
|
||||||
|
|
@ -491,7 +491,7 @@ export default function PublishMediahub() {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="md"
|
size="md"
|
||||||
className={cn(
|
className={cn(
|
||||||
" justify-between text-left font-normal border-default-200 text-default-600 md:px-4 w-3/12",
|
" justify-between text-left font-normal border-default-200 text-default-600 md:px-4 w-3/12 dark:border dark:border-gray-500",
|
||||||
!startDate && "text-muted-foreground"
|
!startDate && "text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -548,8 +548,8 @@ export default function FormTaskTa() {
|
||||||
>
|
>
|
||||||
<RadioGroupItem value="atensi-khusus" id="khusus" />
|
<RadioGroupItem value="atensi-khusus" id="khusus" />
|
||||||
<Label htmlFor="atensi-khusus">Atensi Khusus</Label>
|
<Label htmlFor="atensi-khusus">Atensi Khusus</Label>
|
||||||
<RadioGroupItem value="tugas-harian" id="harian" />
|
{/* <RadioGroupItem value="tugas-harian" id="harian" />
|
||||||
<Label htmlFor="tugas-harian">Tugas Harian</Label>
|
<Label htmlFor="tugas-harian">Tugas Harian</Label> */}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-2 mt-5">
|
<div className="flex flex-col space-y-2 mt-5">
|
||||||
|
|
|
||||||
|
|
@ -1124,7 +1124,7 @@ export default function FormTaskDetail() {
|
||||||
className="object-fill h-full w-full rounded-md"
|
className="object-fill h-full w-full rounded-md"
|
||||||
src={selectedVideo}
|
src={selectedVideo}
|
||||||
controls
|
controls
|
||||||
title={`Video`} // Mengganti alt dengan title
|
title={`Video`}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1165,7 +1165,7 @@ export default function FormTaskDetail() {
|
||||||
{imageUploadedFiles?.length > 0 && (
|
{imageUploadedFiles?.length > 0 && (
|
||||||
<Label>{t("image", { defaultValue: "Image" })}</Label>
|
<Label>{t("image", { defaultValue: "Image" })}</Label>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div className="dark:border dark:border-gray-500">
|
||||||
{selectedImage && (
|
{selectedImage && (
|
||||||
<Card className="mt-2">
|
<Card className="mt-2">
|
||||||
<img
|
<img
|
||||||
|
|
|
||||||
|
|
@ -994,7 +994,7 @@ export default function FormTaskEdit() {
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md dark:border dark:border-gray-500"
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
|
|
@ -1034,7 +1034,7 @@ export default function FormTaskEdit() {
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
className=" flex justify-between border px-3.5 py-3 my-6 rounded-md dark:border dark:border-gray-500"
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="file-preview">
|
<div className="file-preview">
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import { loading } from "@/lib/swal";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import UpdateSection from "@/app/[locale]/(public)/inbox/update/page";
|
import UpdateSection from "@/app/[locale]/(public)/inbox/update/page";
|
||||||
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -98,12 +99,11 @@ export default function FormTask() {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const editor = useRef(null);
|
const editor = useRef(null);
|
||||||
|
const levelNumber = Number(getCookiesDecrypt("ulne")) || 0;
|
||||||
type TaskSchema = z.infer<typeof taskSchema>;
|
type TaskSchema = z.infer<typeof taskSchema>;
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
console.log(id);
|
console.log(id);
|
||||||
const [listDest, setListDest] = useState<Destination[]>([]);
|
const [listDest, setListDest] = useState<Destination[]>([]);
|
||||||
|
|
||||||
// State for various form fields
|
// State for various form fields
|
||||||
const [taskOutput, setTaskOutput] = useState({
|
const [taskOutput, setTaskOutput] = useState({
|
||||||
all: false,
|
all: false,
|
||||||
|
|
@ -148,9 +148,11 @@ export default function FormTask() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// State untuk melacak apakah perubahan berasal dari checkbox Penerima Tugas
|
// State untuk melacak apakah perubahan berasal dari checkbox Penerima Tugas
|
||||||
const [isUpdatingFromPenerimaTugas, setIsUpdatingFromPenerimaTugas] = useState(false);
|
const [isUpdatingFromPenerimaTugas, setIsUpdatingFromPenerimaTugas] =
|
||||||
|
useState(false);
|
||||||
// State untuk melacak jenis perubahan spesifik
|
// State untuk melacak jenis perubahan spesifik
|
||||||
const [penerimaTugasChangeType, setPenerimaTugasChangeType] = useState<string>("");
|
const [penerimaTugasChangeType, setPenerimaTugasChangeType] =
|
||||||
|
useState<string>("");
|
||||||
|
|
||||||
const [links, setLinks] = useState<string[]>([""]);
|
const [links, setLinks] = useState<string[]>([""]);
|
||||||
const {
|
const {
|
||||||
|
|
@ -206,7 +208,9 @@ export default function FormTask() {
|
||||||
updatedLevels.delete(levelId);
|
updatedLevels.delete(levelId);
|
||||||
|
|
||||||
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
|
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
|
||||||
const poldaItem = listDest.find((item: any) => Number(item.id) === levelId);
|
const poldaItem = listDest.find(
|
||||||
|
(item: any) => Number(item.id) === levelId
|
||||||
|
);
|
||||||
if (poldaItem && poldaItem.subDestination) {
|
if (poldaItem && poldaItem.subDestination) {
|
||||||
poldaItem.subDestination.forEach((polres: any) => {
|
poldaItem.subDestination.forEach((polres: any) => {
|
||||||
updatedLevels.delete(Number(polres.id));
|
updatedLevels.delete(Number(polres.id));
|
||||||
|
|
@ -247,15 +251,18 @@ export default function FormTask() {
|
||||||
// Validasi khusus untuk POLRES
|
// Validasi khusus untuk POLRES
|
||||||
if (key === "polres" && value) {
|
if (key === "polres" && value) {
|
||||||
// Cek apakah ada POLDA yang sudah dichecklist di modal
|
// Cek apakah ada POLDA yang sudah dichecklist di modal
|
||||||
const hasCheckedPolda = listDest.some((item: any) =>
|
const hasCheckedPolda = listDest.some(
|
||||||
item.levelNumber === 2 &&
|
(item: any) =>
|
||||||
item.name !== "SATKER POLRI" &&
|
item.levelNumber === 2 &&
|
||||||
checkedLevels.has(Number(item.id))
|
item.name !== "SATKER POLRI" &&
|
||||||
|
checkedLevels.has(Number(item.id))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasCheckedPolda) {
|
if (!hasCheckedPolda) {
|
||||||
// Jika tidak ada POLDA yang dichecklist di modal, tampilkan peringatan dan batalkan
|
// Jika tidak ada POLDA yang dichecklist di modal, tampilkan peringatan dan batalkan
|
||||||
alert("Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES.");
|
alert(
|
||||||
|
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."
|
||||||
|
);
|
||||||
return; // Batalkan perubahan
|
return; // Batalkan perubahan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,8 +403,8 @@ export default function FormTask() {
|
||||||
const updateUnitSelectionFromModal = () => {
|
const updateUnitSelectionFromModal = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Hitung total item yang tersedia untuk setiap kategori
|
// Hitung total item yang tersedia untuk setiap kategori
|
||||||
const totalPolda = listDest.filter((item: any) =>
|
const totalPolda = listDest.filter(
|
||||||
item.levelNumber === 2 && item.name !== "SATKER POLRI"
|
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const totalPolres = listDest.reduce((total: number, item: any) => {
|
const totalPolres = listDest.reduce((total: number, item: any) => {
|
||||||
|
|
@ -407,41 +414,56 @@ export default function FormTask() {
|
||||||
return total;
|
return total;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const satkerItem = listDest.find((item: any) => item.name === "SATKER POLRI");
|
const satkerItem = listDest.find(
|
||||||
const totalSatker = satkerItem ? (1 + (satkerItem.subDestination?.length || 0)) : 0;
|
(item: any) => item.name === "SATKER POLRI"
|
||||||
|
);
|
||||||
|
const totalSatker = satkerItem
|
||||||
|
? 1 + (satkerItem.subDestination?.length || 0)
|
||||||
|
: 0;
|
||||||
|
|
||||||
// Hitung item yang dichecklist untuk setiap kategori
|
// Hitung item yang dichecklist untuk setiap kategori
|
||||||
const checkedPoldaCount = listDest.filter((item: any) =>
|
const checkedPoldaCount = listDest.filter(
|
||||||
item.levelNumber === 2 &&
|
(item: any) =>
|
||||||
item.name !== "SATKER POLRI" &&
|
item.levelNumber === 2 &&
|
||||||
checkedLevels.has(Number(item.id))
|
item.name !== "SATKER POLRI" &&
|
||||||
|
checkedLevels.has(Number(item.id))
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const checkedPolresCount = listDest.reduce((total: number, item: any) => {
|
const checkedPolresCount = listDest.reduce((total: number, item: any) => {
|
||||||
if (item.subDestination) {
|
if (item.subDestination) {
|
||||||
return total + item.subDestination.filter((sub: any) => checkedLevels.has(Number(sub.id))).length;
|
return (
|
||||||
|
total +
|
||||||
|
item.subDestination.filter((sub: any) =>
|
||||||
|
checkedLevels.has(Number(sub.id))
|
||||||
|
).length
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const checkedSatkerCount = satkerItem ? (
|
const checkedSatkerCount = satkerItem
|
||||||
(checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
|
? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
|
||||||
(satkerItem.subDestination?.filter((sub: any) => checkedLevels.has(Number(sub.id))).length || 0)
|
(satkerItem.subDestination?.filter((sub: any) =>
|
||||||
) : 0;
|
checkedLevels.has(Number(sub.id))
|
||||||
|
).length || 0)
|
||||||
|
: 0;
|
||||||
|
|
||||||
// Checkbox hanya aktif jika SEMUA item dalam kategori tersebut dichecklist
|
// Checkbox hanya aktif jika SEMUA item dalam kategori tersebut dichecklist
|
||||||
const hasCheckedPolda = totalPolda > 0 && checkedPoldaCount === totalPolda;
|
const hasCheckedPolda =
|
||||||
const hasCheckedPolres = totalPolres > 0 && checkedPolresCount === totalPolres;
|
totalPolda > 0 && checkedPoldaCount === totalPolda;
|
||||||
const hasCheckedSatker = totalSatker > 0 && checkedSatkerCount === totalSatker;
|
const hasCheckedPolres =
|
||||||
|
totalPolres > 0 && checkedPolresCount === totalPolres;
|
||||||
|
const hasCheckedSatker =
|
||||||
|
totalSatker > 0 && checkedSatkerCount === totalSatker;
|
||||||
|
|
||||||
// Update unitSelection berdasarkan checkbox yang aktif di modal
|
// Update unitSelection berdasarkan checkbox yang aktif di modal
|
||||||
setUnitSelection(prev => ({
|
setUnitSelection((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
polda: hasCheckedPolda,
|
polda: hasCheckedPolda,
|
||||||
polres: hasCheckedPolres,
|
polres: hasCheckedPolres,
|
||||||
satker: hasCheckedSatker,
|
satker: hasCheckedSatker,
|
||||||
// allUnit hanya true jika semua kategori terpenuhi
|
// allUnit hanya true jika semua kategori terpenuhi
|
||||||
allUnit: hasCheckedPolda && hasCheckedPolres && hasCheckedSatker
|
allUnit: hasCheckedPolda && hasCheckedPolres && hasCheckedSatker,
|
||||||
}));
|
}));
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
@ -456,7 +478,11 @@ export default function FormTask() {
|
||||||
|
|
||||||
// Hapus semua polres dari modal, tapi pertahankan polda
|
// Hapus semua polres dari modal, tapi pertahankan polda
|
||||||
listDest.forEach((item: any) => {
|
listDest.forEach((item: any) => {
|
||||||
if (item.subDestination && item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
if (
|
||||||
|
item.subDestination &&
|
||||||
|
item.levelNumber === 2 &&
|
||||||
|
item.name !== "SATKER POLRI"
|
||||||
|
) {
|
||||||
item.subDestination.forEach((polres: any) => {
|
item.subDestination.forEach((polres: any) => {
|
||||||
newCheckedLevels.delete(Number(polres.id));
|
newCheckedLevels.delete(Number(polres.id));
|
||||||
});
|
});
|
||||||
|
|
@ -466,13 +492,21 @@ export default function FormTask() {
|
||||||
setCheckedLevels(newCheckedLevels);
|
setCheckedLevels(newCheckedLevels);
|
||||||
}
|
}
|
||||||
// Untuk perubahan lainnya, jalankan logika normal
|
// Untuk perubahan lainnya, jalankan logika normal
|
||||||
else if (unitSelection.polda || unitSelection.polres || unitSelection.satker) {
|
else if (
|
||||||
|
unitSelection.polda ||
|
||||||
|
unitSelection.polres ||
|
||||||
|
unitSelection.satker
|
||||||
|
) {
|
||||||
// Mulai dengan checkbox yang sudah ada untuk mempertahankan pilihan manual user
|
// Mulai dengan checkbox yang sudah ada untuk mempertahankan pilihan manual user
|
||||||
const newCheckedLevels = new Set<number>(checkedLevels);
|
const newCheckedLevels = new Set<number>(checkedLevels);
|
||||||
|
|
||||||
listDest.forEach((item: any) => {
|
listDest.forEach((item: any) => {
|
||||||
// Jika polda dichecklist, checklist semua polda (levelNumber 2, bukan SATKER POLRI)
|
// Jika polda dichecklist, checklist semua polda (levelNumber 2, bukan SATKER POLRI)
|
||||||
if (unitSelection.polda && item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
if (
|
||||||
|
unitSelection.polda &&
|
||||||
|
item.levelNumber === 2 &&
|
||||||
|
item.name !== "SATKER POLRI"
|
||||||
|
) {
|
||||||
newCheckedLevels.add(Number(item.id));
|
newCheckedLevels.add(Number(item.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -489,13 +523,21 @@ export default function FormTask() {
|
||||||
// Jika polres dichecklist
|
// Jika polres dichecklist
|
||||||
if (unitSelection.polres && item.subDestination) {
|
if (unitSelection.polres && item.subDestination) {
|
||||||
// Jika checkbox POLDA di Penerima Tugas juga aktif, checklist semua polres
|
// Jika checkbox POLDA di Penerima Tugas juga aktif, checklist semua polres
|
||||||
if (unitSelection.polda && item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
if (
|
||||||
|
unitSelection.polda &&
|
||||||
|
item.levelNumber === 2 &&
|
||||||
|
item.name !== "SATKER POLRI"
|
||||||
|
) {
|
||||||
item.subDestination.forEach((polres: any) => {
|
item.subDestination.forEach((polres: any) => {
|
||||||
newCheckedLevels.add(Number(polres.id));
|
newCheckedLevels.add(Number(polres.id));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Jika checkbox POLDA di Penerima Tugas tidak aktif, tapi ada POLDA yang dichecklist di modal
|
// Jika checkbox POLDA di Penerima Tugas tidak aktif, tapi ada POLDA yang dichecklist di modal
|
||||||
else if (!unitSelection.polda && item.levelNumber === 2 && item.name !== "SATKER POLRI") {
|
else if (
|
||||||
|
!unitSelection.polda &&
|
||||||
|
item.levelNumber === 2 &&
|
||||||
|
item.name !== "SATKER POLRI"
|
||||||
|
) {
|
||||||
// Cek apakah POLDA ini sudah dichecklist di modal
|
// Cek apakah POLDA ini sudah dichecklist di modal
|
||||||
if (checkedLevels.has(Number(item.id))) {
|
if (checkedLevels.has(Number(item.id))) {
|
||||||
// Jika ya, checklist semua polres dari POLDA ini
|
// Jika ya, checklist semua polres dari POLDA ini
|
||||||
|
|
@ -756,7 +798,7 @@ export default function FormTask() {
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-3 mt-5 lg:pt-7 lg:ml-3 ">
|
<div className="flex flex-wrap gap-3 mt-5 lg:pt-7 lg:ml-3 ">
|
||||||
{Object.keys(unitSelection).map((key) => {
|
{/* {Object.keys(unitSelection).map((key) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2" key={key}>
|
<div className="flex items-center gap-2" key={key}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
@ -776,7 +818,37 @@ export default function FormTask() {
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})} */}
|
||||||
|
{Object.keys(unitSelection)
|
||||||
|
.filter((key) => {
|
||||||
|
if (levelNumber === 2) {
|
||||||
|
return key === "polda" || key === "polres";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((key) => {
|
||||||
|
return (
|
||||||
|
<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 === "allUnit"
|
||||||
|
? "Semua Unit"
|
||||||
|
: key.charAt(0).toUpperCase() + key.slice(1)}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 lg:pt-6 lg:pl-3">
|
<div className="mt-6 lg:pt-6 lg:pl-3">
|
||||||
<Dialog>
|
<Dialog>
|
||||||
|
|
@ -790,77 +862,83 @@ export default function FormTask() {
|
||||||
<DialogTitle>Daftar Wilayah Polda dan Polres</DialogTitle>
|
<DialogTitle>Daftar Wilayah Polda dan Polres</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
|
||||||
{listDest.map((polda: any) => (
|
{listDest.map((polda: any) => (
|
||||||
<div key={polda.id} className="border p-2">
|
<div key={polda.id} className="border p-2">
|
||||||
<Label className="flex items-center">
|
<Label className="flex items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={checkedLevels.has(Number(polda.id))}
|
checked={checkedLevels.has(Number(polda.id))}
|
||||||
onCheckedChange={() => handleCheckboxChange(Number(polda.id))}
|
onCheckedChange={() =>
|
||||||
className="mr-3"
|
handleCheckboxChange(Number(polda.id))
|
||||||
/>
|
}
|
||||||
{polda.name}
|
className="mr-3"
|
||||||
<button
|
/>
|
||||||
type="button"
|
{polda.name}
|
||||||
onClick={(e) => {
|
<button
|
||||||
e.preventDefault();
|
type="button"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
toggleExpand(polda.id);
|
e.preventDefault();
|
||||||
}}
|
e.stopPropagation();
|
||||||
className="ml-2 focus:outline-none"
|
toggleExpand(polda.id);
|
||||||
>
|
}}
|
||||||
{expandedPolda[polda.id] ? (
|
className="ml-2 focus:outline-none"
|
||||||
<ChevronUp size={16} />
|
>
|
||||||
) : (
|
{expandedPolda[polda.id] ? (
|
||||||
<ChevronDown size={16} />
|
<ChevronUp size={16} />
|
||||||
)}
|
) : (
|
||||||
</button>
|
<ChevronDown size={16} />
|
||||||
</Label>
|
)}
|
||||||
{expandedPolda[polda.id] && (
|
</button>
|
||||||
<div className="ml-6 mt-2">
|
</Label>
|
||||||
<Label className="block">
|
{expandedPolda[polda.id] && (
|
||||||
<Checkbox
|
<div className="ml-6 mt-2">
|
||||||
checked={polda?.subDestination?.every(
|
<Label className="block">
|
||||||
(polres: any) =>
|
<Checkbox
|
||||||
checkedLevels.has(Number(polres.id))
|
checked={polda?.subDestination?.every(
|
||||||
)}
|
(polres: any) =>
|
||||||
onCheckedChange={(isChecked) => {
|
checkedLevels.has(Number(polres.id))
|
||||||
const updatedLevels = new Set(
|
)}
|
||||||
checkedLevels
|
onCheckedChange={(isChecked) => {
|
||||||
);
|
const updatedLevels = new Set(
|
||||||
polda?.subDestination?.forEach(
|
checkedLevels
|
||||||
(polres: any) => {
|
);
|
||||||
if (isChecked) {
|
polda?.subDestination?.forEach(
|
||||||
updatedLevels.add(Number(polres.id));
|
(polres: any) => {
|
||||||
} else {
|
if (isChecked) {
|
||||||
updatedLevels.delete(Number(polres.id));
|
updatedLevels.add(Number(polres.id));
|
||||||
}
|
} else {
|
||||||
|
updatedLevels.delete(
|
||||||
|
Number(polres.id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
setCheckedLevels(updatedLevels);
|
);
|
||||||
|
setCheckedLevels(updatedLevels);
|
||||||
|
|
||||||
// Update unitSelection berdasarkan perubahan
|
// Update unitSelection berdasarkan perubahan
|
||||||
updateUnitSelectionFromModal();
|
updateUnitSelectionFromModal();
|
||||||
}}
|
}}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
Pilih Semua
|
||||||
|
</Label>
|
||||||
|
{polda?.subDestination?.map((polres: any) => (
|
||||||
|
<Label key={polres.id} className="block mt-1">
|
||||||
|
<Checkbox
|
||||||
|
checked={checkedLevels.has(
|
||||||
|
Number(polres.id)
|
||||||
|
)}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(Number(polres.id))
|
||||||
|
}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
/>
|
/>
|
||||||
Pilih Semua
|
{polres.name}
|
||||||
</Label>
|
</Label>
|
||||||
{polda?.subDestination?.map((polres: any) => (
|
))}
|
||||||
<Label key={polres.id} className="block mt-1">
|
</div>
|
||||||
<Checkbox
|
)}
|
||||||
checked={checkedLevels.has(Number(polres.id))}
|
</div>
|
||||||
onCheckedChange={() =>
|
))}
|
||||||
handleCheckboxChange(Number(polres.id))
|
|
||||||
}
|
|
||||||
className="mr-2"
|
|
||||||
/>
|
|
||||||
{polres.name}
|
|
||||||
</Label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -158,8 +158,11 @@ const DetailDocument = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const size = [{ label: "DOC" }, { label: "PPT" }, { label: "PDF" }];
|
const sizeOptions = [
|
||||||
|
{ label: "DOC", format: ".docx" },
|
||||||
|
{ label: "PPT", format: ".pptx" },
|
||||||
|
{ label: "PDF", format: ".pdf" },
|
||||||
|
];
|
||||||
function formatBytes(kb: any, decimals = 2) {
|
function formatBytes(kb: any, decimals = 2) {
|
||||||
if (kb == 0 || kb == null) return "0 KB";
|
if (kb == 0 || kb == null) return "0 KB";
|
||||||
|
|
||||||
|
|
@ -582,7 +585,7 @@ const DetailDocument = () => {
|
||||||
{t("docSize", { defaultValue: "Doc Size" })}
|
{t("docSize", { defaultValue: "Doc Size" })}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="border-t border-black my-4"></div>
|
<div className="border-t border-black my-4"></div>
|
||||||
<div className="space-y-2">
|
{/* <div className="space-y-2">
|
||||||
{size.map((size: any) => (
|
{size.map((size: any) => (
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<div
|
<div
|
||||||
|
|
@ -604,6 +607,44 @@ const DetailDocument = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</div> */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{sizeOptions.map((opt) => {
|
||||||
|
// cari file dengan format sesuai
|
||||||
|
let matchedFile = detailDataDocument?.files?.find(
|
||||||
|
(f: any) =>
|
||||||
|
f.format.toLowerCase() === opt.format.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
// kalau tidak ada, pakai file pertama
|
||||||
|
if (!matchedFile && detailDataDocument?.files?.length > 0) {
|
||||||
|
matchedFile = detailDataDocument.files[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={opt.label}
|
||||||
|
className="flex flex-row justify-between"
|
||||||
|
>
|
||||||
|
<div className="items-center flex flex-row gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="size"
|
||||||
|
value={opt.label}
|
||||||
|
checked={selectedSize === opt.label}
|
||||||
|
onChange={() => setSelectedSize(opt.label)}
|
||||||
|
className="text-red-600 focus:ring-red-600"
|
||||||
|
/>
|
||||||
|
<div className="text-sm">{opt.label}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm">
|
||||||
|
{matchedFile
|
||||||
|
? formatBytes(Number(matchedFile.size))
|
||||||
|
: "0 KB"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Semua */}
|
{/* Download Semua */}
|
||||||
|
|
|
||||||
|
|
@ -386,7 +386,7 @@ export default function SingleViewSocialMediaTable() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center bg-slate-200 rounded-full py-1">
|
<div className="flex justify-center bg-slate-200 dark:bg-black rounded-full py-1">
|
||||||
Rencana Bulanan Belum Tersedia
|
Rencana Bulanan Belum Tersedia
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -408,7 +408,7 @@ export default function SingleViewSocialMediaTable() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center bg-slate-200 rounded-full py-1">
|
<div className="flex justify-center bg-slate-200 dark:bg-black rounded-full py-1">
|
||||||
Rencana Mingguan Belum Tersedia
|
Rencana Mingguan Belum Tersedia
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -434,7 +434,7 @@ export default function SingleViewSocialMediaTable() {
|
||||||
<li
|
<li
|
||||||
key={`day-${index}`}
|
key={`day-${index}`}
|
||||||
className={`p-2 text-end min-h-[100px] ${
|
className={`p-2 text-end min-h-[100px] ${
|
||||||
day.isCurrentMonth ? "" : "bg-slate-200 text-slate-400"
|
day.isCurrentMonth ? "" : "bg-slate-200 dark:bg-black text-slate-400"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ export default function SingleViewTable() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center bg-slate-200 rounded-full py-1">
|
<div className="flex justify-center bg-slate-200 dark:bg-black rounded-full py-1">
|
||||||
Rencana Bulanan Belum Tersedia
|
Rencana Bulanan Belum Tersedia
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -295,7 +295,7 @@ export default function SingleViewTable() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center bg-slate-200 rounded-full py-1">
|
<div className="flex justify-center bg-slate-200 dark:bg-black rounded-full py-1">
|
||||||
Rencana Mingguan Belum Tersedia
|
Rencana Mingguan Belum Tersedia
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ export default function ContentProductionVisualization() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 bg-white rounded-lg p-3">
|
<div className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3">
|
||||||
<p className="text-lg">
|
<p className="text-lg">
|
||||||
<b>
|
<b>
|
||||||
{isInternational[0]
|
{isInternational[0]
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export default function ManagementUserVisualization() {
|
||||||
}, [isInternational]);
|
}, [isInternational]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 bg-white rounded-lg p-3">
|
<div className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3">
|
||||||
{isInternational ? (
|
{isInternational ? (
|
||||||
<p className="font-semibold">STATISTICS TO THE NUMBER OF USERS</p>
|
<p className="font-semibold">STATISTICS TO THE NUMBER OF USERS</p>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export default function PatternRelationVisualization() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 bg-white rounded-lg p-3">
|
<div className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3">
|
||||||
<p className="text-lg">
|
<p className="text-lg">
|
||||||
<b>
|
<b>
|
||||||
{isInternational[0]
|
{isInternational[0]
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export default function PerformancePoldaViz() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 bg-white rounded-lg p-3">
|
<div className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3">
|
||||||
<p className="text-lg">
|
<p className="text-lg">
|
||||||
<b>{levelName == "MABES POLRI" ? "PERFORMANCE PER POLDA" : "PERFORMANCE POLRES"}</b>
|
<b>{levelName == "MABES POLRI" ? "PERFORMANCE PER POLDA" : "PERFORMANCE POLRES"}</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
94
lib/menus.ts
94
lib/menus.ts
|
|
@ -3169,7 +3169,99 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
label: t("ticketing"),
|
label: t("ticketing"),
|
||||||
active: pathname.includes("/ticketing"),
|
active: pathname.includes("/ticketing"),
|
||||||
icon: "mdi:ticket-outline",
|
icon: "mdi:ticket-outline",
|
||||||
submenus: [],
|
submenus: [
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'All',
|
||||||
|
active: pathname.includes("/ticketing/all"),
|
||||||
|
icon: "solar:inbox-line-outline",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Instagram',
|
||||||
|
active: pathname.includes("/ticketing/instagram"),
|
||||||
|
icon: "ri:chat-private-line",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Facebook',
|
||||||
|
active: pathname.includes("/ticketing/facebook"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Youtube',
|
||||||
|
active: pathname.includes("/ticketing/youtube"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Tiktok',
|
||||||
|
active: pathname.includes("/ticketing/tiktok"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Kolom Komentar',
|
||||||
|
active: pathname.includes("/ticketing/comment"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Hubungi Kami',
|
||||||
|
active: pathname.includes("/ticketing/contact-us"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Play Store',
|
||||||
|
active: pathname.includes("/ticketing/play-store"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'App Store',
|
||||||
|
active: pathname.includes("/ticketing/app-store"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Web Humas',
|
||||||
|
active: pathname.includes("/ticketing/web-humas"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'e-PPID',
|
||||||
|
active: pathname.includes("/ticketing/e-ppid"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Emergency Issues',
|
||||||
|
active: pathname.includes("/ticketing/emergency-issues"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/",
|
||||||
|
label: 'Campaignpool',
|
||||||
|
active: pathname.includes("/ticketing/campaignpool"),
|
||||||
|
icon: "ri:share-forward-2-fill",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,13 @@ export async function getMediaBlastCampaignPage(page: number) {
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMediaBlastAccountPage(page: number, category: string) {
|
export async function getMediaBlastAccountPage(page: number, category: string, campaignId: string) {
|
||||||
const url = `media/blast/account/list?enablePage=1&size=10&page=${page}&category=${category}`;
|
const url = `media/blast/account/list?enablePage=1&size=10&page=${page}&category=${category}&campaignId=${campaignId}`;
|
||||||
|
return httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMediaBlastCampaignAccountList(page: any, category: string, id: any) {
|
||||||
|
const url = `media/blast/campaign-account/list?enablePage=1&page=${page}&category=${category}&campaignId=${id}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +63,16 @@ export async function saveMediaBlastCampaign(data: {
|
||||||
return httpPostInterceptor(url, data);
|
return httpPostInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveMediaBlastCampaignAccountBulk(data: any) {
|
||||||
|
const url = `media/blast/campaign-account-bulk`;
|
||||||
|
return httpPostInterceptor( url, data );
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteMediaBlastCampaignAccount(id: any) {
|
||||||
|
const url = `media/blast/campaign-account?id=${id}`;
|
||||||
|
return httpDeleteInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getMediaBlastCampaignById(id: string) {
|
export async function getMediaBlastCampaignById(id: string) {
|
||||||
const url = `media/blast/campaign?id=${id}`;
|
const url = `media/blast/campaign?id=${id}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
|
|
@ -96,7 +111,7 @@ export async function getMediaBlastAccount(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteMediaBlastAccount(id: string) {
|
export async function deleteMediaBlastAccount(id: string) {
|
||||||
const url = `media/blast/account?id=${id}`;
|
const url = `media/blast/campaign-account?id=${id}`;
|
||||||
return httpDeleteInterceptor(url);
|
return httpDeleteInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,16 @@ export async function generateDataRewrite(data: any) {
|
||||||
return await httpPost("ai-writer/create-rewriter", headers, data);
|
return await httpPost("ai-writer/create-rewriter", headers, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function translateText(data: any) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
Authorization:
|
||||||
|
"Basic bmdETFBQaW9ycGx6bncyalRxVmUzWUZDejV4cUtmVUo6UHJEaERXUmNvdkJSNlc1Sg==",
|
||||||
|
};
|
||||||
|
return await httpPost("ai-writer/translation/process", headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getGenerateTitle(data: any) {
|
export async function getGenerateTitle(data: any) {
|
||||||
const headers = {
|
const headers = {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
httpGetInterceptor,
|
httpGetInterceptor,
|
||||||
httpPostInterceptor,
|
httpPostInterceptor,
|
||||||
} from "./http-config/http-interceptor-service";
|
} from "./http-config/http-interceptor-service";
|
||||||
|
import { httpGet } from "./http-config/http-base-service";
|
||||||
|
|
||||||
// export async function listTask(size: number, page: number) {
|
// export async function listTask(size: number, page: number) {
|
||||||
// return await httpGetInterceptor(
|
// return await httpGetInterceptor(
|
||||||
|
|
@ -33,6 +34,29 @@ export async function listTask(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listTaskMabesForTa(
|
||||||
|
page: any,
|
||||||
|
title: string = "",
|
||||||
|
size: any,
|
||||||
|
code: any,
|
||||||
|
createdAt: any,
|
||||||
|
taskType: string,
|
||||||
|
status: number[]
|
||||||
|
) {
|
||||||
|
let statusQuery = "";
|
||||||
|
|
||||||
|
if (status.includes(1)) {
|
||||||
|
statusQuery = "&isDone=true";
|
||||||
|
} else if (status.includes(2)) {
|
||||||
|
statusQuery = "&isDone=false";
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpGet(
|
||||||
|
`assignment/list?enablePage=1&size=${size}&page=${page}&title=${title}&taskType=${taskType}&uniqueCode=${code}&createdAt=${createdAt}${statusQuery}`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// export async function createTask(data: any) {
|
// export async function createTask(data: any) {
|
||||||
// const url = "assignment";
|
// const url = "assignment";
|
||||||
// return httpPostInterceptor(url, data);
|
// return httpPostInterceptor(url, data);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,215 @@
|
||||||
|
/* CKEditor Custom Styling */
|
||||||
|
|
||||||
|
/* Main editor container */
|
||||||
|
.ck.ck-editor {
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar styling */
|
||||||
|
.ck.ck-toolbar {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-toolbar .ck-toolbar__items {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main editable area */
|
||||||
|
.ck.ck-editor__editable {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
padding: 1.5em 2em !important;
|
||||||
|
min-height: 400px;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus state */
|
||||||
|
.ck.ck-editor__editable.ck-focused {
|
||||||
|
border-color: #1a9aef;
|
||||||
|
box-shadow: 0 0 0 2px rgba(26, 154, 239, 0.2);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content styling */
|
||||||
|
.ck.ck-editor__editable .ck-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography improvements */
|
||||||
|
.ck.ck-editor__editable p {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable h1,
|
||||||
|
.ck.ck-editor__editable h2,
|
||||||
|
.ck.ck-editor__editable h3,
|
||||||
|
.ck.ck-editor__editable h4,
|
||||||
|
.ck.ck-editor__editable h5,
|
||||||
|
.ck.ck-editor__editable h6 {
|
||||||
|
margin: 1em 0 0.5em 0;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable h1 { font-size: 1.75em; }
|
||||||
|
.ck.ck-editor__editable h2 { font-size: 1.5em; }
|
||||||
|
.ck.ck-editor__editable h3 { font-size: 1.25em; }
|
||||||
|
.ck.ck-editor__editable h4 { font-size: 1.1em; }
|
||||||
|
.ck.ck-editor__editable h5 { font-size: 1em; }
|
||||||
|
.ck.ck-editor__editable h6 { font-size: 0.9em; }
|
||||||
|
|
||||||
|
/* Lists */
|
||||||
|
.ck.ck-editor__editable ul,
|
||||||
|
.ck.ck-editor__editable ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable li {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blockquotes */
|
||||||
|
.ck.ck-editor__editable blockquote {
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.75em 1em;
|
||||||
|
border-left: 4px solid #1a9aef;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.ck.ck-editor__editable table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable table td,
|
||||||
|
.ck.ck-editor__editable table th {
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable table th {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
.ck.ck-editor__editable a {
|
||||||
|
color: #1a9aef;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable a:hover {
|
||||||
|
color: #0d7cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
.ck.ck-editor__editable pre {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1em;
|
||||||
|
margin: 1em 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable code {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Images */
|
||||||
|
.ck.ck-editor__editable img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Horizontal rule */
|
||||||
|
.ck.ck-editor__editable hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #d1d5db;
|
||||||
|
margin: 2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder text */
|
||||||
|
.ck.ck-editor__editable.ck-blurred:empty::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
color: #9ca3af;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile responsiveness */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ck.ck-editor__editable {
|
||||||
|
padding: 1em 1.5em !important;
|
||||||
|
font-size: 16px; /* Better for mobile */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-toolbar {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-toolbar .ck-toolbar__items {
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode support */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.ck.ck-editor__editable {
|
||||||
|
background: #1f2937;
|
||||||
|
color: #f9fafb;
|
||||||
|
border-color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable h1,
|
||||||
|
.ck.ck-editor__editable h2,
|
||||||
|
.ck.ck-editor__editable h3,
|
||||||
|
.ck.ck-editor__editable h4,
|
||||||
|
.ck.ck-editor__editable h5,
|
||||||
|
.ck.ck-editor__editable h6 {
|
||||||
|
color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable blockquote {
|
||||||
|
background-color: #374151;
|
||||||
|
border-left-color: #1a9aef;
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable pre {
|
||||||
|
background-color: #374151;
|
||||||
|
border-color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-editor__editable code {
|
||||||
|
background-color: #4b5563;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue