fix: retry
This commit is contained in:
commit
314279f136
|
|
@ -34,6 +34,9 @@ const FormSchema = z.object({
|
||||||
email: z.string({
|
email: z.string({
|
||||||
required_error: "Required",
|
required_error: "Required",
|
||||||
}),
|
}),
|
||||||
|
position: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
region: z.string({
|
region: z.string({
|
||||||
required_error: "Required",
|
required_error: "Required",
|
||||||
}),
|
}),
|
||||||
|
|
@ -147,7 +150,28 @@ export default function AddExpertForm() {
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="position"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Posisi</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih Region" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="koor-kurator">Koor Kurator</SelectItem>
|
||||||
|
<SelectItem value="kurator">Kurator</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="region"
|
name="region"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
||||||
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuItem,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
formatDateToIndonesian,
|
||||||
|
getOnlyDate,
|
||||||
|
htmlToString,
|
||||||
|
} from "@/utils/globals";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
|
|
||||||
|
const columns: ColumnDef<any>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "no",
|
||||||
|
header: "No",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "title",
|
||||||
|
header: "Judul",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "fileTypeName",
|
||||||
|
header: "Konten",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("fileTypeName")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "categoryName",
|
||||||
|
header: "Kategori",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "createdAt",
|
||||||
|
header: "Tanggal Unggah",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "statusName",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("statusName")}</span>,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
accessorKey: "action",
|
||||||
|
header: "Actions",
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
|
<Link
|
||||||
|
href={`/contributor/content/image/detail/${row.original.id}`}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
|
<Link href={`/admin/broadcast/email/${row.original.id}`}>
|
||||||
|
Email Blast
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
|
<Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
|
||||||
|
Whatsapp Blast
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default columns;
|
||||||
|
|
@ -60,13 +60,16 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { listDataMedia } from "@/service/broadcast/broadcast";
|
import {
|
||||||
|
listDataMedia,
|
||||||
|
listDataMediaBroadCast,
|
||||||
|
} from "@/service/broadcast/broadcast";
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
const BroadcastTable = () => {
|
const BroadcastEmailTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [search, setSearch] = React.useState("");
|
const [search, setSearch] = React.useState("");
|
||||||
|
|
@ -144,7 +147,7 @@ const BroadcastTable = () => {
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
loading();
|
loading();
|
||||||
const res = await listDataMedia(
|
const res = await listDataMediaBroadCast(
|
||||||
page - 1,
|
page - 1,
|
||||||
showData,
|
showData,
|
||||||
search,
|
search,
|
||||||
|
|
@ -202,17 +205,13 @@ const BroadcastTable = () => {
|
||||||
|
|
||||||
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 p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between mb-10 items-center">
|
<div className="flex justify-between ">
|
||||||
<p className="text-xl font-medium text-default-900">Brodcast</p>
|
<Link href="/admin/broadcast/campaign-list" className="mr-3">
|
||||||
<Link href="/admin/broadcast/campaign-list">
|
|
||||||
<Button color="primary" size="md" className="text-sm">
|
<Button color="primary" size="md" className="text-sm">
|
||||||
<UserIcon />
|
<UserIcon />
|
||||||
Daftar Campaign
|
Daftar Campaign
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-between ">
|
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
|
|
@ -401,4 +400,4 @@ const BroadcastTable = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BroadcastTable;
|
export default BroadcastEmailTable;
|
||||||
|
|
@ -1,11 +1,51 @@
|
||||||
|
"use client";
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import BroadcastTable from "./component/table";
|
import BroadcastTable from "./email/component/table";
|
||||||
|
import { PlusIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import EscalationTable from "../../shared/communication/escalation/components/escalation-table";
|
||||||
|
import InternalTable from "../../shared/communication/internal/components/internal-table";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import BroadcastEmailTable from "./email/component/table";
|
||||||
|
import BroadcastWhatsAppTable from "./whatsapp/component/table";
|
||||||
|
|
||||||
export default function AdminBroadcast() {
|
export default function AdminBroadcast() {
|
||||||
|
const [tab, setTab] = useState("Email Blast");
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<BroadcastTable />
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
|
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
||||||
|
<Button
|
||||||
|
rounded="md"
|
||||||
|
onClick={() => setTab("Email Blast")}
|
||||||
|
className={` hover:text-white
|
||||||
|
${
|
||||||
|
tab === "Email Blast"
|
||||||
|
? "bg-black text-white "
|
||||||
|
: "bg-white text-black "
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Email Blast
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
rounded="md"
|
||||||
|
onClick={() => setTab("WhatsApp Blast")}
|
||||||
|
className={` hover:text-white
|
||||||
|
${
|
||||||
|
tab === "WhatsApp Blast"
|
||||||
|
? "bg-black text-white "
|
||||||
|
: "bg-white text-black "
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
WhatsApp Blast
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{tab === "Email Blast" && <BroadcastEmailTable />}
|
||||||
|
{tab === "WhatsApp Blast" && <BroadcastWhatsAppTable />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,403 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Eye,
|
||||||
|
MoreVertical,
|
||||||
|
Search,
|
||||||
|
SquarePen,
|
||||||
|
Trash2,
|
||||||
|
TrendingDown,
|
||||||
|
TrendingUp,
|
||||||
|
UserIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { paginationBlog } from "@/service/blog/blog";
|
||||||
|
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
|
import columns from "./column";
|
||||||
|
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import {
|
||||||
|
listDataMedia,
|
||||||
|
listDataMediaBroadCast,
|
||||||
|
} from "@/service/broadcast/broadcast";
|
||||||
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
|
const BroadcastWhatsAppTable = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [search, setSearch] = React.useState("");
|
||||||
|
const [showData, setShowData] = React.useState("10");
|
||||||
|
const [categories, setCategories] = React.useState<any>();
|
||||||
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: Number(showData),
|
||||||
|
});
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
||||||
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
|
const [page, setPage] = React.useState(1);
|
||||||
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
|
const table = useReactTable({
|
||||||
|
data: dataTable,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let typingTimer: any;
|
||||||
|
const doneTypingInterval = 1500;
|
||||||
|
|
||||||
|
const handleKeyUp = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doneTyping() {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const pageFromUrl = searchParams?.get("page");
|
||||||
|
if (pageFromUrl) {
|
||||||
|
setPage(Number(pageFromUrl));
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
setPagination({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: Number(showData),
|
||||||
|
});
|
||||||
|
}, [page, showData]);
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
loading();
|
||||||
|
const res = await listDataMediaBroadCast(
|
||||||
|
page - 1,
|
||||||
|
showData,
|
||||||
|
search,
|
||||||
|
categoryFilter?.sort().join(","),
|
||||||
|
statusFilter?.sort().join(",")
|
||||||
|
);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
const contentData = data?.content;
|
||||||
|
contentData.forEach((item: any, index: number) => {
|
||||||
|
item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("contentData : ", data);
|
||||||
|
|
||||||
|
setDataTable(contentData);
|
||||||
|
setTotalData(data?.totalElements);
|
||||||
|
setTotalPage(data?.totalPages);
|
||||||
|
close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching tasks:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
getCategories();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (type: string, id: number, checked: boolean) => {
|
||||||
|
if (type === "category") {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...categoryFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = categoryFilter.filter((a) => a !== id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...statusFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = statusFilter.filter((a) => a !== id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
|
<div className="flex justify-between ">
|
||||||
|
<Link href="/admin/broadcast/campaign-list" className="mr-3">
|
||||||
|
<Button color="primary" size="md" className="text-sm">
|
||||||
|
<UserIcon />
|
||||||
|
Daftar Campaign
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
className="max-w-[300px]"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button size="md" variant="outline">
|
||||||
|
1 - {showData} Data
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="w-56">
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
value={showData}
|
||||||
|
onValueChange={setShowData}
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem value="10">
|
||||||
|
1 - 10 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="20">
|
||||||
|
1 - 20 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="25">
|
||||||
|
1 - 25 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="50">
|
||||||
|
1 - 50 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button size="md" variant="outline">
|
||||||
|
Filter
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-80 ">
|
||||||
|
<div className="flex flex-col gap-2 px-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<p>Filter</p>
|
||||||
|
<a
|
||||||
|
onClick={() => fetchData()}
|
||||||
|
className="cursor-pointer text-primary"
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
||||||
|
<p>Kategory</p>
|
||||||
|
{categories?.map((category: any) => (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={category.id}
|
||||||
|
checked={categoryFilter.includes(category.id)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("category", category.id, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={category.id}
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<p className="mt-3">Status</p>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(1)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 1, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Menunggu Review
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(2)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 2, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Diterima
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(3)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 3, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Minta Update{" "}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(4)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 4, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Ditolak
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Table className="overflow-hidden">
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
className="h-[75px]"
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<TablePagination
|
||||||
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BroadcastWhatsAppTable;
|
||||||
|
|
@ -232,6 +232,10 @@ export default function CreateUserForm() {
|
||||||
id: "ADM-ID",
|
id: "ADM-ID",
|
||||||
name: "Admin",
|
name: "Admin",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "EXE-ID",
|
||||||
|
name: "Eksekutif",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "APP-ID",
|
id: "APP-ID",
|
||||||
name: "Approver",
|
name: "Approver",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
||||||
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuItem,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
formatDateToIndonesian,
|
||||||
|
getOnlyDate,
|
||||||
|
htmlToString,
|
||||||
|
} from "@/utils/globals";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
|
|
||||||
|
const columns: ColumnDef<any>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "no",
|
||||||
|
header: "No",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "title",
|
||||||
|
header: "Nama Media Online",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "link",
|
||||||
|
header: "Link",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default columns;
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Eye,
|
||||||
|
MoreVertical,
|
||||||
|
Search,
|
||||||
|
SquarePen,
|
||||||
|
Trash2,
|
||||||
|
TrendingDown,
|
||||||
|
TrendingUp,
|
||||||
|
UserIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { paginationBlog } from "@/service/blog/blog";
|
||||||
|
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
|
import columns from "./column";
|
||||||
|
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { listDataMedia } from "@/service/broadcast/broadcast";
|
||||||
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
|
const MediaOnlineTable = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [search, setSearch] = React.useState("");
|
||||||
|
const [showData, setShowData] = React.useState("10");
|
||||||
|
const [categories, setCategories] = React.useState<any>();
|
||||||
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: Number(showData),
|
||||||
|
});
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
||||||
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
|
const [page, setPage] = React.useState(1);
|
||||||
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
|
const table = useReactTable({
|
||||||
|
data: dataTable,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let typingTimer: any;
|
||||||
|
const doneTypingInterval = 1500;
|
||||||
|
|
||||||
|
// const handleKeyUp = () => {
|
||||||
|
// clearTimeout(typingTimer);
|
||||||
|
// typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// async function doneTyping() {
|
||||||
|
// fetchData();
|
||||||
|
// }
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const pageFromUrl = searchParams?.get("page");
|
||||||
|
if (pageFromUrl) {
|
||||||
|
setPage(Number(pageFromUrl));
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
// React.useEffect(() => {
|
||||||
|
// fetchData();
|
||||||
|
// setPagination({
|
||||||
|
// pageIndex: 0,
|
||||||
|
// pageSize: Number(showData),
|
||||||
|
// });
|
||||||
|
// }, [page, showData]);
|
||||||
|
|
||||||
|
// async function fetchData() {
|
||||||
|
// try {
|
||||||
|
// loading();
|
||||||
|
// const res = await listDataMedia(
|
||||||
|
// page - 1,
|
||||||
|
// showData,
|
||||||
|
// search,
|
||||||
|
// categoryFilter?.sort().join(","),
|
||||||
|
// statusFilter?.sort().join(",")
|
||||||
|
// );
|
||||||
|
// const data = res?.data?.data;
|
||||||
|
// const contentData = data?.content;
|
||||||
|
// contentData.forEach((item: any, index: number) => {
|
||||||
|
// item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log("contentData : ", data);
|
||||||
|
|
||||||
|
// setDataTable(contentData);
|
||||||
|
// setTotalData(data?.totalElements);
|
||||||
|
// setTotalPage(data?.totalPages);
|
||||||
|
// close();
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error("Error fetching tasks:", error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
getCategories();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (type: string, id: number, checked: boolean) => {
|
||||||
|
if (type === "category") {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...categoryFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = categoryFilter.filter((a) => a !== id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...statusFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = statusFilter.filter((a) => a !== id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
|
<div className="flex justify-between mb-10 items-center">
|
||||||
|
<p className="text-xl font-medium text-default-900">Media Online</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between ">
|
||||||
|
<Link href="/admin/broadcast/campaign-list">
|
||||||
|
<Button color="primary" size="md" className="text-sm mr-3">
|
||||||
|
Tambah Media Online
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
// onKeyUp={handleKeyUp}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
className="max-w-[300px]"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button size="md" variant="outline">
|
||||||
|
1 - {showData} Data
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="w-56">
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
value={showData}
|
||||||
|
onValueChange={setShowData}
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem value="10">
|
||||||
|
1 - 10 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="20">
|
||||||
|
1 - 20 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="25">
|
||||||
|
1 - 25 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="50">
|
||||||
|
1 - 50 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Table className="overflow-hidden">
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
className="h-[75px]"
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<TablePagination
|
||||||
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MediaOnlineTable;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import MediaOnlineTable from "./component/table";
|
||||||
|
|
||||||
|
export default function AdminMediaOnline() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<MediaOnlineTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
||||||
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuItem,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
formatDateToIndonesian,
|
||||||
|
getOnlyDate,
|
||||||
|
htmlToString,
|
||||||
|
} from "@/utils/globals";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
|
|
||||||
|
const columns: ColumnDef<any>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "no",
|
||||||
|
header: "No",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "date",
|
||||||
|
header: "Tanggal",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "title",
|
||||||
|
header: "Media Online",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "link",
|
||||||
|
header: "Link Berita",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default columns;
|
||||||
|
|
@ -0,0 +1,305 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Eye,
|
||||||
|
MoreVertical,
|
||||||
|
Search,
|
||||||
|
SquarePen,
|
||||||
|
Trash2,
|
||||||
|
TrendingDown,
|
||||||
|
TrendingUp,
|
||||||
|
UserIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||||
|
import { paginationBlog } from "@/service/blog/blog";
|
||||||
|
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
|
import columns from "./column";
|
||||||
|
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { listDataMedia } from "@/service/broadcast/broadcast";
|
||||||
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
|
const NewsTable = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [search, setSearch] = React.useState("");
|
||||||
|
const [showData, setShowData] = React.useState("10");
|
||||||
|
const [categories, setCategories] = React.useState<any>();
|
||||||
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [showTable, setShowTable] = React.useState(false);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: Number(showData),
|
||||||
|
});
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
||||||
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
|
const [page, setPage] = React.useState(1);
|
||||||
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
|
const table = useReactTable({
|
||||||
|
data: dataTable,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let typingTimer: any;
|
||||||
|
const doneTypingInterval = 1500;
|
||||||
|
|
||||||
|
// const handleKeyUp = () => {
|
||||||
|
// clearTimeout(typingTimer);
|
||||||
|
// typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// async function doneTyping() {
|
||||||
|
// fetchData();
|
||||||
|
// }
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const pageFromUrl = searchParams?.get("page");
|
||||||
|
if (pageFromUrl) {
|
||||||
|
setPage(Number(pageFromUrl));
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
// React.useEffect(() => {
|
||||||
|
// fetchData();
|
||||||
|
// setPagination({
|
||||||
|
// pageIndex: 0,
|
||||||
|
// pageSize: Number(showData),
|
||||||
|
// });
|
||||||
|
// }, [page, showData]);
|
||||||
|
|
||||||
|
// async function fetchData() {
|
||||||
|
// try {
|
||||||
|
// loading();
|
||||||
|
// const res = await listDataMedia(
|
||||||
|
// page - 1,
|
||||||
|
// showData,
|
||||||
|
// search,
|
||||||
|
// categoryFilter?.sort().join(","),
|
||||||
|
// statusFilter?.sort().join(",")
|
||||||
|
// );
|
||||||
|
// const data = res?.data?.data;
|
||||||
|
// const contentData = data?.content;
|
||||||
|
// contentData.forEach((item: any, index: number) => {
|
||||||
|
// item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log("contentData : ", data);
|
||||||
|
|
||||||
|
// setDataTable(contentData);
|
||||||
|
// setTotalData(data?.totalElements);
|
||||||
|
// setTotalPage(data?.totalPages);
|
||||||
|
// close();
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error("Error fetching tasks:", error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
getCategories();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (type: string, id: number, checked: boolean) => {
|
||||||
|
if (type === "category") {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...categoryFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = categoryFilter.filter((a) => a !== id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...statusFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = statusFilter.filter((a) => a !== id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3 border">
|
||||||
|
<div className="flex justify-between mb-10 items-center">
|
||||||
|
<p className="text-xl font-medium text-default-900">
|
||||||
|
Tracking Berita hari ini!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>
|
||||||
|
Masukan Link <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input></Input>
|
||||||
|
<p className="text-sm">Sisa kuota harian: 30 Artikel</p>
|
||||||
|
</div>
|
||||||
|
{!showTable && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
className="text-sm mr-3"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
className="text-sm mr-3"
|
||||||
|
onClick={() => setShowTable(true)}
|
||||||
|
>
|
||||||
|
Tracking Berita
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showTable && (
|
||||||
|
<>
|
||||||
|
<Table className="overflow-hidden mt-4">
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow key={row.id} className="h-[75px]">
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext()
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<TablePagination
|
||||||
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end mt-4">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
className="text-sm mr-3"
|
||||||
|
onClick={() => setShowTable(false)}
|
||||||
|
>
|
||||||
|
Tracking Berita Baru
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewsTable;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import NewsTable from "./component/table";
|
||||||
|
|
||||||
|
export default function AdminNews() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<NewsTable />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,15 @@ import { Label } from "@/components/ui/label";
|
||||||
import ExternalDraggingevent from "./dragging-events";
|
import ExternalDraggingevent from "./dragging-events";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Book, CheckSquare2, CheckSquare2Icon, Plus } from "lucide-react";
|
import {
|
||||||
|
Book,
|
||||||
|
CheckCheck,
|
||||||
|
CheckSquare2,
|
||||||
|
CheckSquare2Icon,
|
||||||
|
Plus,
|
||||||
|
Timer,
|
||||||
|
TimerIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { EventContentArg } from "@fullcalendar/core";
|
import { EventContentArg } from "@fullcalendar/core";
|
||||||
import EventModal from "./event-modal";
|
import EventModal from "./event-modal";
|
||||||
|
|
@ -46,6 +54,7 @@ export type CalendarEvent = {
|
||||||
end: Date;
|
end: Date;
|
||||||
createBy: string;
|
createBy: string;
|
||||||
createdByName: string;
|
createdByName: string;
|
||||||
|
isPublish: boolean | null;
|
||||||
allDay: boolean;
|
allDay: boolean;
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
calendar: string;
|
calendar: string;
|
||||||
|
|
@ -94,7 +103,7 @@ interface ListItemProps {
|
||||||
item: any;
|
item: any;
|
||||||
text: string;
|
text: string;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
isPublish: boolean;
|
isPublish: boolean | null;
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,12 +185,15 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
end: new Date(event.endDate),
|
end: new Date(event.endDate),
|
||||||
allDay: true,
|
allDay: true,
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
|
isPublish: event.isPublish,
|
||||||
calendar: event.agendaType,
|
calendar: event.agendaType,
|
||||||
description: event.description,
|
description: event.description,
|
||||||
createdByName: event.createdByName,
|
createdByName: event.createdByName,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log("Dataaa event : ", events);
|
||||||
|
|
||||||
setCalendarEvents(events);
|
setCalendarEvents(events);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -239,6 +251,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
title: item.title,
|
title: item.title,
|
||||||
createBy: "Mabes Polri - Approver",
|
createBy: "Mabes Polri - Approver",
|
||||||
createdByName: item.createdByName,
|
createdByName: item.createdByName,
|
||||||
|
isPublish: item.isPublish,
|
||||||
start: new Date(item.startDate),
|
start: new Date(item.startDate),
|
||||||
end: new Date(item.endDate),
|
end: new Date(item.endDate),
|
||||||
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
allDay: true, // Sesuaikan jika memang ada event sepanjang hari
|
||||||
|
|
@ -335,14 +348,13 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
|
|
||||||
const renderEventContent = (eventInfo: any) => {
|
const renderEventContent = (eventInfo: any) => {
|
||||||
const { title } = eventInfo.event;
|
const { title } = eventInfo.event;
|
||||||
const { isPublish } = eventInfo.event.extendedProps;
|
const { createdByName, isPublish } = eventInfo.event.extendedProps;
|
||||||
const { createdByName } = eventInfo.event.extendedProps;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
{" "}
|
{" "}
|
||||||
{isPublish == true && <CheckSquare2 />}
|
{isPublish === true ? <CheckCheck size={15} /> : <Timer size={15} />}
|
||||||
<p className="ml-1">{title}</p>
|
<p className="ml-1">{title}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -417,6 +429,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
end: new Date(item.endDate),
|
end: new Date(item.endDate),
|
||||||
createBy: "Mabes Polri - Approver", // Sesuaikan dengan data yang sebenarnya jika ada
|
createBy: "Mabes Polri - Approver", // Sesuaikan dengan data yang sebenarnya jika ada
|
||||||
createdByName: item.createdByName,
|
createdByName: item.createdByName,
|
||||||
|
isPublish: item.isPublish,
|
||||||
allDay: true,
|
allDay: true,
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
calendar: item.agendaType,
|
calendar: item.agendaType,
|
||||||
|
|
@ -447,7 +460,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
|
||||||
onClick={() => handleClickListItem(item)}
|
onClick={() => handleClickListItem(item)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
{isPublish == true && <CheckSquare2Icon />}
|
{isPublish ? <CheckCheck size={15} /> : <Timer size={15} />}
|
||||||
<p className="ml-1">{text}</p>
|
<p className="ml-1">{text}</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
<p className="ml-1 text-xs text-start mt-2">Created By: {createdBy}</p>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const BlogPage = async () => {
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-1 text-xl font-medium text-default-900">
|
<div className="flex-1 text-xl font-medium text-default-900">
|
||||||
Table Indeks
|
Tabel Indeks
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
<Link href={"/contributor/blog/create"}>
|
<Link href={"/contributor/blog/create"}>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import FormImageDetail from "@/components/form/content/image-detail-form";
|
||||||
|
import FormImageUpdate from "@/components/form/content/image-update-form";
|
||||||
|
import FormAudioUpdate from "@/components/form/content/audio-update-form";
|
||||||
|
import FormAudioSeo from "@/components/form/content/audio-update-seo";
|
||||||
|
|
||||||
|
const AudioUpdatePage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormAudioSeo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioUpdatePage;
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import FormImageDetail from "@/components/form/content/image-detail-form";
|
||||||
|
import FormImageUpdate from "@/components/form/content/image-update-form";
|
||||||
|
import FormImageSeo from "@/components/form/content/image-update-seo";
|
||||||
|
|
||||||
|
const ImageUpdateSeoPage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormImageSeo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageUpdateSeoPage;
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import FormTeksUpdate from "@/components/form/content/teks-update-form";
|
||||||
|
import FormTeksSeo from "@/components/form/content/teks-update-seo";
|
||||||
|
|
||||||
|
const TeksUpdatePage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormTeksSeo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeksUpdatePage;
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import FormImageDetail from "@/components/form/content/image-detail-form";
|
||||||
|
import FormImageUpdate from "@/components/form/content/image-update-form";
|
||||||
|
import FormVideoUpdate from "@/components/form/content/video-update-form";
|
||||||
|
import FormVideoSeo from "@/components/form/content/video-update-seo";
|
||||||
|
|
||||||
|
const VideoUpdatePage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormVideoSeo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoUpdatePage;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import FormAudio from "@/components/form/content/audio-form";
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
|
||||||
|
const AudioScheduleCreatePage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormAudio />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioScheduleCreatePage;
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import FormTask from "@/components/form/task/task-form";
|
||||||
|
import FormImage from "@/components/form/content/image-form";
|
||||||
|
|
||||||
|
const ImageScheduleCreatePage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormImage />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageScheduleCreatePage;
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import FormAudio from "@/components/form/content/audio-form";
|
||||||
|
import FormTeks from "@/components/form/content/teks-form";
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
|
||||||
|
const TeksScheduleCreatePage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormTeks />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeksScheduleCreatePage;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import FormVideo from "@/components/form/content/video-form";
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
|
||||||
|
const VideoScheduleCreatePage = async () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormVideo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoScheduleCreatePage;
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Eye,
|
Eye,
|
||||||
|
|
@ -52,6 +53,8 @@ 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 } from "@/service/task";
|
import { listTask } from "@/service/task";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
|
||||||
const TaskTable = () => {
|
const TaskTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -70,6 +73,10 @@ const TaskTable = () => {
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
});
|
});
|
||||||
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
|
const [dateFilter, setDateFilter] = React.useState("");
|
||||||
|
const [endDate, setEndDate] = React.useState("");
|
||||||
|
const [filterByCode, setFilterByCode] = React.useState<string>("");
|
||||||
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 [limit, setLimit] = React.useState(10);
|
const [limit, setLimit] = React.useState(10);
|
||||||
|
|
@ -106,18 +113,43 @@ const TaskTable = () => {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, limit, isSpecificAttention, search]);
|
}, [
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
isSpecificAttention,
|
||||||
|
search,
|
||||||
|
dateFilter,
|
||||||
|
filterByCode,
|
||||||
|
statusFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
const formattedStartDate = dateFilter
|
||||||
|
? format(new Date(dateFilter), "yyyy-MM-dd")
|
||||||
|
: "";
|
||||||
try {
|
try {
|
||||||
const res = await listTask(
|
const res = await listTask(
|
||||||
search,
|
|
||||||
page - 1,
|
page - 1,
|
||||||
|
search,
|
||||||
limit,
|
limit,
|
||||||
isSpecificAttention ? "atensi-khusus" : "tugas-harian"
|
filterByCode,
|
||||||
|
formattedStartDate,
|
||||||
|
isSpecificAttention ? "atensi-khusus" : "tugas-harian",
|
||||||
|
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) * limit + index + 1;
|
item.no = (page - 1) * limit + index + 1;
|
||||||
});
|
});
|
||||||
|
|
@ -133,10 +165,26 @@ const TaskTable = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value); // Perbarui state search
|
setFilterByCode(e.target.value);
|
||||||
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
|
setSearch(e.target.value);
|
||||||
|
table.getColumn("judul")?.setFilterValue(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function handleStatusCheckboxChange(value: number) {
|
||||||
|
setStatusFilter((prev) =>
|
||||||
|
prev.includes(value)
|
||||||
|
? prev.filter((status) => status !== value)
|
||||||
|
: [...prev, value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// const handleSearchFilterByCode = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
// const value = e.target.value;
|
||||||
|
// console.log("code :", value);
|
||||||
|
// setFilterByCode(value);
|
||||||
|
// fetchData();
|
||||||
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto">
|
<div className="w-full overflow-x-auto">
|
||||||
<div className="mx-5 mb-3">
|
<div className="mx-5 mb-3">
|
||||||
|
|
@ -184,24 +232,88 @@ const TaskTable = () => {
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search Judul..."
|
placeholder="Search Judul dan Code"
|
||||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white w-full"
|
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white w-full"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<div className="flex flex-row items-center gap-2">
|
||||||
<Input
|
<div className="flex flex-row items-center gap-3">
|
||||||
placeholder="Filter Status..."
|
<div className="flex items-center py-4">
|
||||||
value={
|
<DropdownMenu>
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
<DropdownMenuTrigger asChild>
|
||||||
}
|
<Button variant="outline" className="ml-auto" size="md">
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
Filter <ChevronDown />
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
</Button>
|
||||||
}
|
</DropdownMenuTrigger>
|
||||||
className="max-w-sm "
|
<DropdownMenuContent
|
||||||
/>
|
align="end"
|
||||||
|
className="w-64 h-[200px] overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between my-1 mx-1">
|
||||||
|
<p>Filter</p>
|
||||||
|
</div>
|
||||||
|
<div className="mx-2 my-1">
|
||||||
|
<Label>Tanggal Awal</Label>
|
||||||
|
<Input
|
||||||
|
type="date"
|
||||||
|
value={dateFilter}
|
||||||
|
onChange={(e) => setDateFilter(e.target.value)}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* <div className="mx-2 my-1">
|
||||||
|
<Label>Code</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={filterByCode}
|
||||||
|
// onChange={handleSearchFilterByCode}
|
||||||
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
<Label className="ml-2 mt-2">Status</Label>
|
||||||
|
<div className="flex items-center px-4 py-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="status-1"
|
||||||
|
className="mr-2"
|
||||||
|
checked={statusFilter.includes(1)}
|
||||||
|
onChange={() => handleStatusCheckboxChange(1)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="status-1" className="text-sm">
|
||||||
|
Selesai
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center px-4 py-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="status-2"
|
||||||
|
className="mr-2"
|
||||||
|
checked={statusFilter.includes(2)}
|
||||||
|
onChange={() => handleStatusCheckboxChange(2)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="status-2" className="text-sm">
|
||||||
|
Aktif
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <div className="flex-none">
|
||||||
|
<Input
|
||||||
|
placeholder="Filter Status..."
|
||||||
|
value={
|
||||||
|
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
||||||
|
}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
table.getColumn("status")?.setFilterValue(event.target.value)
|
||||||
|
}
|
||||||
|
className="max-w-sm "
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Table className="overflow-hidden mt-3">
|
<Table className="overflow-hidden mt-3">
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const TaskPage = () => {
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<div className="flex flex-col sm:flex-row lg:flex-row lg:items-center">
|
<div className="flex flex-col sm:flex-row lg:flex-row lg:items-center">
|
||||||
<div className="flex-1 text-xl font-medium text-default-900">
|
<div className="flex-1 text-xl font-medium text-default-900">
|
||||||
Table Penugasan
|
Tabel Penugasan
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
<Link href={"/contributor/task/create"}>
|
<Link href={"/contributor/task/create"}>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const ContestPage = () => {
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-1 text-xl font-medium text-default-900">
|
<div className="flex-1 text-xl font-medium text-default-900">
|
||||||
Table Lomba
|
Tabel Lomba
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
<Link href={"/shared/contest/create"}>
|
<Link href={"/shared/contest/create"}>
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,101 @@
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
ColumnDef,
|
|
||||||
} from "@tanstack/react-table";
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
import {
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
DropdownMenuItem,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import { error } from "@/config/swal";
|
||||||
|
import { deleteCategory, deleteDataFAQ } from "@/service/settings/settings";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Menubar,
|
||||||
|
MenubarContent,
|
||||||
|
MenubarMenu,
|
||||||
|
MenubarTrigger,
|
||||||
|
} from "@/components/ui/menubar";
|
||||||
|
import { htmlToString } from "@/utils/globals";
|
||||||
|
import EditSpvFAQModal from "./edit";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "no",
|
accessorKey: "no",
|
||||||
header: "No",
|
header: "No",
|
||||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "question",
|
accessorKey: "question",
|
||||||
header: "Question",
|
header: "Pertanyaan",
|
||||||
cell: ({ row }) => <span>{row.getValue("question")}</span>,
|
cell: ({ row }) => (
|
||||||
|
<span className="normal-case">
|
||||||
|
{htmlToString(row.getValue("question"))}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "answer",
|
accessorKey: "answer",
|
||||||
header: "Answer",
|
header: "Answer",
|
||||||
cell: ({ row }) => <span>{row.getValue("answer")}</span>,
|
cell: ({ row }) => (
|
||||||
|
<span className="normal-case">
|
||||||
|
{htmlToString(row.getValue("answer"))}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
accessorKey: "action",
|
accessorKey: "action",
|
||||||
header: "Actions",
|
header: "Actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const faqDelete = async (id: string) => {
|
||||||
|
const response = await deleteDataFAQ(id);
|
||||||
|
console.log(response);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: "Sukses",
|
||||||
|
description: "Berhasil Delete",
|
||||||
|
});
|
||||||
|
router.push("/supervisor/faq?dataChange=true");
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<Menubar className="border-none">
|
||||||
<DropdownMenuTrigger asChild>
|
<MenubarMenu>
|
||||||
<Button
|
<MenubarTrigger>
|
||||||
size="icon"
|
<Button
|
||||||
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
size="icon"
|
||||||
>
|
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||||
<span className="sr-only">Open menu</span>
|
>
|
||||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</MenubarTrigger>
|
||||||
<DropdownMenuContent className="p-0" align="end">
|
<MenubarContent className="flex flex-col gap-2 justify-center items-start p-4">
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
<EditSpvFAQModal id={row.original.id} isDetail={true} />
|
||||||
<Eye className="w-4 h-4 me-1.5" />
|
|
||||||
View
|
<EditSpvFAQModal id={row.original.id} isDetail={false} />
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
<a
|
||||||
<SquarePen className="w-4 h-4 me-1.5" />
|
onClick={() => faqDelete(row.original.id)}
|
||||||
Edit
|
className="hover:underline cursor-pointer hover:text-destructive"
|
||||||
</DropdownMenuItem>
|
>
|
||||||
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
|
Delete
|
||||||
<Trash2 className="w-4 h-4 me-1.5" />
|
</a>
|
||||||
Delete
|
</MenubarContent>
|
||||||
</DropdownMenuItem>
|
</MenubarMenu>
|
||||||
</DropdownMenuContent>
|
</Menubar>
|
||||||
</DropdownMenu>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default columns;
|
export default columns;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getUserRoles,
|
||||||
|
postCategory,
|
||||||
|
postDataFAQ,
|
||||||
|
} from "@/service/settings/settings";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { stringify } from "querystring";
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
answer: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
question: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
publishTo: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
|
||||||
|
// publishTo: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||||
|
// message: "Required",
|
||||||
|
// }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const publishToList = [
|
||||||
|
{
|
||||||
|
id: "mabes",
|
||||||
|
name: "Nasional",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "polda",
|
||||||
|
name: "Polda",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "satker",
|
||||||
|
name: "Satker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "internasional",
|
||||||
|
name: "Internasional",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function CreateSpvFAQModal() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
defaultValues: { publishTo: "wilayah" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const target = form.watch("publishTo");
|
||||||
|
const isAllTargetChecked = publishToList.every((item) =>
|
||||||
|
target?.includes(item.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||||
|
const request = {
|
||||||
|
question: data.question,
|
||||||
|
answer: data.answer,
|
||||||
|
isInternational: data.publishTo === "wilayah" ? false : true,
|
||||||
|
isActive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await postDataFAQ(request);
|
||||||
|
close();
|
||||||
|
if (response?.error) {
|
||||||
|
toast({ title: stringify(response.message), variant: "destructive" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: "Succes",
|
||||||
|
description: "FAQ berhasil dibuat",
|
||||||
|
});
|
||||||
|
router.push("/supervisor/faq?dataChange=true");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button color="primary" size="md">
|
||||||
|
Tambah FAQ
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent size="md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Tambah FAQ</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="space-y-3 bg-white rounded-sm"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="publishTo"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-3">
|
||||||
|
<FormLabel>Wilayah Publish</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroup
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value}
|
||||||
|
className="flex flex-row space-x-1"
|
||||||
|
>
|
||||||
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="wilayah" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">Wilayah</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="international" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">
|
||||||
|
Internasional
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="question"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Pertanyaan</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="Masukkan pertanyaan" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="answer"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Jawaban</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea placeholder="Masukkan jawaban" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
Tambah FAQ
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import {
|
||||||
|
detailDataFAQ,
|
||||||
|
getUserRoles,
|
||||||
|
postCategory,
|
||||||
|
postDataFAQ,
|
||||||
|
} from "@/service/settings/settings";
|
||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { stringify } from "querystring";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { CloudUpload } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { id } from "date-fns/locale";
|
||||||
|
import { htmlToString } from "@/utils/globals";
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
answer: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
question: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
publishTo: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
|
||||||
|
// publishTo: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||||
|
// message: "Required",
|
||||||
|
// }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const publishToList = [
|
||||||
|
{
|
||||||
|
id: "mabes",
|
||||||
|
name: "Nasional",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "polda",
|
||||||
|
name: "Polda",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "satker",
|
||||||
|
name: "Satker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "internasional",
|
||||||
|
name: "Internasional",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function EditSpvFAQModal(props: {
|
||||||
|
id: string;
|
||||||
|
isDetail: boolean;
|
||||||
|
}) {
|
||||||
|
const { id, isDetail } = props;
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initState();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const initState = async () => {
|
||||||
|
const res = await detailDataFAQ(id);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
form.setValue("question", htmlToString(data.question));
|
||||||
|
form.setValue("answer", htmlToString(data.answer));
|
||||||
|
form.setValue(
|
||||||
|
"publishTo",
|
||||||
|
data.isInternational ? "international" : "wilayah"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||||
|
const request = {
|
||||||
|
id: Number(id),
|
||||||
|
question: data.question,
|
||||||
|
answer: data.answer,
|
||||||
|
isInternational: data.publishTo === "wilayah" ? false : true,
|
||||||
|
isActive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await postDataFAQ(request);
|
||||||
|
close();
|
||||||
|
if (response?.error) {
|
||||||
|
toast({ title: stringify(response.message), variant: "destructive" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: "Succes",
|
||||||
|
description: "FAQ berhasil diubah",
|
||||||
|
});
|
||||||
|
router.push("/supervisor/faq?dataChange=true");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<a className="hover:underline cursor-pointer">
|
||||||
|
{isDetail ? "Detail" : "Edit"}
|
||||||
|
</a>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent size="md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{isDetail ? "Detail" : "Edit"} FAQ</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="space-y-3 bg-white rounded-sm"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="publishTo"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-3">
|
||||||
|
<FormLabel>Wilayah Publish</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroup
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value}
|
||||||
|
className="flex flex-row space-x-1"
|
||||||
|
>
|
||||||
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="wilayah" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">Wilayah</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value="international" />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">
|
||||||
|
Internasional
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="question"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Pertanyaan</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
readOnly={isDetail}
|
||||||
|
placeholder="Masukkan pertanyaan"
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="answer"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Jawaban</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
readOnly={isDetail}
|
||||||
|
placeholder="Masukkan jawaban"
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{!isDetail && (
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
Edit FAQ
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -24,37 +24,32 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import {
|
import { useSearchParams } from "next/navigation";
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
Search,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
|
||||||
import TablePagination from "@/components/table/table-pagination";
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
import { getFaqList } from "@/service/master/faq";
|
|
||||||
import columns from "./column";
|
import columns from "./column";
|
||||||
|
|
||||||
const FaqTable = () => {
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import { NewCampaignIcon } from "@/components/icon";
|
||||||
|
import { getCategories, getListFAQ } from "@/service/settings/settings";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import CreateSpvFAQModal from "./create";
|
||||||
|
|
||||||
|
const SpvFAQTable = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const dataChange = searchParams?.get("dataChange");
|
||||||
|
const [openModal, setOpenModal] = React.useState(false);
|
||||||
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>([]);
|
||||||
|
|
@ -68,10 +63,9 @@ const FaqTable = () => {
|
||||||
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 [limit, setLimit] = React.useState(10);
|
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataTable,
|
data: dataTable,
|
||||||
columns,
|
columns,
|
||||||
|
|
@ -94,63 +88,50 @@ const FaqTable = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const pageFromUrl = searchParams?.get('page');
|
if (dataChange) {
|
||||||
|
router.push("/admin/settings/faq");
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dataChange]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const pageFromUrl = searchParams?.get("page");
|
||||||
if (pageFromUrl) {
|
if (pageFromUrl) {
|
||||||
setPage(Number(pageFromUrl));
|
setPage(Number(pageFromUrl));
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page, limit]);
|
}, [page]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
const res = await getFaqList();
|
loading();
|
||||||
const contentData = res?.data?.data;
|
const response = await getListFAQ();
|
||||||
contentData.forEach((item: any, index: number) => {
|
const data = response?.data?.data;
|
||||||
item.no = (page - 1) * limit + index + 1;
|
console.log("respone", response);
|
||||||
|
data.forEach((item: any, index: number) => {
|
||||||
|
item.no = (page - 1) * 10 + index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("contentData : ", contentData);
|
setDataTable(data);
|
||||||
|
setTotalData(data?.length);
|
||||||
setDataTable(contentData);
|
setTotalPage(1);
|
||||||
setTotalData(contentData?.totalElements || contentData?.length);
|
close();
|
||||||
setTotalPage(contentData?.totalPages || 1);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching tasks:", error);
|
console.error("Error fetching tasks:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto">
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
<div className="flex justify-between items-center px-6">
|
<div className="flex justify-between mb-10 items-center">
|
||||||
<div>
|
<p className="text-xl font-medium text-default-900">FAQ</p>
|
||||||
<InputGroup merged>
|
<CreateSpvFAQModal />
|
||||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
|
||||||
<Search className=" h-4 w-4 dark:text-white" />
|
|
||||||
</InputGroupText>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search Judul..."
|
|
||||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
</div>
|
|
||||||
<div className="flex-none">
|
|
||||||
<Input
|
|
||||||
placeholder="Filter Status..."
|
|
||||||
value={
|
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
|
||||||
}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
|
||||||
}
|
|
||||||
className="max-w-sm "
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Table className="overflow-hidden mt-3">
|
|
||||||
|
<Table className="overflow-hidden">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
|
@ -191,9 +172,13 @@ const FaqTable = () => {
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<TablePagination table={table} totalData={totalData} totalPage={totalPage} visiblePageCount={5} />
|
{/* <TablePagination
|
||||||
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FaqTable;
|
export default SpvFAQTable;
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,11 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import SpvFAQTable from "./components/table";
|
||||||
import FaqTable from "./components/table";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Plus } from "lucide-react";
|
|
||||||
|
|
||||||
const FaqPage = async () => {
|
const FaqPage = async () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="space-y-4">
|
<SpvFAQTable />
|
||||||
<Card>
|
|
||||||
<CardHeader className="border-b border-solid border-default-200 mb-6">
|
|
||||||
<CardTitle>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="flex-1 text-xl font-medium text-default-900">
|
|
||||||
FAQ Data
|
|
||||||
</div>
|
|
||||||
<div className="flex-none">
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
size="md"
|
|
||||||
>
|
|
||||||
<Plus className="w-6 h-6 me-1.5"/>
|
|
||||||
New FAQ
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-0">
|
|
||||||
<FaqTable />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,7 @@
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
ColumnDef,
|
|
||||||
} from "@tanstack/react-table";
|
|
||||||
|
|
||||||
import {
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -19,6 +11,7 @@ import {
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { formatDateToIndonesian } from "@/utils/globals";
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -34,19 +27,23 @@ const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "title",
|
accessorKey: "title",
|
||||||
header: "Title",
|
header: "Title",
|
||||||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
cell: ({ row }) => (
|
||||||
|
<span className="normal-case">{row.getValue("title")}</span>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "commentFromUserName",
|
accessorKey: "commentFromUserName",
|
||||||
header: "Sender",
|
header: "Sender",
|
||||||
cell: ({ row }) => <span>{row.getValue("commentFromUserName")}</span>,
|
cell: ({ row }) => (
|
||||||
|
<span className="normal-case">{row.getValue("commentFromUserName")}</span>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "type",
|
accessorKey: "type",
|
||||||
header: "Channel",
|
header: "Channel",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const type = row.getValue("type") as { name: string };
|
const type = row.getValue("type") as { name: string };
|
||||||
return <span>{type?.name}</span>;
|
return <span className="normal-case">{type?.name}</span>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -54,14 +51,16 @@ const columns: ColumnDef<any>[] = [
|
||||||
header: "Operator",
|
header: "Operator",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const createdBy = row.getValue("createdBy") as { fullname: string };
|
const createdBy = row.getValue("createdBy") as { fullname: string };
|
||||||
return <span>{createdBy?.fullname}</span>;
|
return <span className="normal-case">{createdBy?.fullname}</span>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "createdAt",
|
accessorKey: "createdAt",
|
||||||
header: "Tanggal Unggah ",
|
header: "Tanggal Unggah ",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span className="whitespace-nowrap">{row.getValue("createdAt")}</span>
|
<span className="whitespace-nowrap">
|
||||||
|
{formatDateToIndonesian(row.getValue("createdAt"))}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -72,18 +71,18 @@ const columns: ColumnDef<any>[] = [
|
||||||
open: "bg-primary/20 text-primary",
|
open: "bg-primary/20 text-primary",
|
||||||
close: "bg-success/20 text-success",
|
close: "bg-success/20 text-success",
|
||||||
};
|
};
|
||||||
const status = row.getValue("status") as { id: number, name: string };;
|
const status = row.getValue("status") as { id: number; name: string };
|
||||||
const statusName = status?.name?.toLocaleLowerCase();
|
const statusName = status?.name?.toLocaleLowerCase();
|
||||||
console.log(statusName);
|
console.log(statusName);
|
||||||
const statusStyles = statusColors[statusName] || "default";
|
const statusStyles = statusColors[statusName] || "default";
|
||||||
if (statusName) {
|
if (statusName) {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge className={cn("rounded-full px-5", statusStyles)}>
|
||||||
className={cn("rounded-full px-5",statusStyles)}
|
{statusName}{" "}
|
||||||
>{statusName} </Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
|
|
@ -122,4 +121,4 @@ const columns: ColumnDef<any>[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default columns;
|
export default columns;
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -62,16 +64,19 @@ const TicketingTable = () => {
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
const [search, setSearch] = React.useState("");
|
||||||
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
const [columnVisibility, setColumnVisibility] =
|
||||||
React.useState<VisibilityState>({});
|
React.useState<VisibilityState>({});
|
||||||
|
const [showData, setShowData] = React.useState("10");
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
const [pagination, setPagination] = React.useState<PaginationState>({
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: Number(showData),
|
||||||
});
|
});
|
||||||
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 [limit, setLimit] = React.useState(10);
|
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: dataTable,
|
data: dataTable,
|
||||||
|
|
@ -94,24 +99,40 @@ const TicketingTable = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let typingTimer: any;
|
||||||
|
const doneTypingInterval = 1500;
|
||||||
|
|
||||||
|
const handleKeyUp = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doneTyping() {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
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, limit]);
|
}, [page]);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
const res = await ticketingPagination('', limit, page-1);
|
const res = await ticketingPagination(search, Number(showData), page - 1);
|
||||||
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) * limit + index + 1;
|
item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("contentData : ", contentData);
|
console.log("contentData : ", contentData);
|
||||||
|
|
@ -125,34 +146,46 @@ const TicketingTable = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full overflow-x-auto">
|
<>
|
||||||
<div className="flex justify-between items-center px-5">
|
<div className="flex justify-between py-3">
|
||||||
<div>
|
<Input
|
||||||
<InputGroup merged>
|
type="text"
|
||||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
placeholder="Search"
|
||||||
<Search className=" h-4 w-4 dark:text-white" />
|
onKeyUp={handleKeyUp}
|
||||||
</InputGroupText>
|
onKeyDown={handleKeyDown}
|
||||||
<Input
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
type="text"
|
className="max-w-[300px]"
|
||||||
placeholder="Search Judul..."
|
/>
|
||||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
|
<div className="flex flex-row gap-2">
|
||||||
/>
|
<DropdownMenu>
|
||||||
</InputGroup>
|
<DropdownMenuTrigger asChild>
|
||||||
</div>
|
<Button size="md" variant="outline">
|
||||||
<div className="flex-none">
|
1 - {showData} Data
|
||||||
<Input
|
</Button>
|
||||||
placeholder="Filter Status..."
|
</DropdownMenuTrigger>
|
||||||
value={
|
<DropdownMenuContent className="w-56 text-sm">
|
||||||
(table.getColumn("status")?.getFilterValue() as string) ?? ""
|
<DropdownMenuRadioGroup
|
||||||
}
|
value={showData}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onValueChange={setShowData}
|
||||||
table.getColumn("status")?.setFilterValue(event.target.value)
|
>
|
||||||
}
|
<DropdownMenuRadioItem value="10">
|
||||||
className="max-w-sm "
|
1 - 10 Data
|
||||||
/>
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="20">
|
||||||
|
1 - 20 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="25">
|
||||||
|
1 - 25 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="50">
|
||||||
|
1 - 50 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Table className="overflow-hidden mt-3">
|
<Table className="overflow-hidden">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id} className="bg-default-200">
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
|
@ -193,8 +226,12 @@ const TicketingTable = () => {
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<TablePagination table={table} totalData={totalData} totalPage={totalPage} />
|
<TablePagination
|
||||||
</div>
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,21 @@
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { UploadIcon } from "lucide-react";
|
|
||||||
import TicketingTable from "./components/table";
|
import TicketingTable from "./components/table";
|
||||||
|
|
||||||
const TicketingPage = async () => {
|
const TicketingPage = async () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SiteBreadcrumb />
|
<SiteBreadcrumb />
|
||||||
<div className="space-y-4">
|
|
||||||
<Card>
|
<section
|
||||||
<CardHeader className="border-b border-solid border-default-200 mb-6">
|
id="table"
|
||||||
<CardTitle>
|
className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5"
|
||||||
<div className="flex items-center">
|
>
|
||||||
<div className="flex-1 text-xl font-medium text-default-900">
|
<div className="flex justify-between py-3">
|
||||||
Ticket Data
|
<p className="text-lg">Semua Ticket</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
|
||||||
{/* <Button
|
<TicketingTable />
|
||||||
fullWidth
|
</section>
|
||||||
size="md"
|
|
||||||
>
|
|
||||||
<Plus className="w-6 h-6 me-1.5"/>
|
|
||||||
New Ticket
|
|
||||||
</Button> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="p-0">
|
|
||||||
<TicketingTable />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ import { error, loading } from "@/config/swal";
|
||||||
import { Item } from "@radix-ui/react-dropdown-menu";
|
import { Item } from "@radix-ui/react-dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
interface FileWithPreview extends File {
|
interface FileWithPreview extends File {
|
||||||
preview: string;
|
preview: string;
|
||||||
|
|
@ -921,7 +922,7 @@ export default function FormAudio() {
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleArticleIdClick(id)}
|
onClick={() => handleArticleIdClick(id)}
|
||||||
>
|
>
|
||||||
{id}
|
{"Narasi " + (index + 1)}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -930,18 +931,8 @@ export default function FormAudio() {
|
||||||
<div className="pt-3">
|
<div className="pt-3">
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
{selectedArticleId && (
|
{selectedArticleId && (
|
||||||
<a
|
<Link
|
||||||
href={`/admin/media/${
|
href={`/contributor/content/audio/update-seo/${selectedArticleId}`}
|
||||||
fileTypeId === "1"
|
|
||||||
? "image"
|
|
||||||
: fileTypeId === "2"
|
|
||||||
? "video"
|
|
||||||
: fileTypeId === "3"
|
|
||||||
? "text"
|
|
||||||
: "audio"
|
|
||||||
}/update-new/${selectedArticleId}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
|
|
@ -951,7 +942,7 @@ export default function FormAudio() {
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,757 @@
|
||||||
|
"use client";
|
||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
Fragment,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as z from "zod";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import {
|
||||||
|
createMedia,
|
||||||
|
deleteFile,
|
||||||
|
deleteMedia,
|
||||||
|
getTagsBySubCategoryId,
|
||||||
|
listEnableCategory,
|
||||||
|
uploadThumbnail,
|
||||||
|
} from "@/service/content/content";
|
||||||
|
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { CloudUpload, MailIcon, PieChart, XIcon } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import ViewEditor from "@/components/editor/view-editor";
|
||||||
|
import { getDetailArticle, getSeoScore } from "@/service/content/ai";
|
||||||
|
import { Gauge } from "@mui/x-charts/Gauge";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { Pie } from "react-chartjs-2";
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
ArcElement,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ChartOptions,
|
||||||
|
} from "chart.js";
|
||||||
|
|
||||||
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
const imageSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||||
|
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||||
|
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Category = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Detail = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
slug: string;
|
||||||
|
category: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
publishedFor: string;
|
||||||
|
|
||||||
|
publishedForObject: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
htmlDescription: string;
|
||||||
|
creatorName: string;
|
||||||
|
categoryName: string;
|
||||||
|
thumbnailLink: string;
|
||||||
|
tags: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomEditor = dynamic(
|
||||||
|
() => {
|
||||||
|
return import("@/components/editor/custom-editor");
|
||||||
|
},
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface FileWithPreview extends File {
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FormAudioSeo() {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { id } = useParams() as { id: string };
|
||||||
|
console.log(id);
|
||||||
|
const editor = useRef(null);
|
||||||
|
type ImageSchema = z.infer<typeof imageSchema>;
|
||||||
|
|
||||||
|
let progressInfo: any = [];
|
||||||
|
let counterUpdateProgress = 0;
|
||||||
|
const [progressList, setProgressList] = useState<any>([]);
|
||||||
|
let uploadPersen = 0;
|
||||||
|
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||||
|
const [counterProgress, setCounterProgress] = useState(0);
|
||||||
|
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
|
const taskId = Cookies.get("taskId");
|
||||||
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
|
const scheduleType = Cookies.get("scheduleType");
|
||||||
|
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||||
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
|
const [detail, setDetail] = useState<Detail>();
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||||
|
const [articleBody, setArticleBody] = useState<string>("");
|
||||||
|
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||||
|
const [filesTemp, setFilesTemp] = useState<File[]>([]);
|
||||||
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
|
[fileId: number]: string;
|
||||||
|
}>({});
|
||||||
|
const [articleData, setArticleData] = useState({
|
||||||
|
title: "",
|
||||||
|
mainKeyword: "",
|
||||||
|
additionalKeywords: "",
|
||||||
|
metaTitle: "",
|
||||||
|
metaDescription: "",
|
||||||
|
});
|
||||||
|
const [totalScoreSEO, setTotalScoreSEO] = useState<number>(0);
|
||||||
|
const [errorSEO, setErrorSEO] = useState<string[]>([]);
|
||||||
|
const [warningSEO, setWarningSEO] = useState<string[]>([]);
|
||||||
|
const [optimizedSEO, setOptimizedSEO] = useState<string[]>([]);
|
||||||
|
// const [errorData, setErrorData] = useState<string[]>([]);
|
||||||
|
const [warningData, setWarningData] = useState<string[]>([]);
|
||||||
|
const [optimizedData, setOptimizedData] = useState<string[]>([]);
|
||||||
|
const [errorsData, setErrorData] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
|
||||||
|
detail?.category.id
|
||||||
|
);
|
||||||
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
|
allUnit: false,
|
||||||
|
mabes: false,
|
||||||
|
polda: false,
|
||||||
|
polres: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let fileTypeId = "1";
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
onDrop: (acceptedFiles) => {
|
||||||
|
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<ImageSchema>({
|
||||||
|
resolver: zodResolver(imageSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
// const handleKeyDown = (e: any) => {
|
||||||
|
// const newTag = e.target.value.trim(); // Ambil nilai input
|
||||||
|
// if (e.key === "Enter" && newTag) {
|
||||||
|
// e.preventDefault(); // Hentikan submit form
|
||||||
|
// if (!tags.includes(newTag)) {
|
||||||
|
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
|
||||||
|
// setValue("tags", ""); // Kosongkan input
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files) {
|
||||||
|
const files = Array.from(event.target.files);
|
||||||
|
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
|
||||||
|
console.log("DATAFILE::", selectedFiles);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveImage = (index: number) => {
|
||||||
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
// const handleCheckboxChange = (id: number) => {
|
||||||
|
// setSelectedPublishers((prev) =>
|
||||||
|
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function initState() {
|
||||||
|
getCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
initState();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getCategories = async () => {
|
||||||
|
try {
|
||||||
|
const category = await listEnableCategory(fileTypeId);
|
||||||
|
const resCategory: Category[] = category?.data?.data?.content;
|
||||||
|
|
||||||
|
setCategories(resCategory);
|
||||||
|
console.log("data category", resCategory);
|
||||||
|
|
||||||
|
if (scheduleId && scheduleType === "3") {
|
||||||
|
const findCategory = resCategory.find((o) =>
|
||||||
|
o.name.toLowerCase().includes("pers rilis")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (findCategory) {
|
||||||
|
// setValue("categoryId", findCategory.id);
|
||||||
|
setSelectedCategory(findCategory.id); // Set the selected category
|
||||||
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
|
setTags(response?.data?.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch categories:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchArticleData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getDetailArticle(id);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const cleanArticleBody = data.articleBody.replace(/<img[^>]*>/g, "");
|
||||||
|
|
||||||
|
setArticleData({
|
||||||
|
title: data.title || "",
|
||||||
|
mainKeyword: data.mainKeyword || "",
|
||||||
|
additionalKeywords: data.additionalKeywords || "",
|
||||||
|
metaTitle: data.metaTitle || "",
|
||||||
|
metaDescription: data.metaDescription || "",
|
||||||
|
});
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
|
||||||
|
// reset({
|
||||||
|
// title: data.title,
|
||||||
|
// mainKeyword: data.mainKeyword,
|
||||||
|
// additionalKeywords: data.additionalKeywords,
|
||||||
|
// metaTitle: data.metaTitle,
|
||||||
|
// metaDescription: data.metaDescription,
|
||||||
|
// });
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch article data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
fetchArticleData();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSeoScore = async () => {
|
||||||
|
const res = await getSeoScore(id);
|
||||||
|
if (res.error) {
|
||||||
|
error(res.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0);
|
||||||
|
let errorList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.error,
|
||||||
|
];
|
||||||
|
setErrorSEO(errorList);
|
||||||
|
let warningList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.warning,
|
||||||
|
];
|
||||||
|
setWarningSEO(warningList);
|
||||||
|
let optimizedList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization
|
||||||
|
?.optimized,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized,
|
||||||
|
];
|
||||||
|
setOptimizedSEO(optimizedList);
|
||||||
|
setErrorData(errorList);
|
||||||
|
setWarningData(warningList);
|
||||||
|
setOptimizedData(optimizedList);
|
||||||
|
};
|
||||||
|
fetchSeoScore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: ["SEO Score (" + totalScoreSEO + "%)"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [totalScoreSEO],
|
||||||
|
backgroundColor: ["#4CAF50"],
|
||||||
|
hoverBackgroundColor: ["#388E3C"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: ChartOptions<"pie"> = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "bottom", // TypeScript now correctly recognizes this as a valid option
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (data: ImageSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
|
const requestData = {
|
||||||
|
...data,
|
||||||
|
id: detail?.id,
|
||||||
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
|
htmlDescription: data.description,
|
||||||
|
fileTypeId,
|
||||||
|
categoryId: selectedTarget,
|
||||||
|
subCategoryId: selectedTarget,
|
||||||
|
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||||
|
statusId: "1",
|
||||||
|
publishedFor: publishedFor.join(","),
|
||||||
|
creatorName: data.creatorName,
|
||||||
|
tags: finalTags,
|
||||||
|
isYoutube: false,
|
||||||
|
isInternationalMedia: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await createMedia(requestData);
|
||||||
|
console.log("Form Data Submitted:", requestData);
|
||||||
|
|
||||||
|
const formMedia = new FormData();
|
||||||
|
const thumbnail = files[0];
|
||||||
|
formMedia.append("file", thumbnail);
|
||||||
|
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||||
|
if (responseThumbnail?.error == true) {
|
||||||
|
error(responseThumbnail?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressInfoArr = [];
|
||||||
|
for (const item of files) {
|
||||||
|
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||||
|
}
|
||||||
|
progressInfo = progressInfoArr;
|
||||||
|
setIsStartUpload(true);
|
||||||
|
setProgressList(progressInfoArr);
|
||||||
|
|
||||||
|
close();
|
||||||
|
// showProgress();
|
||||||
|
files.map(async (item: any, index: number) => {
|
||||||
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item,
|
||||||
|
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/en/contributor/content/image");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function uploadResumableFile(
|
||||||
|
idx: number,
|
||||||
|
id: string,
|
||||||
|
file: any,
|
||||||
|
duration: string
|
||||||
|
) {
|
||||||
|
console.log(idx, id, file, duration);
|
||||||
|
|
||||||
|
// const placements = getPlacement(file.placements);
|
||||||
|
// console.log("Placementttt: : ", placements);
|
||||||
|
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
|
const headers = {
|
||||||
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = new Upload(file, {
|
||||||
|
endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
|
||||||
|
headers: headers,
|
||||||
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
|
chunkSize: 20_000,
|
||||||
|
metadata: {
|
||||||
|
mediaid: id,
|
||||||
|
filename: file.name,
|
||||||
|
filetype: file.type,
|
||||||
|
duration,
|
||||||
|
isWatermark: "true", // hardcode
|
||||||
|
},
|
||||||
|
onBeforeRequest: function (req) {
|
||||||
|
var xhr = req.getUnderlyingObject();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
},
|
||||||
|
onError: async (e: any) => {
|
||||||
|
console.log("Error upload :", e);
|
||||||
|
error(e);
|
||||||
|
},
|
||||||
|
onChunkComplete: (
|
||||||
|
chunkSize: any,
|
||||||
|
bytesAccepted: any,
|
||||||
|
bytesTotal: any
|
||||||
|
) => {
|
||||||
|
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||||
|
progressInfo[idx].percentage = uploadPersen;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
console.log(counterUpdateProgress);
|
||||||
|
setProgressList(progressInfo);
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
uploadPersen = 100;
|
||||||
|
progressInfo[idx].percentage = 100;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
successTodo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
upload.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = (data: ImageSchema) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data",
|
||||||
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const successSubmit = (redirect: string) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push(redirect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function successTodo() {
|
||||||
|
let counter = 0;
|
||||||
|
for (const element of progressInfo) {
|
||||||
|
if (element.percentage == 100) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (counter == progressInfo.length) {
|
||||||
|
setIsStartUpload(false);
|
||||||
|
// hideProgress();
|
||||||
|
Cookies.remove("idCreate");
|
||||||
|
successSubmit("/in/contributor/content/image/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemoveFile = (file: FileWithPreview) => {
|
||||||
|
const uploadedFiles = files;
|
||||||
|
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||||
|
setFiles([...filtered]);
|
||||||
|
};
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteFile = (id: number) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus file",
|
||||||
|
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doDelete(id: number) {
|
||||||
|
const data = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await deleteFile(data);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika berhasil, hapus file dari state lokal
|
||||||
|
setFiles((prevFiles: any) =>
|
||||||
|
prevFiles.filter((file: any) => file.id !== id)
|
||||||
|
);
|
||||||
|
success();
|
||||||
|
} catch (err) {
|
||||||
|
error("Terjadi kesalahan saat menghapus file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mx-5 mb-3">
|
||||||
|
<Card>
|
||||||
|
<Tabs defaultValue="content" className="">
|
||||||
|
<TabsList className="grid w-[300px] grid-cols-2 bg-slate-400 my-3 mx-3 ">
|
||||||
|
<TabsTrigger
|
||||||
|
value="content"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Konten
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="checker"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Checker
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="content">
|
||||||
|
{articleData !== undefined ? (
|
||||||
|
<CardContent className="space-y-2 my-3">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="name">Judul</Label>
|
||||||
|
<Input id="name" defaultValue={articleData?.title} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Main Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="mainKeyword"
|
||||||
|
value={articleData?.mainKeyword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Additional Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="additionalKeywords"
|
||||||
|
value={articleData?.additionalKeywords}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Title</Label>
|
||||||
|
<Textarea id="metaTitle" value={articleData?.metaTitle} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="metaDescription"
|
||||||
|
value={articleData?.metaDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="checker">
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<Pie
|
||||||
|
data={data}
|
||||||
|
options={options}
|
||||||
|
className="text-left flex items-start justify-start"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Accordion type="single" collapsible className="w-full ">
|
||||||
|
<AccordionItem
|
||||||
|
value="error"
|
||||||
|
className="border border-red-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-red-600" />
|
||||||
|
Errors ({errorSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{errorSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{errorSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No errors found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="warning"
|
||||||
|
className="border border-yellow-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-yellow-600" />
|
||||||
|
Warnings ({warningSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{warningSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{warningSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No warnings found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="optimized"
|
||||||
|
className="border border-green-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-green-600" />
|
||||||
|
Optimized ({optimizedSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{optimizedSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{optimizedSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No optimizations found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<div className="flex flex-row justify-between items-center mb-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Button size="md" className="bg-blue-500">
|
||||||
|
Select Image From Content Bank
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -55,6 +55,7 @@ import { data } from "jquery";
|
||||||
import { options } from "@fullcalendar/core/preact.js";
|
import { options } from "@fullcalendar/core/preact.js";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
interface FileWithPreview extends File {
|
interface FileWithPreview extends File {
|
||||||
preview: string;
|
preview: string;
|
||||||
|
|
@ -930,7 +931,7 @@ export default function FormImage() {
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleArticleIdClick(id)}
|
onClick={() => handleArticleIdClick(id)}
|
||||||
>
|
>
|
||||||
{id}
|
{"Narasi " + (index + 1)}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -939,18 +940,8 @@ export default function FormImage() {
|
||||||
<div className="pt-3">
|
<div className="pt-3">
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
{selectedArticleId && (
|
{selectedArticleId && (
|
||||||
<a
|
<Link
|
||||||
href={`/admin/media/${
|
href={`/contributor/content/image/update-seo/${selectedArticleId}`}
|
||||||
fileTypeId === "1"
|
|
||||||
? "image"
|
|
||||||
: fileTypeId === "2"
|
|
||||||
? "video"
|
|
||||||
: fileTypeId === "3"
|
|
||||||
? "text"
|
|
||||||
: "audio"
|
|
||||||
}/update-new/${selectedArticleId}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
|
|
@ -960,7 +951,7 @@ export default function FormImage() {
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,757 @@
|
||||||
|
"use client";
|
||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
Fragment,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as z from "zod";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import {
|
||||||
|
createMedia,
|
||||||
|
deleteFile,
|
||||||
|
deleteMedia,
|
||||||
|
getTagsBySubCategoryId,
|
||||||
|
listEnableCategory,
|
||||||
|
uploadThumbnail,
|
||||||
|
} from "@/service/content/content";
|
||||||
|
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { CloudUpload, MailIcon, PieChart, XIcon } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import ViewEditor from "@/components/editor/view-editor";
|
||||||
|
import { getDetailArticle, getSeoScore } from "@/service/content/ai";
|
||||||
|
import { Gauge } from "@mui/x-charts/Gauge";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { Pie } from "react-chartjs-2";
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
ArcElement,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ChartOptions,
|
||||||
|
} from "chart.js";
|
||||||
|
|
||||||
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
const imageSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||||
|
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||||
|
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Category = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Detail = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
slug: string;
|
||||||
|
category: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
publishedFor: string;
|
||||||
|
|
||||||
|
publishedForObject: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
htmlDescription: string;
|
||||||
|
creatorName: string;
|
||||||
|
categoryName: string;
|
||||||
|
thumbnailLink: string;
|
||||||
|
tags: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomEditor = dynamic(
|
||||||
|
() => {
|
||||||
|
return import("@/components/editor/custom-editor");
|
||||||
|
},
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface FileWithPreview extends File {
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FormImageSeo() {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { id } = useParams() as { id: string };
|
||||||
|
console.log(id);
|
||||||
|
const editor = useRef(null);
|
||||||
|
type ImageSchema = z.infer<typeof imageSchema>;
|
||||||
|
|
||||||
|
let progressInfo: any = [];
|
||||||
|
let counterUpdateProgress = 0;
|
||||||
|
const [progressList, setProgressList] = useState<any>([]);
|
||||||
|
let uploadPersen = 0;
|
||||||
|
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||||
|
const [counterProgress, setCounterProgress] = useState(0);
|
||||||
|
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
|
const taskId = Cookies.get("taskId");
|
||||||
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
|
const scheduleType = Cookies.get("scheduleType");
|
||||||
|
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||||
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
|
const [detail, setDetail] = useState<Detail>();
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||||
|
const [articleBody, setArticleBody] = useState<string>("");
|
||||||
|
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||||
|
const [filesTemp, setFilesTemp] = useState<File[]>([]);
|
||||||
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
|
[fileId: number]: string;
|
||||||
|
}>({});
|
||||||
|
const [articleData, setArticleData] = useState({
|
||||||
|
title: "",
|
||||||
|
mainKeyword: "",
|
||||||
|
additionalKeywords: "",
|
||||||
|
metaTitle: "",
|
||||||
|
metaDescription: "",
|
||||||
|
});
|
||||||
|
const [totalScoreSEO, setTotalScoreSEO] = useState<number>(0);
|
||||||
|
const [errorSEO, setErrorSEO] = useState<string[]>([]);
|
||||||
|
const [warningSEO, setWarningSEO] = useState<string[]>([]);
|
||||||
|
const [optimizedSEO, setOptimizedSEO] = useState<string[]>([]);
|
||||||
|
// const [errorData, setErrorData] = useState<string[]>([]);
|
||||||
|
const [warningData, setWarningData] = useState<string[]>([]);
|
||||||
|
const [optimizedData, setOptimizedData] = useState<string[]>([]);
|
||||||
|
const [errorsData, setErrorData] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
|
||||||
|
detail?.category.id
|
||||||
|
);
|
||||||
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
|
allUnit: false,
|
||||||
|
mabes: false,
|
||||||
|
polda: false,
|
||||||
|
polres: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let fileTypeId = "1";
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
onDrop: (acceptedFiles) => {
|
||||||
|
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<ImageSchema>({
|
||||||
|
resolver: zodResolver(imageSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
// const handleKeyDown = (e: any) => {
|
||||||
|
// const newTag = e.target.value.trim(); // Ambil nilai input
|
||||||
|
// if (e.key === "Enter" && newTag) {
|
||||||
|
// e.preventDefault(); // Hentikan submit form
|
||||||
|
// if (!tags.includes(newTag)) {
|
||||||
|
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
|
||||||
|
// setValue("tags", ""); // Kosongkan input
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files) {
|
||||||
|
const files = Array.from(event.target.files);
|
||||||
|
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
|
||||||
|
console.log("DATAFILE::", selectedFiles);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveImage = (index: number) => {
|
||||||
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
// const handleCheckboxChange = (id: number) => {
|
||||||
|
// setSelectedPublishers((prev) =>
|
||||||
|
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function initState() {
|
||||||
|
getCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
initState();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getCategories = async () => {
|
||||||
|
try {
|
||||||
|
const category = await listEnableCategory(fileTypeId);
|
||||||
|
const resCategory: Category[] = category?.data?.data?.content;
|
||||||
|
|
||||||
|
setCategories(resCategory);
|
||||||
|
console.log("data category", resCategory);
|
||||||
|
|
||||||
|
if (scheduleId && scheduleType === "3") {
|
||||||
|
const findCategory = resCategory.find((o) =>
|
||||||
|
o.name.toLowerCase().includes("pers rilis")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (findCategory) {
|
||||||
|
// setValue("categoryId", findCategory.id);
|
||||||
|
setSelectedCategory(findCategory.id); // Set the selected category
|
||||||
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
|
setTags(response?.data?.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch categories:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchArticleData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getDetailArticle(id);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const cleanArticleBody = data.articleBody.replace(/<img[^>]*>/g, "");
|
||||||
|
|
||||||
|
setArticleData({
|
||||||
|
title: data.title || "",
|
||||||
|
mainKeyword: data.mainKeyword || "",
|
||||||
|
additionalKeywords: data.additionalKeywords || "",
|
||||||
|
metaTitle: data.metaTitle || "",
|
||||||
|
metaDescription: data.metaDescription || "",
|
||||||
|
});
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
|
||||||
|
// reset({
|
||||||
|
// title: data.title,
|
||||||
|
// mainKeyword: data.mainKeyword,
|
||||||
|
// additionalKeywords: data.additionalKeywords,
|
||||||
|
// metaTitle: data.metaTitle,
|
||||||
|
// metaDescription: data.metaDescription,
|
||||||
|
// });
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch article data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
fetchArticleData();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSeoScore = async () => {
|
||||||
|
const res = await getSeoScore(id);
|
||||||
|
if (res.error) {
|
||||||
|
error(res.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0);
|
||||||
|
let errorList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.error,
|
||||||
|
];
|
||||||
|
setErrorSEO(errorList);
|
||||||
|
let warningList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.warning,
|
||||||
|
];
|
||||||
|
setWarningSEO(warningList);
|
||||||
|
let optimizedList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization
|
||||||
|
?.optimized,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized,
|
||||||
|
];
|
||||||
|
setOptimizedSEO(optimizedList);
|
||||||
|
setErrorData(errorList);
|
||||||
|
setWarningData(warningList);
|
||||||
|
setOptimizedData(optimizedList);
|
||||||
|
};
|
||||||
|
fetchSeoScore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: ["SEO Score (" + totalScoreSEO + "%)"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [totalScoreSEO],
|
||||||
|
backgroundColor: ["#4CAF50"],
|
||||||
|
hoverBackgroundColor: ["#388E3C"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: ChartOptions<"pie"> = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "bottom", // TypeScript now correctly recognizes this as a valid option
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (data: ImageSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
|
const requestData = {
|
||||||
|
...data,
|
||||||
|
id: detail?.id,
|
||||||
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
|
htmlDescription: data.description,
|
||||||
|
fileTypeId,
|
||||||
|
categoryId: selectedTarget,
|
||||||
|
subCategoryId: selectedTarget,
|
||||||
|
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||||
|
statusId: "1",
|
||||||
|
publishedFor: publishedFor.join(","),
|
||||||
|
creatorName: data.creatorName,
|
||||||
|
tags: finalTags,
|
||||||
|
isYoutube: false,
|
||||||
|
isInternationalMedia: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await createMedia(requestData);
|
||||||
|
console.log("Form Data Submitted:", requestData);
|
||||||
|
|
||||||
|
const formMedia = new FormData();
|
||||||
|
const thumbnail = files[0];
|
||||||
|
formMedia.append("file", thumbnail);
|
||||||
|
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||||
|
if (responseThumbnail?.error == true) {
|
||||||
|
error(responseThumbnail?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressInfoArr = [];
|
||||||
|
for (const item of files) {
|
||||||
|
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||||
|
}
|
||||||
|
progressInfo = progressInfoArr;
|
||||||
|
setIsStartUpload(true);
|
||||||
|
setProgressList(progressInfoArr);
|
||||||
|
|
||||||
|
close();
|
||||||
|
// showProgress();
|
||||||
|
files.map(async (item: any, index: number) => {
|
||||||
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item,
|
||||||
|
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/en/contributor/content/image");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function uploadResumableFile(
|
||||||
|
idx: number,
|
||||||
|
id: string,
|
||||||
|
file: any,
|
||||||
|
duration: string
|
||||||
|
) {
|
||||||
|
console.log(idx, id, file, duration);
|
||||||
|
|
||||||
|
// const placements = getPlacement(file.placements);
|
||||||
|
// console.log("Placementttt: : ", placements);
|
||||||
|
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
|
const headers = {
|
||||||
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = new Upload(file, {
|
||||||
|
endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
|
||||||
|
headers: headers,
|
||||||
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
|
chunkSize: 20_000,
|
||||||
|
metadata: {
|
||||||
|
mediaid: id,
|
||||||
|
filename: file.name,
|
||||||
|
filetype: file.type,
|
||||||
|
duration,
|
||||||
|
isWatermark: "true", // hardcode
|
||||||
|
},
|
||||||
|
onBeforeRequest: function (req) {
|
||||||
|
var xhr = req.getUnderlyingObject();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
},
|
||||||
|
onError: async (e: any) => {
|
||||||
|
console.log("Error upload :", e);
|
||||||
|
error(e);
|
||||||
|
},
|
||||||
|
onChunkComplete: (
|
||||||
|
chunkSize: any,
|
||||||
|
bytesAccepted: any,
|
||||||
|
bytesTotal: any
|
||||||
|
) => {
|
||||||
|
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||||
|
progressInfo[idx].percentage = uploadPersen;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
console.log(counterUpdateProgress);
|
||||||
|
setProgressList(progressInfo);
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
uploadPersen = 100;
|
||||||
|
progressInfo[idx].percentage = 100;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
successTodo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
upload.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = (data: ImageSchema) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data",
|
||||||
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const successSubmit = (redirect: string) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push(redirect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function successTodo() {
|
||||||
|
let counter = 0;
|
||||||
|
for (const element of progressInfo) {
|
||||||
|
if (element.percentage == 100) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (counter == progressInfo.length) {
|
||||||
|
setIsStartUpload(false);
|
||||||
|
// hideProgress();
|
||||||
|
Cookies.remove("idCreate");
|
||||||
|
successSubmit("/in/contributor/content/image/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemoveFile = (file: FileWithPreview) => {
|
||||||
|
const uploadedFiles = files;
|
||||||
|
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||||
|
setFiles([...filtered]);
|
||||||
|
};
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteFile = (id: number) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus file",
|
||||||
|
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doDelete(id: number) {
|
||||||
|
const data = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await deleteFile(data);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika berhasil, hapus file dari state lokal
|
||||||
|
setFiles((prevFiles: any) =>
|
||||||
|
prevFiles.filter((file: any) => file.id !== id)
|
||||||
|
);
|
||||||
|
success();
|
||||||
|
} catch (err) {
|
||||||
|
error("Terjadi kesalahan saat menghapus file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mx-5 mb-3">
|
||||||
|
<Card>
|
||||||
|
<Tabs defaultValue="content" className="">
|
||||||
|
<TabsList className="grid w-[300px] grid-cols-2 bg-slate-400 my-3 mx-3 ">
|
||||||
|
<TabsTrigger
|
||||||
|
value="content"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Konten
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="checker"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Checker
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="content">
|
||||||
|
{articleData !== undefined ? (
|
||||||
|
<CardContent className="space-y-2 my-3">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="name">Judul</Label>
|
||||||
|
<Input id="name" defaultValue={articleData?.title} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Main Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="mainKeyword"
|
||||||
|
value={articleData?.mainKeyword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Additional Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="additionalKeywords"
|
||||||
|
value={articleData?.additionalKeywords}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Title</Label>
|
||||||
|
<Textarea id="metaTitle" value={articleData?.metaTitle} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="metaDescription"
|
||||||
|
value={articleData?.metaDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="checker">
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<Pie
|
||||||
|
data={data}
|
||||||
|
options={options}
|
||||||
|
className="text-left flex items-start justify-start"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Accordion type="single" collapsible className="w-full ">
|
||||||
|
<AccordionItem
|
||||||
|
value="error"
|
||||||
|
className="border border-red-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-red-600" />
|
||||||
|
Errors ({errorSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{errorSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{errorSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No errors found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="warning"
|
||||||
|
className="border border-yellow-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-yellow-600" />
|
||||||
|
Warnings ({warningSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{warningSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{warningSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No warnings found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="optimized"
|
||||||
|
className="border border-green-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-green-600" />
|
||||||
|
Optimized ({optimizedSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{optimizedSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{optimizedSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No optimizations found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<div className="flex flex-row justify-between items-center mb-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Button size="md" className="bg-blue-500">
|
||||||
|
Select Image From Content Bank
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
convertSPIT,
|
convertSPIT,
|
||||||
createMedia,
|
createMedia,
|
||||||
|
deleteSPIT,
|
||||||
detailSPIT,
|
detailSPIT,
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listCategory,
|
listCategory,
|
||||||
|
|
@ -47,6 +48,7 @@ import { request } from "http";
|
||||||
import { generateDataArticle, getDetailArticle } from "@/service/content/ai";
|
import { generateDataArticle, getDetailArticle } from "@/service/content/ai";
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import { error } from "@/lib/swal";
|
||||||
|
|
||||||
const imageSchema = z.object({
|
const imageSchema = z.object({
|
||||||
contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
|
contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -538,6 +540,49 @@ export default function FormConvertSPIT() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function deleteSpitContent() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Apakah anda ingin menghapus konten?",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#dc3545",
|
||||||
|
confirmButtonText: "Iya",
|
||||||
|
cancelButtonText: "Tidak",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDeleteSPIT();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doDeleteSPIT() {
|
||||||
|
const response = await deleteSPIT(id);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
successBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
function successBack() {
|
||||||
|
MySwal?.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
if (window.history.state && window.history.state.idx > 0) {
|
||||||
|
console.log("backkkkk");
|
||||||
|
console.log(window.history.state);
|
||||||
|
router.back();
|
||||||
|
} else {
|
||||||
|
router.push("/in/contributor/content/spit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
|
|
@ -653,7 +698,7 @@ export default function FormConvertSPIT() {
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleArticleIdClick(id)}
|
onClick={() => handleArticleIdClick(id)}
|
||||||
>
|
>
|
||||||
{id}
|
{"Narasi " + (index + 1)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -737,6 +782,9 @@ export default function FormConvertSPIT() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-3">
|
||||||
|
<Label className="text-xl">Penempatan file</Label>
|
||||||
|
</div>
|
||||||
{files?.map((file, index) => (
|
{files?.map((file, index) => (
|
||||||
<div
|
<div
|
||||||
key={file.contentId}
|
key={file.contentId}
|
||||||
|
|
@ -919,8 +967,12 @@ export default function FormConvertSPIT() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Button type="submit" color="primary" variant="outline">
|
<Button
|
||||||
Cancel
|
type="submit"
|
||||||
|
className="bg-red-500 hover:bg-red-700"
|
||||||
|
onClick={() => deleteSpitContent()}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ import { error, loading } from "@/config/swal";
|
||||||
import { Item } from "@radix-ui/react-dropdown-menu";
|
import { Item } from "@radix-ui/react-dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
interface FileWithPreview extends File {
|
interface FileWithPreview extends File {
|
||||||
preview: string;
|
preview: string;
|
||||||
|
|
@ -919,7 +920,7 @@ export default function FormTeks() {
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleArticleIdClick(id)}
|
onClick={() => handleArticleIdClick(id)}
|
||||||
>
|
>
|
||||||
{id}
|
{"Narasi " + (index + 1)}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -928,18 +929,8 @@ export default function FormTeks() {
|
||||||
<div className="pt-3">
|
<div className="pt-3">
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
{selectedArticleId && (
|
{selectedArticleId && (
|
||||||
<a
|
<Link
|
||||||
href={`/admin/media/${
|
href={`/contributor/content/teks/update-seo/${selectedArticleId}`}
|
||||||
fileTypeId === "1"
|
|
||||||
? "image"
|
|
||||||
: fileTypeId === "2"
|
|
||||||
? "video"
|
|
||||||
: fileTypeId === "3"
|
|
||||||
? "text"
|
|
||||||
: "audio"
|
|
||||||
}/update-new/${selectedArticleId}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
|
|
@ -949,7 +940,7 @@ export default function FormTeks() {
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,757 @@
|
||||||
|
"use client";
|
||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
Fragment,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as z from "zod";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import {
|
||||||
|
createMedia,
|
||||||
|
deleteFile,
|
||||||
|
deleteMedia,
|
||||||
|
getTagsBySubCategoryId,
|
||||||
|
listEnableCategory,
|
||||||
|
uploadThumbnail,
|
||||||
|
} from "@/service/content/content";
|
||||||
|
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { CloudUpload, MailIcon, PieChart, XIcon } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import ViewEditor from "@/components/editor/view-editor";
|
||||||
|
import { getDetailArticle, getSeoScore } from "@/service/content/ai";
|
||||||
|
import { Gauge } from "@mui/x-charts/Gauge";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { Pie } from "react-chartjs-2";
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
ArcElement,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ChartOptions,
|
||||||
|
} from "chart.js";
|
||||||
|
|
||||||
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
const imageSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||||
|
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||||
|
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Category = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Detail = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
slug: string;
|
||||||
|
category: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
publishedFor: string;
|
||||||
|
|
||||||
|
publishedForObject: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
htmlDescription: string;
|
||||||
|
creatorName: string;
|
||||||
|
categoryName: string;
|
||||||
|
thumbnailLink: string;
|
||||||
|
tags: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomEditor = dynamic(
|
||||||
|
() => {
|
||||||
|
return import("@/components/editor/custom-editor");
|
||||||
|
},
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface FileWithPreview extends File {
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FormTeksSeo() {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { id } = useParams() as { id: string };
|
||||||
|
console.log(id);
|
||||||
|
const editor = useRef(null);
|
||||||
|
type ImageSchema = z.infer<typeof imageSchema>;
|
||||||
|
|
||||||
|
let progressInfo: any = [];
|
||||||
|
let counterUpdateProgress = 0;
|
||||||
|
const [progressList, setProgressList] = useState<any>([]);
|
||||||
|
let uploadPersen = 0;
|
||||||
|
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||||
|
const [counterProgress, setCounterProgress] = useState(0);
|
||||||
|
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
|
const taskId = Cookies.get("taskId");
|
||||||
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
|
const scheduleType = Cookies.get("scheduleType");
|
||||||
|
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||||
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
|
const [detail, setDetail] = useState<Detail>();
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||||
|
const [articleBody, setArticleBody] = useState<string>("");
|
||||||
|
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||||
|
const [filesTemp, setFilesTemp] = useState<File[]>([]);
|
||||||
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
|
[fileId: number]: string;
|
||||||
|
}>({});
|
||||||
|
const [articleData, setArticleData] = useState({
|
||||||
|
title: "",
|
||||||
|
mainKeyword: "",
|
||||||
|
additionalKeywords: "",
|
||||||
|
metaTitle: "",
|
||||||
|
metaDescription: "",
|
||||||
|
});
|
||||||
|
const [totalScoreSEO, setTotalScoreSEO] = useState<number>(0);
|
||||||
|
const [errorSEO, setErrorSEO] = useState<string[]>([]);
|
||||||
|
const [warningSEO, setWarningSEO] = useState<string[]>([]);
|
||||||
|
const [optimizedSEO, setOptimizedSEO] = useState<string[]>([]);
|
||||||
|
// const [errorData, setErrorData] = useState<string[]>([]);
|
||||||
|
const [warningData, setWarningData] = useState<string[]>([]);
|
||||||
|
const [optimizedData, setOptimizedData] = useState<string[]>([]);
|
||||||
|
const [errorsData, setErrorData] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
|
||||||
|
detail?.category.id
|
||||||
|
);
|
||||||
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
|
allUnit: false,
|
||||||
|
mabes: false,
|
||||||
|
polda: false,
|
||||||
|
polres: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let fileTypeId = "1";
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
onDrop: (acceptedFiles) => {
|
||||||
|
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<ImageSchema>({
|
||||||
|
resolver: zodResolver(imageSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
// const handleKeyDown = (e: any) => {
|
||||||
|
// const newTag = e.target.value.trim(); // Ambil nilai input
|
||||||
|
// if (e.key === "Enter" && newTag) {
|
||||||
|
// e.preventDefault(); // Hentikan submit form
|
||||||
|
// if (!tags.includes(newTag)) {
|
||||||
|
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
|
||||||
|
// setValue("tags", ""); // Kosongkan input
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files) {
|
||||||
|
const files = Array.from(event.target.files);
|
||||||
|
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
|
||||||
|
console.log("DATAFILE::", selectedFiles);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveImage = (index: number) => {
|
||||||
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
// const handleCheckboxChange = (id: number) => {
|
||||||
|
// setSelectedPublishers((prev) =>
|
||||||
|
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function initState() {
|
||||||
|
getCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
initState();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getCategories = async () => {
|
||||||
|
try {
|
||||||
|
const category = await listEnableCategory(fileTypeId);
|
||||||
|
const resCategory: Category[] = category?.data?.data?.content;
|
||||||
|
|
||||||
|
setCategories(resCategory);
|
||||||
|
console.log("data category", resCategory);
|
||||||
|
|
||||||
|
if (scheduleId && scheduleType === "3") {
|
||||||
|
const findCategory = resCategory.find((o) =>
|
||||||
|
o.name.toLowerCase().includes("pers rilis")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (findCategory) {
|
||||||
|
// setValue("categoryId", findCategory.id);
|
||||||
|
setSelectedCategory(findCategory.id); // Set the selected category
|
||||||
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
|
setTags(response?.data?.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch categories:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchArticleData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getDetailArticle(id);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const cleanArticleBody = data.articleBody.replace(/<img[^>]*>/g, "");
|
||||||
|
|
||||||
|
setArticleData({
|
||||||
|
title: data.title || "",
|
||||||
|
mainKeyword: data.mainKeyword || "",
|
||||||
|
additionalKeywords: data.additionalKeywords || "",
|
||||||
|
metaTitle: data.metaTitle || "",
|
||||||
|
metaDescription: data.metaDescription || "",
|
||||||
|
});
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
|
||||||
|
// reset({
|
||||||
|
// title: data.title,
|
||||||
|
// mainKeyword: data.mainKeyword,
|
||||||
|
// additionalKeywords: data.additionalKeywords,
|
||||||
|
// metaTitle: data.metaTitle,
|
||||||
|
// metaDescription: data.metaDescription,
|
||||||
|
// });
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch article data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
fetchArticleData();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSeoScore = async () => {
|
||||||
|
const res = await getSeoScore(id);
|
||||||
|
if (res.error) {
|
||||||
|
error(res.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0);
|
||||||
|
let errorList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.error,
|
||||||
|
];
|
||||||
|
setErrorSEO(errorList);
|
||||||
|
let warningList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.warning,
|
||||||
|
];
|
||||||
|
setWarningSEO(warningList);
|
||||||
|
let optimizedList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization
|
||||||
|
?.optimized,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized,
|
||||||
|
];
|
||||||
|
setOptimizedSEO(optimizedList);
|
||||||
|
setErrorData(errorList);
|
||||||
|
setWarningData(warningList);
|
||||||
|
setOptimizedData(optimizedList);
|
||||||
|
};
|
||||||
|
fetchSeoScore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: ["SEO Score (" + totalScoreSEO + "%)"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [totalScoreSEO],
|
||||||
|
backgroundColor: ["#4CAF50"],
|
||||||
|
hoverBackgroundColor: ["#388E3C"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: ChartOptions<"pie"> = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "bottom", // TypeScript now correctly recognizes this as a valid option
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (data: ImageSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
|
const requestData = {
|
||||||
|
...data,
|
||||||
|
id: detail?.id,
|
||||||
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
|
htmlDescription: data.description,
|
||||||
|
fileTypeId,
|
||||||
|
categoryId: selectedTarget,
|
||||||
|
subCategoryId: selectedTarget,
|
||||||
|
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||||
|
statusId: "1",
|
||||||
|
publishedFor: publishedFor.join(","),
|
||||||
|
creatorName: data.creatorName,
|
||||||
|
tags: finalTags,
|
||||||
|
isYoutube: false,
|
||||||
|
isInternationalMedia: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await createMedia(requestData);
|
||||||
|
console.log("Form Data Submitted:", requestData);
|
||||||
|
|
||||||
|
const formMedia = new FormData();
|
||||||
|
const thumbnail = files[0];
|
||||||
|
formMedia.append("file", thumbnail);
|
||||||
|
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||||
|
if (responseThumbnail?.error == true) {
|
||||||
|
error(responseThumbnail?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressInfoArr = [];
|
||||||
|
for (const item of files) {
|
||||||
|
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||||
|
}
|
||||||
|
progressInfo = progressInfoArr;
|
||||||
|
setIsStartUpload(true);
|
||||||
|
setProgressList(progressInfoArr);
|
||||||
|
|
||||||
|
close();
|
||||||
|
// showProgress();
|
||||||
|
files.map(async (item: any, index: number) => {
|
||||||
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item,
|
||||||
|
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/en/contributor/content/image");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function uploadResumableFile(
|
||||||
|
idx: number,
|
||||||
|
id: string,
|
||||||
|
file: any,
|
||||||
|
duration: string
|
||||||
|
) {
|
||||||
|
console.log(idx, id, file, duration);
|
||||||
|
|
||||||
|
// const placements = getPlacement(file.placements);
|
||||||
|
// console.log("Placementttt: : ", placements);
|
||||||
|
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
|
const headers = {
|
||||||
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = new Upload(file, {
|
||||||
|
endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
|
||||||
|
headers: headers,
|
||||||
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
|
chunkSize: 20_000,
|
||||||
|
metadata: {
|
||||||
|
mediaid: id,
|
||||||
|
filename: file.name,
|
||||||
|
filetype: file.type,
|
||||||
|
duration,
|
||||||
|
isWatermark: "true", // hardcode
|
||||||
|
},
|
||||||
|
onBeforeRequest: function (req) {
|
||||||
|
var xhr = req.getUnderlyingObject();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
},
|
||||||
|
onError: async (e: any) => {
|
||||||
|
console.log("Error upload :", e);
|
||||||
|
error(e);
|
||||||
|
},
|
||||||
|
onChunkComplete: (
|
||||||
|
chunkSize: any,
|
||||||
|
bytesAccepted: any,
|
||||||
|
bytesTotal: any
|
||||||
|
) => {
|
||||||
|
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||||
|
progressInfo[idx].percentage = uploadPersen;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
console.log(counterUpdateProgress);
|
||||||
|
setProgressList(progressInfo);
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
uploadPersen = 100;
|
||||||
|
progressInfo[idx].percentage = 100;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
successTodo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
upload.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = (data: ImageSchema) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data",
|
||||||
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const successSubmit = (redirect: string) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push(redirect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function successTodo() {
|
||||||
|
let counter = 0;
|
||||||
|
for (const element of progressInfo) {
|
||||||
|
if (element.percentage == 100) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (counter == progressInfo.length) {
|
||||||
|
setIsStartUpload(false);
|
||||||
|
// hideProgress();
|
||||||
|
Cookies.remove("idCreate");
|
||||||
|
successSubmit("/in/contributor/content/image/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemoveFile = (file: FileWithPreview) => {
|
||||||
|
const uploadedFiles = files;
|
||||||
|
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||||
|
setFiles([...filtered]);
|
||||||
|
};
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteFile = (id: number) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus file",
|
||||||
|
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doDelete(id: number) {
|
||||||
|
const data = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await deleteFile(data);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika berhasil, hapus file dari state lokal
|
||||||
|
setFiles((prevFiles: any) =>
|
||||||
|
prevFiles.filter((file: any) => file.id !== id)
|
||||||
|
);
|
||||||
|
success();
|
||||||
|
} catch (err) {
|
||||||
|
error("Terjadi kesalahan saat menghapus file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mx-5 mb-3">
|
||||||
|
<Card>
|
||||||
|
<Tabs defaultValue="content" className="">
|
||||||
|
<TabsList className="grid w-[300px] grid-cols-2 bg-slate-400 my-3 mx-3 ">
|
||||||
|
<TabsTrigger
|
||||||
|
value="content"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Konten
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="checker"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Checker
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="content">
|
||||||
|
{articleData !== undefined ? (
|
||||||
|
<CardContent className="space-y-2 my-3">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="name">Judul</Label>
|
||||||
|
<Input id="name" defaultValue={articleData?.title} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Main Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="mainKeyword"
|
||||||
|
value={articleData?.mainKeyword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Additional Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="additionalKeywords"
|
||||||
|
value={articleData?.additionalKeywords}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Title</Label>
|
||||||
|
<Textarea id="metaTitle" value={articleData?.metaTitle} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="metaDescription"
|
||||||
|
value={articleData?.metaDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="checker">
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<Pie
|
||||||
|
data={data}
|
||||||
|
options={options}
|
||||||
|
className="text-left flex items-start justify-start"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Accordion type="single" collapsible className="w-full ">
|
||||||
|
<AccordionItem
|
||||||
|
value="error"
|
||||||
|
className="border border-red-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-red-600" />
|
||||||
|
Errors ({errorSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{errorSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{errorSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No errors found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="warning"
|
||||||
|
className="border border-yellow-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-yellow-600" />
|
||||||
|
Warnings ({warningSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{warningSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{warningSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No warnings found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="optimized"
|
||||||
|
className="border border-green-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-green-600" />
|
||||||
|
Optimized ({optimizedSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{optimizedSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{optimizedSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No optimizations found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<div className="flex flex-row justify-between items-center mb-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Button size="md" className="bg-blue-500">
|
||||||
|
Select Image From Content Bank
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,7 @@ import { error, loading } from "@/config/swal";
|
||||||
import { Item } from "@radix-ui/react-dropdown-menu";
|
import { Item } from "@radix-ui/react-dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
const CustomEditor = dynamic(
|
const CustomEditor = dynamic(
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -917,7 +918,7 @@ export default function FormVideo() {
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleArticleIdClick(id)}
|
onClick={() => handleArticleIdClick(id)}
|
||||||
>
|
>
|
||||||
{id}
|
{"Narasi " + (index + 1)}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -926,18 +927,8 @@ export default function FormVideo() {
|
||||||
<div className="pt-3">
|
<div className="pt-3">
|
||||||
<div className="flex flex-row justify-between items-center">
|
<div className="flex flex-row justify-between items-center">
|
||||||
{selectedArticleId && (
|
{selectedArticleId && (
|
||||||
<a
|
<Link
|
||||||
href={`/admin/media/${
|
href={`/contributor/content/video/update-seo/${selectedArticleId}`}
|
||||||
fileTypeId === "1"
|
|
||||||
? "image"
|
|
||||||
: fileTypeId === "2"
|
|
||||||
? "video"
|
|
||||||
: fileTypeId === "3"
|
|
||||||
? "text"
|
|
||||||
: "audio"
|
|
||||||
}/update-new/${selectedArticleId}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
|
|
@ -947,7 +938,7 @@ export default function FormVideo() {
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,757 @@
|
||||||
|
"use client";
|
||||||
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
Fragment,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as z from "zod";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import {
|
||||||
|
createMedia,
|
||||||
|
deleteFile,
|
||||||
|
deleteMedia,
|
||||||
|
getTagsBySubCategoryId,
|
||||||
|
listEnableCategory,
|
||||||
|
uploadThumbnail,
|
||||||
|
} from "@/service/content/content";
|
||||||
|
import { detailMedia } from "@/service/curated-content/curated-content";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { CloudUpload, MailIcon, PieChart, XIcon } from "lucide-react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { error, loading } from "@/lib/swal";
|
||||||
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { Upload } from "tus-js-client";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import ViewEditor from "@/components/editor/view-editor";
|
||||||
|
import { getDetailArticle, getSeoScore } from "@/service/content/ai";
|
||||||
|
import { Gauge } from "@mui/x-charts/Gauge";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { Pie } from "react-chartjs-2";
|
||||||
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
ArcElement,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ChartOptions,
|
||||||
|
} from "chart.js";
|
||||||
|
|
||||||
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
const imageSchema = z.object({
|
||||||
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||||
|
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
|
||||||
|
// tags: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Category = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Detail = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
slug: string;
|
||||||
|
category: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
publishedFor: string;
|
||||||
|
|
||||||
|
publishedForObject: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
htmlDescription: string;
|
||||||
|
creatorName: string;
|
||||||
|
categoryName: string;
|
||||||
|
thumbnailLink: string;
|
||||||
|
tags: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Option = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomEditor = dynamic(
|
||||||
|
() => {
|
||||||
|
return import("@/components/editor/custom-editor");
|
||||||
|
},
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface FileWithPreview extends File {
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FormVideoSeo() {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { id } = useParams() as { id: string };
|
||||||
|
console.log(id);
|
||||||
|
const editor = useRef(null);
|
||||||
|
type ImageSchema = z.infer<typeof imageSchema>;
|
||||||
|
|
||||||
|
let progressInfo: any = [];
|
||||||
|
let counterUpdateProgress = 0;
|
||||||
|
const [progressList, setProgressList] = useState<any>([]);
|
||||||
|
let uploadPersen = 0;
|
||||||
|
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||||
|
const [counterProgress, setCounterProgress] = useState(0);
|
||||||
|
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
|
const taskId = Cookies.get("taskId");
|
||||||
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
|
const scheduleType = Cookies.get("scheduleType");
|
||||||
|
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||||
|
const [tags, setTags] = useState<any[]>([]);
|
||||||
|
const [detail, setDetail] = useState<Detail>();
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
|
||||||
|
const [articleBody, setArticleBody] = useState<string>("");
|
||||||
|
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||||
|
const [filesTemp, setFilesTemp] = useState<File[]>([]);
|
||||||
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [selectedOptions, setSelectedOptions] = useState<{
|
||||||
|
[fileId: number]: string;
|
||||||
|
}>({});
|
||||||
|
const [articleData, setArticleData] = useState({
|
||||||
|
title: "",
|
||||||
|
mainKeyword: "",
|
||||||
|
additionalKeywords: "",
|
||||||
|
metaTitle: "",
|
||||||
|
metaDescription: "",
|
||||||
|
});
|
||||||
|
const [totalScoreSEO, setTotalScoreSEO] = useState<number>(0);
|
||||||
|
const [errorSEO, setErrorSEO] = useState<string[]>([]);
|
||||||
|
const [warningSEO, setWarningSEO] = useState<string[]>([]);
|
||||||
|
const [optimizedSEO, setOptimizedSEO] = useState<string[]>([]);
|
||||||
|
// const [errorData, setErrorData] = useState<string[]>([]);
|
||||||
|
const [warningData, setWarningData] = useState<string[]>([]);
|
||||||
|
const [optimizedData, setOptimizedData] = useState<string[]>([]);
|
||||||
|
const [errorsData, setErrorData] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
|
||||||
|
detail?.category.id
|
||||||
|
);
|
||||||
|
const [unitSelection, setUnitSelection] = useState({
|
||||||
|
allUnit: false,
|
||||||
|
mabes: false,
|
||||||
|
polda: false,
|
||||||
|
polres: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let fileTypeId = "1";
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
onDrop: (acceptedFiles) => {
|
||||||
|
setFiles(acceptedFiles.map((file) => Object.assign(file)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<ImageSchema>({
|
||||||
|
resolver: zodResolver(imageSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
// const handleKeyDown = (e: any) => {
|
||||||
|
// const newTag = e.target.value.trim(); // Ambil nilai input
|
||||||
|
// if (e.key === "Enter" && newTag) {
|
||||||
|
// e.preventDefault(); // Hentikan submit form
|
||||||
|
// if (!tags.includes(newTag)) {
|
||||||
|
// setTags((prevTags) => [...prevTags, newTag]); // Tambah tag baru
|
||||||
|
// setValue("tags", ""); // Kosongkan input
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files) {
|
||||||
|
const files = Array.from(event.target.files);
|
||||||
|
setSelectedFiles((prevImages: any) => [...prevImages, ...files]);
|
||||||
|
console.log("DATAFILE::", selectedFiles);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveImage = (index: number) => {
|
||||||
|
setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
// const handleCheckboxChange = (id: number) => {
|
||||||
|
// setSelectedPublishers((prev) =>
|
||||||
|
// prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function initState() {
|
||||||
|
getCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
initState();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getCategories = async () => {
|
||||||
|
try {
|
||||||
|
const category = await listEnableCategory(fileTypeId);
|
||||||
|
const resCategory: Category[] = category?.data?.data?.content;
|
||||||
|
|
||||||
|
setCategories(resCategory);
|
||||||
|
console.log("data category", resCategory);
|
||||||
|
|
||||||
|
if (scheduleId && scheduleType === "3") {
|
||||||
|
const findCategory = resCategory.find((o) =>
|
||||||
|
o.name.toLowerCase().includes("pers rilis")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (findCategory) {
|
||||||
|
// setValue("categoryId", findCategory.id);
|
||||||
|
setSelectedCategory(findCategory.id); // Set the selected category
|
||||||
|
const response = await getTagsBySubCategoryId(findCategory.id);
|
||||||
|
setTags(response?.data?.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch categories:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchArticleData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getDetailArticle(id);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const cleanArticleBody = data.articleBody.replace(/<img[^>]*>/g, "");
|
||||||
|
|
||||||
|
setArticleData({
|
||||||
|
title: data.title || "",
|
||||||
|
mainKeyword: data.mainKeyword || "",
|
||||||
|
additionalKeywords: data.additionalKeywords || "",
|
||||||
|
metaTitle: data.metaTitle || "",
|
||||||
|
metaDescription: data.metaDescription || "",
|
||||||
|
});
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
|
||||||
|
// reset({
|
||||||
|
// title: data.title,
|
||||||
|
// mainKeyword: data.mainKeyword,
|
||||||
|
// additionalKeywords: data.additionalKeywords,
|
||||||
|
// metaTitle: data.metaTitle,
|
||||||
|
// metaDescription: data.metaDescription,
|
||||||
|
// });
|
||||||
|
setArticleBody(cleanArticleBody || "");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch article data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
fetchArticleData();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSeoScore = async () => {
|
||||||
|
const res = await getSeoScore(id);
|
||||||
|
if (res.error) {
|
||||||
|
error(res.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setTotalScoreSEO(res.data.data?.seo_analysis?.score || 0);
|
||||||
|
let errorList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.error,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.error,
|
||||||
|
];
|
||||||
|
setErrorSEO(errorList);
|
||||||
|
let warningList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization?.warning,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.warning,
|
||||||
|
];
|
||||||
|
setWarningSEO(warningList);
|
||||||
|
let optimizedList = [
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.keyword_optimization
|
||||||
|
?.optimized,
|
||||||
|
...res.data.data?.seo_analysis?.analysis?.content_quality?.optimized,
|
||||||
|
];
|
||||||
|
setOptimizedSEO(optimizedList);
|
||||||
|
setErrorData(errorList);
|
||||||
|
setWarningData(warningList);
|
||||||
|
setOptimizedData(optimizedList);
|
||||||
|
};
|
||||||
|
fetchSeoScore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: ["SEO Score (" + totalScoreSEO + "%)"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [totalScoreSEO],
|
||||||
|
backgroundColor: ["#4CAF50"],
|
||||||
|
hoverBackgroundColor: ["#388E3C"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: ChartOptions<"pie"> = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "bottom", // TypeScript now correctly recognizes this as a valid option
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async (data: ImageSchema) => {
|
||||||
|
loading();
|
||||||
|
const finalTags = tags.join(", ");
|
||||||
|
const requestData = {
|
||||||
|
...data,
|
||||||
|
id: detail?.id,
|
||||||
|
title: data.title,
|
||||||
|
description: data.description,
|
||||||
|
htmlDescription: data.description,
|
||||||
|
fileTypeId,
|
||||||
|
categoryId: selectedTarget,
|
||||||
|
subCategoryId: selectedTarget,
|
||||||
|
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||||
|
statusId: "1",
|
||||||
|
publishedFor: publishedFor.join(","),
|
||||||
|
creatorName: data.creatorName,
|
||||||
|
tags: finalTags,
|
||||||
|
isYoutube: false,
|
||||||
|
isInternationalMedia: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await createMedia(requestData);
|
||||||
|
console.log("Form Data Submitted:", requestData);
|
||||||
|
|
||||||
|
const formMedia = new FormData();
|
||||||
|
const thumbnail = files[0];
|
||||||
|
formMedia.append("file", thumbnail);
|
||||||
|
const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||||
|
if (responseThumbnail?.error == true) {
|
||||||
|
error(responseThumbnail?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressInfoArr = [];
|
||||||
|
for (const item of files) {
|
||||||
|
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||||
|
}
|
||||||
|
progressInfo = progressInfoArr;
|
||||||
|
setIsStartUpload(true);
|
||||||
|
setProgressList(progressInfoArr);
|
||||||
|
|
||||||
|
close();
|
||||||
|
// showProgress();
|
||||||
|
files.map(async (item: any, index: number) => {
|
||||||
|
await uploadResumableFile(
|
||||||
|
index,
|
||||||
|
String(id),
|
||||||
|
item,
|
||||||
|
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push("/en/contributor/content/image");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function uploadResumableFile(
|
||||||
|
idx: number,
|
||||||
|
id: string,
|
||||||
|
file: any,
|
||||||
|
duration: string
|
||||||
|
) {
|
||||||
|
console.log(idx, id, file, duration);
|
||||||
|
|
||||||
|
// const placements = getPlacement(file.placements);
|
||||||
|
// console.log("Placementttt: : ", placements);
|
||||||
|
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.token;
|
||||||
|
console.log("CSRF TOKEN : ", csrfToken);
|
||||||
|
const headers = {
|
||||||
|
"X-XSRF-TOKEN": csrfToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = new Upload(file, {
|
||||||
|
endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
|
||||||
|
headers: headers,
|
||||||
|
retryDelays: [0, 3000, 6000, 12_000, 24_000],
|
||||||
|
chunkSize: 20_000,
|
||||||
|
metadata: {
|
||||||
|
mediaid: id,
|
||||||
|
filename: file.name,
|
||||||
|
filetype: file.type,
|
||||||
|
duration,
|
||||||
|
isWatermark: "true", // hardcode
|
||||||
|
},
|
||||||
|
onBeforeRequest: function (req) {
|
||||||
|
var xhr = req.getUnderlyingObject();
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
},
|
||||||
|
onError: async (e: any) => {
|
||||||
|
console.log("Error upload :", e);
|
||||||
|
error(e);
|
||||||
|
},
|
||||||
|
onChunkComplete: (
|
||||||
|
chunkSize: any,
|
||||||
|
bytesAccepted: any,
|
||||||
|
bytesTotal: any
|
||||||
|
) => {
|
||||||
|
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||||
|
progressInfo[idx].percentage = uploadPersen;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
console.log(counterUpdateProgress);
|
||||||
|
setProgressList(progressInfo);
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
uploadPersen = 100;
|
||||||
|
progressInfo[idx].percentage = 100;
|
||||||
|
counterUpdateProgress++;
|
||||||
|
setCounterProgress(counterUpdateProgress);
|
||||||
|
successTodo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
upload.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = (data: ImageSchema) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data",
|
||||||
|
text: "Apakah Anda yakin ingin menyimpan data ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const successSubmit = (redirect: string) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
text: "Data berhasil disimpan.",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
router.push(redirect);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function successTodo() {
|
||||||
|
let counter = 0;
|
||||||
|
for (const element of progressInfo) {
|
||||||
|
if (element.percentage == 100) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (counter == progressInfo.length) {
|
||||||
|
setIsStartUpload(false);
|
||||||
|
// hideProgress();
|
||||||
|
Cookies.remove("idCreate");
|
||||||
|
successSubmit("/in/contributor/content/image/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemoveFile = (file: FileWithPreview) => {
|
||||||
|
const uploadedFiles = files;
|
||||||
|
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
|
||||||
|
setFiles([...filtered]);
|
||||||
|
};
|
||||||
|
|
||||||
|
function success() {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
// window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteFile = (id: number) => {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Hapus file",
|
||||||
|
text: "Apakah Anda yakin ingin menghapus file ini?",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Hapus",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
doDelete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doDelete(id: number) {
|
||||||
|
const data = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await deleteFile(data);
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika berhasil, hapus file dari state lokal
|
||||||
|
setFiles((prevFiles: any) =>
|
||||||
|
prevFiles.filter((file: any) => file.id !== id)
|
||||||
|
);
|
||||||
|
success();
|
||||||
|
} catch (err) {
|
||||||
|
error("Terjadi kesalahan saat menghapus file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mx-5 mb-3">
|
||||||
|
<Card>
|
||||||
|
<Tabs defaultValue="content" className="">
|
||||||
|
<TabsList className="grid w-[300px] grid-cols-2 bg-slate-400 my-3 mx-3 ">
|
||||||
|
<TabsTrigger
|
||||||
|
value="content"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Konten
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value="checker"
|
||||||
|
className="data-[state=active]:text-black text-gray-500 data-[state=active]:rounded-md"
|
||||||
|
>
|
||||||
|
Checker
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="content">
|
||||||
|
{articleData !== undefined ? (
|
||||||
|
<CardContent className="space-y-2 my-3">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="name">Judul</Label>
|
||||||
|
<Input id="name" defaultValue={articleData?.title} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Main Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="mainKeyword"
|
||||||
|
value={articleData?.mainKeyword}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Additional Keyword</Label>
|
||||||
|
<Textarea
|
||||||
|
id="additionalKeywords"
|
||||||
|
value={articleData?.additionalKeywords}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-3 w-full">
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Title</Label>
|
||||||
|
<Textarea id="metaTitle" value={articleData?.metaTitle} />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label htmlFor="username">Meta Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="metaDescription"
|
||||||
|
value={articleData?.metaDescription}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="checker">
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<Pie
|
||||||
|
data={data}
|
||||||
|
options={options}
|
||||||
|
className="text-left flex items-start justify-start"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Accordion type="single" collapsible className="w-full ">
|
||||||
|
<AccordionItem
|
||||||
|
value="error"
|
||||||
|
className="border border-red-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-red-600" />
|
||||||
|
Errors ({errorSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{errorSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{errorSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No errors found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="warning"
|
||||||
|
className="border border-yellow-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-yellow-600" />
|
||||||
|
Warnings ({warningSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{warningSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{warningSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No warnings found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
value="optimized"
|
||||||
|
className="border border-green-600"
|
||||||
|
>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<XIcon className="text-green-600" />
|
||||||
|
Optimized ({optimizedSEO.length})
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
{optimizedSEO.length > 0 ? (
|
||||||
|
<ul className="list-disc list-inside">
|
||||||
|
{optimizedSEO.map((item, index) => (
|
||||||
|
<li key={index}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No optimizations found.</p>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<div className="flex flex-row justify-between items-center mb-3">
|
||||||
|
<Label>Article</Label>
|
||||||
|
<Button size="md" className="bg-blue-500">
|
||||||
|
Select Image From Content Bank
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="description"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<CustomEditor
|
||||||
|
onChange={onChange}
|
||||||
|
initialData={articleBody}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.description?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.description.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -51,9 +51,14 @@ import { AudioRecorder } from "react-audio-voice-recorder";
|
||||||
import { error, loading } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import { Upload } from "tus-js-client";
|
import { Upload } from "tus-js-client";
|
||||||
import { getCsrfToken } from "@/service/auth";
|
import { getCsrfToken } from "@/service/auth";
|
||||||
|
import { getOnlyDate } from "@/utils/globals";
|
||||||
|
import { duration } from "moment";
|
||||||
|
|
||||||
const contestSchema = z.object({
|
const contestSchema = z.object({
|
||||||
theme: z.string().min(1, { message: "Judul diperlukan" }),
|
theme: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
duration: z
|
||||||
|
.array(z.string().min(1)) // Gunakan array string untuk menyimpan range tanggal
|
||||||
|
.min(1, { message: "Tanggal diperlukan" }),
|
||||||
hastagCode: z.string().min(1, { message: "Judul diperlukan" }),
|
hastagCode: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
description: z.string().min(2, {
|
description: z.string().min(2, {
|
||||||
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
message: "Narasi Penugasan harus lebih dari 2 karakter.",
|
||||||
|
|
@ -76,7 +81,7 @@ export type contestDetail = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
createdAt: string;
|
duration: string;
|
||||||
platformType: string | null;
|
platformType: string | null;
|
||||||
assignmentTypeId: string;
|
assignmentTypeId: string;
|
||||||
targetOutput: string;
|
targetOutput: string;
|
||||||
|
|
@ -190,9 +195,12 @@ export default function FormContestDetail() {
|
||||||
const details = response?.data?.data;
|
const details = response?.data?.data;
|
||||||
|
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
if (details?.createdAt) {
|
if (details?.duration) {
|
||||||
const parsedDate = parseISO(details.createdAt);
|
const [start, end] = details.duration.split(" - "); // Pisahkan tanggal
|
||||||
setDate({ from: parsedDate, to: parsedDate });
|
setDate({
|
||||||
|
from: parseISO(start),
|
||||||
|
to: end ? parseISO(end) : undefined, // Pastikan `to` bisa undefined jika hanya satu tanggal
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -212,19 +220,19 @@ export default function FormContestDetail() {
|
||||||
}
|
}
|
||||||
}, [detail?.targetOutput]);
|
}, [detail?.targetOutput]);
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// if (detail?.targetParticipantTopLevel) {
|
if (detail?.targetParticipantTopLevel) {
|
||||||
// const outputSet = new Set(
|
const outputSet = new Set(
|
||||||
// detail.targetParticipantTopLevel.split(",").map(Number)
|
detail.targetParticipantTopLevel.split(",").map(Number)
|
||||||
// );
|
);
|
||||||
// setUnitSelection({
|
setUnitSelection({
|
||||||
// allUnit: outputSet.has(0),
|
allUnit: outputSet.has(0),
|
||||||
// mabes: outputSet.has(1),
|
mabes: outputSet.has(1),
|
||||||
// polda: outputSet.has(2),
|
polda: outputSet.has(2),
|
||||||
// polres: outputSet.has(3),
|
polres: outputSet.has(3),
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// }, [detail?.targetParticipantTopLevel]);
|
}, [detail?.targetParticipantTopLevel]);
|
||||||
|
|
||||||
const handleCheckboxChange = (levelId: number) => {
|
const handleCheckboxChange = (levelId: number) => {
|
||||||
setCheckedLevels((prev) => {
|
setCheckedLevels((prev) => {
|
||||||
|
|
@ -244,11 +252,11 @@ export default function FormContestDetail() {
|
||||||
|
|
||||||
const save = async (data: ContestSchema) => {
|
const save = async (data: ContestSchema) => {
|
||||||
const fileTypeMapping = {
|
const fileTypeMapping = {
|
||||||
all: "1",
|
all: "0",
|
||||||
video: "2",
|
video: "2",
|
||||||
audio: "3",
|
audio: "4",
|
||||||
image: "4",
|
image: "1",
|
||||||
text: "5",
|
text: "3",
|
||||||
};
|
};
|
||||||
|
|
||||||
const unitMapping = {
|
const unitMapping = {
|
||||||
|
|
@ -268,26 +276,38 @@ export default function FormContestDetail() {
|
||||||
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
|
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) // Konversi ke nilai string
|
||||||
.join(",");
|
.join(",");
|
||||||
|
|
||||||
|
// Pastikan `data.duration` ada dan dikonversi ke `Date`
|
||||||
|
const startDate = data.duration?.[0] ? new Date(data.duration[0]) : null;
|
||||||
|
const endDate = data.duration?.[1] ? new Date(data.duration[1]) : null;
|
||||||
|
|
||||||
|
const formattedDuration = startDate
|
||||||
|
? endDate
|
||||||
|
? `${getOnlyDate(startDate)} - ${getOnlyDate(endDate)}`
|
||||||
|
: getOnlyDate(startDate)
|
||||||
|
: "";
|
||||||
|
|
||||||
const requestData: {
|
const requestData: {
|
||||||
id?: any;
|
id?: any;
|
||||||
theme: string;
|
theme: string;
|
||||||
assignedToLevel: any;
|
duration: string;
|
||||||
assignmentPurpose: any;
|
targetParticipantTopLevel: any;
|
||||||
|
targetParticipant: any;
|
||||||
hastagCode: string;
|
hastagCode: string;
|
||||||
description: string;
|
description: string;
|
||||||
assignmentMainTypeId: any;
|
|
||||||
scoringFormula: string;
|
scoringFormula: string;
|
||||||
fileTypeOutput: any;
|
targetOutput: any;
|
||||||
|
attachmentUrl: string[];
|
||||||
} = {
|
} = {
|
||||||
...data,
|
...data,
|
||||||
hastagCode: data.hastagCode,
|
hastagCode: data.hastagCode,
|
||||||
theme: data.theme,
|
theme: data.theme,
|
||||||
|
duration: formattedDuration,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
scoringFormula: data.scoringFormula,
|
scoringFormula: data.scoringFormula,
|
||||||
assignmentMainTypeId: mainType,
|
targetParticipantTopLevel: handlePoldaPolresChange(),
|
||||||
assignedToLevel: handlePoldaPolresChange(),
|
targetParticipant: assignmentPurposeString,
|
||||||
assignmentPurpose: assignmentPurposeString,
|
targetOutput: selectedOutputs,
|
||||||
fileTypeOutput: selectedOutputs,
|
attachmentUrl: links,
|
||||||
};
|
};
|
||||||
|
|
||||||
// if (id != undefined) {
|
// if (id != undefined) {
|
||||||
|
|
@ -299,7 +319,7 @@ export default function FormContestDetail() {
|
||||||
console.log("Form Data Submitted:", requestData);
|
console.log("Form Data Submitted:", requestData);
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
|
|
||||||
const id = response?.data?.data.id;
|
const id = response?.data?.data?.id;
|
||||||
loading();
|
loading();
|
||||||
if (imageFiles?.length == 0) {
|
if (imageFiles?.length == 0) {
|
||||||
setIsImageUploadFinish(true);
|
setIsImageUploadFinish(true);
|
||||||
|
|
@ -334,16 +354,6 @@ export default function FormContestDetail() {
|
||||||
"0" // Optional: Replace with actual duration if available
|
"0" // Optional: Replace with actual duration if available
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// MySwal.fire({
|
|
||||||
// title: "Sukses",
|
|
||||||
// text: "Data berhasil disimpan.",
|
|
||||||
// icon: "success",
|
|
||||||
// confirmButtonColor: "#3085d6",
|
|
||||||
// confirmButtonText: "OK",
|
|
||||||
// }).then(() => {
|
|
||||||
// router.push("/en/shared/contest");
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (data: ContestSchema) => {
|
const onSubmit = (data: ContestSchema) => {
|
||||||
|
|
@ -584,43 +594,67 @@ export default function FormContestDetail() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col mt-5">
|
<div className="flex flex-col mt-5">
|
||||||
<Label className="mr-3 mb-1">Tanggal</Label>
|
<Label className="mr-3 mb-1">Tanggal</Label>
|
||||||
<Popover>
|
<Controller
|
||||||
<PopoverTrigger asChild>
|
control={control}
|
||||||
<Button
|
name="duration"
|
||||||
value={detail?.createdAt}
|
render={({ field: { onChange, value } }) => (
|
||||||
id="date"
|
<Popover>
|
||||||
variant={"outline"}
|
<PopoverTrigger asChild>
|
||||||
className={cn(
|
<Button
|
||||||
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
id="date"
|
||||||
!date && "text-muted-foreground"
|
variant={"outline"}
|
||||||
)}
|
className={cn(
|
||||||
>
|
"w-[280px] lg:w-[300px] justify-start text-left font-normal",
|
||||||
<CalendarIcon />
|
!date && "text-muted-foreground"
|
||||||
{date?.from ? (
|
)}
|
||||||
date.to ? (
|
value={detail?.duration}
|
||||||
<>
|
>
|
||||||
{format(date.from, "LLL dd, y")} -{" "}
|
<CalendarIcon />
|
||||||
{format(date.to, "LLL dd, y")}
|
{date?.from ? (
|
||||||
</>
|
date.to ? (
|
||||||
) : (
|
<>
|
||||||
format(date.from, "LLL dd, y")
|
{format(date.from, "yyyy-MM-dd")} -{" "}
|
||||||
)
|
{format(date.to, "yyyy-MM-dd")}
|
||||||
) : (
|
</>
|
||||||
<span>Pick a date</span>
|
) : (
|
||||||
)}
|
format(date.from, "yyyy-MM-dd")
|
||||||
</Button>
|
)
|
||||||
</PopoverTrigger>
|
) : (
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<span>Pilih Tanggal</span>
|
||||||
<Calendar
|
)}
|
||||||
initialFocus
|
</Button>
|
||||||
mode="range"
|
</PopoverTrigger>
|
||||||
defaultMonth={date?.from}
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
selected={date}
|
<Calendar
|
||||||
onSelect={setDate}
|
initialFocus
|
||||||
numberOfMonths={1}
|
mode="range"
|
||||||
/>
|
defaultMonth={date?.from}
|
||||||
</PopoverContent>
|
selected={date}
|
||||||
</Popover>
|
onSelect={(newDate) => {
|
||||||
|
setDate(newDate); // Update state lokal
|
||||||
|
if (newDate?.from) {
|
||||||
|
const formattedDate = [
|
||||||
|
format(newDate.from, "yyyy-MM-dd"),
|
||||||
|
newDate.to
|
||||||
|
? format(newDate.to, "yyyy-MM-dd")
|
||||||
|
: "",
|
||||||
|
].filter(Boolean); // Hanya menyimpan yang tidak undefined
|
||||||
|
onChange(formattedDate); // Simpan ke React Hook Form
|
||||||
|
} else {
|
||||||
|
onChange([]); // Reset jika tidak ada tanggal
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
numberOfMonths={1}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.duration?.message && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.duration.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<Label>Output Lomba</Label>
|
<Label>Output Lomba</Label>
|
||||||
|
|
@ -763,7 +797,7 @@ export default function FormContestDetail() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-7">
|
<div className="mt-7">
|
||||||
<Label>Rumus Penilaian</Label>
|
<Label>Panduan Penilaian</Label>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="scoringFormula"
|
name="scoringFormula"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
|
import { CalendarIcon, Clock1, MapPin, Plus, User2 } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -41,6 +41,8 @@ import {
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { formatDate } from "@fullcalendar/core/index.js";
|
import { formatDate } from "@fullcalendar/core/index.js";
|
||||||
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -60,6 +62,7 @@ interface Detail {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormEventDetail() {
|
export default function FormEventDetail() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
console.log(id);
|
console.log(id);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -327,16 +330,110 @@ export default function FormEventDetail() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button color="primary" size="sm" type="button">
|
||||||
|
<Plus /> Tambah Lampiran
|
||||||
|
</Button>
|
||||||
|
<p>0 Lampiran</p>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-md p-6 bg-white rounded-lg shadow-lg h-[420px] w-[700px]">
|
||||||
|
<h2 className="text-lg font-semibold">
|
||||||
|
Pilih Jenis Lampiran
|
||||||
|
</h2>
|
||||||
|
<div className=" space-y-4 gap-y-4">
|
||||||
|
<Link href={"/contributor/schedule/media/video/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-video.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">
|
||||||
|
Audio Visual
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/image/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-image.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Foto</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/text/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-text.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Teks</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/audio/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-audio.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Audio</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{/* Submit Button
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4 flex justify-end">
|
||||||
<Button type="submit" color="primary">
|
<Button type="submit" color="primary">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full lg:w-3/12">
|
<Card className="w-full lg:w-3/12">
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, Plus } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, setDate } from "date-fns";
|
import { addDays, format, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -149,6 +149,23 @@ export default function FormEvent() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUploadAttachment = () => {
|
||||||
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
|
|
||||||
|
if (scheduleId == undefined) {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Jadwal Terlebih Dahulu",
|
||||||
|
icon: "info",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Ok",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col lg:flex-row gap-2">
|
<div className="flex flex-col lg:flex-row gap-2">
|
||||||
<Card className="w-full lg:w-9/12">
|
<Card className="w-full lg:w-9/12">
|
||||||
|
|
@ -351,8 +368,19 @@ export default function FormEvent() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Submit Button */}
|
<div className="flex flex-row items-center justify-between">
|
||||||
<div className="mt-4">
|
<Button
|
||||||
|
onClick={() => handleUploadAttachment()}
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Plus />
|
||||||
|
Tambah Lampiran
|
||||||
|
</Button>
|
||||||
|
<p>0 Llampiran</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex justify-end">
|
||||||
<Button type="submit" color="primary">
|
<Button type="submit" color="primary">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, Plus } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -29,6 +29,8 @@ import { Textarea } from "@/components/ui/textarea";
|
||||||
import { error, loading } from "@/lib/swal";
|
import { error, loading } from "@/lib/swal";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
import { detailSchedule, postSchedule } from "@/service/schedule/schedule";
|
||||||
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -48,6 +50,7 @@ interface Detail {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormEventUpdate() {
|
export default function FormEventUpdate() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
console.log(id);
|
console.log(id);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -379,12 +382,107 @@ export default function FormEventUpdate() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button color="primary" size="sm" type="button">
|
||||||
|
<Plus /> Tambah Lampiran
|
||||||
|
</Button>
|
||||||
|
<p>0 Lampiran</p>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-md p-6 bg-white rounded-lg shadow-lg h-[420px] w-[700px]">
|
||||||
|
<h2 className="text-lg font-semibold">
|
||||||
|
Pilih Jenis Lampiran
|
||||||
|
</h2>
|
||||||
|
<div className=" space-y-4 gap-y-4">
|
||||||
|
<Link href={"/contributor/schedule/media/video/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-video.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">
|
||||||
|
Audio Visual
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/image/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-image.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Foto</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/text/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-text.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Teks</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/audio/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-audio.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Audio</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{/* Submit Button */}
|
|
||||||
<div className="mt-4">
|
<div className="mt-4 flex justify-end">
|
||||||
<Button type="submit" color="primary">
|
<Button type="submit" color="primary">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon, Clock1, MapPin, User2 } from "lucide-react";
|
import { CalendarIcon, Clock1, MapPin, Plus, User2 } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -48,6 +48,8 @@ import {
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { formatDate } from "@fullcalendar/core/index.js";
|
import { formatDate } from "@fullcalendar/core/index.js";
|
||||||
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -67,7 +69,9 @@ interface Detail {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormDetailPressRillis() {
|
export default function FormDetailPressRillis() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
console.log(id);
|
console.log(id);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isLiveStreamingEnabled, setIsLiveStreamingEnabled] = useState(false);
|
const [isLiveStreamingEnabled, setIsLiveStreamingEnabled] = useState(false);
|
||||||
|
|
@ -135,6 +139,23 @@ export default function FormDetailPressRillis() {
|
||||||
setEndTime(e.target.value);
|
setEndTime(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUploadAttachment = () => {
|
||||||
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
|
|
||||||
|
if (scheduleId == undefined) {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Jadwal Terlebih Dahulu",
|
||||||
|
icon: "info",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Ok",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col lg:flex-row gap-2">
|
<div className="flex flex-col lg:flex-row gap-2">
|
||||||
<Card className="w-full lg:w-9/12">
|
<Card className="w-full lg:w-9/12">
|
||||||
|
|
@ -352,16 +373,110 @@ export default function FormDetailPressRillis() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button color="primary" size="sm" type="button">
|
||||||
|
<Plus /> Tambah Lampiran
|
||||||
|
</Button>
|
||||||
|
<p>0 Lampiran</p>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-md p-6 bg-white rounded-lg shadow-lg h-[420px] w-[700px]">
|
||||||
|
<h2 className="text-lg font-semibold">
|
||||||
|
Pilih Jenis Lampiran
|
||||||
|
</h2>
|
||||||
|
<div className=" space-y-4 gap-y-4">
|
||||||
|
<Link href={"/contributor/schedule/media/video/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-video.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">
|
||||||
|
Audio Visual
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/image/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-image.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Foto</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/text/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-text.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Teks</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/audio/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-audio.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Audio</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{/* Submit Button
|
<div className="mt-4 flex justify-end">
|
||||||
<div className="mt-4">
|
<Button type="submit" color="primary">
|
||||||
<Button type="submit" color="primary">
|
Submit
|
||||||
Submit
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="w-full lg:w-3/12">
|
<Card className="w-full lg:w-3/12">
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, Plus } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, parseISO, setDate } from "date-fns";
|
import { addDays, format, parseISO, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -36,6 +36,8 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
|
||||||
const taskSchema = z.object({
|
const taskSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
|
|
@ -55,6 +57,7 @@ interface Detail {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormUpdatePressRelease() {
|
export default function FormUpdatePressRelease() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
console.log(id);
|
console.log(id);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -403,6 +406,101 @@ export default function FormUpdatePressRelease() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button color="primary" size="sm" type="button">
|
||||||
|
<Plus /> Tambah Lampiran
|
||||||
|
</Button>
|
||||||
|
<p>0 Lampiran</p>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-md p-6 bg-white rounded-lg shadow-lg h-[420px] w-[700px]">
|
||||||
|
<h2 className="text-lg font-semibold">
|
||||||
|
Pilih Jenis Lampiran
|
||||||
|
</h2>
|
||||||
|
<div className=" space-y-4 gap-y-4">
|
||||||
|
<Link href={"/contributor/schedule/media/video/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-video.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">
|
||||||
|
Audio Visual
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/image/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-image.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Foto</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/text/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-text.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Teks</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<Link href={"/contributor/schedule/media/audio/create"}>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div className="flex flex-col w-4/12 items-center">
|
||||||
|
<img
|
||||||
|
src={"/assets/img/upload-audio.png"}
|
||||||
|
alt={"item.label"}
|
||||||
|
className="w-12 h-12"
|
||||||
|
/>
|
||||||
|
<h3 className="text-base font-semibold">Audio</h3>
|
||||||
|
</div>
|
||||||
|
<div className="w-8/12">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Unggah media berupa video dengan format avi, wmv,
|
||||||
|
atau mp4 dengan ukuran minimal 2mb dan maksimal
|
||||||
|
500mb.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon, Plus } from "lucide-react";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { addDays, format, setDate } from "date-fns";
|
import { addDays, format, setDate } from "date-fns";
|
||||||
import { DateRange } from "react-day-picker";
|
import { DateRange } from "react-day-picker";
|
||||||
|
|
@ -133,6 +133,23 @@ export default function FormPressRelease() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUploadAttachment = () => {
|
||||||
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
|
|
||||||
|
if (scheduleId == undefined) {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Jadwal Terlebih Dahulu",
|
||||||
|
icon: "info",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Ok",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmit = (data: TaskSchema) => {
|
const onSubmit = (data: TaskSchema) => {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Simpan Data",
|
title: "Simpan Data",
|
||||||
|
|
@ -367,9 +384,19 @@ export default function FormPressRelease() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-between">
|
||||||
{/* Submit Button */}
|
<Button
|
||||||
<div className="mt-4">
|
onClick={() => handleUploadAttachment()}
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Plus />
|
||||||
|
Tambah Lampiran
|
||||||
|
</Button>
|
||||||
|
<p>0 Llampiran</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex justify-end">
|
||||||
<Button type="submit" color="primary">
|
<Button type="submit" color="primary">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,7 @@ interface UploadResult {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
creatorGroupLevelName: string;
|
||||||
category: { name: string };
|
category: { name: string };
|
||||||
fileType: { name: string };
|
fileType: { name: string };
|
||||||
uploadStatus: { name: string };
|
uploadStatus: { name: string };
|
||||||
|
|
@ -194,7 +195,7 @@ export default function FormTaskDetail() {
|
||||||
|
|
||||||
const userLevelNumber = getCookiesDecrypt("ulne");
|
const userLevelNumber = getCookiesDecrypt("ulne");
|
||||||
const userId = getCookiesDecrypt("uie");
|
const userId = getCookiesDecrypt("uie");
|
||||||
const userLevelId = getCookiesDecrypt("ulie");
|
const userLevelId = "";
|
||||||
|
|
||||||
// State for various form fields
|
// State for various form fields
|
||||||
const [taskOutput, setTaskOutput] = useState({
|
const [taskOutput, setTaskOutput] = useState({
|
||||||
|
|
@ -328,17 +329,25 @@ export default function FormTaskDetail() {
|
||||||
fetchPoldaPolres();
|
fetchPoldaPolres();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchFilteredData = async (selectedLevels?: any[]) => {
|
const fetchAllData = async () => {
|
||||||
try {
|
try {
|
||||||
if (selectedLevels) {
|
const response = await getMediaUpload(id, userLevelId);
|
||||||
const levels =
|
setUploadResults(response?.data?.data || []);
|
||||||
selectedLevels.length === 0 ? userLevelId : selectedLevels.join(",");
|
} catch (error) {
|
||||||
const response = await getMediaUpload(id, levels);
|
console.error("Error fetching all data:", error);
|
||||||
setUploadResults(response?.data?.data || []);
|
}
|
||||||
} else {
|
};
|
||||||
const response = await getMediaUpload(id, '');
|
|
||||||
setUploadResults(response?.data?.data || []);
|
const fetchFilteredData = async (selectedLevels: any[]) => {
|
||||||
|
try {
|
||||||
|
if (selectedLevels.length === 0) {
|
||||||
|
fetchAllData(); // Jika tidak ada filter, panggil semua data
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const levels = selectedLevels.join(",");
|
||||||
|
const response = await getMediaUpload(id, levels);
|
||||||
|
setUploadResults(response?.data?.data || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching filtered data:", error);
|
console.error("Error fetching filtered data:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -387,7 +396,7 @@ export default function FormTaskDetail() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initState();
|
initState();
|
||||||
fetchFilteredData();
|
// fetchFilteredData();
|
||||||
}, [id, refresh]);
|
}, [id, refresh]);
|
||||||
|
|
||||||
const handleUrlChange = (index: number, newUrl: string) => {
|
const handleUrlChange = (index: number, newUrl: string) => {
|
||||||
|
|
@ -467,7 +476,6 @@ export default function FormTaskDetail() {
|
||||||
|
|
||||||
console.log("Checked Levels:", Array.from(updatedLevels));
|
console.log("Checked Levels:", Array.from(updatedLevels));
|
||||||
|
|
||||||
// Fetch data dengan filter userLevelId
|
|
||||||
fetchFilteredData(Array.from(updatedLevels));
|
fetchFilteredData(Array.from(updatedLevels));
|
||||||
|
|
||||||
return updatedLevels;
|
return updatedLevels;
|
||||||
|
|
@ -1418,7 +1426,10 @@ export default function FormTaskDetail() {
|
||||||
type="button"
|
type="button"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant={"default"}
|
variant={"default"}
|
||||||
onClick={() => setIsTableResult(!isTableResult)}
|
onClick={() => {
|
||||||
|
setIsTableResult(!isTableResult);
|
||||||
|
if (!isTableResult) fetchAllData(); // Panggil API saat tombol diklik
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Hasil Upload {Number(userId)}
|
Hasil Upload {Number(userId)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -1459,7 +1470,7 @@ export default function FormTaskDetail() {
|
||||||
<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(polda.id)}
|
// checked={checkedLevels.has(polda.id)}
|
||||||
onCheckedChange={() =>
|
onCheckedChange={() =>
|
||||||
handleCheckboxChangeFilter(polda.id)
|
handleCheckboxChangeFilter(polda.id)
|
||||||
}
|
}
|
||||||
|
|
@ -1481,10 +1492,10 @@ export default function FormTaskDetail() {
|
||||||
<div className="ml-6 mt-2">
|
<div className="ml-6 mt-2">
|
||||||
<Label className="block">
|
<Label className="block">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={polda?.subDestination?.every(
|
// checked={polda?.subDestination?.every(
|
||||||
(polres: any) =>
|
// (polres: any) =>
|
||||||
checkedLevels.has(polres.id)
|
// checkedLevels.has(polres.id)
|
||||||
)}
|
// )}
|
||||||
onCheckedChange={(isChecked) => {
|
onCheckedChange={(isChecked) => {
|
||||||
const updatedLevels = new Set(
|
const updatedLevels = new Set(
|
||||||
checkedLevels
|
checkedLevels
|
||||||
|
|
@ -1507,7 +1518,7 @@ export default function FormTaskDetail() {
|
||||||
{polda?.subDestination?.map((polres: any) => (
|
{polda?.subDestination?.map((polres: any) => (
|
||||||
<Label key={polres.id} className="block mt-1">
|
<Label key={polres.id} className="block mt-1">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={checkedLevels.has(polres.id)}
|
// checked={checkedLevels.has(polres.id)}
|
||||||
onCheckedChange={() =>
|
onCheckedChange={() =>
|
||||||
handleCheckboxChangeFilter(polres.id)
|
handleCheckboxChangeFilter(polres.id)
|
||||||
}
|
}
|
||||||
|
|
@ -1529,7 +1540,8 @@ export default function FormTaskDetail() {
|
||||||
<tr className="bg-gray-100 border-b">
|
<tr className="bg-gray-100 border-b">
|
||||||
<th className="px-4 py-2 text-left">Judul</th>
|
<th className="px-4 py-2 text-left">Judul</th>
|
||||||
<th className="px-4 py-2 text-left">Konten</th>
|
<th className="px-4 py-2 text-left">Konten</th>
|
||||||
<th className="px-4 py-2 text-left">Kategori</th>
|
<th className="px-4 py-2 text-left">Kategory</th>
|
||||||
|
<th className="px-4 py-2 text-left">Kreator</th>
|
||||||
<th className="px-4 py-2 text-left">Diupload Oleh</th>
|
<th className="px-4 py-2 text-left">Diupload Oleh</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -1541,7 +1553,10 @@ export default function FormTaskDetail() {
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2">{item.fileType.name}</td>
|
<td className="px-4 py-2">{item.fileType.name}</td>
|
||||||
<td className="px-4 py-2">{item.category.name}</td>
|
<td className="px-4 py-2">{item.category.name}</td>
|
||||||
<td className="px-4 py-2">{item.creatorGroup}</td>
|
<td className="px-4 py-2">
|
||||||
|
{item.creatorGroupLevelName}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2">{item.creatorName}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ const WelcomePolda = () => {
|
||||||
|
|
||||||
{/* Button */}
|
{/* Button */}
|
||||||
<button className="flex justify-center items-center px-6 w-full lg:w-[20%] py-2 bg-[#bb3523] gap-2 text-white rounded-lg hover:bg-red-700">
|
<button className="flex justify-center items-center px-6 w-full lg:w-[20%] py-2 bg-[#bb3523] gap-2 text-white rounded-lg hover:bg-red-700">
|
||||||
{t("searchCoverage")} <Icon icon="ri:arrow-right-s-line" fontSize={20} />
|
{t("searchCoverage")} <Icon icon="ri:arrow-right-s-line" fontSize={20} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ import { useTranslations } from "next-intl";
|
||||||
// Schema validasi menggunakan zod
|
// Schema validasi menggunakan zod
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
username: z.string().min(1, { message: "Judul diperlukan" }),
|
username: z.string().min(1, { message: "Judul diperlukan" }),
|
||||||
password: z.string().min(4, { message: "Password must be at least 4 characters." }),
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(4, { message: "Password must be at least 4 characters." }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tipe untuk form values
|
// Tipe untuk form values
|
||||||
|
|
@ -37,7 +39,9 @@ const LoginForm = () => {
|
||||||
const t = useTranslations("LandingPage");
|
const t = useTranslations("LandingPage");
|
||||||
|
|
||||||
const togglePasswordType = () => {
|
const togglePasswordType = () => {
|
||||||
setPasswordType((prevType) => (prevType === "password" ? "text" : "password"));
|
setPasswordType((prevType) =>
|
||||||
|
prevType === "password" ? "text" : "password"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -86,18 +90,29 @@ const LoginForm = () => {
|
||||||
const profile = await getProfile(access_token);
|
const profile = await getProfile(access_token);
|
||||||
console.log("PROFILE : ", profile?.data?.data);
|
console.log("PROFILE : ", profile?.data?.data);
|
||||||
|
|
||||||
if (profile?.data?.data?.isInternational == true || profile?.data?.data?.isActive == false || profile?.data?.data?.isDelete == true) {
|
if (
|
||||||
|
profile?.data?.data?.isInternational == true ||
|
||||||
|
profile?.data?.data?.isActive == false ||
|
||||||
|
profile?.data?.data?.isDelete == true
|
||||||
|
) {
|
||||||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||||
Cookies.remove(cookieName);
|
Cookies.remove(cookieName);
|
||||||
});
|
});
|
||||||
warning("Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri", "/auth/login");
|
warning(
|
||||||
|
"Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri",
|
||||||
|
"/auth/login"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Cookies.set("home_path", profile?.data?.data?.homePath, {
|
Cookies.set("home_path", profile?.data?.data?.homePath, {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
});
|
||||||
Cookies.set("profile_picture", profile?.data?.data?.profilePictureUrl, {
|
Cookies.set(
|
||||||
expires: 1,
|
"profile_picture",
|
||||||
});
|
profile?.data?.data?.profilePictureUrl,
|
||||||
|
{
|
||||||
|
expires: 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
Cookies.set("state", profile?.data?.data?.userLevel?.name, {
|
Cookies.set("state", profile?.data?.data?.userLevel?.name, {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
});
|
||||||
|
|
@ -113,12 +128,20 @@ const LoginForm = () => {
|
||||||
setCookiesEncrypt("ulie", profile?.data?.data?.userLevel?.id, {
|
setCookiesEncrypt("ulie", profile?.data?.data?.userLevel?.id, {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
});
|
||||||
setCookiesEncrypt("uplie", profile?.data?.data?.userLevel?.parentLevelId, {
|
setCookiesEncrypt(
|
||||||
expires: 1,
|
"uplie",
|
||||||
});
|
profile?.data?.data?.userLevel?.parentLevelId,
|
||||||
setCookiesEncrypt("ulne", profile?.data?.data?.userLevel?.levelNumber, {
|
{
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
setCookiesEncrypt(
|
||||||
|
"ulne",
|
||||||
|
profile?.data?.data?.userLevel?.levelNumber,
|
||||||
|
{
|
||||||
|
expires: 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
setCookiesEncrypt("ufne", profile?.data?.data?.fullname, {
|
setCookiesEncrypt("ufne", profile?.data?.data?.fullname, {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
});
|
||||||
|
|
@ -128,7 +151,7 @@ const LoginForm = () => {
|
||||||
setCookiesEncrypt("uinse", profile?.data?.data?.instituteId, {
|
setCookiesEncrypt("uinse", profile?.data?.data?.instituteId, {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
});
|
});
|
||||||
|
console.log("ssaddd", profile?.data?.data?.roleId);
|
||||||
if (
|
if (
|
||||||
Number(profile?.data?.data?.roleId) == 2 ||
|
Number(profile?.data?.data?.roleId) == 2 ||
|
||||||
Number(profile?.data?.data?.roleId) == 3 ||
|
Number(profile?.data?.data?.roleId) == 3 ||
|
||||||
|
|
@ -136,9 +159,19 @@ const LoginForm = () => {
|
||||||
Number(profile?.data?.data?.roleId) == 9 ||
|
Number(profile?.data?.data?.roleId) == 9 ||
|
||||||
Number(profile?.data?.data?.roleId) == 10 ||
|
Number(profile?.data?.data?.roleId) == 10 ||
|
||||||
Number(profile?.data?.data?.roleId) == 11 ||
|
Number(profile?.data?.data?.roleId) == 11 ||
|
||||||
Number(profile?.data?.data?.roleId) == 12
|
Number(profile?.data?.data?.roleId) == 12 ||
|
||||||
|
Number(profile?.data?.data?.roleId) == 18
|
||||||
) {
|
) {
|
||||||
if (profile?.data?.data?.userLevel?.id == 761 || profile?.data?.data?.userLevel?.parentLevelId == 761) {
|
if (profile?.data?.data?.roleId === 18) {
|
||||||
|
window.location.href = "/in/dashboard/executive";
|
||||||
|
// router.push('/admin/dashboard');
|
||||||
|
Cookies.set("status", "login", {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
profile?.data?.data?.userLevel?.id == 761 ||
|
||||||
|
profile?.data?.data?.userLevel?.parentLevelId == 761
|
||||||
|
) {
|
||||||
window.location.href = "/in/welcome";
|
window.location.href = "/in/welcome";
|
||||||
Cookies.set("status", "login", {
|
Cookies.set("status", "login", {
|
||||||
expires: 1,
|
expires: 1,
|
||||||
|
|
@ -182,7 +215,11 @@ const LoginForm = () => {
|
||||||
"border-destructive": errors.username,
|
"border-destructive": errors.username,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.username?.message && <div className="text-destructive mt-2 text-sm">{errors.username.message}</div>}
|
{errors.username?.message && (
|
||||||
|
<div className="text-destructive mt-2 text-sm">
|
||||||
|
{errors.username.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3.5 space-y-2">
|
<div className="mt-3.5 space-y-2">
|
||||||
|
|
@ -190,12 +227,33 @@ const LoginForm = () => {
|
||||||
{t("password")}
|
{t("password")}
|
||||||
</Label>
|
</Label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input size="lg" disabled={isPending} {...register("password")} id="password" type={passwordType} className="peer" />
|
<Input
|
||||||
<div className="absolute top-1/2 -translate-y-1/2 ltr:right-4 rtl:left-4 cursor-pointer" onClick={togglePasswordType}>
|
size="lg"
|
||||||
{passwordType === "password" ? <Icon icon="heroicons:eye" className="w-5 h-5 text-default-400" /> : <Icon icon="heroicons:eye-slash" className="w-5 h-5 text-default-400" />}
|
disabled={isPending}
|
||||||
|
{...register("password")}
|
||||||
|
id="password"
|
||||||
|
type={passwordType}
|
||||||
|
className="peer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute top-1/2 -translate-y-1/2 ltr:right-4 rtl:left-4 cursor-pointer"
|
||||||
|
onClick={togglePasswordType}
|
||||||
|
>
|
||||||
|
{passwordType === "password" ? (
|
||||||
|
<Icon icon="heroicons:eye" className="w-5 h-5 text-default-400" />
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
icon="heroicons:eye-slash"
|
||||||
|
className="w-5 h-5 text-default-400"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{errors.password?.message && <div className="text-destructive mt-2 text-sm">{errors.password.message}</div>}
|
{errors.password?.message && (
|
||||||
|
<div className="text-destructive mt-2 text-sm">
|
||||||
|
{errors.password.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
|
|
@ -203,7 +261,10 @@ const LoginForm = () => {
|
||||||
<Checkbox id="checkbox" defaultChecked />
|
<Checkbox id="checkbox" defaultChecked />
|
||||||
<Label htmlFor="checkbox">{t("rememberMe")}</Label>
|
<Label htmlFor="checkbox">{t("rememberMe")}</Label>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/auth/forgot-password" className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium">
|
<Link
|
||||||
|
href="/auth/forgot-password"
|
||||||
|
className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium"
|
||||||
|
>
|
||||||
{t("forgotPass")}
|
{t("forgotPass")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -24,20 +24,6 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import {
|
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Eye,
|
|
||||||
MoreVertical,
|
|
||||||
PlusIcon,
|
|
||||||
Search,
|
|
||||||
SquarePen,
|
|
||||||
Trash2,
|
|
||||||
TrendingDown,
|
|
||||||
TrendingUp,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -61,8 +47,6 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { listDataMedia } from "@/service/broadcast/broadcast";
|
|
||||||
import { listEnableCategory } from "@/service/content/content";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
|
@ -75,7 +59,6 @@ const UserInternalTable = () => {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const dataChange = searchParams?.get("dataChange");
|
const dataChange = searchParams?.get("dataChange");
|
||||||
const [showData, setShowData] = React.useState("10");
|
const [showData, setShowData] = React.useState("10");
|
||||||
const [categories, setCategories] = React.useState<any>();
|
|
||||||
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>([]);
|
||||||
|
|
|
||||||
51
lib/menus.ts
51
lib/menus.ts
|
|
@ -1599,7 +1599,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
id: "contest",
|
id: "contest",
|
||||||
href: "/shared/contest",
|
href: "/shared/contest",
|
||||||
label: t("contest"),
|
label: t("lomba"),
|
||||||
active: pathname.includes("/contest"),
|
active: pathname.includes("/contest"),
|
||||||
icon: "ic:outline-emoji-events",
|
icon: "ic:outline-emoji-events",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
|
|
@ -1607,7 +1607,10 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else if ((Number(roleId) == 3 || Number(roleId) == 4) && Number(levelNumber) == 2) {
|
} else if (
|
||||||
|
(Number(roleId) == 3 || Number(roleId) == 4) &&
|
||||||
|
Number(levelNumber) == 2
|
||||||
|
) {
|
||||||
if (Number(userLevelId) != 761) {
|
if (Number(userLevelId) != 761) {
|
||||||
menusSelected = [
|
menusSelected = [
|
||||||
{
|
{
|
||||||
|
|
@ -1780,7 +1783,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
id: "contest",
|
id: "contest",
|
||||||
href: "/shared/contest",
|
href: "/shared/contest",
|
||||||
label: t("contest"),
|
label: t("lomba"),
|
||||||
active: pathname.includes("/contest"),
|
active: pathname.includes("/contest"),
|
||||||
icon: "ic:outline-emoji-events",
|
icon: "ic:outline-emoji-events",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
|
|
@ -1946,7 +1949,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
id: "contest",
|
id: "contest",
|
||||||
href: "/shared/contest",
|
href: "/shared/contest",
|
||||||
label: t("contest"),
|
label: t("lomba"),
|
||||||
active: pathname.includes("/contest"),
|
active: pathname.includes("/contest"),
|
||||||
icon: "ic:outline-emoji-events",
|
icon: "ic:outline-emoji-events",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
|
|
@ -1955,7 +1958,10 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
} else if ((Number(roleId) == 3 || Number(roleId) == 4) && Number(levelNumber) == 3) {
|
} else if (
|
||||||
|
(Number(roleId) == 3 || Number(roleId) == 4) &&
|
||||||
|
Number(levelNumber) == 3
|
||||||
|
) {
|
||||||
if (Number(userParentLevelId) != 761) {
|
if (Number(userParentLevelId) != 761) {
|
||||||
menusSelected = [
|
menusSelected = [
|
||||||
{
|
{
|
||||||
|
|
@ -2171,7 +2177,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
id: "contest",
|
id: "contest",
|
||||||
href: "/shared/contest",
|
href: "/shared/contest",
|
||||||
label: t("contest"),
|
label: t("lomba"),
|
||||||
active: pathname.includes("/contest"),
|
active: pathname.includes("/contest"),
|
||||||
icon: "ic:outline-emoji-events",
|
icon: "ic:outline-emoji-events",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
|
|
@ -2380,7 +2386,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
id: "contest",
|
id: "contest",
|
||||||
href: "/shared/contest",
|
href: "/shared/contest",
|
||||||
label: t("contest"),
|
label: "Lomba",
|
||||||
active: pathname.includes("/contest"),
|
active: pathname.includes("/contest"),
|
||||||
icon: "ic:outline-emoji-events",
|
icon: "ic:outline-emoji-events",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
|
|
@ -2663,7 +2669,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
id: "contest",
|
id: "contest",
|
||||||
href: "/shared/contest",
|
href: "/shared/contest",
|
||||||
label: t("contest"),
|
label: t("Lomba"),
|
||||||
active: pathname.includes("/contest"),
|
active: pathname.includes("/contest"),
|
||||||
icon: "ic:outline-emoji-events",
|
icon: "ic:outline-emoji-events",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
|
|
@ -2850,7 +2856,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/admin/media-tracking/news",
|
href: "/admin/media-tracking/tb-news",
|
||||||
label: "Tracking Beritra Hari Ini",
|
label: "Tracking Beritra Hari Ini",
|
||||||
active: pathname === "/media-tracking/news",
|
active: pathname === "/media-tracking/news",
|
||||||
icon: "heroicons:arrow-trending-up",
|
icon: "heroicons:arrow-trending-up",
|
||||||
|
|
@ -2867,7 +2873,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
{
|
{
|
||||||
id: "contest",
|
id: "contest",
|
||||||
href: "/shared/contest",
|
href: "/shared/contest",
|
||||||
label: t("contest"),
|
label: "Lomba",
|
||||||
active: pathname.includes("/contest"),
|
active: pathname.includes("/contest"),
|
||||||
icon: "ic:outline-emoji-events",
|
icon: "ic:outline-emoji-events",
|
||||||
submenus: [],
|
submenus: [],
|
||||||
|
|
@ -3188,6 +3194,31 @@ export function getMenuList(pathname: string, t: any): Group[] {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
} else if (Number(roleId) === 18) {
|
||||||
|
menusSelected = [
|
||||||
|
{
|
||||||
|
groupLabel: t("apps"),
|
||||||
|
id: "dashboard",
|
||||||
|
menus: [
|
||||||
|
{
|
||||||
|
id: "dashboard",
|
||||||
|
href: "/dashboard",
|
||||||
|
label: t("dashboard"),
|
||||||
|
active: pathname.includes("/dashboard"),
|
||||||
|
icon: "material-symbols:dashboard",
|
||||||
|
submenus: [
|
||||||
|
{
|
||||||
|
href: "/dashboard/executive",
|
||||||
|
label: "Executive",
|
||||||
|
active: pathname === "/dashboard/executive",
|
||||||
|
icon: "heroicons:arrow-trending-up",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return menusSelected;
|
return menusSelected;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -24,6 +24,7 @@
|
||||||
"@fullcalendar/timegrid": "^6.1.15",
|
"@fullcalendar/timegrid": "^6.1.15",
|
||||||
"@hookform/resolvers": "^3.9.0",
|
"@hookform/resolvers": "^3.9.0",
|
||||||
"@iconify/react": "^5.0.2",
|
"@iconify/react": "^5.0.2",
|
||||||
|
"@mui/x-charts": "^7.25.0",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.0.3",
|
"@radix-ui/react-aspect-ratio": "^1.0.3",
|
||||||
|
|
@ -68,8 +69,10 @@
|
||||||
"@types/sanitize-html": "^2.13.0",
|
"@types/sanitize-html": "^2.13.0",
|
||||||
"@vercel/analytics": "^1.3.1",
|
"@vercel/analytics": "^1.3.1",
|
||||||
"@wavesurfer/react": "^1.0.8",
|
"@wavesurfer/react": "^1.0.8",
|
||||||
|
"add": "^2.0.6",
|
||||||
"apexcharts": "^3.49.2",
|
"apexcharts": "^3.49.2",
|
||||||
"axios": "^1.7.8",
|
"axios": "^1.7.8",
|
||||||
|
"chart": "^0.1.2",
|
||||||
"chart.js": "^4.4.3",
|
"chart.js": "^4.4.3",
|
||||||
"ckeditor5-custom-build": "file:vendor/ckeditor5",
|
"ckeditor5-custom-build": "file:vendor/ckeditor5",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
|
@ -133,6 +136,7 @@
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
"rtl-detect": "^1.1.2",
|
"rtl-detect": "^1.1.2",
|
||||||
"sanitize-html": "^2.14.0",
|
"sanitize-html": "^2.14.0",
|
||||||
|
"shadcn": "^2.3.0",
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"sonner": "^1.5.0",
|
"sonner": "^1.5.0",
|
||||||
"sweetalert2": "^11.10.5",
|
"sweetalert2": "^11.10.5",
|
||||||
|
|
|
||||||
1366
pnpm-lock.yaml
1366
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
|
|
@ -16,6 +16,18 @@ export async function listDataMedia(
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listDataMediaBroadCast(
|
||||||
|
page: number,
|
||||||
|
limit: string,
|
||||||
|
search: string,
|
||||||
|
categoryFilter: string,
|
||||||
|
statusFilter: string
|
||||||
|
) {
|
||||||
|
const name = search || "";
|
||||||
|
const url = `media/list?isForAdmin=true&title=${name}&enablePage=1&sortBy=createdAt&sort=desc&size=${limit}&page=${page}&categoryId=${categoryFilter}&statusId=${statusFilter}`;
|
||||||
|
return httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getMediaBlastCampaignPage(page: number) {
|
export async function getMediaBlastCampaignPage(page: number) {
|
||||||
const url = `media/blast/campaign/list?enablePage=1&page=${page}`;
|
const url = `media/blast/campaign/list?enablePage=1&page=${page}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
|
|
|
||||||
|
|
@ -214,3 +214,8 @@ export async function deleteFile(data: any) {
|
||||||
const url = "media/file";
|
const url = "media/file";
|
||||||
return httpDeleteInterceptor(url, data);
|
return httpDeleteInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteSPIT(id: any) {
|
||||||
|
const url = `media/spit?id=${id}`;
|
||||||
|
return httpDeleteInterceptor(url);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,25 @@ import {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export async function listTask(
|
export async function listTask(
|
||||||
title: string = "",
|
|
||||||
page: any,
|
page: any,
|
||||||
|
title: string = "",
|
||||||
size: any,
|
size: any,
|
||||||
taskType: string
|
code: any,
|
||||||
|
createdAt: any,
|
||||||
|
taskType: string,
|
||||||
|
status: number[]
|
||||||
) {
|
) {
|
||||||
const url = `assignment/list?enablePage=1&size=${size}&page=${page}&title=${title}&taskType=${taskType}`;
|
let statusQuery = "";
|
||||||
return httpGetInterceptor(url);
|
|
||||||
|
if (status.includes(1)) {
|
||||||
|
statusQuery = "&isDone=true";
|
||||||
|
} else if (status.includes(2)) {
|
||||||
|
statusQuery = "&isDone=false";
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpGetInterceptor(
|
||||||
|
`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) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type Editor from './editor/editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
export declare const DEFAULT_GROUP_ID: "common";
|
export declare const DEFAULT_GROUP_ID: "common";
|
||||||
/**
|
/**
|
||||||
* A common namespace for various accessibility features of the editor.
|
* A common namespace for various accessibility features of the editor.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* @module core/command
|
* @module core/command
|
||||||
*/
|
*/
|
||||||
import { type DecoratedMethodEvent } from '@ckeditor/ckeditor5-utils';
|
import { type DecoratedMethodEvent } from '@ckeditor/ckeditor5-utils';
|
||||||
import type Editor from './editor/editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
declare const Command_base: {
|
declare const Command_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
|
|
|
||||||
2
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-core/src/commandcollection.d.ts
generated
vendored
2
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-core/src/commandcollection.d.ts
generated
vendored
|
|
@ -2,7 +2,7 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type Command from './command.js';
|
import type Command from '@ckeditor/ckeditor5-core/src/command.js';
|
||||||
/**
|
/**
|
||||||
* Collection of commands. Its instance is available in {@link module:core/editor/editor~Editor#commands `editor.commands`}.
|
* Collection of commands. Its instance is available in {@link module:core/editor/editor~Editor#commands `editor.commands`}.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@
|
||||||
* @module core/context
|
* @module core/context
|
||||||
*/
|
*/
|
||||||
import { Config, Collection, Locale, type LocaleTranslate } from '@ckeditor/ckeditor5-utils';
|
import { Config, Collection, Locale, type LocaleTranslate } from '@ckeditor/ckeditor5-utils';
|
||||||
import PluginCollection from './plugincollection.js';
|
import PluginCollection from '@ckeditor/ckeditor5-core/src/plugincollection.js';
|
||||||
import type Editor from './editor/editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
import type { LoadedPlugins, PluginConstructor } from './plugin.js';
|
import type { LoadedPlugins, PluginConstructor } from '@ckeditor/ckeditor5-core/src/plugin.js';
|
||||||
import type { EditorConfig } from './editor/editorconfig.js';
|
import type { EditorConfig } from '@ckeditor/ckeditor5-core/src/editor/editorconfig.js';
|
||||||
/**
|
/**
|
||||||
* Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors}
|
* Provides a common, higher-level environment for solutions that use multiple {@link module:core/editor/editor~Editor editors}
|
||||||
* or plugins that work outside the editor. Use it instead of {@link module:core/editor/editor~Editor.create `Editor.create()`}
|
* or plugins that work outside the editor. Use it instead of {@link module:core/editor/editor~Editor.create `Editor.create()`}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
* @module core/contextplugin
|
* @module core/contextplugin
|
||||||
*/
|
*/
|
||||||
import { type Collection, type Config, type Locale, type LocaleTranslate } from '@ckeditor/ckeditor5-utils';
|
import { type Collection, type Config, type Locale, type LocaleTranslate } from '@ckeditor/ckeditor5-utils';
|
||||||
import type Editor from './editor/editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
import type { EditorConfig } from './editor/editorconfig.js';
|
import type { EditorConfig } from '@ckeditor/ckeditor5-core/src/editor/editorconfig.js';
|
||||||
import type Context from './context.js';
|
import type Context from '@ckeditor/ckeditor5-core/src/context.js';
|
||||||
import type { PluginDependencies, PluginInterface } from './plugin.js';
|
import type { PluginDependencies, PluginInterface } from '@ckeditor/ckeditor5-core/src/plugin.js';
|
||||||
import type PluginCollection from './plugincollection.js';
|
import type PluginCollection from '@ckeditor/ckeditor5-core/src/plugincollection.js';
|
||||||
declare const ContextPlugin_base: {
|
declare const ContextPlugin_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* @module core/editingkeystrokehandler
|
* @module core/editingkeystrokehandler
|
||||||
*/
|
*/
|
||||||
import { KeystrokeHandler, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
import { KeystrokeHandler, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
||||||
import type Editor from './editor/editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
/**
|
/**
|
||||||
* A keystroke handler for editor editing. Its instance is available
|
* A keystroke handler for editor editing. Its instance is available
|
||||||
* in {@link module:core/editor/editor~Editor#keystrokes} so plugins
|
* in {@link module:core/editor/editor~Editor#keystrokes} so plugins
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@
|
||||||
import { Config, type Locale, type LocaleTranslate } from '@ckeditor/ckeditor5-utils';
|
import { Config, type Locale, type LocaleTranslate } from '@ckeditor/ckeditor5-utils';
|
||||||
import { Conversion, DataController, EditingController, Model } from '@ckeditor/ckeditor5-engine';
|
import { Conversion, DataController, EditingController, Model } from '@ckeditor/ckeditor5-engine';
|
||||||
import type { EditorUI } from '@ckeditor/ckeditor5-ui';
|
import type { EditorUI } from '@ckeditor/ckeditor5-ui';
|
||||||
import Context from '../context.js';
|
import Context from '@ckeditor/ckeditor5-core/src/context.js';
|
||||||
import PluginCollection from '../plugincollection.js';
|
import PluginCollection from '@ckeditor/ckeditor5-core/src/plugincollection.js';
|
||||||
import CommandCollection, { type CommandsMap } from '../commandcollection.js';
|
import CommandCollection, { type CommandsMap } from '@ckeditor/ckeditor5-core/src/commandcollection.js';
|
||||||
import EditingKeystrokeHandler from '../editingkeystrokehandler.js';
|
import EditingKeystrokeHandler from '@ckeditor/ckeditor5-core/src/editingkeystrokehandler.js';
|
||||||
import Accessibility from '../accessibility.js';
|
import Accessibility from '@ckeditor/ckeditor5-core/src/accessibility.js';
|
||||||
import type { LoadedPlugins, PluginConstructor } from '../plugin.js';
|
import type { LoadedPlugins, PluginConstructor } from '@ckeditor/ckeditor5-core/src/plugin.js';
|
||||||
import type { EditorConfig } from './editorconfig.js';
|
import type { EditorConfig } from '@ckeditor/ckeditor5-core/src/editor/editorconfig.js';
|
||||||
declare const Editor_base: {
|
declare const Editor_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
|
|
|
||||||
6
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-core/src/editor/editorconfig.d.ts
generated
vendored
6
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-core/src/editor/editorconfig.d.ts
generated
vendored
|
|
@ -6,9 +6,9 @@
|
||||||
* @module core/editor/editorconfig
|
* @module core/editor/editorconfig
|
||||||
*/
|
*/
|
||||||
import type { ArrayOrItem, Translations } from '@ckeditor/ckeditor5-utils';
|
import type { ArrayOrItem, Translations } from '@ckeditor/ckeditor5-utils';
|
||||||
import type Context from '../context.js';
|
import type Context from '@ckeditor/ckeditor5-core/src/context.js';
|
||||||
import type { PluginConstructor } from '../plugin.js';
|
import type { PluginConstructor } from '@ckeditor/ckeditor5-core/src/plugin.js';
|
||||||
import type Editor from './editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
import type { MenuBarConfig } from '@ckeditor/ckeditor5-ui';
|
import type { MenuBarConfig } from '@ckeditor/ckeditor5-ui';
|
||||||
/**
|
/**
|
||||||
* CKEditor configuration options.
|
* CKEditor configuration options.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type { default as Editor } from '../editor.js';
|
import type { default as Editor } from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
import type { ElementApi } from './elementapimixin.js';
|
import type { ElementApi } from '@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin.js';
|
||||||
/**
|
/**
|
||||||
* Checks if the editor is initialized on a `<textarea>` element that belongs to a form. If yes, it updates the editor's element
|
* Checks if the editor is initialized on a `<textarea>` element that belongs to a form. If yes, it updates the editor's element
|
||||||
* content before submitting the form.
|
* content before submitting the form.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
/**
|
/**
|
||||||
* @module core/editor/utils/dataapimixin
|
* @module core/editor/utils/dataapimixin
|
||||||
*/
|
*/
|
||||||
import type Editor from '../editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
import type { Constructor } from '@ckeditor/ckeditor5-utils';
|
import type { Constructor } from '@ckeditor/ckeditor5-utils';
|
||||||
/**
|
/**
|
||||||
* Implementation of the {@link module:core/editor/utils/dataapimixin~DataApi}.
|
* Implementation of the {@link module:core/editor/utils/dataapimixin~DataApi}.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* @module core/editor/utils/elementapimixin
|
* @module core/editor/utils/elementapimixin
|
||||||
*/
|
*/
|
||||||
import { type Constructor, type Mixed } from '@ckeditor/ckeditor5-utils';
|
import { type Constructor, type Mixed } from '@ckeditor/ckeditor5-utils';
|
||||||
import type Editor from '../editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
/**
|
/**
|
||||||
* Implementation of the {@link module:core/editor/utils/elementapimixin~ElementApi}.
|
* Implementation of the {@link module:core/editor/utils/elementapimixin~ElementApi}.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type { default as Editor } from '../editor.js';
|
import type { default as Editor } from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
/**
|
/**
|
||||||
* Marks the source element on which the editor was initialized. This prevents other editor instances from using this element.
|
* Marks the source element on which the editor was initialized. This prevents other editor instances from using this element.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,23 @@
|
||||||
/**
|
/**
|
||||||
* @module core
|
* @module core
|
||||||
*/
|
*/
|
||||||
export { default as Plugin, type PluginDependencies, type PluginConstructor } from './plugin.js';
|
export { default as Plugin, type PluginDependencies, type PluginConstructor } from '@ckeditor/ckeditor5-core/src/plugin.js';
|
||||||
export { default as Command, type CommandExecuteEvent } from './command.js';
|
export { default as Command, type CommandExecuteEvent } from '@ckeditor/ckeditor5-core/src/command.js';
|
||||||
export { default as MultiCommand } from './multicommand.js';
|
export { default as MultiCommand } from '@ckeditor/ckeditor5-core/src/multicommand.js';
|
||||||
export type { CommandsMap } from './commandcollection.js';
|
export type { CommandsMap } from '@ckeditor/ckeditor5-core/src/commandcollection.js';
|
||||||
export type { PluginsMap, default as PluginCollection } from './plugincollection.js';
|
export type { PluginsMap, default as PluginCollection } from '@ckeditor/ckeditor5-core/src/plugincollection.js';
|
||||||
export { default as Context, type ContextConfig } from './context.js';
|
export { default as Context, type ContextConfig } from '@ckeditor/ckeditor5-core/src/context.js';
|
||||||
export { default as ContextPlugin, type ContextPluginDependencies } from './contextplugin.js';
|
export { default as ContextPlugin, type ContextPluginDependencies } from '@ckeditor/ckeditor5-core/src/contextplugin.js';
|
||||||
export { type EditingKeystrokeCallback } from './editingkeystrokehandler.js';
|
export { type EditingKeystrokeCallback } from '@ckeditor/ckeditor5-core/src/editingkeystrokehandler.js';
|
||||||
export type { PartialBy, NonEmptyArray } from './typings.js';
|
export type { PartialBy, NonEmptyArray } from '@ckeditor/ckeditor5-core/src/typings.js';
|
||||||
export { default as Editor, type EditorReadyEvent, type EditorDestroyEvent } from './editor/editor.js';
|
export { default as Editor, type EditorReadyEvent, type EditorDestroyEvent } from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
export type { EditorConfig, LanguageConfig, ToolbarConfig, ToolbarConfigItem, UiConfig } from './editor/editorconfig.js';
|
export type { EditorConfig, LanguageConfig, ToolbarConfig, ToolbarConfigItem, UiConfig } from '@ckeditor/ckeditor5-core/src/editor/editorconfig.js';
|
||||||
export { default as attachToForm } from './editor/utils/attachtoform.js';
|
export { default as attachToForm } from '@ckeditor/ckeditor5-core/src/editor/utils/attachtoform.js';
|
||||||
export { default as DataApiMixin, type DataApi } from './editor/utils/dataapimixin.js';
|
export { default as DataApiMixin, type DataApi } from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin.js';
|
||||||
export { default as ElementApiMixin, type ElementApi } from './editor/utils/elementapimixin.js';
|
export { default as ElementApiMixin, type ElementApi } from '@ckeditor/ckeditor5-core/src/editor/utils/elementapimixin.js';
|
||||||
export { default as secureSourceElement } from './editor/utils/securesourceelement.js';
|
export { default as secureSourceElement } from '@ckeditor/ckeditor5-core/src/editor/utils/securesourceelement.js';
|
||||||
export { default as PendingActions, type PendingAction } from './pendingactions.js';
|
export { default as PendingActions, type PendingAction } from '@ckeditor/ckeditor5-core/src/pendingactions.js';
|
||||||
export type { KeystrokeInfos as KeystrokeInfoDefinitions, KeystrokeInfoGroup as KeystrokeInfoGroupDefinition, KeystrokeInfoCategory as KeystrokeInfoCategoryDefinition, KeystrokeInfoDefinition as KeystrokeInfoDefinition } from './accessibility.js';
|
export type { KeystrokeInfos as KeystrokeInfoDefinitions, KeystrokeInfoGroup as KeystrokeInfoGroupDefinition, KeystrokeInfoCategory as KeystrokeInfoCategoryDefinition, KeystrokeInfoDefinition as KeystrokeInfoDefinition } from '@ckeditor/ckeditor5-core/src/accessibility.js';
|
||||||
export declare const icons: {
|
export declare const icons: {
|
||||||
bold: string;
|
bold: string;
|
||||||
cancel: string;
|
cancel: string;
|
||||||
|
|
@ -86,4 +86,4 @@ export declare const icons: {
|
||||||
outdent: string;
|
outdent: string;
|
||||||
table: string;
|
table: string;
|
||||||
};
|
};
|
||||||
import './augmentation.js';
|
import '@ckeditor/ckeditor5-core/src/augmentation.js';
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
/**
|
/**
|
||||||
* @module core/multicommand
|
* @module core/multicommand
|
||||||
*/
|
*/
|
||||||
import Command from './command.js';
|
import Command from '@ckeditor/ckeditor5-core/src/command.js';
|
||||||
import { type PriorityString } from '@ckeditor/ckeditor5-utils';
|
import { type PriorityString } from '@ckeditor/ckeditor5-utils';
|
||||||
/**
|
/**
|
||||||
* A CKEditor command that aggregates other commands.
|
* A CKEditor command that aggregates other commands.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
/**
|
/**
|
||||||
* @module core/pendingactions
|
* @module core/pendingactions
|
||||||
*/
|
*/
|
||||||
import ContextPlugin from './contextplugin.js';
|
import ContextPlugin from '@ckeditor/ckeditor5-core/src/contextplugin.js';
|
||||||
import { type CollectionAddEvent, type CollectionRemoveEvent, type Observable } from '@ckeditor/ckeditor5-utils';
|
import { type CollectionAddEvent, type CollectionRemoveEvent, type Observable } from '@ckeditor/ckeditor5-utils';
|
||||||
/**
|
/**
|
||||||
* The list of pending editor actions.
|
* The list of pending editor actions.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type Editor from './editor/editor.js';
|
import type Editor from '@ckeditor/ckeditor5-core/src/editor/editor.js';
|
||||||
declare const Plugin_base: {
|
declare const Plugin_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
|
|
|
||||||
2
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-core/src/plugincollection.d.ts
generated
vendored
2
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-core/src/plugincollection.d.ts
generated
vendored
|
|
@ -2,7 +2,7 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type { LoadedPlugins, PluginClassConstructor, PluginConstructor, PluginInterface } from './plugin.js';
|
import type { LoadedPlugins, PluginClassConstructor, PluginConstructor, PluginInterface } from '@ckeditor/ckeditor5-core/src/plugin.js';
|
||||||
declare const PluginCollection_base: {
|
declare const PluginCollection_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,21 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import Mapper from '../conversion/mapper.js';
|
import Mapper from '@ckeditor/ckeditor5-engine/src/conversion/mapper.js';
|
||||||
import DowncastDispatcher from '../conversion/downcastdispatcher.js';
|
import DowncastDispatcher from '@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js';
|
||||||
import UpcastDispatcher from '../conversion/upcastdispatcher.js';
|
import UpcastDispatcher from '@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js';
|
||||||
import ViewDocumentFragment from '../view/documentfragment.js';
|
import ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment.js';
|
||||||
import ViewDocument from '../view/document.js';
|
import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document.js';
|
||||||
import type ViewElement from '../view/element.js';
|
import type ViewElement from '@ckeditor/ckeditor5-engine/src/view/element.js';
|
||||||
import type { StylesProcessor } from '../view/stylesmap.js';
|
import type { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap.js';
|
||||||
import type { MatcherPattern } from '../view/matcher.js';
|
import type { MatcherPattern } from '@ckeditor/ckeditor5-engine/src/view/matcher.js';
|
||||||
import type Model from '../model/model.js';
|
import type Model from '@ckeditor/ckeditor5-engine/src/model/model.js';
|
||||||
import type ModelElement from '../model/element.js';
|
import type ModelElement from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import type ModelDocumentFragment from '../model/documentfragment.js';
|
import type ModelDocumentFragment from '@ckeditor/ckeditor5-engine/src/model/documentfragment.js';
|
||||||
import type { SchemaContextDefinition } from '../model/schema.js';
|
import type { SchemaContextDefinition } from '@ckeditor/ckeditor5-engine/src/model/schema.js';
|
||||||
import type { BatchType } from '../model/batch.js';
|
import type { BatchType } from '@ckeditor/ckeditor5-engine/src/model/batch.js';
|
||||||
import HtmlDataProcessor from '../dataprocessor/htmldataprocessor.js';
|
import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js';
|
||||||
import type DataProcessor from '../dataprocessor/dataprocessor.js';
|
import type DataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/dataprocessor.js';
|
||||||
declare const DataController_base: {
|
declare const DataController_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import View from '../view/view.js';
|
import View from '@ckeditor/ckeditor5-engine/src/view/view.js';
|
||||||
import Mapper from '../conversion/mapper.js';
|
import Mapper from '@ckeditor/ckeditor5-engine/src/conversion/mapper.js';
|
||||||
import DowncastDispatcher from '../conversion/downcastdispatcher.js';
|
import DowncastDispatcher from '@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js';
|
||||||
import type { default as Model } from '../model/model.js';
|
import type { default as Model } from '@ckeditor/ckeditor5-engine/src/model/model.js';
|
||||||
import type ModelItem from '../model/item.js';
|
import type ModelItem from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
import type { Marker } from '../model/markercollection.js';
|
import type { Marker } from '@ckeditor/ckeditor5-engine/src/model/markercollection.js';
|
||||||
import type { StylesProcessor } from '../view/stylesmap.js';
|
import type { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap.js';
|
||||||
declare const EditingController_base: {
|
declare const EditingController_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
new (): import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
prototype: import("@ckeditor/ckeditor5-utils").Observable;
|
||||||
|
|
|
||||||
12
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversion.d.ts
generated
vendored
12
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/conversion/conversion.d.ts
generated
vendored
|
|
@ -6,12 +6,12 @@
|
||||||
* @module engine/conversion/conversion
|
* @module engine/conversion/conversion
|
||||||
*/
|
*/
|
||||||
import { type ArrayOrItem, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
import { type ArrayOrItem, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
||||||
import UpcastHelpers from './upcasthelpers.js';
|
import UpcastHelpers from '@ckeditor/ckeditor5-engine/src/conversion/upcasthelpers.js';
|
||||||
import DowncastHelpers, { type AttributeCreatorFunction, type AttributeDescriptor } from './downcasthelpers.js';
|
import DowncastHelpers, { type AttributeCreatorFunction, type AttributeDescriptor } from '@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers.js';
|
||||||
import type DowncastDispatcher from './downcastdispatcher.js';
|
import type DowncastDispatcher from '@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js';
|
||||||
import type UpcastDispatcher from './upcastdispatcher.js';
|
import type UpcastDispatcher from '@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js';
|
||||||
import type ElementDefinition from '../view/elementdefinition.js';
|
import type ElementDefinition from '@ckeditor/ckeditor5-engine/src/view/elementdefinition.js';
|
||||||
import type { MatcherPattern } from '../view/matcher.js';
|
import type { MatcherPattern } from '@ckeditor/ckeditor5-engine/src/view/matcher.js';
|
||||||
/**
|
/**
|
||||||
* A utility class that helps add converters to upcast and downcast dispatchers.
|
* A utility class that helps add converters to upcast and downcast dispatchers.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,19 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/conversion/downcastdispatcher
|
* @module engine/conversion/downcastdispatcher
|
||||||
*/
|
*/
|
||||||
import Consumable from './modelconsumable.js';
|
import Consumable from '@ckeditor/ckeditor5-engine/src/conversion/modelconsumable.js';
|
||||||
import Range from '../model/range.js';
|
import Range from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
import type { default as Differ, DiffItem } from '../model/differ.js';
|
import type { default as Differ, DiffItem } from '@ckeditor/ckeditor5-engine/src/model/differ.js';
|
||||||
import type { default as MarkerCollection } from '../model/markercollection.js';
|
import type { default as MarkerCollection } from '@ckeditor/ckeditor5-engine/src/model/markercollection.js';
|
||||||
import type DocumentSelection from '../model/documentselection.js';
|
import type DocumentSelection from '@ckeditor/ckeditor5-engine/src/model/documentselection.js';
|
||||||
import type DowncastWriter from '../view/downcastwriter.js';
|
import type DowncastWriter from '@ckeditor/ckeditor5-engine/src/view/downcastwriter.js';
|
||||||
import type Element from '../model/element.js';
|
import type Element from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import type Item from '../model/item.js';
|
import type Item from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
import type Mapper from './mapper.js';
|
import type Mapper from '@ckeditor/ckeditor5-engine/src/conversion/mapper.js';
|
||||||
import type Position from '../model/position.js';
|
import type Position from '@ckeditor/ckeditor5-engine/src/model/position.js';
|
||||||
import type Schema from '../model/schema.js';
|
import type Schema from '@ckeditor/ckeditor5-engine/src/model/schema.js';
|
||||||
import type Selection from '../model/selection.js';
|
import type Selection from '@ckeditor/ckeditor5-engine/src/model/selection.js';
|
||||||
import type ViewElement from '../view/element.js';
|
import type ViewElement from '@ckeditor/ckeditor5-engine/src/view/element.js';
|
||||||
declare const DowncastDispatcher_base: {
|
declare const DowncastDispatcher_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,23 @@
|
||||||
*
|
*
|
||||||
* @module engine/conversion/downcasthelpers
|
* @module engine/conversion/downcasthelpers
|
||||||
*/
|
*/
|
||||||
import ModelRange from '../model/range.js';
|
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
import ModelSelection from '../model/selection.js';
|
import ModelSelection from '@ckeditor/ckeditor5-engine/src/model/selection.js';
|
||||||
import ModelDocumentSelection from '../model/documentselection.js';
|
import ModelDocumentSelection from '@ckeditor/ckeditor5-engine/src/model/documentselection.js';
|
||||||
import ModelElement from '../model/element.js';
|
import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import ModelPosition from '../model/position.js';
|
import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position.js';
|
||||||
import ViewAttributeElement from '../view/attributeelement.js';
|
import ViewAttributeElement from '@ckeditor/ckeditor5-engine/src/view/attributeelement.js';
|
||||||
import ConversionHelpers from './conversionhelpers.js';
|
import ConversionHelpers from '@ckeditor/ckeditor5-engine/src/conversion/conversionhelpers.js';
|
||||||
import type { default as DowncastDispatcher, DowncastConversionApi } from './downcastdispatcher.js';
|
import type { default as DowncastDispatcher, DowncastConversionApi } from '@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js';
|
||||||
import type ModelConsumable from './modelconsumable.js';
|
import type ModelConsumable from '@ckeditor/ckeditor5-engine/src/conversion/modelconsumable.js';
|
||||||
import type ModelNode from '../model/node.js';
|
import type ModelNode from '@ckeditor/ckeditor5-engine/src/model/node.js';
|
||||||
import type ModelItem from '../model/item.js';
|
import type ModelItem from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
import type ModelTextProxy from '../model/textproxy.js';
|
import type ModelTextProxy from '@ckeditor/ckeditor5-engine/src/model/textproxy.js';
|
||||||
import type ModelText from '../model/text.js';
|
import type ModelText from '@ckeditor/ckeditor5-engine/src/model/text.js';
|
||||||
import type DowncastWriter from '../view/downcastwriter.js';
|
import type DowncastWriter from '@ckeditor/ckeditor5-engine/src/view/downcastwriter.js';
|
||||||
import type ElementDefinition from '../view/elementdefinition.js';
|
import type ElementDefinition from '@ckeditor/ckeditor5-engine/src/view/elementdefinition.js';
|
||||||
import type UIElement from '../view/uielement.js';
|
import type UIElement from '@ckeditor/ckeditor5-engine/src/view/uielement.js';
|
||||||
import type ViewElement from '../view/element.js';
|
import type ViewElement from '@ckeditor/ckeditor5-engine/src/view/element.js';
|
||||||
import { type EventInfo, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
import { type EventInfo, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
||||||
/**
|
/**
|
||||||
* Downcast conversion helper functions.
|
* Downcast conversion helper functions.
|
||||||
|
|
|
||||||
18
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/conversion/mapper.d.ts
generated
vendored
18
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/conversion/mapper.d.ts
generated
vendored
|
|
@ -5,15 +5,15 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/conversion/mapper
|
* @module engine/conversion/mapper
|
||||||
*/
|
*/
|
||||||
import ModelPosition from '../model/position.js';
|
import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position.js';
|
||||||
import ModelRange from '../model/range.js';
|
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
import ViewPosition from '../view/position.js';
|
import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position.js';
|
||||||
import ViewRange from '../view/range.js';
|
import ViewRange from '@ckeditor/ckeditor5-engine/src/view/range.js';
|
||||||
import type ViewDocumentFragment from '../view/documentfragment.js';
|
import type ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment.js';
|
||||||
import type ViewElement from '../view/element.js';
|
import type ViewElement from '@ckeditor/ckeditor5-engine/src/view/element.js';
|
||||||
import type ModelElement from '../model/element.js';
|
import type ModelElement from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import type ModelDocumentFragment from '../model/documentfragment.js';
|
import type ModelDocumentFragment from '@ckeditor/ckeditor5-engine/src/model/documentfragment.js';
|
||||||
import type ViewNode from '../view/node.js';
|
import type ViewNode from '@ckeditor/ckeditor5-engine/src/view/node.js';
|
||||||
declare const Mapper_base: {
|
declare const Mapper_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/conversion/modelconsumable
|
* @module engine/conversion/modelconsumable
|
||||||
*/
|
*/
|
||||||
import TextProxy from '../model/textproxy.js';
|
import TextProxy from '@ckeditor/ckeditor5-engine/src/model/textproxy.js';
|
||||||
import type Item from '../model/item.js';
|
import type Item from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
import type Selection from '../model/selection.js';
|
import type Selection from '@ckeditor/ckeditor5-engine/src/model/selection.js';
|
||||||
import type DocumentSelection from '../model/documentselection.js';
|
import type DocumentSelection from '@ckeditor/ckeditor5-engine/src/model/documentselection.js';
|
||||||
import type Range from '../model/range.js';
|
import type Range from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
/**
|
/**
|
||||||
* Manages a list of consumable values for the {@link module:engine/model/item~Item model items}.
|
* Manages a list of consumable values for the {@link module:engine/model/item~Item model items}.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,18 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/conversion/upcastdispatcher
|
* @module engine/conversion/upcastdispatcher
|
||||||
*/
|
*/
|
||||||
import ViewConsumable from './viewconsumable.js';
|
import ViewConsumable from '@ckeditor/ckeditor5-engine/src/conversion/viewconsumable.js';
|
||||||
import ModelRange from '../model/range.js';
|
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
import ModelPosition from '../model/position.js';
|
import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position.js';
|
||||||
import type ModelElement from '../model/element.js';
|
import type ModelElement from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import type ModelNode from '../model/node.js';
|
import type ModelNode from '@ckeditor/ckeditor5-engine/src/model/node.js';
|
||||||
import type ViewElement from '../view/element.js';
|
import type ViewElement from '@ckeditor/ckeditor5-engine/src/view/element.js';
|
||||||
import type ViewText from '../view/text.js';
|
import type ViewText from '@ckeditor/ckeditor5-engine/src/view/text.js';
|
||||||
import type ViewDocumentFragment from '../view/documentfragment.js';
|
import type ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment.js';
|
||||||
import type ModelDocumentFragment from '../model/documentfragment.js';
|
import type ModelDocumentFragment from '@ckeditor/ckeditor5-engine/src/model/documentfragment.js';
|
||||||
import type { default as Schema, SchemaContextDefinition } from '../model/schema.js';
|
import type { default as Schema, SchemaContextDefinition } from '@ckeditor/ckeditor5-engine/src/model/schema.js';
|
||||||
import type ModelWriter from '../model/writer.js';
|
import type ModelWriter from '@ckeditor/ckeditor5-engine/src/model/writer.js';
|
||||||
import type ViewItem from '../view/item.js';
|
import type ViewItem from '@ckeditor/ckeditor5-engine/src/view/item.js';
|
||||||
declare const UpcastDispatcher_base: {
|
declare const UpcastDispatcher_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
prototype: import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import { type ClassPatterns, type MatcherPattern, type PropertyPatterns } from '../view/matcher.js';
|
import { type ClassPatterns, type MatcherPattern, type PropertyPatterns } from '@ckeditor/ckeditor5-engine/src/view/matcher.js';
|
||||||
import ConversionHelpers from './conversionhelpers.js';
|
import ConversionHelpers from '@ckeditor/ckeditor5-engine/src/conversion/conversionhelpers.js';
|
||||||
import type { default as UpcastDispatcher, UpcastConversionApi, UpcastConversionData } from './upcastdispatcher.js';
|
import type { default as UpcastDispatcher, UpcastConversionApi, UpcastConversionData } from '@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js';
|
||||||
import type ModelElement from '../model/element.js';
|
import type ModelElement from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import type { ViewDocumentFragment, ViewElement, ViewText } from '../index.js';
|
import type { ViewDocumentFragment, ViewElement, ViewText } from '@ckeditor/ckeditor5-engine';
|
||||||
import type Mapper from './mapper.js';
|
import type Mapper from '@ckeditor/ckeditor5-engine/src/conversion/mapper.js';
|
||||||
import type Model from '../model/model.js';
|
import type Model from '@ckeditor/ckeditor5-engine/src/model/model.js';
|
||||||
import type ViewSelection from '../view/selection.js';
|
import type ViewSelection from '@ckeditor/ckeditor5-engine/src/view/selection.js';
|
||||||
import type ViewDocumentSelection from '../view/documentselection.js';
|
import type ViewDocumentSelection from '@ckeditor/ckeditor5-engine/src/view/documentselection.js';
|
||||||
import { type EventInfo, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
import { type EventInfo, type PriorityString } from '@ckeditor/ckeditor5-utils';
|
||||||
/**
|
/**
|
||||||
* Contains the {@link module:engine/view/view view} to {@link module:engine/model/model model} converters for
|
* Contains the {@link module:engine/view/view view} to {@link module:engine/model/model model} converters for
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type Element from '../view/element.js';
|
import type Element from '@ckeditor/ckeditor5-engine/src/view/element.js';
|
||||||
import type Node from '../view/node.js';
|
import type Node from '@ckeditor/ckeditor5-engine/src/view/node.js';
|
||||||
import type Text from '../view/text.js';
|
import type Text from '@ckeditor/ckeditor5-engine/src/view/text.js';
|
||||||
import type DocumentFragment from '../view/documentfragment.js';
|
import type DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment.js';
|
||||||
import type { Match } from '../view/matcher.js';
|
import type { Match } from '@ckeditor/ckeditor5-engine/src/view/matcher.js';
|
||||||
/**
|
/**
|
||||||
* Class used for handling consumption of view {@link module:engine/view/element~Element elements},
|
* Class used for handling consumption of view {@link module:engine/view/element~Element elements},
|
||||||
* {@link module:engine/view/text~Text text nodes} and {@link module:engine/view/documentfragment~DocumentFragment document fragments}.
|
* {@link module:engine/view/text~Text text nodes} and {@link module:engine/view/documentfragment~DocumentFragment document fragments}.
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/dataprocessor/dataprocessor
|
* @module engine/dataprocessor/dataprocessor
|
||||||
*/
|
*/
|
||||||
import type ViewDocumentFragment from '../view/documentfragment.js';
|
import type ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment.js';
|
||||||
import type { MatcherPattern } from '../view/matcher.js';
|
import type { MatcherPattern } from '@ckeditor/ckeditor5-engine/src/view/matcher.js';
|
||||||
/**
|
/**
|
||||||
* The data processor interface. It should be implemented by actual data processors.
|
* The data processor interface. It should be implemented by actual data processors.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import DomConverter from '../view/domconverter.js';
|
import DomConverter from '@ckeditor/ckeditor5-engine/src/view/domconverter.js';
|
||||||
import type DataProcessor from './dataprocessor.js';
|
import type DataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/dataprocessor.js';
|
||||||
import type HtmlWriter from './htmlwriter.js';
|
import type HtmlWriter from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmlwriter.js';
|
||||||
import type ViewDocument from '../view/document.js';
|
import type ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document.js';
|
||||||
import type ViewDocumentFragment from '../view/documentfragment.js';
|
import type ViewDocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment.js';
|
||||||
import type { MatcherPattern } from '../view/matcher.js';
|
import type { MatcherPattern } from '@ckeditor/ckeditor5-engine/src/view/matcher.js';
|
||||||
/**
|
/**
|
||||||
* The HTML data processor class.
|
* The HTML data processor class.
|
||||||
* This data processor implementation uses HTML as input and output data.
|
* This data processor implementation uses HTML as input and output data.
|
||||||
|
|
|
||||||
|
|
@ -5,109 +5,109 @@
|
||||||
/**
|
/**
|
||||||
* @module engine
|
* @module engine
|
||||||
*/
|
*/
|
||||||
export * from './view/placeholder.js';
|
export * from '@ckeditor/ckeditor5-engine/src/view/placeholder.js';
|
||||||
export { default as EditingController } from './controller/editingcontroller.js';
|
export { default as EditingController } from '@ckeditor/ckeditor5-engine/src/controller/editingcontroller.js';
|
||||||
export { default as DataController, type DataControllerInitEvent, type DataControllerSetEvent, type DataControllerToModelEvent, type DataControllerToViewEvent } from './controller/datacontroller.js';
|
export { default as DataController, type DataControllerInitEvent, type DataControllerSetEvent, type DataControllerToModelEvent, type DataControllerToViewEvent } from '@ckeditor/ckeditor5-engine/src/controller/datacontroller.js';
|
||||||
export { default as Conversion } from './conversion/conversion.js';
|
export { default as Conversion } from '@ckeditor/ckeditor5-engine/src/conversion/conversion.js';
|
||||||
export type { default as DowncastDispatcher, DowncastAddMarkerEvent, DowncastAttributeEvent, DowncastConversionApi, DowncastInsertEvent, DowncastRemoveEvent, DowncastRemoveMarkerEvent, DowncastSelectionEvent } from './conversion/downcastdispatcher.js';
|
export type { default as DowncastDispatcher, DowncastAddMarkerEvent, DowncastAttributeEvent, DowncastConversionApi, DowncastInsertEvent, DowncastRemoveEvent, DowncastRemoveMarkerEvent, DowncastSelectionEvent } from '@ckeditor/ckeditor5-engine/src/conversion/downcastdispatcher.js';
|
||||||
export type { default as UpcastDispatcher, UpcastConversionApi, UpcastConversionData, UpcastElementEvent, UpcastTextEvent } from './conversion/upcastdispatcher.js';
|
export type { default as UpcastDispatcher, UpcastConversionApi, UpcastConversionData, UpcastElementEvent, UpcastTextEvent } from '@ckeditor/ckeditor5-engine/src/conversion/upcastdispatcher.js';
|
||||||
export type { AddHighlightCallback, AttributeDescriptor, ElementCreatorFunction, HighlightDescriptor, RemoveHighlightCallback, MarkerElementCreatorFunction, SlotFilter } from './conversion/downcasthelpers.js';
|
export type { AddHighlightCallback, AttributeDescriptor, ElementCreatorFunction, HighlightDescriptor, RemoveHighlightCallback, MarkerElementCreatorFunction, SlotFilter } from '@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers.js';
|
||||||
export type { default as Mapper, MapperModelToViewPositionEvent, MapperViewToModelPositionEvent } from './conversion/mapper.js';
|
export type { default as Mapper, MapperModelToViewPositionEvent, MapperViewToModelPositionEvent } from '@ckeditor/ckeditor5-engine/src/conversion/mapper.js';
|
||||||
export type { default as ModelConsumable } from './conversion/modelconsumable.js';
|
export type { default as ModelConsumable } from '@ckeditor/ckeditor5-engine/src/conversion/modelconsumable.js';
|
||||||
export type { Consumables, default as ViewConsumable } from './conversion/viewconsumable.js';
|
export type { Consumables, default as ViewConsumable } from '@ckeditor/ckeditor5-engine/src/conversion/viewconsumable.js';
|
||||||
export type { default as DataProcessor } from './dataprocessor/dataprocessor.js';
|
export type { default as DataProcessor } from '@ckeditor/ckeditor5-engine/src/dataprocessor/dataprocessor.js';
|
||||||
export { default as HtmlDataProcessor } from './dataprocessor/htmldataprocessor.js';
|
export { default as HtmlDataProcessor } from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js';
|
||||||
export type { default as Operation } from './model/operation/operation.js';
|
export type { default as Operation } from '@ckeditor/ckeditor5-engine/src/model/operation/operation.js';
|
||||||
export { default as InsertOperation } from './model/operation/insertoperation.js';
|
export { default as InsertOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/insertoperation.js';
|
||||||
export { default as MoveOperation } from './model/operation/moveoperation.js';
|
export { default as MoveOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/moveoperation.js';
|
||||||
export { default as MergeOperation } from './model/operation/mergeoperation.js';
|
export { default as MergeOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/mergeoperation.js';
|
||||||
export { default as SplitOperation } from './model/operation/splitoperation.js';
|
export { default as SplitOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/splitoperation.js';
|
||||||
export { default as MarkerOperation } from './model/operation/markeroperation.js';
|
export { default as MarkerOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/markeroperation.js';
|
||||||
export { default as OperationFactory } from './model/operation/operationfactory.js';
|
export { default as OperationFactory } from '@ckeditor/ckeditor5-engine/src/model/operation/operationfactory.js';
|
||||||
export { default as AttributeOperation } from './model/operation/attributeoperation.js';
|
export { default as AttributeOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/attributeoperation.js';
|
||||||
export { default as RenameOperation } from './model/operation/renameoperation.js';
|
export { default as RenameOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/renameoperation.js';
|
||||||
export { default as RootAttributeOperation } from './model/operation/rootattributeoperation.js';
|
export { default as RootAttributeOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/rootattributeoperation.js';
|
||||||
export { default as RootOperation } from './model/operation/rootoperation.js';
|
export { default as RootOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/rootoperation.js';
|
||||||
export { default as NoOperation } from './model/operation/nooperation.js';
|
export { default as NoOperation } from '@ckeditor/ckeditor5-engine/src/model/operation/nooperation.js';
|
||||||
export { transformSets } from './model/operation/transform.js';
|
export { transformSets } from '@ckeditor/ckeditor5-engine/src/model/operation/transform.js';
|
||||||
export { default as DocumentSelection, type DocumentSelectionChangeRangeEvent, type DocumentSelectionChangeMarkerEvent, type DocumentSelectionChangeAttributeEvent } from './model/documentselection.js';
|
export { default as DocumentSelection, type DocumentSelectionChangeRangeEvent, type DocumentSelectionChangeMarkerEvent, type DocumentSelectionChangeAttributeEvent } from '@ckeditor/ckeditor5-engine/src/model/documentselection.js';
|
||||||
export { default as Range } from './model/range.js';
|
export { default as Range } from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
export { default as LiveRange, type LiveRangeChangeRangeEvent } from './model/liverange.js';
|
export { default as LiveRange, type LiveRangeChangeRangeEvent } from '@ckeditor/ckeditor5-engine/src/model/liverange.js';
|
||||||
export { default as LivePosition } from './model/liveposition.js';
|
export { default as LivePosition } from '@ckeditor/ckeditor5-engine/src/model/liveposition.js';
|
||||||
export { default as Model } from './model/model.js';
|
export { default as Model } from '@ckeditor/ckeditor5-engine/src/model/model.js';
|
||||||
export { default as TreeWalker, type TreeWalkerValue } from './model/treewalker.js';
|
export { default as TreeWalker, type TreeWalkerValue } from '@ckeditor/ckeditor5-engine/src/model/treewalker.js';
|
||||||
export { default as Element } from './model/element.js';
|
export { default as Element } from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
export { default as Position, type PositionOffset } from './model/position.js';
|
export { default as Position, type PositionOffset } from '@ckeditor/ckeditor5-engine/src/model/position.js';
|
||||||
export { default as DocumentFragment } from './model/documentfragment.js';
|
export { default as DocumentFragment } from '@ckeditor/ckeditor5-engine/src/model/documentfragment.js';
|
||||||
export { default as History } from './model/history.js';
|
export { default as History } from '@ckeditor/ckeditor5-engine/src/model/history.js';
|
||||||
export { default as Text } from './model/text.js';
|
export { default as Text } from '@ckeditor/ckeditor5-engine/src/model/text.js';
|
||||||
export { default as TextProxy } from './model/textproxy.js';
|
export { default as TextProxy } from '@ckeditor/ckeditor5-engine/src/model/textproxy.js';
|
||||||
export type { default as Document, ModelPostFixer } from './model/document.js';
|
export type { default as Document, ModelPostFixer } from '@ckeditor/ckeditor5-engine/src/model/document.js';
|
||||||
export type { Marker } from './model/markercollection.js';
|
export type { Marker } from '@ckeditor/ckeditor5-engine/src/model/markercollection.js';
|
||||||
export type { default as Batch } from './model/batch.js';
|
export type { default as Batch } from '@ckeditor/ckeditor5-engine/src/model/batch.js';
|
||||||
export type { default as Differ, DiffItem, DiffItemAttribute, DiffItemInsert, DiffItemRemove } from './model/differ.js';
|
export type { default as Differ, DiffItem, DiffItemAttribute, DiffItemInsert, DiffItemRemove } from '@ckeditor/ckeditor5-engine/src/model/differ.js';
|
||||||
export type { default as Item } from './model/item.js';
|
export type { default as Item } from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
export type { default as Node, NodeAttributes } from './model/node.js';
|
export type { default as Node, NodeAttributes } from '@ckeditor/ckeditor5-engine/src/model/node.js';
|
||||||
export type { default as RootElement } from './model/rootelement.js';
|
export type { default as RootElement } from '@ckeditor/ckeditor5-engine/src/model/rootelement.js';
|
||||||
export type { default as Schema, SchemaAttributeCheckCallback, SchemaChildCheckCallback, AttributeProperties, SchemaItemDefinition } from './model/schema.js';
|
export type { default as Schema, SchemaAttributeCheckCallback, SchemaChildCheckCallback, AttributeProperties, SchemaItemDefinition } from '@ckeditor/ckeditor5-engine/src/model/schema.js';
|
||||||
export type { default as Selection, Selectable } from './model/selection.js';
|
export type { default as Selection, Selectable } from '@ckeditor/ckeditor5-engine/src/model/selection.js';
|
||||||
export type { default as TypeCheckable } from './model/typecheckable.js';
|
export type { default as TypeCheckable } from '@ckeditor/ckeditor5-engine/src/model/typecheckable.js';
|
||||||
export type { default as Writer } from './model/writer.js';
|
export type { default as Writer } from '@ckeditor/ckeditor5-engine/src/model/writer.js';
|
||||||
export type { DocumentChangeEvent } from './model/document.js';
|
export type { DocumentChangeEvent } from '@ckeditor/ckeditor5-engine/src/model/document.js';
|
||||||
export type { DocumentSelectionChangeEvent } from './model/documentselection.js';
|
export type { DocumentSelectionChangeEvent } from '@ckeditor/ckeditor5-engine/src/model/documentselection.js';
|
||||||
export type { ModelApplyOperationEvent, ModelDeleteContentEvent, ModelGetSelectedContentEvent, ModelInsertContentEvent, ModelInsertObjectEvent, ModelModifySelectionEvent, ModelCanEditAtEvent } from './model/model.js';
|
export type { ModelApplyOperationEvent, ModelDeleteContentEvent, ModelGetSelectedContentEvent, ModelInsertContentEvent, ModelInsertObjectEvent, ModelModifySelectionEvent, ModelCanEditAtEvent } from '@ckeditor/ckeditor5-engine/src/model/model.js';
|
||||||
export type { SelectionChangeRangeEvent } from './model/selection.js';
|
export type { SelectionChangeRangeEvent } from '@ckeditor/ckeditor5-engine/src/model/selection.js';
|
||||||
export { default as DataTransfer } from './view/datatransfer.js';
|
export { default as DataTransfer } from '@ckeditor/ckeditor5-engine/src/view/datatransfer.js';
|
||||||
export { default as DomConverter } from './view/domconverter.js';
|
export { default as DomConverter } from '@ckeditor/ckeditor5-engine/src/view/domconverter.js';
|
||||||
export { default as Renderer } from './view/renderer.js';
|
export { default as Renderer } from '@ckeditor/ckeditor5-engine/src/view/renderer.js';
|
||||||
export { default as EditingView } from './view/view.js';
|
export { default as EditingView } from '@ckeditor/ckeditor5-engine/src/view/view.js';
|
||||||
export { default as ViewDocument } from './view/document.js';
|
export { default as ViewDocument } from '@ckeditor/ckeditor5-engine/src/view/document.js';
|
||||||
export { default as ViewText } from './view/text.js';
|
export { default as ViewText } from '@ckeditor/ckeditor5-engine/src/view/text.js';
|
||||||
export { default as ViewElement, type ElementAttributes as ViewElementAttributes } from './view/element.js';
|
export { default as ViewElement, type ElementAttributes as ViewElementAttributes } from '@ckeditor/ckeditor5-engine/src/view/element.js';
|
||||||
export { default as ViewContainerElement } from './view/containerelement.js';
|
export { default as ViewContainerElement } from '@ckeditor/ckeditor5-engine/src/view/containerelement.js';
|
||||||
export { default as ViewEditableElement } from './view/editableelement.js';
|
export { default as ViewEditableElement } from '@ckeditor/ckeditor5-engine/src/view/editableelement.js';
|
||||||
export { default as ViewRootEditableElement } from './view/rooteditableelement.js';
|
export { default as ViewRootEditableElement } from '@ckeditor/ckeditor5-engine/src/view/rooteditableelement.js';
|
||||||
export { default as ViewAttributeElement } from './view/attributeelement.js';
|
export { default as ViewAttributeElement } from '@ckeditor/ckeditor5-engine/src/view/attributeelement.js';
|
||||||
export { default as ViewEmptyElement } from './view/emptyelement.js';
|
export { default as ViewEmptyElement } from '@ckeditor/ckeditor5-engine/src/view/emptyelement.js';
|
||||||
export { default as ViewRawElement } from './view/rawelement.js';
|
export { default as ViewRawElement } from '@ckeditor/ckeditor5-engine/src/view/rawelement.js';
|
||||||
export { default as ViewUIElement } from './view/uielement.js';
|
export { default as ViewUIElement } from '@ckeditor/ckeditor5-engine/src/view/uielement.js';
|
||||||
export { default as ViewDocumentFragment } from './view/documentfragment.js';
|
export { default as ViewDocumentFragment } from '@ckeditor/ckeditor5-engine/src/view/documentfragment.js';
|
||||||
export { default as ViewTreeWalker, type TreeWalkerValue as ViewTreeWalkerValue } from './view/treewalker.js';
|
export { default as ViewTreeWalker, type TreeWalkerValue as ViewTreeWalkerValue } from '@ckeditor/ckeditor5-engine/src/view/treewalker.js';
|
||||||
export type { default as ViewElementDefinition, ElementObjectDefinition } from './view/elementdefinition.js';
|
export type { default as ViewElementDefinition, ElementObjectDefinition } from '@ckeditor/ckeditor5-engine/src/view/elementdefinition.js';
|
||||||
export type { default as ViewDocumentSelection } from './view/documentselection.js';
|
export type { default as ViewDocumentSelection } from '@ckeditor/ckeditor5-engine/src/view/documentselection.js';
|
||||||
export { default as AttributeElement } from './view/attributeelement.js';
|
export { default as AttributeElement } from '@ckeditor/ckeditor5-engine/src/view/attributeelement.js';
|
||||||
export type { default as ViewItem } from './view/item.js';
|
export type { default as ViewItem } from '@ckeditor/ckeditor5-engine/src/view/item.js';
|
||||||
export type { default as ViewNode } from './view/node.js';
|
export type { default as ViewNode } from '@ckeditor/ckeditor5-engine/src/view/node.js';
|
||||||
export type { default as ViewPosition, PositionOffset as ViewPositionOffset } from './view/position.js';
|
export type { default as ViewPosition, PositionOffset as ViewPositionOffset } from '@ckeditor/ckeditor5-engine/src/view/position.js';
|
||||||
export type { default as ViewRange } from './view/range.js';
|
export type { default as ViewRange } from '@ckeditor/ckeditor5-engine/src/view/range.js';
|
||||||
export type { default as ViewSelection, ViewSelectionChangeEvent, Selectable as ViewSelectable } from './view/selection.js';
|
export type { default as ViewSelection, ViewSelectionChangeEvent, Selectable as ViewSelectable } from '@ckeditor/ckeditor5-engine/src/view/selection.js';
|
||||||
export type { default as ViewTypeCheckable } from './view/typecheckable.js';
|
export type { default as ViewTypeCheckable } from '@ckeditor/ckeditor5-engine/src/view/typecheckable.js';
|
||||||
export { getFillerOffset } from './view/containerelement.js';
|
export { getFillerOffset } from '@ckeditor/ckeditor5-engine/src/view/containerelement.js';
|
||||||
export { default as Observer } from './view/observer/observer.js';
|
export { default as Observer } from '@ckeditor/ckeditor5-engine/src/view/observer/observer.js';
|
||||||
export { default as ClickObserver } from './view/observer/clickobserver.js';
|
export { default as ClickObserver } from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver.js';
|
||||||
export { default as DomEventObserver } from './view/observer/domeventobserver.js';
|
export { default as DomEventObserver } from '@ckeditor/ckeditor5-engine/src/view/observer/domeventobserver.js';
|
||||||
export { default as MouseObserver } from './view/observer/mouseobserver.js';
|
export { default as MouseObserver } from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js';
|
||||||
export { default as TabObserver } from './view/observer/tabobserver.js';
|
export { default as TabObserver } from '@ckeditor/ckeditor5-engine/src/view/observer/tabobserver.js';
|
||||||
export { default as FocusObserver } from './view/observer/focusobserver.js';
|
export { default as FocusObserver } from '@ckeditor/ckeditor5-engine/src/view/observer/focusobserver.js';
|
||||||
export { default as DowncastWriter } from './view/downcastwriter.js';
|
export { default as DowncastWriter } from '@ckeditor/ckeditor5-engine/src/view/downcastwriter.js';
|
||||||
export { default as UpcastWriter } from './view/upcastwriter.js';
|
export { default as UpcastWriter } from '@ckeditor/ckeditor5-engine/src/view/upcastwriter.js';
|
||||||
export { default as Matcher, type MatcherPattern, type MatcherObjectPattern, type Match, type MatchResult } from './view/matcher.js';
|
export { default as Matcher, type MatcherPattern, type MatcherObjectPattern, type Match, type MatchResult } from '@ckeditor/ckeditor5-engine/src/view/matcher.js';
|
||||||
export { default as BubblingEventInfo } from './view/observer/bubblingeventinfo.js';
|
export { default as BubblingEventInfo } from '@ckeditor/ckeditor5-engine/src/view/observer/bubblingeventinfo.js';
|
||||||
export { default as DomEventData } from './view/observer/domeventdata.js';
|
export { default as DomEventData } from '@ckeditor/ckeditor5-engine/src/view/observer/domeventdata.js';
|
||||||
export type { BubblingEvent } from './view/observer/bubblingemittermixin.js';
|
export type { BubblingEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/bubblingemittermixin.js';
|
||||||
export type { ViewDocumentArrowKeyEvent } from './view/observer/arrowkeysobserver.js';
|
export type { ViewDocumentArrowKeyEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/arrowkeysobserver.js';
|
||||||
export type { ViewDocumentCompositionStartEvent, ViewDocumentCompositionUpdateEvent, ViewDocumentCompositionEndEvent } from './view/observer/compositionobserver.js';
|
export type { ViewDocumentCompositionStartEvent, ViewDocumentCompositionUpdateEvent, ViewDocumentCompositionEndEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/compositionobserver.js';
|
||||||
export type { ViewDocumentInputEvent } from './view/observer/inputobserver.js';
|
export type { ViewDocumentInputEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/inputobserver.js';
|
||||||
export type { ViewDocumentKeyDownEvent, ViewDocumentKeyUpEvent, KeyEventData } from './view/observer/keyobserver.js';
|
export type { ViewDocumentKeyDownEvent, ViewDocumentKeyUpEvent, KeyEventData } from '@ckeditor/ckeditor5-engine/src/view/observer/keyobserver.js';
|
||||||
export type { ViewDocumentLayoutChangedEvent } from './view/document.js';
|
export type { ViewDocumentLayoutChangedEvent } from '@ckeditor/ckeditor5-engine/src/view/document.js';
|
||||||
export type { ViewDocumentMouseDownEvent, ViewDocumentMouseUpEvent, ViewDocumentMouseOverEvent, ViewDocumentMouseOutEvent } from './view/observer/mouseobserver.js';
|
export type { ViewDocumentMouseDownEvent, ViewDocumentMouseUpEvent, ViewDocumentMouseOverEvent, ViewDocumentMouseOutEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver.js';
|
||||||
export type { ViewDocumentTabEvent } from './view/observer/tabobserver.js';
|
export type { ViewDocumentTabEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/tabobserver.js';
|
||||||
export type { ViewDocumentClickEvent } from './view/observer/clickobserver.js';
|
export type { ViewDocumentClickEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/clickobserver.js';
|
||||||
export type { ViewDocumentSelectionChangeEvent } from './view/observer/selectionobserver.js';
|
export type { ViewDocumentSelectionChangeEvent } from '@ckeditor/ckeditor5-engine/src/view/observer/selectionobserver.js';
|
||||||
export type { ViewRenderEvent, ViewScrollToTheSelectionEvent } from './view/view.js';
|
export type { ViewRenderEvent, ViewScrollToTheSelectionEvent } from '@ckeditor/ckeditor5-engine/src/view/view.js';
|
||||||
export { default as StylesMap, StylesProcessor, type BoxSides } from './view/stylesmap.js';
|
export { default as StylesMap, StylesProcessor, type BoxSides } from '@ckeditor/ckeditor5-engine/src/view/stylesmap.js';
|
||||||
export * from './view/styles/background.js';
|
export * from '@ckeditor/ckeditor5-engine/src/view/styles/background.js';
|
||||||
export * from './view/styles/border.js';
|
export * from '@ckeditor/ckeditor5-engine/src/view/styles/border.js';
|
||||||
export * from './view/styles/margin.js';
|
export * from '@ckeditor/ckeditor5-engine/src/view/styles/margin.js';
|
||||||
export * from './view/styles/padding.js';
|
export * from '@ckeditor/ckeditor5-engine/src/view/styles/padding.js';
|
||||||
export * from './view/styles/utils.js';
|
export * from '@ckeditor/ckeditor5-engine/src/view/styles/utils.js';
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
||||||
*/
|
*/
|
||||||
import type Operation from './operation/operation.js';
|
import type Operation from '@ckeditor/ckeditor5-engine/src/model/operation/operation.js';
|
||||||
/**
|
/**
|
||||||
* A batch instance groups model changes ({@link module:engine/model/operation/operation~Operation operations}). All operations
|
* A batch instance groups model changes ({@link module:engine/model/operation/operation~Operation operations}). All operations
|
||||||
* grouped in a single batch can be reverted together, so you can also think about a batch as of a single undo step. If you want
|
* grouped in a single batch can be reverted together, so you can also think about a batch as of a single undo step. If you want
|
||||||
|
|
|
||||||
14
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/model/differ.d.ts
generated
vendored
14
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/model/differ.d.ts
generated
vendored
|
|
@ -5,13 +5,13 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/model/differ
|
* @module engine/model/differ
|
||||||
*/
|
*/
|
||||||
import Position from './position.js';
|
import Position from '@ckeditor/ckeditor5-engine/src/model/position.js';
|
||||||
import Range from './range.js';
|
import Range from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
import type { default as MarkerCollection, MarkerData } from './markercollection.js';
|
import type { default as MarkerCollection, MarkerData } from '@ckeditor/ckeditor5-engine/src/model/markercollection.js';
|
||||||
import type Element from './element.js';
|
import type Element from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import type Item from './item.js';
|
import type Item from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
import type RootElement from './rootelement.js';
|
import type RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement.js';
|
||||||
import type Operation from './operation/operation.js';
|
import type Operation from '@ckeditor/ckeditor5-engine/src/model/operation/operation.js';
|
||||||
/**
|
/**
|
||||||
* Calculates the difference between two model states.
|
* Calculates the difference between two model states.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
16
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/model/document.d.ts
generated
vendored
16
vendor/ckeditor5/node_modules/@ckeditor/ckeditor5-engine/src/model/document.d.ts
generated
vendored
|
|
@ -5,14 +5,14 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/model/document
|
* @module engine/model/document
|
||||||
*/
|
*/
|
||||||
import Differ from './differ.js';
|
import Differ from '@ckeditor/ckeditor5-engine/src/model/differ.js';
|
||||||
import DocumentSelection from './documentselection.js';
|
import DocumentSelection from '@ckeditor/ckeditor5-engine/src/model/documentselection.js';
|
||||||
import History from './history.js';
|
import History from '@ckeditor/ckeditor5-engine/src/model/history.js';
|
||||||
import RootElement from './rootelement.js';
|
import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement.js';
|
||||||
import type { default as Model } from './model.js';
|
import type { default as Model } from '@ckeditor/ckeditor5-engine/src/model/model.js';
|
||||||
import type Batch from './batch.js';
|
import type Batch from '@ckeditor/ckeditor5-engine/src/model/batch.js';
|
||||||
import type Range from './range.js';
|
import type Range from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
import type Writer from './writer.js';
|
import type Writer from '@ckeditor/ckeditor5-engine/src/model/writer.js';
|
||||||
import { Collection } from '@ckeditor/ckeditor5-utils';
|
import { Collection } from '@ckeditor/ckeditor5-utils';
|
||||||
declare const Document_base: {
|
declare const Document_base: {
|
||||||
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
new (): import("@ckeditor/ckeditor5-utils").Emitter;
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/model/documentfragment
|
* @module engine/model/documentfragment
|
||||||
*/
|
*/
|
||||||
import TypeCheckable from './typecheckable.js';
|
import TypeCheckable from '@ckeditor/ckeditor5-engine/src/model/typecheckable.js';
|
||||||
import type Item from './item.js';
|
import type Item from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
import type Node from './node.js';
|
import type Node from '@ckeditor/ckeditor5-engine/src/model/node.js';
|
||||||
import type Range from './range.js';
|
import type Range from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
/**
|
/**
|
||||||
* DocumentFragment represents a part of model which does not have a common root but its top-level nodes
|
* DocumentFragment represents a part of model which does not have a common root but its top-level nodes
|
||||||
* can be seen as siblings. In other words, it is a detached part of model tree, without a root.
|
* can be seen as siblings. In other words, it is a detached part of model tree, without a root.
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
/**
|
/**
|
||||||
* @module engine/model/documentselection
|
* @module engine/model/documentselection
|
||||||
*/
|
*/
|
||||||
import TypeCheckable from './typecheckable.js';
|
import TypeCheckable from '@ckeditor/ckeditor5-engine/src/model/typecheckable.js';
|
||||||
import Selection, { type SelectionChangeAttributeEvent, type SelectionChangeRangeEvent } from './selection.js';
|
import Selection, { type SelectionChangeAttributeEvent, type SelectionChangeRangeEvent } from '@ckeditor/ckeditor5-engine/src/model/selection.js';
|
||||||
import type { default as Document } from './document.js';
|
import type { default as Document } from '@ckeditor/ckeditor5-engine/src/model/document.js';
|
||||||
import type { Marker } from './markercollection.js';
|
import type { Marker } from '@ckeditor/ckeditor5-engine/src/model/markercollection.js';
|
||||||
import type Element from './element.js';
|
import type Element from '@ckeditor/ckeditor5-engine/src/model/element.js';
|
||||||
import type Item from './item.js';
|
import type Item from '@ckeditor/ckeditor5-engine/src/model/item.js';
|
||||||
import type { default as Position, PositionOffset } from './position.js';
|
import type { default as Position, PositionOffset } from '@ckeditor/ckeditor5-engine/src/model/position.js';
|
||||||
import type Range from './range.js';
|
import type Range from '@ckeditor/ckeditor5-engine/src/model/range.js';
|
||||||
import { Collection } from '@ckeditor/ckeditor5-utils';
|
import { Collection } from '@ckeditor/ckeditor5-utils';
|
||||||
declare const DocumentSelection_base: import("@ckeditor/ckeditor5-utils").Mixed<typeof TypeCheckable, import("@ckeditor/ckeditor5-utils").Emitter>;
|
declare const DocumentSelection_base: import("@ckeditor/ckeditor5-utils").Mixed<typeof TypeCheckable, import("@ckeditor/ckeditor5-utils").Emitter>;
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue