This commit is contained in:
Sabda Yagra 2025-04-24 15:41:00 +07:00
commit 0fe7721eb6
131 changed files with 13891 additions and 2008 deletions

View File

@ -17,7 +17,8 @@ COPY package.json pnpm-lock.yaml ./
COPY vendor/ckeditor5 ./vendor/ckeditor5 COPY vendor/ckeditor5 ./vendor/ckeditor5
# Install dependencies # Install dependencies
RUN pnpm install --frozen-lockfile RUN pnpm install
# RUN pnpm install --frozen-lockfile
# Menyalin source code aplikasi # Menyalin source code aplikasi
COPY . . COPY . .

View File

@ -61,6 +61,12 @@ const columns: ColumnDef<any>[] = [
cell: ({ row }) => <span>{row.getValue("experience")}</span>, cell: ({ row }) => <span>{row.getValue("experience")}</span>,
}, },
{
accessorKey: "experience",
header: "Posisi",
cell: ({ row }) => <span>{row.getValue("experience")}</span>,
},
{ {
id: "actions", id: "actions",
accessorKey: "action", accessorKey: "action",

View File

@ -24,7 +24,13 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { AdministrationLevelList, getListCompetencies, getListExperiences, saveUserInternal, saveUserRolePlacements } from "@/service/management-user/management-user"; import {
AdministrationLevelList,
getListCompetencies,
getListExperiences,
saveUserInternal,
saveUserRolePlacements,
} from "@/service/management-user/management-user";
import { loading } from "@/config/swal"; import { loading } from "@/config/swal";
const FormSchema = z.object({ const FormSchema = z.object({
@ -58,7 +64,7 @@ export type Placements = {
index: number; index: number;
roleId?: string; roleId?: string;
userLevelId?: number; userLevelId?: number;
} };
export default function AddExpertForm() { export default function AddExpertForm() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
@ -67,7 +73,9 @@ export default function AddExpertForm() {
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
}); });
const [incrementId, setIncrementId] = useState(1); const [incrementId, setIncrementId] = useState(1);
const [placementRows, setPlacementRows] = useState<Placements[]>([{ index: 0, roleId: "", userLevelId: 0 }]); const [placementRows, setPlacementRows] = useState<Placements[]>([
{ index: 0, roleId: "", userLevelId: 0 },
]);
const [userCompetencies, setUserCompetencies] = useState<any>(); const [userCompetencies, setUserCompetencies] = useState<any>();
const [userExperiences, setUserExperiences] = useState<any>(); const [userExperiences, setUserExperiences] = useState<any>();
const [userLevels, setUserLevels] = useState<any>(); const [userLevels, setUserLevels] = useState<any>();
@ -80,7 +88,7 @@ export default function AddExpertForm() {
{ {
id: "12", id: "12",
name: "Kurator", name: "Kurator",
} },
]; ];
const onSubmit = async (data: z.infer<typeof FormSchema>) => { const onSubmit = async (data: z.infer<typeof FormSchema>) => {
@ -113,12 +121,12 @@ export default function AddExpertForm() {
userCompetencyId: data.skills, userCompetencyId: data.skills,
userExperienceId: data.experiences, userExperienceId: data.experiences,
companyName: data.company, companyName: data.company,
} };
loading(); loading();
const res = await saveUserInternal(dataReq); const res = await saveUserInternal(dataReq);
const resData = res?.data?.data; const resData = res?.data?.data;
const userProfileId = resData.id; const userProfileId = resData?.id;
var placementArr: any[] = []; var placementArr: any[] = [];
placementRows.forEach((row: any) => { placementRows.forEach((row: any) => {
@ -131,15 +139,14 @@ export default function AddExpertForm() {
const dataReq2 = { const dataReq2 = {
userId: userProfileId, userId: userProfileId,
placements: placementArr placements: placementArr,
} };
const res2 = await saveUserRolePlacements(dataReq2); const res2 = await saveUserRolePlacements(dataReq2);
const resData2 = res2?.data?.data; const resData2 = res2?.data?.data;
success("/admin/add-experts"); success("/admin/add-experts");
}; };
function success(redirect: string): void { function success(redirect: string): void {
MySwal.fire({ MySwal.fire({
title: '<p class="text-green-600 font-bold">Sukses</p>', title: '<p class="text-green-600 font-bold">Sukses</p>',
@ -180,7 +187,6 @@ export default function AddExpertForm() {
setUserLevels(levelsArr); setUserLevels(levelsArr);
} }
function successSubmit() { function successSubmit() {
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
@ -194,7 +200,11 @@ export default function AddExpertForm() {
}); });
} }
const handleSelectionChange = (index: number, type: "roleId" | "userLevelId", value: string) => { const handleSelectionChange = (
index: number,
type: "roleId" | "userLevelId",
value: string
) => {
setPlacementRows((prevRows) => setPlacementRows((prevRows) =>
prevRows.map((row) => prevRows.map((row) =>
row.index === index ? { ...row, [type]: value } : row row.index === index ? { ...row, [type]: value } : row
@ -202,7 +212,6 @@ export default function AddExpertForm() {
); );
}; };
const handleRemoveRow = (index: number) => { const handleRemoveRow = (index: number) => {
console.log(index); console.log(index);
console.log(placementRows); console.log(placementRows);
@ -212,7 +221,10 @@ export default function AddExpertForm() {
}; };
const handleAddRow = () => { const handleAddRow = () => {
setPlacementRows((prevRows: any) => [...prevRows, { index: incrementId, roleId: "", userLevelId: 0 }]); setPlacementRows((prevRows: any) => [
...prevRows,
{ index: incrementId, roleId: "", userLevelId: 0 },
]);
setIncrementId((prevId) => prevId + 1); setIncrementId((prevId) => prevId + 1);
}; };
@ -372,10 +384,14 @@ export default function AddExpertForm() {
/> />
<div className="mt-4"> <div className="mt-4">
<FormLabel>Penempatan</FormLabel> <FormLabel>Posisi</FormLabel>
{placementRows?.map((row: any) => ( {placementRows?.map((row: any) => (
<div key={row.index} className="flex items-center gap-2 my-2"> <div key={row.index} className="flex items-center gap-2 my-2">
<Select onValueChange={(e) => handleSelectionChange(row.index, "roleId", e)} > <Select
onValueChange={(e) =>
handleSelectionChange(row.index, "roleId", e)
}
>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Pilih Role" /> <SelectValue placeholder="Pilih Role" />
@ -389,7 +405,11 @@ export default function AddExpertForm() {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<Select onValueChange={(e) => handleSelectionChange(row.index, "userLevelId", e)}> <Select
onValueChange={(e) =>
handleSelectionChange(row.index, "userLevelId", e)
}
>
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Pilih User Level" /> <SelectValue placeholder="Pilih User Level" />
@ -415,11 +435,7 @@ export default function AddExpertForm() {
)} )}
</div> </div>
))} ))}
<Button <Button type="button" size="md" onClick={() => handleAddRow()}>
type="button"
size="md"
onClick={() => handleAddRow()}
>
Tambah Tambah
</Button> </Button>
</div> </div>

View File

@ -43,8 +43,8 @@ export default function ContentManagement() {
const [ticket6, setTicket6] = useState(""); const [ticket6, setTicket6] = useState("");
const [isInternational, setIsInternational] = useState([false, false, false]); const [isInternational, setIsInternational] = useState([false, false, false]);
const baseUrl = "https://db-mediahub.polri.go.id/"; const baseUrl = "https://analytic.sitani.info/";
const url = "https://db-mediahub.polri.go.id/trusted/"; const url = "https://analytic.sitani.info/trusted/";
const view1 = const view1 =
levelName == "MABES POLRI" levelName == "MABES POLRI"

View File

@ -43,8 +43,8 @@ export default function EmergencyIssue() {
const [ticket6, setTicket6] = useState(""); const [ticket6, setTicket6] = useState("");
const [isInternational, setIsInternational] = useState([false, false, false]); const [isInternational, setIsInternational] = useState([false, false, false]);
const baseUrl = "https://db-mediahub.polri.go.id/"; const baseUrl = "https://analytic.sitani.info/";
const url = "https://db-mediahub.polri.go.id/trusted/"; const url = "https://analytic.sitani.info/trusted/";
const view1 = const view1 =
levelName == "MABES POLRI" levelName == "MABES POLRI"

View File

@ -43,8 +43,8 @@ export default function FeedbackCenter() {
const [ticket6, setTicket6] = useState(""); const [ticket6, setTicket6] = useState("");
const [isInternational, setIsInternational] = useState([false, false, false]); const [isInternational, setIsInternational] = useState([false, false, false]);
const baseUrl = "https://db-mediahub.polri.go.id/"; const baseUrl = "https://analytic.sitani.info/";
const url = "https://db-mediahub.polri.go.id/trusted/"; const url = "https://analytic.sitani.info/trusted/";
const view1 = const view1 =
levelName == "MABES POLRI" levelName == "MABES POLRI"

View File

@ -38,8 +38,8 @@ export default function ContentManagement() {
const [ticket6, setTicket6] = useState(""); const [ticket6, setTicket6] = useState("");
const [isInternational, setIsInternational] = useState([false, false, false]); const [isInternational, setIsInternational] = useState([false, false, false]);
const baseUrl = "https://db-mediahub.polri.go.id/"; const baseUrl = "https://analytic.sitani.info/";
const url = "https://db-mediahub.polri.go.id/trusted/"; const url = "https://analytic.sitani.info/trusted/";
const view1 = const view1 =
levelName == "MABES POLRI" levelName == "MABES POLRI"

View File

@ -2,20 +2,33 @@
import SiteBreadcrumb from "@/components/site-breadcrumb"; import SiteBreadcrumb from "@/components/site-breadcrumb";
import UserExternalTable from "@/components/table/management-user/management-user-external-table"; import UserExternalTable from "@/components/table/management-user/management-user-external-table";
import UserInternalTable from "@/components/table/management-user/management-user-internal-table"; import UserInternalTable from "@/components/table/management-user/management-user-internal-table";
import InternalTable from "@/components/table/management-user/management-user-internal-table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import DashboardVisualization from "@/components/visualization/dashboard-viz";
import ManagementUserVisualization from "@/components/visualization/management-user-viz"; import ManagementUserVisualization from "@/components/visualization/management-user-viz";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { PlusIcon } from "lucide-react"; import { PlusIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Cookies from "js-cookie";
import { getCookiesDecrypt } from "@/lib/utils";
export default function ManagementUser() { export default function ManagementUser() {
const [isInternal, setIsInternal] = useState(true); const [isInternal, setIsInternal] = useState(true);
const [levelNumber, setLevelNumber] = useState<number | null>(null);
const router = useRouter(); const router = useRouter();
useEffect(() => {
const encryptedLevel = Cookies.get("ulne");
if (encryptedLevel) {
const decryptedLevel = getCookiesDecrypt("ulne");
setLevelNumber(Number(decryptedLevel));
}
}, []);
useEffect(() => { useEffect(() => {
router.push("?page=1"); router.push("?page=1");
}, [isInternal]); }, [isInternal]);
const showExternalButton = levelNumber !== 2 && levelNumber !== 3;
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
@ -23,10 +36,7 @@ export default function ManagementUser() {
<ManagementUserVisualization /> <ManagementUserVisualization />
</section> </section>
<section <section className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5">
id="table"
className="flex flex-col gap-2 bg-white rounded-lg p-3 mt-5"
>
<div className="flex justify-between py-3"> <div className="flex justify-between py-3">
<p className="text-lg"> <p className="text-lg">
Data User {isInternal ? "Internal" : "Eksternal"} Data User {isInternal ? "Internal" : "Eksternal"}
@ -40,28 +50,31 @@ export default function ManagementUser() {
</Link> </Link>
)} )}
</div> </div>
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5"> <div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
<Button <Button
rounded="md" rounded="md"
onClick={() => setIsInternal(true)} onClick={() => setIsInternal(true)}
className={` hover:text-white className={`hover:text-white ${
${ !isInternal ? "bg-white text-black" : "bg-black text-white"
!isInternal ? "bg-white text-black " : "bg-black text-white " }`}
}`}
> >
User Internal User Internal
</Button> </Button>
<Button
rounded="md" {showExternalButton && (
onClick={() => setIsInternal(false)} <Button
className={`hover:text-white ${ rounded="md"
!isInternal ? "bg-black text-white " : "bg-white text-black " onClick={() => setIsInternal(false)}
} className={`hover:text-white ${
`} !isInternal ? "bg-black text-white" : "bg-white text-black"
> }`}
User Eksternal >
</Button> User Eksternal
</Button>
)}
</div> </div>
{isInternal ? <UserInternalTable /> : <UserExternalTable />} {isInternal ? <UserInternalTable /> : <UserExternalTable />}
</section> </section>
</div> </div>

View File

@ -243,7 +243,10 @@ export default function CreateCategoryModal() {
{t("add-category")} {t("add-category")}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent size="md"> <DialogContent
size="md"
className="sm:h-[300px] md:h-[300px] lg:h-[500px] overflow-y-auto"
>
<DialogHeader> <DialogHeader>
<DialogTitle> {t("add-category")}</DialogTitle> <DialogTitle> {t("add-category")}</DialogTitle>
</DialogHeader> </DialogHeader>

View File

@ -0,0 +1,103 @@
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 { Link, useRouter } from "@/i18n/routing";
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: "No",
cell: ({ row }) => <span>{row.getValue("no")}</span>,
},
{
accessorKey: "createdAt",
header: "Tanggal",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("createdAt")}</span>
),
},
{
accessorKey: "account-type",
header: "Jenis Akun",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("account-type")}</span>
),
},
{
accessorKey: "userName",
header: "UserName",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("userName")}</span>
),
},
{
accessorKey: "accessMediahub",
header: "Akses Mediahub",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("accessMediahub")}</span>
),
},
{
accessorKey: "desaignWeb",
header: "Tampilan Desain Web",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("desaignWeb")}</span>
),
},
{
accessorKey: "navigation",
header: "Kemudahan Navigasi",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("navigation")}</span>
),
},
{
accessorKey: "fastAccess",
header: "Kecepatan Akses",
cell: ({ row }) => (
<span className="normal-case">{row.getValue("fastAccess")}</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={`/admin/broadcast/campaign-list/detail/${row.original.id}`}
>
Detail
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
export default columns;

View File

@ -0,0 +1,346 @@
"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 {
getMediaBlastCampaignPage,
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 { NewCampaignIcon } from "@/components/icon";
import search from "../../../app/chat/components/search";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Card, CardContent } from "@/components/ui/card";
import {
Bar,
BarChart,
CartesianGrid,
Label,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
const data = [
{
question: "Seberapa Sering User Mengakses\nMediaHub Polri",
total: 100,
},
{
question: "Tampilan dan Desain pada Website\nMediaHub Polri",
total: 90,
},
{
question: "Kemudahan Navigasi pada Website\nMediaHub Polri",
total: 80,
},
{
question: "Kecepatan Akses pada Website\nMediaHub Polri",
total: 70,
},
{
question:
"Seberapa Akurat dan Terpercaya\nInformasi pada Website MediaHub Polri",
total: 60,
},
{
question:
"Seberapa Lengkap Informasi dan Berita\npada Website MediaHub Polri",
total: 55,
},
{
question: "Seberapa membantu dalam\nmendapatkan Informasi terkait Polri",
total: 52,
},
];
const SurveyListTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1);
const [search, setSearch] = React.useState<string>("");
const [showData, setShowData] = React.useState("polri");
const [startDate, setStartDate] = React.useState("");
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: 10,
});
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,
},
});
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
React.useEffect(() => {
fetchData();
}, [page]);
async function fetchData() {
try {
loading();
const res = await getMediaBlastCampaignPage(page - 1);
const data = res?.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * 10 + index + 1;
});
console.log("contentData : ", data);
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(data?.totalPages);
close();
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
return (
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<div className="flex-1 text-xl font-medium text-default-900">Survey</div>
<div className="flex flex-row gap-2 items-center justify-between">
<div className="w-full md:w-[200px] lg:w-[300px] px-2">
<InputGroup merged>
<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"
value={search}
onChange={handleSearch}
/>
</InputGroup>
</div>
<div className="flex flex-row gap-2 items-center">
<div className="mx-2 my-1">
<Input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="max-w-sm"
/>
</div>
<div className="mx-3">
<Select>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder="Select a filter" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Filter</SelectLabel>
<SelectItem value="polri">Polri</SelectItem>
<SelectItem value="umum">Umum</SelectItem>
<SelectItem value="jurnalist">Journalist</SelectItem>
<SelectItem value="ksp">Ksp</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="min-h-screen p-3">
<h2 className="text-center font-semibold mb-4">
Survei Kepuasan Pengguna MediaHub Polri
</h2>
<ResponsiveContainer width="100%" height={500}>
<BarChart
layout="vertical"
data={data}
margin={{ top: 20, right: 30, left: 30, bottom: 80 }}
>
<CartesianGrid strokeDasharray="5 5" />
<XAxis type="number">
<Label
value="Total Survei"
offset={-30}
position="insideBottom"
style={{ textAnchor: "middle", fontStyle: "italic" }}
/>
</XAxis>
<YAxis
type="category"
dataKey="question"
width={240} // Lebarkan agar teks muat
tick={{
fontSize: 12,
}}
tickFormatter={(value: string) =>
value.length > 40 ? value.slice(0, 37) + "..." : value
} // Atur potong teks panjang, atau hapus kalau mau tampil semua
>
<Label
value="Pertanyaan"
angle={-90}
position="insideLeft"
style={{ textAnchor: "middle", fontStyle: "italic" }}
dx={-20}
/>
</YAxis>
<Tooltip />
<Bar dataKey="total" fill="#3163d4" />
</BarChart>
</ResponsiveContainer>
</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 SurveyListTable;

View File

@ -0,0 +1,18 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { useState } from "react";
import { Link } from "@/i18n/routing";
import { Button } from "@/components/ui/button";
import SurveyListTable from "./component/table";
export default function AdminSurvey() {
const [tab, setTab] = useState("Email Blast");
return (
<div>
<SiteBreadcrumb />
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
<SurveyListTable />
</div>
</div>
);
}

View File

@ -581,7 +581,7 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
{roleId == 3 || roleId == 11 || roleId == 2 || roleId == 12 ? ( {roleId == 3 || roleId == 11 || roleId == 2 || roleId == 12 ? (
<Button <Button
onClick={handleDateClick} onClick={handleDateClick}
className="dark:bg-background dark:text-foreground w-[250px]" className="dark:bg-background dark:text-foreground w-full"
> >
<Plus className="w-4 h-4 me-1" /> <Plus className="w-4 h-4 me-1" />
{t("addEvent")} {t("addEvent")}
@ -593,8 +593,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
{roleId === 3 && userLevelId === 216 ? ( {roleId === 3 && userLevelId === 216 ? (
<Button className="dark:bg-background dark:text-foreground w-[250px]"> <Button className="dark:bg-background dark:text-foreground w-full">
<Book className="w-4 h-4" /> <Book size={15} className="w-4 h-4 mr-3" />
{t("bag-pa-monitoring-results")} {t("bag-pa-monitoring-results")}
</Button> </Button>
) : null} ) : null}

View File

@ -775,7 +775,7 @@ const EventModal = ({
checked={wilayahPublish.semua} checked={wilayahPublish.semua}
onCheckedChange={() => toggleWilayah("semua")} onCheckedChange={() => toggleWilayah("semua")}
/> />
<label htmlFor="semua" className="ml-2"> <label htmlFor="semua" className="ml-2 text-sm">
Semua Semua
</label> </label>
</div> </div>
@ -785,7 +785,7 @@ const EventModal = ({
checked={wilayahPublish.nasional} checked={wilayahPublish.nasional}
onCheckedChange={() => toggleWilayah("nasional")} onCheckedChange={() => toggleWilayah("nasional")}
/> />
<label htmlFor="nasional" className="ml-2"> <label htmlFor="nasional" className="ml-2 text-sm mr-2">
Nasional Nasional
</label> </label>
</div> </div>
@ -795,7 +795,7 @@ const EventModal = ({
checked={wilayahPublish.polda} checked={wilayahPublish.polda}
onCheckedChange={() => toggleWilayah("polda")} onCheckedChange={() => toggleWilayah("polda")}
/> />
<label htmlFor="polda" className="mx-2"> <label htmlFor="polda" className="mx-2 text-sm mr-2">
Polda Polda
</label> </label>
{wilayahPublish.polda && ( {wilayahPublish.polda && (
@ -814,7 +814,7 @@ const EventModal = ({
checked={wilayahPublish.polres} checked={wilayahPublish.polres}
onCheckedChange={() => toggleWilayah("polres")} onCheckedChange={() => toggleWilayah("polres")}
/> />
<label htmlFor="polres" className="ml-2"> <label htmlFor="polres" className="ml-2 text-sm mr-2">
Polres Polres
</label> </label>
{wilayahPublish.polres && ( {wilayahPublish.polres && (
@ -833,7 +833,7 @@ const EventModal = ({
checked={wilayahPublish.satker} checked={wilayahPublish.satker}
onCheckedChange={() => toggleWilayah("satker")} onCheckedChange={() => toggleWilayah("satker")}
/> />
<label htmlFor="satker" className="mx-2"> <label htmlFor="satker" className="mx-2 text-sm mr-2">
Satker Satker
</label> </label>
{wilayahPublish.satker && ( {wilayahPublish.satker && (
@ -852,7 +852,10 @@ const EventModal = ({
checked={wilayahPublish.international} checked={wilayahPublish.international}
onCheckedChange={() => toggleWilayah("international")} onCheckedChange={() => toggleWilayah("international")}
/> />
<label htmlFor="international" className="ml-2"> <label
htmlFor="international"
className="ml-2 text-sm mr-2"
>
Internasional Internasional
</label> </label>
</div> </div>

View File

@ -38,6 +38,8 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
@ -60,9 +62,10 @@ const BlogTable = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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);
@ -106,12 +109,12 @@ const BlogTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
getCategories(); getCategories();
}, [categoryFilter, statusFilter, page, limit, search]); }, [categoryFilter, statusFilter, page, showData, search]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationBlog( const res = await paginationBlog(
limit, showData,
page - 1, page - 1,
search, search,
categoryFilter, categoryFilter,
@ -120,7 +123,7 @@ const BlogTable = () => {
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);
@ -183,7 +186,7 @@ const BlogTable = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/blog/create"}> <Link href={"/contributor/blog/create"}>
<Button fullWidth color="primary"> <Button fullWidth color="primary">
<Plus className="w-6 h-6 me-1.5" /> <Plus size={18} className=" me-1.5" />
{t("create-indeks")} {t("create-indeks")}
</Button> </Button>
</Link> </Link>
@ -210,6 +213,34 @@ const BlogTable = () => {
</div> </div>
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className="flex items-center py-4"> <div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md"> <Button variant="outline" className="ml-auto" size="md">

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
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";
@ -76,9 +78,10 @@ const TableAudio = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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);
@ -132,7 +135,15 @@ const TableAudio = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
getCategories(); getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]); }, [
categoryFilter,
statusFilter,
page,
showData,
search,
startDate,
endDate,
]);
async function getCategories() { async function getCategories() {
const category = await listEnableCategory("4"); const category = await listEnableCategory("4");
@ -171,7 +182,7 @@ const TableAudio = () => {
try { try {
const isForSelf = Number(roleId) === 4; const isForSelf = Number(roleId) === 4;
const res = await listDataAudio( const res = await listDataAudio(
limit, showData,
page - 1, page - 1,
isForSelf, isForSelf,
!isForSelf, !isForSelf,
@ -189,7 +200,7 @@ const TableAudio = () => {
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;
}); });
setDataTable(contentData); setDataTable(contentData);
@ -248,6 +259,34 @@ const TableAudio = () => {
</div> </div>
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className="flex items-center py-4"> <div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
{showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md"> <Button variant="outline" className="ml-auto" size="md">

View File

@ -63,7 +63,7 @@ const ReactTableAudioPage = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/content/audio/create"}> <Link href={"/contributor/content/audio/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-audio")} {t("create-audio")}
</Button> </Button>
</Link> </Link>

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
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";
@ -81,13 +83,13 @@ const TableImage = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("10");
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 [search, setSearch] = React.useState(""); const [search, setSearch] = React.useState("");
const userId = getCookiesDecrypt("uie"); const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
@ -96,6 +98,7 @@ const TableImage = () => {
const [selectedCategories, setSelectedCategories] = React.useState<number[]>( const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
[] []
); );
const [categoryFilter, setCategoryFilter] = React.useState<string>(""); const [categoryFilter, setCategoryFilter] = React.useState<string>("");
const [statusFilter, setStatusFilter] = React.useState<any[]>([]); const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
const [startDate, setStartDate] = React.useState(""); const [startDate, setStartDate] = React.useState("");
@ -135,10 +138,17 @@ const TableImage = () => {
}, [searchParams]); }, [searchParams]);
React.useEffect(() => { React.useEffect(() => {
// Panggil fetchData saat filter kategori berubah
fetchData(); fetchData();
getCategories(); getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]); }, [
categoryFilter,
statusFilter,
page,
showData,
search,
startDate,
endDate,
]);
async function getCategories() { async function getCategories() {
const category = await listEnableCategory("1"); const category = await listEnableCategory("1");
@ -177,7 +187,7 @@ const TableImage = () => {
try { try {
const isForSelf = Number(roleId) === 4; const isForSelf = Number(roleId) === 4;
const res = await listDataImage( const res = await listDataImage(
limit, showData,
page - 1, page - 1,
isForSelf, isForSelf,
!isForSelf, !isForSelf,
@ -195,7 +205,7 @@ const TableImage = () => {
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;
}); });
setDataTable(contentData); setDataTable(contentData);
@ -254,6 +264,34 @@ const TableImage = () => {
</div> </div>
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className="flex items-center py-4"> <div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
{showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md"> <Button variant="outline" className="ml-auto" size="md">

View File

@ -63,7 +63,7 @@ const ReactTableImagePage = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/content/image/create"}> <Link href={"/contributor/content/image/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-image")} {t("create-image")}
</Button> </Button>
</Link> </Link>

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
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";
@ -75,9 +77,10 @@ const TableTeks = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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);
@ -131,7 +134,15 @@ const TableTeks = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
getCategories(); getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]); }, [
categoryFilter,
statusFilter,
page,
showData,
search,
startDate,
endDate,
]);
async function getCategories() { async function getCategories() {
const category = await listEnableCategory("3"); const category = await listEnableCategory("3");
@ -170,7 +181,7 @@ const TableTeks = () => {
try { try {
const isForSelf = Number(roleId) === 4; const isForSelf = Number(roleId) === 4;
const res = await listDataTeks( const res = await listDataTeks(
limit, showData,
page - 1, page - 1,
isForSelf, isForSelf,
!isForSelf, !isForSelf,
@ -188,7 +199,7 @@ const TableTeks = () => {
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;
}); });
setDataTable(contentData); setDataTable(contentData);
@ -247,6 +258,34 @@ const TableTeks = () => {
</div> </div>
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className="flex items-center py-4"> <div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
{showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md"> <Button variant="outline" className="ml-auto" size="md">

View File

@ -66,7 +66,7 @@ const ReactTableTeksPage = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/content/teks/create"}> <Link href={"/contributor/content/teks/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-text")} {t("create-text")}
</Button> </Button>
</Link> </Link>

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
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";
@ -75,9 +77,10 @@ const TableVideo = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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);
@ -131,7 +134,16 @@ const TableVideo = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
getCategories(); getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]); }, [
categoryFilter,
statusFilter,
page,
showData,
,
search,
startDate,
endDate,
]);
async function getCategories() { async function getCategories() {
const category = await listEnableCategory("2"); const category = await listEnableCategory("2");
@ -170,7 +182,7 @@ const TableVideo = () => {
try { try {
const isForSelf = Number(roleId) === 4; const isForSelf = Number(roleId) === 4;
const res = await listDataVideo( const res = await listDataVideo(
limit, showData,
page - 1, page - 1,
isForSelf, isForSelf,
!isForSelf, !isForSelf,
@ -188,7 +200,7 @@ const TableVideo = () => {
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;
}); });
setDataTable(contentData); setDataTable(contentData);
@ -248,6 +260,34 @@ const TableVideo = () => {
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className="flex items-center py-4"> <div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
{showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md"> <Button variant="outline" className="ml-auto" size="md">

View File

@ -64,7 +64,7 @@ const ReactTableVideoPage = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/content/video/create"}> <Link href={"/contributor/content/video/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-video")} {t("create-video")}
</Button> </Button>
</Link> </Link>

View File

@ -0,0 +1,192 @@
import * as React from "react";
import { ColumnDef } from "@tanstack/react-table";
import {
Eye,
MoreVertical,
SquarePen,
Trash2,
Upload,
UploadCloud,
} 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 { format } from "date-fns";
import { Link, useRouter } from "@/components/navigation";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { deleteBlog } from "@/service/blog/blog";
import { error, loading } from "@/lib/swal";
import { useTranslations } from "next-intl";
import axios from "axios";
const useTableColumns = ({
handlePreview,
}: {
handlePreview: (id: string) => void;
}) => {
const t = useTranslations("Table"); // Panggil di dalam hook
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: t("no"),
cell: ({ row }) => <span>{row.getValue("no")}</span>,
},
{
accessorKey: "title",
header: t("title"),
cell: ({ row }) => (
<span className="whitespace-normal">{row.getValue("title")}</span>
),
},
{
accessorKey: "createdAt",
header: t("upload-date"),
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "version",
header: t("version"),
cell: ({ row }) => <span className="">{row.getValue("version")}</span>,
},
{
id: "actions",
accessorKey: "action",
header: t("action"),
enableHiding: false,
cell: ({ row }) => {
const router = useRouter();
const MySwal = withReactContent(Swal);
async function deleteProcess(id: any) {
loading();
const resDelete = await deleteBlog(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const handleDeleteBlog = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
deleteProcess(id);
}
});
};
const handleDownload = async (id: string) => {
try {
const response = await axios.get(
`https://netidhub.com/api/media/report/download?id=${id}`,
{
responseType: "blob",
}
);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", `report-${id}.pdf`);
document.body.appendChild(link);
link.click();
link.remove();
} catch (error) {
console.error("Download failed", error);
MySwal.fire({
title: "Gagal",
text: "Terjadi kesalahan saat mengunduh file.",
icon: "error",
});
}
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
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" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<DropdownMenuItem
onClick={() => handlePreview(row.original.id)}
className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"
>
<Eye className="w-4 h-4 me-1.5" />
Preview
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDownload(row.original.id)}
className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none"
>
<Upload className="w-4 h-4 me-1.5" />
Download
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDeleteBlog(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
>
<Trash2 className="w-4 h-4 me-1.5" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
return columns;
};
export default useTableColumns;

View File

@ -0,0 +1,479 @@
"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 { ChevronDown, Plus, Search } from "lucide-react";
import { cn } from "@/lib/utils";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { getBlogCategory, paginationBlog } from "@/service/blog/blog";
import { ticketingPagination } from "@/service/ticketing/ticketing";
import { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination";
import columns from "./columns";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
import Swal from "sweetalert2";
import { listEnableCategory } from "@/service/content/content";
import { useTranslations } from "next-intl";
import { CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "@/i18n/routing";
import useTableColumns from "./columns";
import {
getPreviewById,
paginationReport,
saveReport,
} from "@/service/report/report";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { format } from "date-fns";
import withReactContent from "sweetalert2-react-content";
type PreviewApiResponse = {
error: boolean;
message: string;
data: {
id: number;
title: string;
filePath: string;
version: number;
} | null;
};
const ReportTable = () => {
const router = useRouter();
const MySwal = withReactContent(Swal);
const searchParams = useSearchParams();
const t = useTranslations("Report");
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 [showData, setShowData] = React.useState("50");
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: Number(showData),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>("");
const [categories, setCategories] = React.useState<any[]>([]);
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
[]
);
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
const [dateFilter, setDateFilter] = React.useState("");
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
const [openPreview, setOpenPreview] = React.useState(false);
const [previewData, setPreviewData] = React.useState<any>(null);
const handlePreview = (id: string) => {
const url = `https://netidhub.com/api/media/report/view?id=${id}`;
setPreviewData({ url });
setOpenPreview(true);
};
const columns = useTableColumns({ handlePreview });
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,
},
});
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
React.useEffect(() => {
fetchData();
getCategories();
}, [categoryFilter, statusFilter, page, showData, search]);
async function fetchData() {
try {
const res = await paginationReport(
showData,
page - 1,
search,
categoryFilter,
statusFilter
);
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 : ", contentData);
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(data?.totalPages);
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
async function getCategories() {
const category = await getBlogCategory();
const resCategory = category?.data?.data?.content;
setCategories(resCategory || []);
}
const handleCheckboxChange = (categoryId: number) => {
setSelectedCategories((prev: any) =>
prev.includes(categoryId)
? prev.filter((id: any) => id !== categoryId)
: [...prev, categoryId]
);
// Perbarui filter kategori
setCategoryFilter((prev) => {
const updatedCategories = prev.split(",").filter(Boolean).map(Number);
const newCategories = updatedCategories.includes(categoryId)
? updatedCategories.filter((id) => id !== categoryId)
: [...updatedCategories, categoryId];
return newCategories.join(",");
});
};
function handleStatusCheckboxChange(value: any) {
setStatusFilter((prev: any) =>
prev.includes(value)
? prev.filter((status: any) => status !== value)
: [...prev, value]
);
}
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
};
const handleGenerateReport = async () => {
const today = new Date();
const formattedDate = format(today, "dd-MM-yyyy"); // Hasil: 22-04-2025
const title = `Report ${formattedDate}`;
const requestData = {
title,
};
try {
const response = await saveReport(requestData);
if (response?.error) {
MySwal.fire(
"Error",
response?.message || "Gagal menyimpan laporan",
"error"
);
return;
}
MySwal.fire({
title: "Sukses",
text: "Laporan berhasil dibuat.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
fetchData(); // Refresh tabel setelah generate
});
} catch (error) {
console.error("Generate report error:", error);
MySwal.fire("Error", "Terjadi kesalahan saat membuat laporan", "error");
}
};
return (
<div>
<Dialog open={openPreview} onOpenChange={setOpenPreview}>
<DialogContent className="min-w-max h-[500px] p-0 overflow-hidden">
<DialogHeader className="p-4 border-b">
<DialogTitle>Preview Laporan</DialogTitle>
</DialogHeader>
<div className="h-full w-[1000px] overflow-auto">
{previewData ? (
<iframe
src={previewData.url}
className="w-full h-[calc(100vh-100px)]"
/>
) : (
<div className="p-4">Loading preview...</div>
)}
</div>
</DialogContent>
</Dialog>
<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">
{t("table")} {t("report")}
</div>
<div className="flex-none">
<Button fullWidth color="primary" onClick={handleGenerateReport}>
<Plus size={18} className=" me-1.5" />
{t("generate-report")}
</Button>
</div>
</div>
</CardTitle>
</CardHeader>
<div className="w-full overflow-x-auto">
<div className="flex flex-col md:flex-row lg:flex-row md:justify-between lg:justify-between items-center md:px-5 lg:px-5">
<div className="w-full md:w-[200px] lg:w-[200px] px-2">
<InputGroup merged>
<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 Title"
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white"
value={search}
onChange={handleSearch}
/>
</InputGroup>
</div>
<div className="flex flex-row items-center gap-3">
<div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="ml-auto w-full sm:w-[100px]"
size="md"
>
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<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>{t("date")}</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">
{t("wait-review")}
</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">
{t("acc")}
</label>
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center py-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
<Table className="overflow-hidden mt-3">
<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>
</div>
);
};
export default ReportTable;

View File

@ -0,0 +1,9 @@
export const metadata = {
title: "Blog",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -0,0 +1,24 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import BlogTable from "./components/report-table";
import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Link } from "@/components/navigation";
import ReportTable from "./components/report-table";
const ReportPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<Card>
<CardContent className="p-0">
<ReportTable />
</CardContent>
</Card>
</div>
</div>
);
};
export default ReportPage;

View File

@ -22,7 +22,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { Search, UploadIcon } from "lucide-react"; import { ChevronDown, Search, UploadIcon } from "lucide-react";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group"; import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
@ -34,6 +34,14 @@ import { CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import useTableColumns from "./columns"; import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const EventTable = () => { const EventTable = () => {
const router = useRouter(); const router = useRouter();
@ -48,14 +56,16 @@ const EventTable = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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 [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState<string>("");
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns(); const columns = useTableColumns();
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -87,15 +97,21 @@ const EventTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit, search]); }, [page, showData, search, statusFilter]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationSchedule(limit, page - 1, 2, search); const res = await paginationSchedule(
showData,
page - 1,
2,
search,
statusFilter
);
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);
@ -108,6 +124,14 @@ const EventTable = () => {
} }
} }
const handleStatusCheckboxChange = (statusId: number) => {
const updatedFilter = statusFilter.includes(statusId)
? statusFilter.filter((id) => id !== statusId)
: [...statusFilter, statusId];
setStatusFilter(updatedFilter);
};
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -124,7 +148,7 @@ const EventTable = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/schedule/event/create"}> <Link href={"/contributor/schedule/event/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-schedule")} {t("create-schedule")}
</Button> </Button>
</Link> </Link>
@ -133,8 +157,8 @@ const EventTable = () => {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5 gap-2"> <div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" /> <Search className=" h-4 w-4 dark:text-white" />
@ -148,17 +172,78 @@ const EventTable = () => {
/> />
</InputGroup> </InputGroup>
</div> </div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="flex flex-row">
<Input <div className="mx-3">
placeholder="Filter Status..." <DropdownMenu>
value={ <DropdownMenuTrigger asChild>
(table.getColumn("status")?.getFilterValue() as string) ?? "" <Button size="md" variant="outline">
} 1 - {showData} Data
onChange={(event: React.ChangeEvent<HTMLInputElement>) => </Button>
table.getColumn("status")?.setFilterValue(event.target.value) </DropdownMenuTrigger>
} <DropdownMenuContent className="w-56 text-sm">
className="max-w-sm " <DropdownMenuRadioGroup
/> value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[150px] overflow-y-auto"
>
<div className="flex flex-row justify-between my-1 mx-1">
<p>Filter</p>
</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">
Menunggu Review
</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">
Diterima
</label>
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div> </div>
</div> </div>
<Table className="overflow-hidden mt-3"> <Table className="overflow-hidden mt-3">

View File

@ -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,
@ -47,6 +48,14 @@ import { CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import useTableColumns from "./columns"; import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const PressConferenceTable = () => { const PressConferenceTable = () => {
const router = useRouter(); const router = useRouter();
@ -61,14 +70,16 @@ const PressConferenceTable = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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 [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState<string>("");
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns(); const columns = useTableColumns();
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -100,15 +111,21 @@ const PressConferenceTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit, search]); }, [page, showData, search, statusFilter]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationSchedule(limit, page - 1, 1, search); const res = await paginationSchedule(
showData,
page - 1,
1,
search,
statusFilter
);
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);
@ -121,6 +138,14 @@ const PressConferenceTable = () => {
} }
} }
const handleStatusCheckboxChange = (statusId: number) => {
const updatedFilter = statusFilter.includes(statusId)
? statusFilter.filter((id) => id !== statusId)
: [...statusFilter, statusId];
setStatusFilter(updatedFilter);
};
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -137,7 +162,7 @@ const PressConferenceTable = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/schedule/press-conference/create"}> <Link href={"/contributor/schedule/press-conference/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-schedule")} {t("create-schedule")}
</Button> </Button>
</Link> </Link>
@ -146,8 +171,8 @@ const PressConferenceTable = () => {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5 gap-2"> <div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" /> <Search className=" h-4 w-4 dark:text-white" />
@ -161,17 +186,78 @@ const PressConferenceTable = () => {
/> />
</InputGroup> </InputGroup>
</div> </div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="flex flex-row gap-3">
<Input <div className="mx-3">
placeholder="Filter Status..." <DropdownMenu>
value={ <DropdownMenuTrigger asChild>
(table.getColumn("status")?.getFilterValue() as string) ?? "" <Button size="md" variant="outline">
} 1 - {showData} Data
onChange={(event: React.ChangeEvent<HTMLInputElement>) => </Button>
table.getColumn("status")?.setFilterValue(event.target.value) </DropdownMenuTrigger>
} <DropdownMenuContent className="w-56 text-sm">
className="max-w-sm " <DropdownMenuRadioGroup
/> value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[150px] overflow-y-auto"
>
<div className="flex flex-row justify-between my-1 mx-1">
<p>Filter</p>
</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">
Menunggu Review
</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">
Diterima
</label>
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div> </div>
</div> </div>
<Table className="overflow-hidden mt-3"> <Table className="overflow-hidden mt-3">

View File

@ -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,
@ -48,6 +49,14 @@ import { useTranslations } from "next-intl";
import { CardHeader, CardTitle } from "@/components/ui/card"; import { CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import useTableColumns from "./columns"; import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const PressReleaseTable = () => { const PressReleaseTable = () => {
const router = useRouter(); const router = useRouter();
@ -62,14 +71,16 @@ const PressReleaseTable = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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 [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState<string>("");
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns(); const columns = useTableColumns();
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -101,15 +112,21 @@ const PressReleaseTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit, search]); }, [page, showData, search, statusFilter]);
async function fetchData() { async function fetchData() {
try { try {
const res = await paginationSchedule(limit, page - 1, 3, search); const res = await paginationSchedule(
showData,
page - 1,
3,
search,
statusFilter
);
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);
@ -122,6 +139,14 @@ const PressReleaseTable = () => {
} }
} }
const handleStatusCheckboxChange = (statusId: number) => {
const updatedFilter = statusFilter.includes(statusId)
? statusFilter.filter((id) => id !== statusId)
: [...statusFilter, statusId];
setStatusFilter(updatedFilter);
};
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -138,7 +163,7 @@ const PressReleaseTable = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/schedule/press-release/create"}> <Link href={"/contributor/schedule/press-release/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-schedule")} {t("create-schedule")}
</Button> </Button>
</Link> </Link>
@ -147,8 +172,8 @@ const PressReleaseTable = () => {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5 gap-2"> <div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" /> <Search className=" h-4 w-4 dark:text-white" />
@ -162,17 +187,78 @@ const PressReleaseTable = () => {
/> />
</InputGroup> </InputGroup>
</div> </div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="flex flex-row">
<Input <div className="mx-3">
placeholder="Filter Status..." <DropdownMenu>
value={ <DropdownMenuTrigger asChild>
(table.getColumn("status")?.getFilterValue() as string) ?? "" <Button size="md" variant="outline">
} 1 - {showData} Data
onChange={(event: React.ChangeEvent<HTMLInputElement>) => </Button>
table.getColumn("status")?.setFilterValue(event.target.value) </DropdownMenuTrigger>
} <DropdownMenuContent className="w-56 text-sm">
className="w-full " <DropdownMenuRadioGroup
/> value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[150px] overflow-y-auto"
>
<div className="flex flex-row justify-between my-1 mx-1">
<p>Filter</p>
</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">
Menunggu Review
</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">
Diterima
</label>
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div> </div>
</div> </div>
<Table className="overflow-hidden mt-3"> <Table className="overflow-hidden mt-3">
@ -228,7 +314,6 @@ const PressReleaseTable = () => {
totalPage={totalPage} totalPage={totalPage}
/> />
</div> </div>
\{" "}
</div> </div>
); );
}; };

View File

@ -0,0 +1,190 @@
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 { format } from "date-fns";
import { Link } from "@/components/navigation";
import { useRouter } from "next/navigation";
import { useToast } from "@/components/ui/use-toast";
import { deleteCategory } from "@/service/settings/settings";
import { deleteTask } from "@/service/task";
import { error, loading } from "@/lib/swal";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import { useTranslations } from "next-intl";
const useTableColumns = () => {
const t = useTranslations("Table"); // Panggil di dalam hook
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: t("no"),
cell: ({ row }) => <span>{row.getValue("no")}</span>,
},
{
accessorKey: "title",
header: t("title"),
cell: ({ row }) => (
<div>
<span>{row.getValue("title")}</span>
{row.original.isForward && (
<Button
variant={"outline"}
color="primary"
size="sm"
className="ml-3 rounded-xl"
>
Forward
</Button>
)}
</div>
),
},
{
accessorKey: "createdAt",
header: t("upload-date"),
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const isActive = row.original.isActive;
const isDone = row.original.isDone;
let statusText = "";
if (isDone) {
statusText = "Selesai";
} else if (isActive) {
statusText = "Aktif";
} else {
statusText = "Nonaktif";
}
const statusColors: Record<string, string> = {
Aktif: "bg-primary/20 text-primary",
Selesai: "bg-success/20 text-success",
Nonaktif: "bg-gray-200 text-gray-500",
};
const statusStyles = statusColors[statusText] || "default";
return (
<Badge className={cn("rounded-full px-5", statusStyles)}>
{statusText}
</Badge>
);
},
},
{
id: "actions",
accessorKey: "action",
header: t("action"),
enableHiding: false,
cell: ({ row }) => {
const router = useRouter();
const MySwal = withReactContent(Swal);
async function deleteProcess(id: any) {
loading();
const resDelete = await deleteTask(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
success();
}
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
window.location.reload();
}
});
}
const TaskDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data",
text: "",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
deleteProcess(id);
}
});
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
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" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
<Link href={`/contributor/task-ta/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
</Link>
<Link href={`/contributor/task-ta/update/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
</Link>
<DropdownMenuItem
onClick={() => TaskDelete(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
>
<Trash2 className="w-4 h-4 me-1.5" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
return columns;
};
export default useTableColumns;

View File

@ -0,0 +1,405 @@
"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 {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
MoreVertical,
Search,
SquarePen,
Trash2,
TrendingDown,
TrendingUp,
} 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 "./columns";
import { listTask, listTaskTa } from "@/service/task";
import { Label } from "@/components/ui/label";
import { format } from "date-fns";
import { useTranslations } from "next-intl";
import useTableColumns from "./columns";
const TaskTaTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
const t = useTranslations("AnalyticsDashboard");
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 [showData, setShowData] = React.useState("50");
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: Number(showData),
});
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 [totalPage, setTotalPage] = React.useState(1);
const [limit, setLimit] = React.useState(10);
const [isSpecificAttention, setIsSpecificAttention] = React.useState(true);
const [search, setSearch] = React.useState<string>("");
const columns = useTableColumns();
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,
},
});
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
React.useEffect(() => {
fetchData();
}, [
page,
showData,
isSpecificAttention,
search,
dateFilter,
filterByCode,
statusFilter,
]);
async function fetchData() {
const formattedStartDate = dateFilter
? format(new Date(dateFilter), "yyyy-MM-dd")
: "";
try {
const res = await listTaskTa(
page - 1,
search,
showData,
filterByCode,
formattedStartDate,
isSpecificAttention ? "atensi-khusus" : "tugas-harian",
statusFilter
);
const data = res?.data?.data;
const contentData = data?.content;
// let contentDataFilter = res?.data?.data?.content || [];
// Filter berdasarkan status
// contentDataFilter = contentDataFilter.filter((item: any) => {
// const isSelesai = statusFilter.includes(1) ? item.isDone : true;
// const isAktif = statusFilter.includes(2) ? item.isActive : true;
// return isSelesai && isAktif;
// });
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * Number(showData) + index + 1;
});
console.log("contentData : ", contentData);
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(data?.totalPages);
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setFilterByCode(e.target.value);
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 (
<div className="w-full overflow-x-auto">
<div className="mx-5 mb-3">
<div className="">
<div className="row">
<div className="flex justify-between mb-6">
<label className="inline-flex text-md cursor-pointer">
<input
type="checkbox"
onChange={() => setIsSpecificAttention(!isSpecificAttention)}
hidden
/>
<span
className={` ${
isSpecificAttention
? "bg-default-900 text-white"
: "dark:text-default-700 border-2"
}
px-[18px] py-1 transition duration-100 rounded`}
>
{t("special-attention")}
</span>
<span
className={`
${
!isSpecificAttention
? "bg-default-900 text-white"
: " dark:text-default-700 border-2"
}
px-[18px] py-1 transition duration-100 rounded
`}
>
{t("daily-tasks")}
</span>
</label>
</div>
</div>
</div>
</div>
<div className="flex flex-col sm:flex-row lg:flex-row justify-between sm:items-center md:items-center lg:items-center px-5">
<div className="mb-3 sm:mb-0 lg-mb-0">
<InputGroup merged>
<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 Title dan Code"
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white w-full"
value={search}
onChange={handleSearch}
/>
</InputGroup>
</div>
<div className=" flex flex-row items-center gap-3">
<div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="ml-auto w-full sm:w-[100px]"
size="md"
>
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<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>{t("date")}</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">
{t("done")}
</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">
{t("active")}
</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>
<Table className="overflow-hidden mt-3">
<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 TaskTaTable;

View File

@ -0,0 +1,16 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTaskTa from "@/components/form/task-ta/task-ta-form";
const TaskTaCreatePage = () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTaskTa />
</div>
</div>
);
};
export default TaskTaCreatePage;

View File

@ -0,0 +1,18 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTask from "@/components/form/task/task-form";
import FormTaskDetail from "@/components/form/task/task-detail-form";
import FormTaskTaDetail from "@/components/form/task-ta/task-ta-detail-form";
const TaskTaDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTaskTaDetail />
</div>
</div>
);
};
export default TaskTaDetailPage;

View File

@ -0,0 +1,9 @@
export const metadata = {
title: "Task",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -0,0 +1,55 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import TaskTable from "./components/task-ta-table";
import { Button } from "@/components/ui/button";
import { UploadIcon } from "lucide-react";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Link } from "@/components/navigation";
import { checkAuthorization, checkLoginSession } from "@/lib/utils";
import React, { useEffect } from "react";
import { useTranslations } from "next-intl";
import TaskTaTable from "./components/task-ta-table";
const TaskTaPage = () => {
const t = useTranslations("AnalyticsDashboard");
useEffect(() => {
function initState() {
checkAuthorization("admin"); // Specify the page, e.g., "admin" or another value
checkLoginSession();
}
initState();
}, []);
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<Card>
<CardHeader className="border-b border-solid border-default-200 mb-6">
<CardTitle>
<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">
{t("tabel")} {t("task-ta")}
</div>
<div className="flex-none">
<Link href={"/contributor/task-ta/create"}>
<Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" />
{t("create-task")}
</Button>
</Link>
</div>
</div>
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<TaskTaTable />
</CardContent>
</Card>
</div>
</div>
);
};
export default TaskTaPage;

View File

@ -0,0 +1,19 @@
import { Card, CardContent } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormTask from "@/components/form/task/task-form";
import FormTaskDetail from "@/components/form/task/task-detail-form";
import FormTaskEdit from "@/components/form/task/task-edit-form";
import FormTaskTaEdit from "@/components/form/task-ta/task-ta-edit-form";
const TaskTaDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTaskTaEdit />
</div>
</div>
);
};
export default TaskTaDetailPage;

View File

@ -42,6 +42,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";
@ -71,9 +73,10 @@ const TaskTable = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: Number(showData),
}); });
const [statusFilter, setStatusFilter] = React.useState<number[]>([]); const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const [dateFilter, setDateFilter] = React.useState(""); const [dateFilter, setDateFilter] = React.useState("");
@ -117,7 +120,7 @@ const TaskTable = () => {
fetchData(); fetchData();
}, [ }, [
page, page,
limit, showData,
isSpecificAttention, isSpecificAttention,
search, search,
dateFilter, dateFilter,
@ -133,7 +136,7 @@ const TaskTable = () => {
const res = await listTask( const res = await listTask(
page - 1, page - 1,
search, search,
limit, showData,
filterByCode, filterByCode,
formattedStartDate, formattedStartDate,
isSpecificAttention ? "atensi-khusus" : "tugas-harian", isSpecificAttention ? "atensi-khusus" : "tugas-harian",
@ -153,7 +156,7 @@ const TaskTable = () => {
// }); // });
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);
@ -241,36 +244,64 @@ const TaskTable = () => {
/> />
</InputGroup> </InputGroup>
</div> </div>
<div className="w-full sm:w-[100px] items-center gap-2">
<div className=" gap-3"> <div className=" flex flex-row items-center gap-3">
<div className="flex items-center py-4"> <div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button size="md" variant="outline">
variant="outline" 1 - {showData} Data
className="ml-auto w-full sm:w-[100px]"
size="md"
>
Filter <ChevronDown />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent className="w-56 text-sm">
align="end" <DropdownMenuRadioGroup
className="w-64 h-[200px] overflow-y-auto" value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="ml-auto w-full sm:w-[100px]"
size="md"
> >
<div className="flex flex-row justify-between my-1 mx-1"> Filter <ChevronDown />
<p>Filter</p> </Button>
</div> </DropdownMenuTrigger>
<div className="mx-2 my-1"> <DropdownMenuContent
<Label>{t("date")}</Label> align="end"
<Input className="w-64 h-[200px] overflow-y-auto"
type="date" >
value={dateFilter} <div className="flex flex-row justify-between my-1 mx-1">
onChange={(e) => setDateFilter(e.target.value)} <p>Filter</p>
className="max-w-sm" </div>
/> <div className="mx-2 my-1">
</div> <Label>{t("date")}</Label>
{/* <div className="mx-2 my-1"> <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> <Label>Code</Label>
<Input <Input
placeholder="Filter Status..." placeholder="Filter Status..."
@ -279,36 +310,36 @@ const TaskTable = () => {
className="max-w-sm" className="max-w-sm"
/> />
</div> */} </div> */}
<Label className="ml-2 mt-2">Status</Label> <Label className="ml-2 mt-2">Status</Label>
<div className="flex items-center px-4 py-1"> <div className="flex items-center px-4 py-1">
<input <input
type="checkbox" type="checkbox"
id="status-1" id="status-1"
className="mr-2" className="mr-2"
checked={statusFilter.includes(1)} checked={statusFilter.includes(1)}
onChange={() => handleStatusCheckboxChange(1)} onChange={() => handleStatusCheckboxChange(1)}
/> />
<label htmlFor="status-1" className="text-sm"> <label htmlFor="status-1" className="text-sm">
{t("done")} {t("done")}
</label> </label>
</div> </div>
<div className="flex items-center px-4 py-1"> <div className="flex items-center px-4 py-1">
<input <input
type="checkbox" type="checkbox"
id="status-2" id="status-2"
className="mr-2" className="mr-2"
checked={statusFilter.includes(2)} checked={statusFilter.includes(2)}
onChange={() => handleStatusCheckboxChange(2)} onChange={() => handleStatusCheckboxChange(2)}
/> />
<label htmlFor="status-2" className="text-sm"> <label htmlFor="status-2" className="text-sm">
{t("active")} {t("active")}
</label> </label>
</div> </div>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div>
</div> </div>
{/* <div className="flex-none"> </div>
{/* <div className="flex-none">
<Input <Input
placeholder="Filter Status..." placeholder="Filter Status..."
value={ value={
@ -320,7 +351,6 @@ const TaskTable = () => {
className="max-w-sm " className="max-w-sm "
/> />
</div> */} </div> */}
</div>
</div> </div>
<Table className="overflow-hidden mt-3"> <Table className="overflow-hidden mt-3">
<TableHeader> <TableHeader>

View File

@ -34,7 +34,7 @@ const TaskPage = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/contributor/task/create"}> <Link href={"/contributor/task/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-task")} {t("create-task")}
</Button> </Button>
</Link> </Link>

View File

@ -168,8 +168,8 @@ export default function ExecutiveDashboard() {
const [ticket6, setTicket6] = useState(""); const [ticket6, setTicket6] = useState("");
const [isInternational, setIsInternational] = useState([false, false, false]); const [isInternational, setIsInternational] = useState([false, false, false]);
const baseUrl = "https://db-mediahub.polri.go.id/"; const baseUrl = "https://analytic.sitani.info/";
const url = "https://db-mediahub.polri.go.id/trusted/"; const url = "https://analytic.sitani.info/trusted/";
const view1 = const view1 =
levelName == "MABES POLRI" levelName == "MABES POLRI"
@ -182,15 +182,15 @@ export default function ExecutiveDashboard() {
levelName == "MABES POLRI" levelName == "MABES POLRI"
? isInternational[1] ? isInternational[1]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-published-produksi?" ? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-published-produksi?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-published-produksi-executive?" : "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-executive?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-konten-publisher-polda-executive?provinsi-polda=${poldaState}&`; : `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-publisher-polda-executive?provinsi-polda=${poldaState}&`;
const view3 = const view3 =
levelName == "MABES POLRI" levelName == "MABES POLRI"
? isInternational[2] ? isInternational[2]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-waktu-akses-pengguna?" ? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-waktu-akses-pengguna?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-waktu-akses-pengguna-executive?" : "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-executive?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev202/db-waktu-akses-pengguna-polda-executive?provinsi-polda=${poldaState}&`; : `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?provinsi-polda=${poldaState}&`;
const view4 = const view4 =
levelName == "MABES POLRI" levelName == "MABES POLRI"

View File

@ -19,6 +19,7 @@ import BlogTable from "../contributor/blog/components/blog-table";
import ContentTable from "./routine-task/components/content-table"; import ContentTable from "./routine-task/components/content-table";
import RecentActivity from "./routine-task/components/recent-activity"; import RecentActivity from "./routine-task/components/recent-activity";
import { Link } from "@/components/navigation"; import { Link } from "@/components/navigation";
import ReportTable from "../contributor/report/components/report-table";
const DashboardPage = () => { const DashboardPage = () => {
const t = useTranslations("AnalyticsDashboard"); const t = useTranslations("AnalyticsDashboard");
@ -59,6 +60,12 @@ const DashboardPage = () => {
> >
{t("indeks")} {t("indeks")}
</TabsTrigger> </TabsTrigger>
<TabsTrigger
value="report"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
>
{t("report")}
</TabsTrigger>
</TabsList> </TabsList>
</Card> </Card>
<TabsContent value="routine-task"> <TabsContent value="routine-task">
@ -107,7 +114,7 @@ const DashboardPage = () => {
<Card> <Card>
<CardHeader className="flex flex-row items-center"> <CardHeader className="flex flex-row items-center">
<CardTitle className="flex-1">{t("tabel")}</CardTitle> <CardTitle className="flex-1">{t("tabel")}</CardTitle>
<DashboardDropdown /> {/* <DashboardDropdown /> */}
</CardHeader> </CardHeader>
<CardContent className="p-0"> <CardContent className="p-0">
<ContentTable /> <ContentTable />
@ -157,7 +164,7 @@ const DashboardPage = () => {
<div className="grid grid-cols-12 gap-5"> <div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12"> <div className="lg:col-span-12 col-span-12">
<Card> <Card>
<Card className="py-4 px-3"> {/* <Card className="py-4 px-3">
<div className="flex flex-col md:flex-row md:justify-between md:items-center lg:flex-row lg:justify-between lg:items-center"> <div className="flex flex-col md:flex-row md:justify-between md:items-center lg:flex-row lg:justify-between lg:items-center">
<div className="flex-1 text-xl font-medium text-default-900 mb-2"> <div className="flex-1 text-xl font-medium text-default-900 mb-2">
Table Indeks Table Indeks
@ -171,7 +178,7 @@ const DashboardPage = () => {
</Link> </Link>
</div> </div>
</div> </div>
</Card> </Card> */}
<CardContent className="p-0 mt-3"> <CardContent className="p-0 mt-3">
<BlogTable /> <BlogTable />
</CardContent> </CardContent>
@ -179,6 +186,17 @@ const DashboardPage = () => {
</div> </div>
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="report">
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<Card>
<CardContent className="p-0 mt-3">
<ReportTable />
</CardContent>
</Card>
</div>
</div>
</TabsContent>
</Tabs> </Tabs>
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ const CommunicationPage = () => {
{tab === "Pertanyaan Internal" && ( {tab === "Pertanyaan Internal" && (
<Link href="/shared/communication/internal/create"> <Link href="/shared/communication/internal/create">
<Button color="primary" size="md"> <Button color="primary" size="md">
<PlusIcon /> <PlusIcon size={18} className="mr-2" />
{t("new-question")} {t("new-question")}
</Button> </Button>
</Link> </Link>
@ -32,7 +32,7 @@ const CommunicationPage = () => {
{tab === "Kolaborasi" && ( {tab === "Kolaborasi" && (
<Link href="/shared/communication/collaboration/create"> <Link href="/shared/communication/collaboration/create">
<Button color="primary" size="md"> <Button color="primary" size="md">
<PlusIcon /> <PlusIcon size={18} className="mr-2" />
{t("new-collaboration")} {t("new-collaboration")}
</Button> </Button>
</Link> </Link>

View File

@ -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,
@ -43,6 +44,14 @@ import TablePagination from "@/components/table/table-pagination";
import columns from "./columns"; import columns from "./columns";
import { listContest } from "@/service/contest/contest"; import { listContest } from "@/service/contest/contest";
import useTableColumns from "./columns"; import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const TaskTable = () => { const TaskTable = () => {
const router = useRouter(); const router = useRouter();
@ -56,14 +65,16 @@ const TaskTable = () => {
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("50");
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 [limit, setLimit] = React.useState(10);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState<string>("");
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns(); const columns = useTableColumns();
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
@ -95,15 +106,30 @@ const TaskTable = () => {
React.useEffect(() => { React.useEffect(() => {
fetchData(); fetchData();
}, [page, limit, search]); }, [page, showData, search, statusFilter]);
async function fetchData() { async function fetchData() {
try { try {
const res = await listContest(search, limit, page - 1); const res = await listContest(search, showData, page - 1);
const data = res?.data?.data; const data = res?.data?.data;
const contentData = data?.content; let contentData = data?.content;
if (statusFilter.length > 0) {
contentData = contentData.filter((item: any) => {
const { isPublishForAll, isPublishForMabes } = item;
const status = (() => {
if (isPublishForAll && isPublishForMabes) return 1; // Publish
if (!isPublishForAll && isPublishForMabes) return 3; // Terkirim
return 2; // Pending
})();
return statusFilter.includes(status);
});
}
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);
@ -116,6 +142,14 @@ const TaskTable = () => {
} }
} }
const handleStatusCheckboxChange = (status: number) => {
setStatusFilter((prev) =>
prev.includes(status)
? prev.filter((item) => item !== status)
: [...prev, status]
);
};
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); setSearch(e.target.value);
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -123,8 +157,8 @@ const TaskTable = () => {
return ( return (
<div className="w-full overflow-x-auto"> <div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5"> <div className="flex flex-col sm:flex-row md:flex-row lg:flex-row justify-between items-center px-3 gap-y-2">
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="w-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged> <InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary"> <InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" /> <Search className=" h-4 w-4 dark:text-white" />
@ -138,17 +172,90 @@ const TaskTable = () => {
/> />
</InputGroup> </InputGroup>
</div> </div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]"> <div className="flex flex-row">
<Input <div className="mx-3">
placeholder="Filter Status..." <DropdownMenu>
value={ <DropdownMenuTrigger asChild>
(table.getColumn("status")?.getFilterValue() as string) ?? "" <Button size="md" variant="outline">
} 1 - {showData} Data
onChange={(event: React.ChangeEvent<HTMLInputElement>) => </Button>
table.getColumn("status")?.setFilterValue(event.target.value) </DropdownMenuTrigger>
} <DropdownMenuContent className="w-56 text-sm">
className="max-w-sm " <DropdownMenuRadioGroup
/> value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[150px] overflow-y-auto"
>
<div className="flex flex-row justify-between my-1 mx-1">
<p>Filter</p>
</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">
Publish
</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">
Pending
</label>
</div>
<div className="flex items-center px-4 py-1">
<input
type="checkbox"
id="status-3"
className="mr-2"
checked={statusFilter.includes(3)}
onChange={() => handleStatusCheckboxChange(3)}
/>
<label htmlFor="status-3" className="text-sm">
Terkirim
</label>
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div> </div>
</div> </div>
<Table className="overflow-hidden mt-3"> <Table className="overflow-hidden mt-3">

View File

@ -31,7 +31,7 @@ const ContestPage = () => {
<div className="flex-none"> <div className="flex-none">
<Link href={"/shared/contest/create"}> <Link href={"/shared/contest/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon size={18} className="mr-2" />
{t("create-contest")} {t("create-contest")}
</Button> </Button>
</Link> </Link>

View File

@ -113,19 +113,18 @@ const CuratedContentPage = () => {
</div> </div>
</div> </div>
<div className="ml-5 pb-3"> <div className="ml-5 pb-3">
<Label>Audio Visual</Label>
<div className="px-5 my-5"> <div className="px-5 my-5">
<VideoSliderPage /> <VideoSliderPage />
</div> </div>
<Label>Audio</Label>
<div className="px-5 my-5"> <div className="px-5 my-5">
<AudioSliderPage /> <AudioSliderPage />
</div> </div>
<Label>Foto</Label>
<div className="px-5 my-5"> <div className="px-5 my-5">
<ImageSliderPage /> <ImageSliderPage />
</div> </div>
<Label>Teks</Label>
<div className="px-5 my-5"> <div className="px-5 my-5">
<TeksSliderPage /> <TeksSliderPage />
</div> </div>

View File

@ -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,
@ -27,50 +19,60 @@ const columns: ColumnDef<any>[] = [
cell: ({ row }) => <span>{row.getValue("no")}</span>, cell: ({ row }) => <span>{row.getValue("no")}</span>,
}, },
{ {
accessorKey: "question", accessorKey: "report",
header: "Question", header: "Pelapor",
cell: ({ row }) => <span>{row.getValue("question")}</span>, cell: ({ row }) => <span>{row.getValue("report")}</span>,
}, },
{ {
accessorKey: "answer", accessorKey: "reportDetail",
header: "Answer", header: "Detail Laporan",
cell: ({ row }) => <span>{row.getValue("answer")}</span>, cell: ({ row }) => <span>{row.getValue("reportDetail")}</span>,
}, },
{ {
id: "actions", accessorKey: "reportAccount",
accessorKey: "action", header: "Terlapor",
header: "Actions", cell: ({ row }) => <span>{row.getValue("reportAccount")}</span>,
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"
>
<span className="sr-only">Open menu</span>
<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">
<Eye className="w-4 h-4 me-1.5" />
View
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem>
<DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
<Trash2 className="w-4 h-4 me-1.5" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
}, },
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => <span>{row.getValue("status")}</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"
// >
// <span className="sr-only">Open menu</span>
// <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">
// <Eye className="w-4 h-4 me-1.5" />
// View
// </DropdownMenuItem>
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
// <SquarePen className="w-4 h-4 me-1.5" />
// Edit
// </DropdownMenuItem>
// <DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
// <Trash2 className="w-4 h-4 me-1.5" />
// Delete
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// );
// },
// },
]; ];
export default columns; export default columns;

View File

@ -51,7 +51,7 @@ import TablePagination from "@/components/table/table-pagination";
import { getFaqList } from "@/service/master/faq"; import { getFaqList } from "@/service/master/faq";
import columns from "./column"; import columns from "./column";
const FaqTable = () => { const ReportTable = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@ -94,11 +94,11 @@ const FaqTable = () => {
}); });
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();
@ -191,9 +191,14 @@ const FaqTable = () => {
)} )}
</TableBody> </TableBody>
</Table> </Table>
<TablePagination table={table} totalData={totalData} totalPage={totalPage} visiblePageCount={5} /> <TablePagination
table={table}
totalData={totalData}
totalPage={totalPage}
visiblePageCount={5}
/>
</div> </div>
); );
}; };
export default FaqTable; export default ReportTable;

View File

@ -3,35 +3,44 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import FaqTable from "./components/table"; import FaqTable from "./components/table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { StatisticsBlock } from "@/components/blocks/statistics-block";
import ReportTable from "./components/table";
const FaqPage = async () => { const FaqPage = async () => {
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center">
<div className="flex-1 text-xl font-medium text-default-900">
Pelaporan Akun
</div>
<div className="flex-none"></div>
</div>
<Card> <Card>
<CardHeader className="border-b border-solid border-default-200 mb-6"> <CardContent className="p-4">
<CardTitle> <div className="grid md:grid-cols-3 gap-4">
<div className="flex items-center"> <StatisticsBlock
<div className="flex-1 text-xl font-medium text-default-900"> total="64"
FAQ Data title="Total Pelaporan"
</div> className=" border shadow-none"
<div className="flex-none"> />
<Button <StatisticsBlock
fullWidth title="Total Disetujui"
size="md" total="564"
> className=" border shadow-none"
<Plus className="w-6 h-6 me-1.5"/> chartColor="#FB8F65"
New FAQ />
</Button> <StatisticsBlock
</div> title="Pelaporan Pending"
</div> total="+5.0%"
</CardTitle> className=" border shadow-none"
</CardHeader> chartColor="#2563eb"
<CardContent className="p-0"> />
<FaqTable /> </div>
</CardContent> </CardContent>
</Card> </Card>
<ReportTable />
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,109 @@
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 { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: "No",
cell: ({ row }) => <span> {row.getValue("no")}</span>,
},
{
accessorKey: "title",
header: "Nama",
cell: ({ row }) => (
<span className="normal-case"> {row.getValue("title")}</span>
),
},
{
accessorKey: "phoneNumber",
header: "No.Telp",
cell: ({ row }) => {
const createdBy = row.original.createdBy; // Akses properti category
return (
<span className="normal-case">{createdBy?.fullname || "N/A"}</span>
);
},
},
{
accessorKey: "email",
header: "Email",
cell: ({ row }) => {
const sendTo = row.original.sendTo; // Akses properti category
return <span className="normal-case">{sendTo?.fullname || "N/A"}</span>;
},
},
{
accessorKey: "createdName",
header: "Admin",
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</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"
// >
// <span className="sr-only">Open menu</span>
// <MoreVertical className="h-4 w-4 text-default-800" />
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent className="p-0" align="end">
// <Link
// href={`/supervisor/communications/internal/detail/${row.original.id}`}
// >
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
// <Eye className="w-4 h-4 me-1.5" />
// View
// </DropdownMenuItem>
// </Link>
// <Link
// href={`/supervisor/communications/internal/update/${row.original.id}`}
// >
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
// <SquarePen className="w-4 h-4 me-1.5" />
// Edit
// </DropdownMenuItem>
// </Link>
// <DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
// <Trash2 className="w-4 h-4 me-1.5" />
// Delete
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// );
// },
// },
];
export default columns;

View File

@ -0,0 +1,299 @@
"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,
UploadIcon,
} from "lucide-react";
import { cn, getCookiesDecrypt } 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 "./columns";
import {
listDataAudio,
listDataImage,
listDataVideo,
} from "@/service/content/content";
import { listTicketingInternal } from "@/service/communication/communication";
import { Link } from "@/components/navigation";
import { Card } from "nextra-theme-docs";
import { CardContent } from "@/components/ui/card";
const ContactTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
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 [showData, setShowData] = React.useState("10");
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: Number(showData),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
const [search, setSearch] = React.useState<string>("");
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const [activeCategory, setActiveCategory] = React.useState<string | null>(
null
);
const roleId = getCookiesDecrypt("urie");
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,
},
});
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
// React.useEffect(() => {
// fetchData();
// setPagination({
// pageIndex: 0,
// pageSize: Number(showData),
// });
// }, [page, showData]);
React.useEffect(() => {
if (activeCategory) {
fetchData();
setPagination({
pageIndex: 0,
pageSize: Number(showData),
});
}
}, [page, showData, activeCategory]);
let typingTimer: any;
const doneTypingInterval = 1500;
const handleKeyUp = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
const handleKeyDown = () => {
clearTimeout(typingTimer);
};
async function doneTyping() {
fetchData();
}
// async function fetchData() {
// try {
// const res = await listTicketingInternal(
// page - 1,
// Number(showData),
// search
// );
// 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 : ", contentData);
// setDataTable(contentData);
// setTotalData(data?.totalElements);
// setTotalPage(data?.totalPages);
// } catch (error) {
// console.error("Error fetching tasks:", error);
// }
// }
async function fetchData() {
try {
const res = await listTicketingInternal(
page - 1,
Number(showData),
search,
activeCategory
);
const data = res?.data?.data;
const contentData = data?.content || [];
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(data?.totalPages);
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
return (
<div className="w-full overflow-x-auto ">
<div className="flex justify-between items-center">
<div className="mt-3 flex flex-row items-center gap-2">
<InputGroup merged>
<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"
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
/>
</InputGroup>
</div>
</div>
<div className="flex flex-row items-center justify-around my-3">
{["Polda", "Satker Humas", "Satker Mabes"].map((category, index) => (
<CardContent
key={index}
className="shadow-lg border p-2 cursor-pointer"
onClick={() => setActiveCategory(category)}
>
<div className="flex flex-col gap-3 text-center py-5 w-[250px]">
<p className="text-3xl font-bold">
{index === 0 ? 34 : index === 1 ? 1 : 43}
</p>
<p>{category}</p>
<p className="text-blue-700">Lihat Kontak</p>
</div>
</CardContent>
))}
</div>
{activeCategory && (
<div>
<Table className="overflow-hidden mt-3">
<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>
)}
</div>
);
};
export default ContactTable;

View File

@ -0,0 +1,359 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
import { Link, useRouter } from "@/i18n/routing";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useEffect, useState } from "react";
import {
AdministrationLevelList,
getListCompetencies,
getListExperiences,
saveUserInternal,
saveUserRolePlacements,
} from "@/service/management-user/management-user";
import { loading } from "@/config/swal";
import { Label } from "@/components/ui/label";
const FormSchema = z.object({
name: z.string({
required_error: "Required",
}),
username: z.string({
required_error: "Required",
}),
password: z.string({
required_error: "Required",
}),
phoneNumber: z.string({
required_error: "Required",
}),
email: z.string({
required_error: "Required",
}),
skills: z.string({
required_error: "Required",
}),
experiences: z.string({
required_error: "Required",
}),
company: z.string({
required_error: "Required",
}),
});
export type Placements = {
index: number;
roleId?: string;
userLevelId?: number;
};
export default function AddContactForm() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
const [incrementId, setIncrementId] = useState(1);
const [placementRows, setPlacementRows] = useState<Placements[]>([
{ index: 0, roleId: "", userLevelId: 0 },
]);
const [userCompetencies, setUserCompetencies] = useState<any>();
const [userExperiences, setUserExperiences] = useState<any>();
const [userLevels, setUserLevels] = useState<any>();
const roleSelection = [
{
id: "11",
name: "Polda",
},
{
id: "12",
name: "Satker Humas",
},
{
id: "13",
name: "Satker Mabes",
},
];
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
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 save = async (data: z.infer<typeof FormSchema>) => {
console.log("data", data);
const dataReq = {
firstName: data.name,
username: data.username,
email: data.email,
password: data.password,
adress: "",
roleId: "EXP-ID",
phoneNumber: data.phoneNumber,
userCompetencyId: data.skills,
userExperienceId: data.experiences,
companyName: data.company,
};
loading();
const res = await saveUserInternal(dataReq);
const resData = res?.data?.data;
const userProfileId = resData?.id;
var placementArr: any[] = [];
placementRows.forEach((row: any) => {
placementArr.push({
roleId: Number(row.roleId),
userLevelId: Number(row.userLevelId),
userProfileId: userProfileId,
});
});
const dataReq2 = {
userId: userProfileId,
placements: placementArr,
};
const res2 = await saveUserRolePlacements(dataReq2);
const resData2 = res2?.data?.data;
success("/admin/add-experts");
};
function success(redirect: string): void {
MySwal.fire({
title: '<p class="text-green-600 font-bold">Sukses</p>',
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: '<span class="text-white">OK</span>',
allowOutsideClick: false,
}).then((result) => {
if (result.isConfirmed) {
router.push(redirect);
}
});
}
function successSubmit() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/admin/add-experts");
}
});
}
const handleSelectionChange = (
index: number,
type: "roleId" | "userLevelId",
value: string
) => {
setPlacementRows((prevRows) =>
prevRows.map((row) =>
row.index === index ? { ...row, [type]: value } : row
)
);
};
const handleRemoveRow = (index: number) => {
console.log(index);
console.log(placementRows);
const newPlacements = placementRows.filter((row) => row.index != index);
console.log(newPlacements);
setPlacementRows(newPlacements);
};
const handleAddRow = () => {
setPlacementRows((prevRows: any) => [
...prevRows,
{ index: incrementId, roleId: "", userLevelId: 0 },
]);
setIncrementId((prevId) => prevId + 1);
};
return (
<div>
<SiteBreadcrumb />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4"
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<Input
type="email"
value={field.value}
placeholder="Masukkan Nama Lengkap"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Nama Lengkap</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Nama Lengkap"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phoneNumber"
render={({ field }) => (
<FormItem>
<FormLabel>No. HP</FormLabel>
<Input
type="number"
value={field.value}
placeholder="Masukkan Nama Lengkap"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<div className="mt-2 mb-0 pb-0">
<FormLabel>
Kategori Akun <span className="text-red-500">*</span>
</FormLabel>
<Select
// onValueChange={(e) =>
// handleSelectionChange(row.index, "roleId", e)
// }
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih Role" />
</SelectTrigger>
</FormControl>
<SelectContent>
{roleSelection?.map((item: any) => (
<SelectItem key={item.id} value={String(item.id)}>
{item.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Username Admin</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Nama Lengkap"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Username Approver</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Nama Lengkap"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Username Kontributor</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Nama Lengkap"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row justify-end gap-2 mt-4 pt-4">
<Link href="/supervisor/communications/contact">
<Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel
</Button>
</Link>
<Button size="md" type="submit" color="primary" className="text-xs">
Submit
</Button>
</div>
</form>
</Form>
</div>
);
}

View File

@ -0,0 +1,142 @@
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 { format } from "date-fns";
import { Link } from "@/components/navigation";
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: "No",
cell: ({ row }) => <span> {row.getValue("no")}</span>,
},
{
accessorKey: "title",
header: "Nama",
cell: ({ row }) => (
<span className="normal-case"> {row.getValue("title")}</span>
),
},
{
accessorKey: "phoneNumber",
header: "No.Telp",
cell: ({ row }) => {
const createdBy = row.original.createdBy; // Akses properti category
return (
<span className="normal-case">{createdBy?.fullname || "N/A"}</span>
);
},
},
{
accessorKey: "email",
header: "Email",
cell: ({ row }) => {
const sendTo = row.original.sendTo; // Akses properti category
return <span className="normal-case">{sendTo?.fullname || "N/A"}</span>;
},
},
{
accessorKey: "createdName",
header: "Admin",
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "approver",
header: "Approver",
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "contributor",
header: "Contributor",
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</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"
// >
// <span className="sr-only">Open menu</span>
// <MoreVertical className="h-4 w-4 text-default-800" />
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent className="p-0" align="end">
// <Link
// href={`/supervisor/communications/internal/detail/${row.original.id}`}
// >
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
// <Eye className="w-4 h-4 me-1.5" />
// View
// </DropdownMenuItem>
// </Link>
// <Link
// href={`/supervisor/communications/internal/update/${row.original.id}`}
// >
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
// <SquarePen className="w-4 h-4 me-1.5" />
// Edit
// </DropdownMenuItem>
// </Link>
// <DropdownMenuItem className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none">
// <Trash2 className="w-4 h-4 me-1.5" />
// Delete
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// );
// },
// },
];
export default columns;

View File

@ -0,0 +1,293 @@
"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,
UploadIcon,
} from "lucide-react";
import { cn, getCookiesDecrypt } 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 "./columns";
import {
listDataAudio,
listDataImage,
listDataVideo,
} from "@/service/content/content";
import { listTicketingInternal } from "@/service/communication/communication";
import { Link } from "@/components/navigation";
import { Card } from "nextra-theme-docs";
import { CardContent } from "@/components/ui/card";
const ImportTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
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 [showData, setShowData] = React.useState("10");
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: Number(showData),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
const [search, setSearch] = React.useState<string>("");
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const [activeCategory, setActiveCategory] = React.useState<string | null>(
null
);
const [selectedFile, setSelectedFile] = React.useState(null);
const handleFileChange = (event: any) => {
const file = event.target.files[0];
setSelectedFile(file);
};
const roleId = getCookiesDecrypt("urie");
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,
},
});
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
// React.useEffect(() => {
// fetchData();
// setPagination({
// pageIndex: 0,
// pageSize: Number(showData),
// });
// }, [page, showData]);
React.useEffect(() => {
if (activeCategory) {
fetchData();
setPagination({
pageIndex: 0,
pageSize: Number(showData),
});
}
}, [page, showData, activeCategory]);
let typingTimer: any;
const doneTypingInterval = 1500;
const handleKeyUp = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
const handleKeyDown = () => {
clearTimeout(typingTimer);
};
async function doneTyping() {
fetchData();
}
// async function fetchData() {
// try {
// const res = await listTicketingInternal(
// page - 1,
// Number(showData),
// search
// );
// 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 : ", contentData);
// setDataTable(contentData);
// setTotalData(data?.totalElements);
// setTotalPage(data?.totalPages);
// } catch (error) {
// console.error("Error fetching tasks:", error);
// }
// }
async function fetchData() {
try {
const res = await listTicketingInternal(
page - 1,
Number(showData),
search,
activeCategory
);
const data = res?.data?.data;
const contentData = data?.content || [];
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
setTotalData(data?.totalElements);
setTotalPage(data?.totalPages);
} catch (error) {
console.error("Error fetching tasks:", error);
}
}
return (
<div className="w-full overflow-x-auto ">
<div className="flex">
<input
type="file"
id="fileInput"
onChange={handleFileChange}
className="p-2 border rounded-md"
/>
{/* {selectedFile && (
<p className="text-sm text-gray-600">File: {selectedFile.name}</p>
)} */}
</div>
<div className="my-3">
<Button type="button" variant={"outline"} color="success">
Import Data
</Button>
</div>
<div>
<Table className="overflow-hidden mt-3">
<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>
<div className="my-3">
<Link href="/supervisor/communications/contact">
<Button variant={"outline"} color="primary">
Kembali
</Button>
</Link>
</div>
</div>
);
};
export default ImportTable;

View File

@ -0,0 +1,24 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Button } from "@/components/ui/button";
import { Link } from "@/i18n/routing";
import { PlusIcon, User } from "lucide-react";
import ContactTable from "../components/contact-table";
import ImportTable from "./components/contact-table";
const ImportPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<div className="w-full overflow-x-auto p-4 rounded-sm space-y-3">
<div className="flex justify-between py-3 border rounded-md px-3">
<p className="text-lg font-semibold">Kontak</p>
</div>
<ImportTable />
</div>
</div>
</div>
);
};
export default ImportPage;

View File

@ -0,0 +1,11 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Dashboard Media Hub",
description: "Dashboard Media Hub.",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -0,0 +1,42 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Button } from "@/components/ui/button";
import { Link } from "@/i18n/routing";
import { PlusIcon, User } from "lucide-react";
import InternalSpvTable from "../internal/components/internal-table";
import ContactTable from "./components/contact-table";
const ContactPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<div className="w-full overflow-x-auto p-4 rounded-sm space-y-3">
<div className="flex justify-between py-3 border rounded-md px-3">
<p className="text-lg font-semibold">Kontak</p>
</div>
<div className="flex justify-between py-3">
<p className="text-lg pl-3 font-semibold">Semua Kontak</p>
<div>
<Link href="/supervisor/communications/contact/import-contact">
<Button color="success" size="md">
<PlusIcon />
Import
</Button>
</Link>{" "}
<Link href="/supervisor/communications/contact/create">
<Button color="primary" size="md">
<User />
Tambah Kontak
</Button>
</Link>
</div>
</div>
<ContactTable />
</div>
</div>
</div>
);
};
export default ContactPage;

View File

@ -0,0 +1,197 @@
"use client";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Search } from "lucide-react";
const messages = [
{ name: "Miles Esther", initials: "ME", time: "06.00" },
{ name: "Flores Juanita", initials: "FJ", time: "06.00" },
{ name: "Henry Arthur", initials: "HA", time: "06.00" },
{ name: "Polres Jakarta Selatan", initials: "PJ", time: "06.00" },
];
const WebChatPage = () => {
const [selectedChat, setSelectedChat] = useState<any>(null);
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
{!selectedChat ? (
// Tampilan daftar pesan
<>
<div className="flex justify-between py-3 border rounded-md px-3">
<p className="text-lg font-semibold">Web Chat</p>
</div>
<div className="flex flex-row justify-start rounded-lg ">
<div className="flex flex-row items-center">
<Input placeholder="Cari..." className=" w-[200px] mr-3" />
<Dialog>
<DialogTrigger asChild>
<Button variant="default" size="md" color="primary">
Add
</Button>
</DialogTrigger>
<DialogContent className="max-w-[350px] bg-black text-white rounded-lg p-4">
<DialogHeader>
<DialogTitle className="text-lg font-bold text-white">
New Chat
</DialogTitle>
</DialogHeader>
<div className="relative">
<Search
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
size={16}
/>
<Input
placeholder="Search here..."
className="w-full pl-10 text-white placeholder-gray-400 border-none rounded-md focus:ring-0"
/>
</div>
<div className="mt-4">
<p className="text-sm font-semibold">+ Group Chat</p>
</div>
<div className="mt-4">
<p className="text-sm text-gray-400">
Frequently contacted
</p>
<div className="mt-2 space-y-2">
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center bg-white text-black font-bold rounded-full">
ME
</div>
<span className="text-sm font-medium">
Miles Esther
</span>
</div>
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center bg-white text-black font-bold rounded-full">
HA
</div>
<span className="text-sm font-medium">
Henry Arthur
</span>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
<Card>
<CardContent className="p-4 bg-gray-100 rounded-lg">
<div className="space-y-4">
{messages.map((msg: any, index) => (
<div
key={index}
className="flex items-center justify-between p-4 bg-white rounded-lg shadow cursor-pointer hover:bg-gray-200"
onClick={() => setSelectedChat(msg)}
>
<div className="flex items-center gap-4">
<div className="w-12 h-12 flex items-center justify-center bg-black text-white rounded-full text-lg font-bold">
{msg.initials}
</div>
<div>
<p className="font-bold text-lg">{msg.name}</p>
<p className="text-gray-600 text-sm">
Hallo, untuk patroli hari ini sudah bisa di akses
di daily tasks, silahkan anda periksa
</p>
</div>
</div>
<p className="text-gray-500 text-sm">{msg.time}</p>
</div>
))}
</div>
</CardContent>
</Card>
</>
) : (
// Tampilan chat
<div>
<div className="flex justify-between py-3 border rounded-md px-3 my-3">
<p className="text-lg font-semibold">Web Chat</p>
</div>
<Card className="h-[100vh]">
<CardContent className="p-0 flex flex-col h-full">
{/* Header Chat */}
<div className="flex items-center bg-black text-white p-4">
<button
className="mr-4 text-white text-xl"
onClick={() => setSelectedChat(null)}
>
</button>
<div className="flex items-center gap-4">
<div className="w-10 h-10 flex items-center justify-center bg-white text-black rounded-full text-lg font-bold">
{selectedChat.initials}
</div>
<p className="font-bold text-lg">{selectedChat.name}</p>
</div>
</div>
{/* Body Chat */}
<div className="flex-1 bg-gray-300 p-4 flex flex-col justify-end">
<p className="text-center text-gray-500 mb-4">Today</p>
{/* Pesan masuk */}
<div className="flex items-start mb-4">
<div className="bg-white p-3 rounded-lg shadow max-w-xs">
Hallo, untuk patroli hari ini sudah bisa di akses di daily
tasks, silahkan anda periksa
</div>
<p className="text-gray-500 text-xs ml-2">06.00</p>
</div>
{/* Pesan keluar */}
<div className="flex items-end justify-end mb-4">
<p className="text-gray-500 text-xs mr-2">06.00</p>
<div className="bg-blue-500 text-white p-3 rounded-lg shadow max-w-xs">
Hallo, bisakah mengirimkan rute patroli untuk hari ini?
</div>
</div>
{/* Pesan keluar lainnya */}
<div className="flex items-end justify-end mb-4">
<p className="text-gray-500 text-xs mr-2">06.00</p>
<div className="bg-blue-500 text-white p-3 rounded-lg shadow max-w-xs">
Terima kasih banyak
</div>
</div>
</div>
{/* Input Chat */}
<div className="flex items-center p-4 bg-black">
<Button className="bg-white text-black p-2 rounded-full mr-2">
+
</Button>
<Input
placeholder="Tulis pesan..."
className="flex-1 rounded-lg"
/>
<Button className="ml-2 bg-white text-black p-2 rounded-full">
🎤
</Button>
</div>
</CardContent>
</Card>
</div>
)}
</div>
</div>
);
};
export default WebChatPage;

View File

@ -8,13 +8,29 @@ import NewContent from "@/components/landing-page/new-content";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import { close, error, loading, successCallback, warning } from "@/config/swal"; import { close, error, loading, successCallback, warning } from "@/config/swal";
import { checkWishlistStatus, createPublicSuggestion, deletePublicSuggestion, deleteWishlist, getDetail, getPublicSuggestionList, saveWishlist } from "@/service/landing/landing"; import {
checkWishlistStatus,
createPublicSuggestion,
deletePublicSuggestion,
deleteWishlist,
getDetail,
getPublicSuggestionList,
saveWishlist,
} from "@/service/landing/landing";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking"; import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking";
import { checkMaliciousText, formatDateToIndonesian, getPublicLocaleTimestamp } from "@/utils/globals"; import {
checkMaliciousText,
formatDateToIndonesian,
getPublicLocaleTimestamp,
} from "@/utils/globals";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import parse from "html-react-parser"; import parse from "html-react-parser";
@ -22,7 +38,6 @@ import { Skeleton } from "@/components/ui/skeleton";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Image from "next/image"; import Image from "next/image";
interface Size { interface Size {
label: string; label: string;
value: string; value: string;
@ -64,8 +79,11 @@ const DetailInfo = () => {
const poldaName = params?.polda_name; const poldaName = params?.polda_name;
const satkerName = params?.satker_name; const satkerName = params?.satker_name;
let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/"; let prefixPath = poldaName
? `/polda/${poldaName}`
: satkerName
? `/satker/${satkerName}`
: "/";
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
@ -178,14 +196,14 @@ const DetailInfo = () => {
const sizes: Size[] = Object.entries(scaleFactors).map(([label, factor]) => { const sizes: Size[] = Object.entries(scaleFactors).map(([label, factor]) => {
const width = Number(main?.widthPixel); const width = Number(main?.widthPixel);
const height = Number(main?.heightPixel); const height = Number(main?.heightPixel);
if (isNaN(width) || isNaN(height) || width === 0) { if (isNaN(width) || isNaN(height) || width === 0) {
return { label, value: "Invalid size" }; return { label, value: "Invalid size" };
} }
const newWidth = Math.round(width * factor); const newWidth = Math.round(width * factor);
const newHeight = Math.round((width * factor) / (width / height)); const newHeight = Math.round((width * factor) / (width / height));
return { label, value: `${newWidth} x ${newHeight} px` }; return { label, value: `${newWidth} x ${newHeight} px` };
}); });
@ -263,7 +281,8 @@ const DetailInfo = () => {
xhr.addEventListener("readystatechange", () => { xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = xhr.getResponseHeader("content-type") || "application/octet-stream"; const contentType =
xhr.getResponseHeader("content-type") || "application/octet-stream";
const extension = contentType.split("/")[1]; const extension = contentType.split("/")[1];
const filename = `${name}.${extension}`; const filename = `${name}.${extension}`;
@ -297,7 +316,11 @@ const DetailInfo = () => {
if (type == "wa" && width <= 768) { if (type == "wa" && width <= 768) {
window.open(`whatsapp://send?${url}`, "_blank"); window.open(`whatsapp://send?${url}`, "_blank");
} else if (type == "wa" && width > 768) { } else if (type == "wa" && width > 768) {
window.open(`https://web.whatsapp.com/send?${url}`, "_blank", "noreferrer"); window.open(
`https://web.whatsapp.com/send?${url}`,
"_blank",
"noreferrer"
);
} else { } else {
window.open(url); window.open(url);
} }
@ -386,7 +409,9 @@ const DetailInfo = () => {
} }
async function sendSuggestionChild(parentId: any) { async function sendSuggestionChild(parentId: any) {
const inputElement = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement; const inputElement = document.querySelector(
`#input-comment-${parentId}`
) as HTMLInputElement;
if (inputElement && inputElement.value.length > 3) { if (inputElement && inputElement.value.length > 3) {
loading(); loading();
@ -399,7 +424,9 @@ const DetailInfo = () => {
console.log(data); console.log(data);
const response = await createPublicSuggestion(data); const response = await createPublicSuggestion(data);
console.log(response); console.log(response);
const responseGet: any = await getPublicSuggestionList(slug?.split("-")?.[0]); const responseGet: any = await getPublicSuggestionList(
slug?.split("-")?.[0]
);
console.log(responseGet.data?.data); console.log(responseGet.data?.data);
setListSuggestion(responseGet.data?.data); setListSuggestion(responseGet.data?.data);
@ -477,9 +504,11 @@ const DetailInfo = () => {
<rect id="r" width="${w}" height="${h}" fill="url(#g)" /> <rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" /> <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`; </svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str)); const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return ( return (
<> <>
@ -494,7 +523,16 @@ const DetailInfo = () => {
</div> </div>
) : ( ) : (
<div className="relative"> <div className="relative">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={detailDataImage?.files[selectedImage]?.url} alt="Main" className="rounded-lg w-auto h-fit" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={2560}
height={1440}
src={detailDataImage?.files[selectedImage]?.url}
alt="Main"
className="rounded-lg w-auto h-fit"
/>
<div className="absolute top-4 left-4"></div> <div className="absolute top-4 left-4"></div>
</div> </div>
)} )}
@ -510,7 +548,16 @@ const DetailInfo = () => {
<div className="py-4 flex flex-row gap-3"> <div className="py-4 flex flex-row gap-3">
{detailDataImage?.files?.map((file: any, index: number) => ( {detailDataImage?.files?.map((file: any, index: number) => (
<a onClick={() => setSelectedImage(index)} key={file?.id}> <a onClick={() => setSelectedImage(index)} key={file?.id}>
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} alt="image-small" src={file?.url} className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1920}
height={1080}
alt="image-small"
src={file?.url}
className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600"
/>
</a> </a>
))} ))}
</div> </div>
@ -520,7 +567,10 @@ const DetailInfo = () => {
<div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4"> <div className="text-gray-500 flex flex-col lg:flex-row justify-between items-center border-t mt-4">
<div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between"> <div className="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
<p className="text-xs lg:text-sm"> <p className="text-xs lg:text-sm">
{t("by")}&nbsp;<span className="font-semibold text-black dark:text-white">{detailDataImage?.uploadedBy?.userLevel?.name}</span> {t("by")}&nbsp;
<span className="font-semibold text-black dark:text-white">
{detailDataImage?.uploadedBy?.userLevel?.name}
</span>
</p> </p>
{/* <p className="text-xs lg:text-sm"> {/* <p className="text-xs lg:text-sm">
&nbsp;|&nbsp;{t("updatedOn")} &nbsp;|&nbsp;{t("updatedOn")}
@ -528,7 +578,10 @@ const DetailInfo = () => {
</p> */} </p> */}
<p className="text-xs lg:text-sm"> <p className="text-xs lg:text-sm">
&nbsp;|&nbsp;{t("updatedOn")}&nbsp; &nbsp;|&nbsp;{t("updatedOn")}&nbsp;
{formatDateToIndonesian(new Date(detailDataImage?.updatedAt))} {"WIB"} {formatDateToIndonesian(
new Date(detailDataImage?.updatedAt)
)}{" "}
{"WIB"}
</p> </p>
<p className="text-xs lg:text-sm flex justify-center items-center"> <p className="text-xs lg:text-sm flex justify-center items-center">
&nbsp;|&nbsp; &nbsp;|&nbsp;
@ -546,51 +599,86 @@ const DetailInfo = () => {
{/* Keterangan */} {/* Keterangan */}
<div className="w-full"> <div className="w-full">
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">{detailDataImage?.title}</h1> <h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">
<div className="font-light text-justify mb-5 space-y-4 lg:mb-0" dangerouslySetInnerHTML={{ __html: detailDataImage?.htmlDescription }} /> {detailDataImage?.title}
</h1>
<div
className="font-light text-justify mb-5 space-y-4 lg:mb-0"
dangerouslySetInnerHTML={{
__html: detailDataImage?.htmlDescription,
}}
/>
</div> </div>
</div> </div>
{/* Bagian Kanan */} {/* Bagian Kanan */}
<div className="md:w-1/4 p-4 bg-[#f7f7f7] dark:bg-slate-600 h-fit rounded-lg mx-4"> <div className="md:w-1/4 p-4 bg-[#f7f7f7] dark:bg-slate-600 h-fit rounded-lg mx-4">
{isSaved ? ( {isSaved ? (
<a onClick={() => handleDeleteWishlist()} className="flex flex-col mb-3 items-center justify-center cursor-pointer"> <a
onClick={() => handleDeleteWishlist()}
className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark" width={40} /> <Icon icon="material-symbols:bookmark" width={40} />
<p className="text-base lg:text-lg">{t("delete")}</p> <p className="text-base lg:text-lg">{t("delete")}</p>
</a> </a>
) : ( ) : (
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer"> <a
<Icon icon="material-symbols:bookmark-outline" width={40} /> onClick={() => doBookmark()}
<p className="text-base lg:text-lg">{t("save")}</p> className="flex flex-col mb-3 items-center justify-center cursor-pointer"
>
<Icon icon="material-symbols:bookmark-outline" width={25} />
<p className="text-base lg:text-sm">{t("save")}</p>
</a> </a>
)} )}
{/* garis */} {/* garis */}
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="flex flex-col justify-center items-center gap-3">
<div className="w-auto mb-3">
<Link
href={`/all/filter?title=polda&category=${detailDataImage?.category.id}`}
className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 rounded w-auto"
>
{detailDataImage?.category?.name}
</Link>
</div>
<Link href={`/all/filter?title=polda&category=${detailDataImage?.category.id}`} className="bg-red-600 text-white text-xs font-bold px-3 py-3 my-3 flex justify-center items-center rounded"> <div className="flex justify-center flex-wrap gap-2 mb-4">
{detailDataImage?.category?.name} {detailDataImage?.tags?.split(",").map((tag: string) => (
</Link> <a
onClick={() => router.push(`/all/filter?tag=${tag}`)}
<div className="flex justify-center flex-wrap gap-2 mb-4"> key={tag}
{detailDataImage?.tags?.split(",").map((tag: string) => ( className="bg-gray-200 text-gray-700 text-xs px-3 py-3 font-semibold rounded-full cursor-pointer hover:bg-gray-500"
<a onClick={() => router.push(`/all/filter?tag=${tag}`)} key={tag} className="bg-gray-200 text-gray-700 text-xs px-3 py-1 rounded-full cursor-pointer hover:bg-gray-500"> >
{tag} {tag}
</a> </a>
))} ))}
</div>
</div> </div>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
{/* Opsi Ukuran Foto */} {/* Opsi Ukuran Foto */}
<h4 className="flex text-lg justify-center items-center font-semibold my-3">{t("imageSize")}</h4> <h4 className="flex text-lg justify-center items-center font-semibold my-3">
{t("imageSize")}
</h4>
<div className="border-t border-black my-4"></div> <div className="border-t border-black my-4"></div>
<div className="space-y-2"> <div className="space-y-2">
{sizes.map((size: any) => ( {sizes.map((size: any) => (
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
<div key={size?.label} className="items-center flex flex-row gap-2 cursor-pointer"> <div
<input type="radio" name="size" value={size?.label} checked={selectedSize === size?.label} onChange={(e) => setImageSizeSelected(e.target.value)} className="text-red-600 focus:ring-red-600" /> key={size?.label}
className="items-center flex flex-row gap-2 cursor-pointer"
>
<input
type="radio"
name="size"
value={size?.label}
checked={selectedSize === size?.label}
onChange={(e) => setImageSizeSelected(e.target.value)}
className="text-red-600 focus:ring-red-600"
/>
<div className="text-sm">{size?.label}</div> <div className="text-sm">{size?.label}</div>
</div> </div>
<div className="text-sm">{size?.value}</div> <div className="text-sm">{size?.value}</div>
@ -601,15 +689,30 @@ const DetailInfo = () => {
{/* Download Semua */} {/* Download Semua */}
<div className="mt-4"> <div className="mt-4">
<label className="flex items-center space-x-2 text-sm"> <label className="flex items-center space-x-2 text-sm">
<input type="checkbox" className="text-red-600 focus:ring-red-600" onChange={() => setIsDownloadAll(!isDownloadAll)} /> <input
type="checkbox"
className="text-red-600 focus:ring-red-600"
onChange={() => setIsDownloadAll(!isDownloadAll)}
/>
<span>{t("downloadAll")}</span> <span>{t("downloadAll")}</span>
</label> </label>
</div> </div>
{/* Tombol Download */} {/* Tombol Download */}
<button onClick={handleDownload} className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"> <button
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> onClick={handleDownload}
<path fill="white" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" /> className="mt-4 bg-red-600 text-white w-full py-2 flex justify-center items-center gap-1 rounded-md text-sm hover:bg-red-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="white"
d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"
/>
</svg> </svg>
{t("download")} {t("download")}
</button> </button>
@ -617,29 +720,90 @@ const DetailInfo = () => {
{/* Tombol Bagikan */} {/* Tombol Bagikan */}
<div className="flex flex-row mt-5 justify-center"> <div className="flex flex-row mt-5 justify-center">
<p className="text-base font-semibold">{t("share")}</p> <p className="text-base font-semibold">{t("share")}</p>
<a className="ml-8 cursor-pointer" onClick={() => handleShare("fb", `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`)}> <a
<Icon icon="brandico:facebook" height="20" className="px-auto text-red-600 text-center" /> className="ml-8 cursor-pointer"
onClick={() =>
handleShare(
"fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
)
}
>
<Icon
icon="brandico:facebook"
height="20"
className="px-auto text-red-600 text-center"
/>
</a> </a>
<a className="ml-5 cursor-pointer" onClick={() => handleShare("tw", `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`)}> <a
<Icon icon="mdi:twitter" width="23" className="text-red-600 text-center" /> className="ml-5 cursor-pointer"
onClick={() =>
handleShare(
"tw",
`https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
)
}
>
<Icon
icon="mdi:twitter"
width="23"
className="text-red-600 text-center"
/>
</a> </a>
<a className="ml-5 cursor-pointer" onClick={() => handleShare("wa", `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`)}> <a
<Icon icon="ri:whatsapp-fill" width="23" className="text-red-600 text-center" /> className="ml-5 cursor-pointer"
onClick={() =>
handleShare(
"wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
)
}
>
<Icon
icon="ri:whatsapp-fill"
width="23"
className="text-red-600 text-center"
/>
</a> </a>
<Popover> <Popover>
<PopoverTrigger className="flex justify-end gap-1 cursor-pointer" asChild> <PopoverTrigger
<a className="ml-5 cursor-pointer" data-toggle="dropdown" href="#" aria-expanded="false"> className="flex justify-end gap-1 cursor-pointer"
<Icon icon="material-symbols-light:mail" width="23" className="text-red-600 text-center" /> asChild
>
<a
className="ml-5 cursor-pointer"
data-toggle="dropdown"
href="#"
aria-expanded="false"
>
<Icon
icon="material-symbols-light:mail"
width="23"
className="text-red-600 text-center"
/>
</a> </a>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2">{t("shareTo")}</h1> <h1 className="mb-2">{t("shareTo")}</h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1">{t("destinationEmail")}</p> <p className="text-base font-semibold mb-1">
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter")} /> {t("destinationEmail")}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter")}
/>
</div> </div>
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}> <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail()}
>
{t("send")} {t("send")}
</Button> </Button>
</div> </div>
@ -655,8 +819,15 @@ const DetailInfo = () => {
<div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7] dark:bg-slate-600"> <div className="flex flex-col my-16 p-4 lg:p-10 bg-[#f7f7f7] dark:bg-slate-600">
<div className="gap-5 flex flex-col px-4 lg:px-14"> <div className="gap-5 flex flex-col px-4 lg:px-14">
<p className="flex items-start text-lg">{t("comment")}</p> <p className="flex items-start text-lg">{t("comment")}</p>
<Textarea placeholder={t("leaveComment")} className="flex w-full pb-12" onChange={getInputValue} /> <Textarea
<button onClick={() => postData()} className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1"> placeholder={t("leaveComment")}
className="flex w-full pb-12"
onChange={getInputValue}
/>
<button
onClick={() => postData()}
className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-4 py-1"
>
{t("send")} {t("send")}
</button> </button>
</div> </div>
@ -667,13 +838,29 @@ const DetailInfo = () => {
{listSuggestion?.map((data: any) => ( {listSuggestion?.map((data: any) => (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:px-14"> <div className="flex flex-row mt-2 px-4 lg:px-14">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1080}
height={1080}
src={data?.suggestionFrom?.profilePictureUrl}
className="h-12 lg:h-16 w-12 lg:w-16 mr-2"
onError={addDefaultProfile}
alt=""
/>
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1"> <div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2"> <p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname} {Number(data.suggestionFrom?.roleId) == 2 ||
Number(data.suggestionFrom?.roleId) == 3 ||
Number(data.suggestionFrom?.roleId) == 4
? "HUMAS POLRI"
: data.suggestionFrom?.fullname}
{getPublicLocaleTimestamp(new Date(data.createdAt))} {getPublicLocaleTimestamp(new Date(data.createdAt))}
</p> </p>
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{data?.message}</p> <p className="text-slate-500 text-[13px] lg:text-sm mb-4">
{data?.message}
</p>
<div> <div>
<a <a
style={ style={
@ -686,11 +873,16 @@ const DetailInfo = () => {
onClick={() => showInput(`comment-id-${data.id}`)} onClick={() => showInput(`comment-id-${data.id}`)}
className="mr-2" className="mr-2"
> >
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("reply")}
</small>
</a> </a>
{Number(data.suggestionFrom?.id) == Number(userId) || Number(userRoleId) == 2 ? ( {Number(data.suggestionFrom?.id) == Number(userId) ||
Number(userRoleId) == 2 ? (
<a onClick={() => deleteData(data.id)}> <a onClick={() => deleteData(data.id)}>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("delete")}
</small>
</a> </a>
) : ( ) : (
"" ""
@ -699,14 +891,25 @@ const DetailInfo = () => {
</div> </div>
</div> </div>
{visibleInput === `comment-id-${data.id}` && ( {visibleInput === `comment-id-${data.id}` && (
<div id={`comment-id-${data.id}`} className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 "> <div
<Textarea id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} /> id={`comment-id-${data.id}`}
className="px-4 pl-[72px] lg:px-14 lg:pl-32 mt-2 "
>
<Textarea
id={`input-comment-${data.id}`}
className="p-4 focus:outline-none focus:border-sky-500"
placeholder={t("enterReply")}
/>
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
<a onClick={() => postDataChild(data.id)}> <a onClick={() => postDataChild(data.id)}>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">{t("send")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 mt-2 cursor-pointer">
{t("send")}
</small>
</a> </a>
<a onClick={() => showInput(`comment-id-${data.id}`)}> <a onClick={() => showInput(`comment-id-${data.id}`)}>
<small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg mt-2 w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("cancel")}
</small>
</a> </a>
</div> </div>
</div> </div>
@ -716,30 +919,56 @@ const DetailInfo = () => {
? data.children?.map((child1: any) => ( ? data.children?.map((child1: any) => (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32"> <div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1080}
height={1080}
src={child1.suggestionFrom?.profilePictureUrl}
onError={addDefaultProfile}
alt=""
className="h-10 lg:h-16 w-10 lg:w-16 mr-2"
/>
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1"> <div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2"> <p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "} {" "}
<b>{Number(child1.suggestionFrom?.roleId) == 2 || Number(child1.suggestionFrom?.roleId) == 3 || Number(child1.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.suggestionFrom?.fullname}</b>{" "} <b>
{getPublicLocaleTimestamp(new Date(child1.createdAt))} {Number(child1.suggestionFrom?.roleId) == 2 ||
Number(child1.suggestionFrom?.roleId) == 3 ||
Number(child1.suggestionFrom?.roleId) == 4
? "HUMAS POLRI"
: child1.suggestionFrom?.fullname}
</b>{" "}
{getPublicLocaleTimestamp(
new Date(child1.createdAt)
)}
</p>
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">
{parse(String(child1?.message))}
</p> </p>
<p className="text-slate-500 text-[13px] lg:text-sm mb-4">{parse(String(child1?.message))}</p>
<div> <div>
<a <a
style={ style={
Number(child1.suggestionFrom?.id) == Number(userId) Number(child1.suggestionFrom?.id) ==
Number(userId)
? { ? {
display: "none", display: "none",
} }
: {} : {}
} }
onClick={() => showInput(`comment-id-${child1.id}`)} onClick={() =>
showInput(`comment-id-${child1.id}`)
}
> >
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("reply")}
</small>
</a> </a>
<a <a
style={ style={
Number(child1.suggestionFrom?.id) == Number(userId) Number(child1.suggestionFrom?.id) ==
Number(userId)
? {} ? {}
: { : {
display: "none", display: "none",
@ -747,21 +976,39 @@ const DetailInfo = () => {
} }
onClick={() => deleteData(child1.id)} onClick={() => deleteData(child1.id)}
> >
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("delete")}
</small>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
{visibleInput === `comment-id-${child1.id}` && ( {visibleInput === `comment-id-${child1.id}` && (
<div id={`comment-id-${child1.id}`} className="px-4 lg:px-14 pl-28 lg:pl-[200px]"> <div
<Textarea name="" className="mt-2 " id={`input-comment-${child1.id}`} placeholder={t("enterReply")} /> id={`comment-id-${child1.id}`}
className="px-4 lg:px-14 pl-28 lg:pl-[200px]"
>
<Textarea
name=""
className="mt-2 "
id={`input-comment-${child1.id}`}
placeholder={t("enterReply")}
/>
<div className="flex flex-row mt-2 gap-3"> <div className="flex flex-row mt-2 gap-3">
<a onClick={() => postDataChild(child1.id)}> <a onClick={() => postDataChild(child1.id)}>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("send")}
</small>
</a> </a>
<a onClick={() => showInput(`comment-id-${child1.id}`)}> <a
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small> onClick={() =>
showInput(`comment-id-${child1.id}`)
}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("cancel")}
</small>
</a> </a>
</div> </div>
</div> </div>
@ -771,30 +1018,62 @@ const DetailInfo = () => {
? child1.children?.map((child2: any) => ( ? child1.children?.map((child2: any) => (
<div className=""> <div className="">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48"> <div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1080}
height={1080}
src={
child2.suggestionFrom?.profilePictureUrl
}
className="h-9 lg:h-16 w-9 lg:w-16 mr-2"
onError={addDefaultProfile}
alt=""
/>
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1"> <div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2"> <p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "} {" "}
<b>{Number(child2.suggestionFrom?.roleId) == 2 || Number(child2.suggestionFrom?.roleId) == 3 || Number(child2.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.suggestionFrom?.fullname}</b>{" "} <b>
{getPublicLocaleTimestamp(new Date(child2.createdAt))} {Number(
child2.suggestionFrom?.roleId
) == 2 ||
Number(child2.suggestionFrom?.roleId) ==
3 ||
Number(child2.suggestionFrom?.roleId) ==
4
? "HUMAS POLRI"
: child2.suggestionFrom?.fullname}
</b>{" "}
{getPublicLocaleTimestamp(
new Date(child2.createdAt)
)}
</p>
<p className="text-slate-500 text-sm mb-4">
{parse(String(child2?.message))}
</p> </p>
<p className="text-slate-500 text-sm mb-4">{parse(String(child2?.message))}</p>
<div> <div>
<a <a
style={ style={
Number(child2.suggestionFrom?.id) == Number(userId) Number(child2.suggestionFrom?.id) ==
Number(userId)
? { ? {
display: "none", display: "none",
} }
: {} : {}
} }
onClick={() => showInput(`comment-id-${child2.id}`)} onClick={() =>
showInput(`comment-id-${child2.id}`)
}
> >
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("reply")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("reply")}
</small>
</a> </a>
<a <a
style={ style={
Number(child2.suggestionFrom?.id) == Number(userId) Number(child2.suggestionFrom?.id) ==
Number(userId)
? {} ? {}
: { : {
display: "none", display: "none",
@ -802,20 +1081,40 @@ const DetailInfo = () => {
} }
onClick={() => deleteData(child2.id)} onClick={() => deleteData(child2.id)}
> >
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("delete")}</small> <small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("delete")}
</small>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
{visibleInput === `comment-id-${child2.id}` && ( {visibleInput === `comment-id-${child2.id}` && (
<div id={`comment-id-${child2.id}`} className="px-4 lg:px-14 pl-40 lg:pl-[265px]"> <div
<Textarea name="" id={`input-comment-${child2.id}`} className="my-2" placeholder="Masukkan balasan anda" /> id={`comment-id-${child2.id}`}
className="px-4 lg:px-14 pl-40 lg:pl-[265px]"
>
<Textarea
name=""
id={`input-comment-${child2.id}`}
className="my-2"
placeholder="Masukkan balasan anda"
/>
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
<a onClick={() => postDataChild(child2.id)}> <a
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("send")}</small> onClick={() => postDataChild(child2.id)}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("send")}
</small>
</a> </a>
<a onClick={() => showInput(`comment-id-${child2.id}`)}> <a
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">{t("cancel")}</small> onClick={() =>
showInput(`comment-id-${child2.id}`)
}
>
<small className="flex items-start bg-[#bb3523] rounded-lg w-fit text-white px-2 text-xs lg:text-base lg:px-4 py-1 cursor-pointer">
{t("cancel")}
</small>
</a> </a>
</div> </div>
</div> </div>

View File

@ -3,10 +3,30 @@ import React, { useEffect, useState } from "react";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { formatDateToIndonesian, getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals"; import {
formatDateToIndonesian,
getOnlyDate,
getOnlyMonthAndYear,
} from "@/utils/globals";
import { useParams, usePathname, useSearchParams } from "next/navigation"; import { useParams, usePathname, useSearchParams } from "next/navigation";
import { getUserLevelListByParent, listCategory, listData, listDataRegional } from "@/service/landing/landing"; import {
import { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table"; getUserLevelListByParent,
listCategory,
listData,
listDataRegional,
} from "@/service/landing/landing";
import {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import LandingPagination from "@/components/landing-page/pagination"; import LandingPagination from "@/components/landing-page/pagination";
import { Reveal } from "@/components/landing-page/Reveal"; import { Reveal } from "@/components/landing-page/Reveal";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
@ -38,8 +58,11 @@ const FilterPage = () => {
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [totalPage, setTotalPage] = React.useState<number>(1); const [totalPage, setTotalPage] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]); const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({}); []
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
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,
@ -60,7 +83,9 @@ const FilterPage = () => {
const [categoryFilter, setCategoryFilter] = useState<any>([]); const [categoryFilter, setCategoryFilter] = useState<any>([]);
const [monthYearFilter, setMonthYearFilter] = useState<any>(); const [monthYearFilter, setMonthYearFilter] = useState<any>();
const [searchTitle, setSearchTitle] = useState<string>(""); const [searchTitle, setSearchTitle] = useState<string>("");
const [sortByOpt, setSortByOpt] = useState<any>(sortBy === "popular" ? "clickCount" : "createdAt"); const [sortByOpt, setSortByOpt] = useState<any>(
sortBy === "popular" ? "clickCount" : "createdAt"
);
const isRegional = asPath?.includes("regional"); const isRegional = asPath?.includes("regional");
const isSatker = asPath?.includes("satker"); const isSatker = asPath?.includes("satker");
const [formatFilter, setFormatFilter] = useState<any>([]); const [formatFilter, setFormatFilter] = useState<any>([]);
@ -73,6 +98,7 @@ const FilterPage = () => {
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const [userLevels, setUserLevels] = useState([]); const [userLevels, setUserLevels] = useState([]);
const t = useTranslations("FilterPage"); const t = useTranslations("FilterPage");
const [isFilterOpen, setIsFilterOpen] = useState(true);
// const [startDate, endDate] = dateRange; // const [startDate, endDate] = dateRange;
@ -105,8 +131,14 @@ const FilterPage = () => {
useEffect(() => { useEffect(() => {
if (categorie) { if (categorie) {
setCategoryFilter(categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]); setCategoryFilter(
console.log("Kategori", categorie, categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]); categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
);
console.log(
"Kategori",
categorie,
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
);
} }
}, [categorie]); }, [categorie]);
@ -124,7 +156,19 @@ const FilterPage = () => {
} }
console.log(monthYearFilter, "monthFilter"); console.log(monthYearFilter, "monthFilter");
initState(); initState();
}, [change, asPath, monthYearFilter, page, sortBy, sortByOpt, title, startDateString, endDateString, categorie, formatFilter]); }, [
change,
asPath,
monthYearFilter,
page,
sortBy,
sortByOpt,
title,
startDateString,
endDateString,
categorie,
formatFilter,
]);
async function getCategories() { async function getCategories() {
const category = await listCategory("1"); const category = await listCategory("1");
@ -147,7 +191,10 @@ const FilterPage = () => {
async function getDataAll() { async function getDataAll() {
if (asPath?.includes("/polda/") == true) { if (asPath?.includes("/polda/") == true) {
if (asPath?.split("/")[2] !== "[polda_name]") { if (asPath?.split("/")[2] !== "[polda_name]") {
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || ""; const filter =
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -165,8 +212,14 @@ const FilterPage = () => {
filterGroup, filterGroup,
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "", monthYearFilter
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "", ? getOnlyMonthAndYear(monthYearFilter)
?.split("/")[0]
?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
locale == "en" ? true : false locale == "en" ? true : false
); );
close(); close();
@ -181,7 +234,10 @@ const FilterPage = () => {
setTotalContent(response?.data?.data?.totalElements); setTotalContent(response?.data?.data?.totalElements);
} }
} else { } else {
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || ""; const filter =
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -198,8 +254,12 @@ const FilterPage = () => {
"", "",
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "", monthYearFilter
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "", ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
locale == "en" ? true : false locale == "en" ? true : false
); );
close(); close();
@ -250,7 +310,10 @@ const FilterPage = () => {
}; };
async function getDataRegional() { async function getDataRegional() {
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : categorie || ""; const filter =
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: categorie || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -263,8 +326,12 @@ const FilterPage = () => {
"", "",
startDateString, startDateString,
endDateString, endDateString,
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "", monthYearFilter
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "", ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
12, 12,
pages, pages,
sortByOpt sortByOpt
@ -381,16 +448,20 @@ const FilterPage = () => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" /> <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`; </svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str)); const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
{/* Header */} {/* Header */}
<div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black"> <div className="flex flex-row md:flex-row items-start gap-3 p-10 bg-[#f7f7f7] dark:bg-black">
<p> {t("image")}</p>
{">"}
<p> <p>
{" "} <span className="font-bold">{t("allImage")}</span>
{t("image")} {">"} <span className="font-bold">{t("allImage")}</span>
</p> </p>
<p className="font-bold">|</p> <p className="font-bold">|</p>
<p>{`${t("thereIs")} ${totalContent} ${t("downloadableImage")}`}</p> <p>{`${t("thereIs")} ${totalContent} ${t("downloadableImage")}`}</p>
@ -398,106 +469,182 @@ const FilterPage = () => {
{/* Left */} {/* Left */}
<div className="flex flex-col lg:flex-row gap-6 p-4"> <div className="flex flex-col lg:flex-row gap-6 p-4">
<div className="lg:w-[25%] h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md"> <div className="lg:hidden flex justify-end mb-2">
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1"> <button
<Icon icon="stash:filter-light" fontSize={30} /> onClick={() => setIsFilterOpen(!isFilterOpen)}
Filter className="text-sm text-white bg-[#bb3523] px-4 py-1 rounded-md shadow"
</h2> >
<div className="border-t border-black my-4 dark:border-white"></div> {isFilterOpen ? "Hide Filter" : "Show Filter"}
<div className="space-y-6"> </button>
<div> </div>
<label htmlFor="search" className="block text-sm font-medium text-gray-700 dark:text-white">
{t("search")}
</label>
<Input
value={searchTitle}
onChange={(e) => setSearchTitle(e.target.value)}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
type="text"
id="search"
placeholder={t("searchTitle")}
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
/>
</div>
<div> {isFilterOpen && (
<label className="block text-sm font-medium text-gray-700 dark:text-white">{t("monthYear")}</label> <div className="lg:w-[25%] h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
<ReactDatePicker <h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
selected={monthYearFilter} <Icon icon="stash:filter-light" fontSize={30} />
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" Filter
onChange={(date) => setMonthYearFilter(date)} </h2>
dateFormat="MM | yyyy" <div className="border-t border-black my-4 dark:border-white"></div>
placeholderText={t("selectYear")} <div className="space-y-6">
showMonthYearPicker <div>
/> <label
</div> htmlFor="search"
className="block text-sm font-medium text-gray-700 dark:text-white"
<div> >
<label className="block text-sm font-medium text-gray-700 dark:text-white">{t("date")}</label> {t("search")}
<div className="flex flex-row justify justify-between gap-2"> </label>
<ReactDatePicker <Input
selectsRange value={searchTitle}
className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" onChange={(e) => setSearchTitle(e.target.value)}
startDate={dateRange[0]} onKeyUp={handleKeyUp}
endDate={dateRange[1]} onKeyDown={handleKeyDown}
onChange={(update) => { type="text"
setDateRange(update); id="search"
}} placeholder={t("searchTitle")}
placeholderText={t("selectDate")} className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
onCalendarClose={() => setCalenderState(!calenderState)}
/> />
<div className="flex items-center">{handleClose ? <Icon icon="carbon:close-filled" onClick={handleDeleteDate} width="20" inline color="#216ba5" /> : ""}</div>
</div> </div>
</div>
<div> <div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">{t("categories")}</h3> <label className="block text-sm font-medium text-gray-700 dark:text-white">
<ul className="mt-2 space-y-2"> {t("monthYear")}
{categories.map((category: any) => ( </label>
<li key={category?.id}> <ReactDatePicker
<label className="inline-flex items-center" htmlFor={`${category.id}`}> selected={monthYearFilter}
<Checkbox id={`${category.id}`} value={category.id} checked={categoryFilter.includes(String(category.id))} onCheckedChange={(e) => handleCategoryFilter(Boolean(e), category.id)} /> className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
<span className="ml-2 text-gray-700 dark:text-white">{category?.name}</span> onChange={(date) => setMonthYearFilter(date)}
dateFormat="MM | yyyy"
placeholderText={t("selectYear")}
showMonthYearPicker
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-white">
{t("date")}
</label>
<div className="flex flex-row justify justify-between gap-2">
<ReactDatePicker
selectsRange
className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
startDate={dateRange[0]}
endDate={dateRange[1]}
onChange={(update) => {
setDateRange(update);
}}
placeholderText={t("selectDate")}
onCalendarClose={() => setCalenderState(!calenderState)}
/>
<div className="flex items-center">
{handleClose ? (
<Icon
icon="carbon:close-filled"
onClick={handleDeleteDate}
width="20"
inline
color="#216ba5"
/>
) : (
""
)}
</div>
</div>
</div>
<div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
{t("categories")}
</h3>
<ul className="mt-2 space-y-2">
{categories.map((category: any) => (
<li key={category?.id}>
<label
className="inline-flex items-center"
htmlFor={`${category.id}`}
>
<Checkbox
id={`${category.id}`}
value={category.id}
checked={categoryFilter.includes(String(category.id))}
onCheckedChange={(e) =>
handleCategoryFilter(Boolean(e), category.id)
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
{category?.name}
</span>
</label>
</li>
))}
</ul>
</div>
{/* Garis */}
<div className="border-t border-black my-4 dark:border-white"></div>
{/* Garis */}
<div>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
Format
</h3>
<ul className="mt-2 space-y-2">
<li>
<label className="inline-flex items-center">
<Checkbox
id="png"
value="png"
checked={formatFilter.includes("png")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "png")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
PNG
</span>
</label> </label>
</li> </li>
))} <li>
</ul> <label className="inline-flex items-center">
</div> <Checkbox
{/* Garis */} id="jpeg"
<div className="border-t border-black my-4 dark:border-white"></div> value="jpeg"
{/* Garis */} checked={formatFilter.includes("jpeg")}
<div> onCheckedChange={(e) =>
<h3 className="text-sm font-medium text-gray-700 dark:text-white">Format</h3> handleFormatFilter(Boolean(e), "jpeg")
<ul className="mt-2 space-y-2"> }
<li> />
<label className="inline-flex items-center"> <span className="ml-2 text-gray-700 dark:text-white">
<Checkbox id="png" value="png" checked={formatFilter.includes("png")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "png")} /> JPEG
<span className="ml-2 text-gray-700 dark:text-white">PNG</span> </span>
</label> </label>
</li> </li>
<li> <li>
<label className="inline-flex items-center"> <label className="inline-flex items-center">
<Checkbox id="jpeg" value="jpeg" checked={formatFilter.includes("jpeg")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "jpeg")} /> <Checkbox
<span className="ml-2 text-gray-700 dark:text-white">JPEG</span> id="jpg"
</label> value="jpg"
</li> checked={formatFilter.includes("jpg")}
<li> onCheckedChange={(e) =>
<label className="inline-flex items-center"> handleFormatFilter(Boolean(e), "jpg")
<Checkbox id="jpg" value="jpg" checked={formatFilter.includes("jpg")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "jpg")} /> }
<span className="ml-2 text-gray-700 dark:text-white">JPG</span> />
</label> <span className="ml-2 text-gray-700 dark:text-white">
</li> JPG
</ul> </span>
</div> </label>
<div className="border-t border-black dark:border-white my-4"></div> </li>
<div className="text-center"> </ul>
<a onClick={cleanCheckbox} className="text-[#bb3523] cursor-pointer"> </div>
<b>Reset Filter</b> <div className="border-t border-black dark:border-white my-4"></div>
</a> <div className="text-center">
<a
onClick={cleanCheckbox}
className="text-[#bb3523] cursor-pointer"
>
<b>Reset Filter</b>
</a>
</div>
</div> </div>
</div> </div>
</div> )}
{/* Right */} {/* Right */}
<div className="w-full lg:w-[75%]"> <div className="w-full lg:w-[75%]">
@ -505,7 +652,11 @@ const FilterPage = () => {
<div className="w-full"> <div className="w-full">
<div className="flex flex-col items-end mb-4"> <div className="flex flex-col items-end mb-4">
<h2 className="text-lg font-semibold">{t("sortBy")}</h2> <h2 className="text-lg font-semibold">{t("sortBy")}</h2>
<select defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"} onChange={(e) => handleSorting(e)} className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"> <select
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
onChange={(e) => handleSorting(e)}
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
>
<option value="latest">{t("latest")}</option> <option value="latest">{t("latest")}</option>
<option value="popular">{t("mostPopular")}</option> <option value="popular">{t("mostPopular")}</option>
</select> </select>
@ -529,24 +680,50 @@ const FilterPage = () => {
{imageData?.length > 0 ? ( {imageData?.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{imageData?.map((image: any) => ( {imageData?.map((image: any) => (
<Card key={image?.id} className="hover:scale-105 transition-transform duration-300"> <Card
key={image?.id}
className="hover:scale-105 transition-transform duration-300"
>
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0"> <CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
<Link href={`/image/detail/${image?.slug}`}> <Link href={`/image/detail/${image?.slug}`}>
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */} {/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg"> <div className="img-container h-60 bg-[#e9e9e9] cursor-pointer rounded-lg">
<ImageBlurry src={image?.thumbnailLink} alt={image?.title} style={{ objectFit: "contain", width: "100%", height: "100%" }} /> <ImageBlurry
src={image?.thumbnailLink}
alt={image?.title}
style={{
objectFit: "contain",
width: "100%",
height: "100%",
}}
/>
</div> </div>
<div className="flex flex-row items-center gap-2 text-[10px] mx-2 mt-2"> <div className="flex flex-row items-center gap-2 text-[10px] mx-2 mt-2">
{formatDateToIndonesian(new Date(image?.createdAt))} {image?.timezone ? image?.timezone : "WIB"}| <Icon icon="formkit:eye" width="15" height="15" /> {formatDateToIndonesian(
new Date(image?.createdAt)
)}{" "}
{image?.timezone ? image?.timezone : "WIB"}|{" "}
<Icon
icon="formkit:eye"
width="15"
height="15"
/>
{image?.clickCount}{" "} {image?.clickCount}{" "}
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 20 20"> <svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 20 20"
>
<path <path
fill="#f00" fill="#f00"
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z" d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
/> />
</svg>{" "} </svg>{" "}
</div> </div>
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">{image?.title}</div> <div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
{image?.title}
</div>
</Link> </Link>
</CardContent> </CardContent>
</Card> </Card>
@ -554,12 +731,24 @@ const FilterPage = () => {
</div> </div>
) : ( ) : (
<p className="flex items-center justify-center text-black"> <p className="flex items-center justify-center text-black">
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-60 w-60 my-4" /> <Image
width={1920}
height={1080}
src="/assets/empty-data.png"
alt="empty"
className="h-60 w-60 my-4"
/>
</p> </p>
)} )}
</> </>
)} )}
{totalData > 1 && <LandingPagination table={table} totalData={totalData} totalPage={totalPage} />} {totalData > 1 && (
<LandingPagination
table={table}
totalData={totalData}
totalPage={totalPage}
/>
)}
</div> </div>
</Reveal> </Reveal>
</div> </div>

View File

@ -2,9 +2,22 @@
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { useParams, usePathname, useSearchParams } from "next/navigation"; import { useParams, usePathname, useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { deleteBlogComments, getBlogComments, getDetailIndeks, getPublicSuggestionList, postBlogComments, publicDetailBlog } from "@/service/landing/landing"; import {
deleteBlogComments,
getBlogComments,
getDetailIndeks,
getPublicSuggestionList,
postBlogComments,
publicDetailBlog,
} from "@/service/landing/landing";
import { formatDateToIndonesian } from "@/utils/globals"; import { formatDateToIndonesian } from "@/utils/globals";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
@ -94,7 +107,9 @@ const IndeksDetail = () => {
// } // }
async function sendCommentChild(parentId: any) { async function sendCommentChild(parentId: any) {
const inputMsg = document.querySelector(`#input-comment-${parentId}`) as HTMLInputElement; const inputMsg = document.querySelector(
`#input-comment-${parentId}`
) as HTMLInputElement;
if (inputMsg && inputMsg.value.length > 3) { if (inputMsg && inputMsg.value.length > 3) {
loading(); loading();
@ -166,32 +181,54 @@ const IndeksDetail = () => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" /> <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`; </svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str)); const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return ( return (
<> <>
<div className="p-4 lg:px-60 lg:p-12"> <div className="p-4 lg:px-28 lg:p-12">
{/* Judul */} {/* Judul */}
<div className="flex flex-col mb-5"> <div className="flex flex-col mb-5">
<h1 className="text-base lg:text-lg mb-2">Index / Detail</h1> <h1 className="text-base lg:text-lg mb-2">Index / Detail</h1>
<h1 className="flex flex-row font-bold text-center text-lg lg:text-2xl">{indeksData?.title}</h1> <h1 className="flex flex-row font-bold text-center text-lg lg:text-2xl">
{indeksData?.title}
</h1>
</div> </div>
{/* Gambar Utama */} {/* Gambar Utama */}
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeksData?.thumbnailLink} alt="Main" className="h-fit lg:h-[550px] w-full rounded-lg" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={2560}
height={1440}
src={indeksData?.thumbnailLink}
alt="Main"
className="w-full max-h-[550px] object-contain rounded-xl "
/>
</div> </div>
{/* Footer Informasi */} {/* Footer Informasi */}
<div className="text-gray-500 flex border-t mt-4"> <div className="text-gray-500 flex border-t mt-4">
<div className="flex mt-2"> <div className="flex mt-2">
<p className="text-xs lg:text-sm mb-2 "> <p className="text-xs lg:text-sm mb-2 ">
{t("by")}&nbsp;<span className="font-semibold text-gray-500">{indeksData?.uploaderName}</span>&nbsp; | &nbsp;{t("updatedOn")} {indeksData?.createdAt} WIB &nbsp; {t("by")}&nbsp;
<span className="font-semibold text-gray-500">
{indeksData?.uploaderName}
</span>
&nbsp; | &nbsp;{t("updatedOn")} {indeksData?.createdAt} WIB &nbsp;
</p> </p>
</div> </div>
</div> </div>
{/* Keterangan */} {/* Keterangan */}
<div className="w-auto"> <div className="w-auto">
<p className="font-light text-base lg:text-lg text-justify" dangerouslySetInnerHTML={{ __html: indeksData?.description }} /> <p
className="font-light text-base lg:text-lg text-justify"
dangerouslySetInnerHTML={{ __html: indeksData?.description }}
/>
</div> </div>
</div> </div>
@ -199,9 +236,19 @@ const IndeksDetail = () => {
<div className="w-full"> <div className="w-full">
<div className="flex flex-col py-5 p-0 lg:p-10 bg-[#f7f7f7] dark:bg-slate-600"> <div className="flex flex-col py-5 p-0 lg:p-10 bg-[#f7f7f7] dark:bg-slate-600">
<div className="gap-5 flex flex-col px-4 lg:px-16"> <div className="gap-5 flex flex-col px-4 lg:px-16">
<p className="flex items-start text-bases lg:text-lg">{t("comment")}</p> <p className="flex items-start text-bases lg:text-lg">
<Textarea placeholder="Type your comments here." className="flex w-full" onChange={getInputValue} value={message} /> {t("comment")}
<button className="flex items-start bg-[#bb3523] text-white rounded-lg text-sm lg:text-base w-fit px-3 lg:px-4 py-1" onClick={() => postData()}> </p>
<Textarea
placeholder="Type your comments here."
className="flex w-full"
onChange={getInputValue}
value={message}
/>
<button
className="flex items-start bg-[#bb3523] text-white rounded-lg text-sm lg:text-base w-fit px-3 lg:px-4 py-1"
onClick={() => postData()}
>
{t("send")} {t("send")}
</button> </button>
</div> </div>
@ -212,15 +259,40 @@ const IndeksDetail = () => {
{listComments?.map((data: any) => ( {listComments?.map((data: any) => (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:px-14"> <div className="flex flex-row mt-2 px-4 lg:px-14">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={512}
height={512}
className="h-10 lg:h-20 w-10 lg:w-20"
src="/assets/img/user-avatar-yellow.svg"
alt="#"
/>
<div className="border border-slate-300 w-full p-4 bg-white gap-1"> <div className="border border-slate-300 w-full p-4 bg-white gap-1">
<p className="flex justify-between text-sm text-slate-500 lg:text-base border-b-2 border-slate-200 mb-2"> <p className="flex justify-between text-sm text-slate-500 lg:text-base border-b-2 border-slate-200 mb-2">
<b>{Number(data.commentFrom?.roleId) == 2 || Number(data.commentFrom?.roleId) == 3 || Number(data.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : data.commentFrom?.fullname}</b> <b>
{`${new Date(data.createdAt).getDate()}/${new Date(data.createdAt).getMonth() + 1}/${new Date(data.createdAt).getFullYear()} ${new Date(data.createdAt).getHours()}:${new Date(data.createdAt).getMinutes()}`} {Number(data.commentFrom?.roleId) == 2 ||
Number(data.commentFrom?.roleId) == 3 ||
Number(data.commentFrom?.roleId) == 4
? "HUMAS POLRI"
: data.commentFrom?.fullname}
</b>
{`${new Date(data.createdAt).getDate()}/${
new Date(data.createdAt).getMonth() + 1
}/${new Date(data.createdAt).getFullYear()} ${new Date(
data.createdAt
).getHours()}:${new Date(data.createdAt).getMinutes()}`}
</p>
<p className="text-slate-500 text-sm lg:text-base mb-4">
{data.message}
</p> </p>
<p className="text-slate-500 text-sm lg:text-base mb-4">{data.message}</p>
<div className="gap-3"> <div className="gap-3">
<a href="javascript:void(0)" className="text-xs lg:text-sm mr-2 bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput(`comment-id-${data.id}`)}> <a
href="javascript:void(0)"
className="text-xs lg:text-sm mr-2 bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md"
onClick={() => showInput(`comment-id-${data.id}`)}
>
{t("reply")} {t("reply")}
</a> </a>
<a <a
@ -240,9 +312,21 @@ const IndeksDetail = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="flex flex-row px-4 pl-[55px] lg:px-14 lg:pl-[135px] mt-2" id={`comment-id-${data.id}`}> <div
<Input type="text" id={`input-comment-${data.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} /> className="flex flex-row px-4 pl-[55px] lg:px-14 lg:pl-[135px] mt-2"
<a href="javascript:void(0)" className="flex py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white text-sm lg:text-base" onClick={() => postDataChild(data.id)}> id={`comment-id-${data.id}`}
>
<Input
type="text"
id={`input-comment-${data.id}`}
className="p-4 focus:outline-none focus:border-sky-500"
placeholder={t("enterReply")}
/>
<a
href="javascript:void(0)"
className="flex py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white text-sm lg:text-base"
onClick={() => postDataChild(data.id)}
>
{t("send")} {t("send")}
</a> </a>
</div> </div>
@ -250,24 +334,54 @@ const IndeksDetail = () => {
? data.children?.map((child1: any) => ( ? data.children?.map((child1: any) => (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-12 lg:pl-32"> <div className="flex flex-row mt-2 px-4 lg:pr-14 pl-12 lg:pl-32">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={512}
height={512}
className="h-10 lg:h-20 w-10 lg:w-20"
src="/assets/img/user-avatar-yellow.svg"
alt="#"
/>
<div className="border border-slate-300 w-full p-4 bg-white gap-1"> <div className="border border-slate-300 w-full p-4 bg-white gap-1">
<p className="flex justify-between text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2"> <p className="flex justify-between text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
<b>{Number(child1.commentFrom?.roleId) == 2 || Number(child1.commentFrom?.roleId) == 3 || Number(child1.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.commentFrom?.fullname}</b> <b>
{`${new Date(child1.createdAt).getDate()}/${new Date(child1.createdAt).getMonth() + 1}/${new Date(child1.createdAt).getFullYear()} ${new Date(child1.createdAt).getHours()}:${new Date( {Number(child1.commentFrom?.roleId) == 2 ||
Number(child1.commentFrom?.roleId) == 3 ||
Number(child1.commentFrom?.roleId) == 4
? "HUMAS POLRI"
: child1.commentFrom?.fullname}
</b>
{`${new Date(child1.createdAt).getDate()}/${
new Date(child1.createdAt).getMonth() + 1
}/${new Date(
child1.createdAt
).getFullYear()} ${new Date(
child1.createdAt
).getHours()}:${new Date(
child1.createdAt child1.createdAt
).getMinutes()}`} ).getMinutes()}`}
</p> </p>
<p className="text-slate-500 text-sm mb-4">{child1.message}</p> <p className="text-slate-500 text-sm mb-4">
{child1.message}
</p>
<div className="gap-3"> <div className="gap-3">
<a href="javascript:void(0)" className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput(`comment-id-${child1.id}`)}> <a
href="javascript:void(0)"
className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md"
onClick={() =>
showInput(`comment-id-${child1.id}`)
}
>
{t("reply")} {t("reply")}
</a> </a>
<a <a
href="javascript:void(0)" href="javascript:void(0)"
className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md" className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md"
style={ style={
Number(child1.commentFrom?.id) == Number(userId) Number(child1.commentFrom?.id) ==
Number(userId)
? {} ? {}
: { : {
display: "none", display: "none",
@ -280,9 +394,21 @@ const IndeksDetail = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="flex flex-row justify-center px-4 pl-[87px] lg:px-14 lg:pl-[205px] mt-2" id={`comment-id-${child1.id}`}> <div
<Input type="text" id={`input-comment-${child1.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} /> className="flex flex-row justify-center px-4 pl-[87px] lg:px-14 lg:pl-[205px] mt-2"
<a href="javascript:void(0)" className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white" onClick={() => postDataChild(child1.id)}> id={`comment-id-${child1.id}`}
>
<Input
type="text"
id={`input-comment-${child1.id}`}
className="p-4 focus:outline-none focus:border-sky-500"
placeholder={t("enterReply")}
/>
<a
href="javascript:void(0)"
className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white"
onClick={() => postDataChild(child1.id)}
>
{t("send")} {t("send")}
</a> </a>
</div> </div>
@ -290,24 +416,59 @@ const IndeksDetail = () => {
? child1.children?.map((child2: any) => ( ? child1.children?.map((child2: any) => (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-20 lg:pl-48"> <div className="flex flex-row mt-2 px-4 lg:pr-14 pl-20 lg:pl-48">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" /> <Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={512}
height={512}
className="h-10 lg:h-20 w-10 lg:w-20"
src="/assets/img/user-avatar-yellow.svg"
alt="#"
/>
<div className="border border-slate-300 w-full p-4 bg-white gap-2"> <div className="border border-slate-300 w-full p-4 bg-white gap-2">
<p className="flex justify-between text-slate-500 text-xs lg:text-base border-b-2 border-slate-200 mb-2"> <p className="flex justify-between text-slate-500 text-xs lg:text-base border-b-2 border-slate-200 mb-2">
<b>{Number(child2.commentFrom?.roleId) == 2 || Number(child2.commentFrom?.roleId) == 3 || Number(child2.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.commentFrom?.fullname}</b> <b>
{`${new Date(child2.createdAt).getDate()}/${new Date(child2.createdAt).getMonth() + 1}/${new Date(child2.createdAt).getFullYear()} ${new Date(child2.createdAt).getHours()}:${new Date( {Number(child2.commentFrom?.roleId) ==
2 ||
Number(child2.commentFrom?.roleId) ==
3 ||
Number(child2.commentFrom?.roleId) == 4
? "HUMAS POLRI"
: child2.commentFrom?.fullname}
</b>
{`${new Date(
child2.createdAt
).getDate()}/${
new Date(child2.createdAt).getMonth() +
1
}/${new Date(
child2.createdAt
).getFullYear()} ${new Date(
child2.createdAt
).getHours()}:${new Date(
child2.createdAt child2.createdAt
).getMinutes()}`} ).getMinutes()}`}
</p> </p>
<p className="text-slate-500 text-sm mb-4">{child2.message}</p> <p className="text-slate-500 text-sm mb-4">
{child2.message}
</p>
<div> <div>
<a href="javascript:void(0)" className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md" onClick={() => showInput("comment-id-" + child2.id)}> <a
href="javascript:void(0)"
className="mr-2 text-xs lg:text-sm bg-blue-500 text-white py-1 px-2 hover:bg-blue-300 hover:text-black rounded-md"
onClick={() =>
showInput("comment-id-" + child2.id)
}
>
{t("reply")} {t("reply")}
</a> </a>
<a <a
href="javascript:void(0)" href="javascript:void(0)"
className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md" className="text-xs lg:text-sm bg-red-500 text-white py-1 px-2 hover:bg-red-300 hover:text-black rounded-md"
style={ style={
Number(child2.commentFrom?.id) == Number(userId) Number(child2.commentFrom?.id) ==
Number(userId)
? {} ? {}
: { : {
display: "none", display: "none",
@ -320,9 +481,21 @@ const IndeksDetail = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="flex flex-row px-4 pl-[120px] lg:px-14 lg:pl-[270px] mt-2" id={`comment-id-${child2.id}`}> <div
<Input type="text" id={`comment-id-${child2.id}`} className="p-4 focus:outline-none focus:border-sky-500" placeholder={t("enterReply")} /> className="flex flex-row px-4 pl-[120px] lg:px-14 lg:pl-[270px] mt-2"
<a href="javascript:void(0)" className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white" onClick={() => postDataChild(child1.id)}> id={`comment-id-${child2.id}`}
>
<Input
type="text"
id={`comment-id-${child2.id}`}
className="p-4 focus:outline-none focus:border-sky-500"
placeholder={t("enterReply")}
/>
<a
href="javascript:void(0)"
className="flex text-sm lg:text-base py-1 px-2 rounded-md items-center ml-2 bg-[#f7b357] text-white"
onClick={() => postDataChild(child1.id)}
>
{t("send")} {t("send")}
</a> </a>
</div> </div>
@ -339,14 +512,25 @@ const IndeksDetail = () => {
{/* Konten Serupa */} {/* Konten Serupa */}
<div className="space-x-5 flex flex-col px-4 lg:px-16 py-16 gap-5"> <div className="space-x-5 flex flex-col px-4 lg:px-16 py-16 gap-5">
<h1 className="font-bold text-base lg:text-xl px-4 lg:px-8"> {t("relatedPosts")}</h1> <h1 className="font-bold text-base lg:text-xl px-4 lg:px-8">
{" "}
{t("relatedPosts")}
</h1>
<Carousel> <Carousel>
<CarouselContent className="w-full max-w-7xl"> <CarouselContent className="w-full max-w-7xl">
{indexData?.map((relate: any) => ( {indexData?.map((relate: any) => (
<CarouselItem key={relate?.id} className="md:basis-1/2 lg:basis-1/3"> <CarouselItem
<Link href={`/indeks/detail/${relate?.slug}`} className="relative group overflow-hidden shadow-md hover:shadow-lg"> key={relate?.id}
className="md:basis-1/2 lg:basis-1/3"
>
<Link
href={`/indeks/detail/${relate?.slug}`}
className="relative group overflow-hidden shadow-md hover:shadow-lg"
>
<Image <Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
alt="" alt=""
width={2560} width={2560}
height={1440} height={1440}
@ -354,10 +538,17 @@ const IndeksDetail = () => {
className="w-full rounded-lg h-40 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" className="w-full rounded-lg h-40 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300"
/> />
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2"> <div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{relate?.categoryName}</span> <span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{relate?.title}</h1> {relate?.categoryName}
</span>
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
{relate?.title}
</h1>
<p className="flex flex-row items-center text-[10px] gap-2"> <p className="flex flex-row items-center text-[10px] gap-2">
{formatDateToIndonesian(new Date(relate?.createdAt))} {relate?.timezone ? relate?.timezone : "WIB"} | <Icon icon="formkit:eye" width="15" height="15" /> {relate.clickCount}{" "} {formatDateToIndonesian(new Date(relate?.createdAt))}{" "}
{relate?.timezone ? relate?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{relate.clickCount}{" "}
</p> </p>
</div> </div>
</Link> </Link>

View File

@ -3,7 +3,7 @@
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { getIndeksData } from "@/service/landing/landing"; import { getIndeksData } from "@/service/landing/landing";
import { formatDateToIndonesian } from "@/utils/globals"; import { formatDateToIndonesian, htmlToString } from "@/utils/globals";
import Image from "next/image"; import Image from "next/image";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@ -53,9 +53,11 @@ const Indeks: React.FC = () => {
<rect id="r" width="${w}" height="${h}" fill="url(#g)" /> <rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" /> <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`; </svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str)); const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return ( return (
<div className="px-4 lg:px-14"> <div className="px-4 lg:px-14">
@ -70,22 +72,44 @@ const Indeks: React.FC = () => {
{indeksData?.map( {indeksData?.map(
(indeks: any, index: number) => (indeks: any, index: number) =>
index == count && ( index == count && (
<div key={indeks?.id} className="relative h-[310px] lg:h-[435px]"> <div
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeks?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[435px] rounded-lg object-cover" /> key={indeks?.id}
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg"> className="relative h-[310px] lg:h-[435px]"
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeks?.categoryName}</span> >
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={2560}
height={1440}
src={indeks?.thumbnailLink}
alt="image"
className="w-full h-[310px] lg:h-[435px] rounded-lg object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-black/15 backdrop-brightness-50 text-white p-4 rounded-b-lg">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">
{indeks?.categoryName}
</span>
<Link href={`/indeks/detail/${indeks?.slug}`}> <Link href={`/indeks/detail/${indeks?.slug}`}>
<h2 className="text-2xl font-bold mt-2">{indeks?.title}</h2> <h2 className="text-2xl font-bold mt-2">
{indeks?.title}
</h2>
</Link> </Link>
<p className="text-xs flex flex-row items-center gap-1 mt-1"> <p className="text-xs flex flex-row items-center gap-1 mt-1">
{formatDateToIndonesian(new Date(indeks?.createdAt))} {indeks?.timezone ? indeks?.timezone : "WIB"} |{" "} {formatDateToIndonesian(new Date(indeks?.createdAt))}{" "}
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"> {indeks?.timezone ? indeks?.timezone : "WIB"} |{" "}
{/* <svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path <path
fill="currentColor" fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9" d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/> />
</svg>{" "} </svg>{" "} */}
{indeks?.clickCount} {/* {indeks?.clickCount} */}
</p> </p>
</div> </div>
</div> </div>
@ -108,22 +132,49 @@ const Indeks: React.FC = () => {
{indeksData?.map( {indeksData?.map(
(indeksRight: any, index: number) => (indeksRight: any, index: number) =>
(index == count + 1 || index == count + 2) && ( (index == count + 1 || index == count + 2) && (
<div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]"> <div
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg " /> key={indeksRight?.id}
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg"> className="relative h-[310px] lg:h-[215px]"
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeksRight?.categoryName}</span> >
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={1920}
height={1080}
src={indeksRight?.thumbnailLink}
alt="image"
className="w-full h-[310px] lg:h-[215px] rounded-lg "
/>
<div className="absolute bottom-0 left-0 right-0 bg-black/15 backdrop-brightness-50 text-white p-4 rounded-b-lg">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">
{indeksRight?.categoryName}
</span>
<Link href={`/indeks/detail/${indeksRight?.slug}`}> <Link href={`/indeks/detail/${indeksRight?.slug}`}>
<h2 className="text-xl font-bold mt-2">{indeksRight?.title}</h2> <h2 className="text-xl font-bold mt-2">
{indeksRight?.title}
</h2>
</Link> </Link>
<p className="text-xs flex flex-row items-center gap-1 mt-1 ml-2"> <p className="text-xs flex flex-row items-center gap-1 mt-1 ml-2">
{formatDateToIndonesian(new Date(indeksRight?.createdAt))} {indeksRight?.timezone ? indeksRight?.timezone : "WIB"}|{" "} {formatDateToIndonesian(
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"> new Date(indeksRight?.createdAt)
)}{" "}
{indeksRight?.timezone
? indeksRight?.timezone
: "WIB"}
|{" "}
{/* <svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path <path
fill="currentColor" fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9" d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/> />
</svg>{" "} </svg>{" "}
{indeksRight?.clickCount} {indeksRight?.clickCount} */}
</p> </p>
</div> </div>
</div> </div>
@ -165,14 +216,31 @@ const Indeks: React.FC = () => {
{indeksData?.map( {indeksData?.map(
(indeksBottom: any, index: number) => (indeksBottom: any, index: number) =>
index < 3 && ( index < 3 && (
<div key={indeksBottom?.id} className="flex flex-col md:flex-row items-start p-4 gap-4"> <div
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" /> key={indeksBottom?.id}
className="flex flex-col md:flex-row items-start p-4 gap-4"
>
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={500}
height={250}
src={indeksBottom?.thumbnailLink}
alt=""
className="h-40 object-cover rounded-lg w-full lg:w-[550px] lg:h-[200px]"
/>
<div className="flex flex-col justify-between w-full"> <div className="flex flex-col justify-between w-full">
<p className="text-sm">{indeksBottom?.date}</p> <p className="text-sm">{indeksBottom?.date}</p>
<Link href={`/indeks/detail/${indeksBottom?.slug}`} className="text-2xl font-semibold text-gray-800 dark:text-white"> <Link
href={`/indeks/detail/${indeksBottom?.slug}`}
className="text-2xl font-semibold text-gray-800 dark:text-white"
>
{indeksBottom?.title} {indeksBottom?.title}
</Link> </Link>
<p className="text-sm text-gray-600 dark:text-white mt-2">{indeksBottom?.description}</p> <p className="text-sm text-gray-600 dark:text-white mt-2">
{htmlToString(indeksBottom?.description)}
</p>
</div> </div>
</div> </div>
) )

View File

@ -1,18 +1,50 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import {
import { Popover, PopoverArrow, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Popover,
PopoverArrow,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { CalendarIcon } from "lucide-react"; import { CalendarIcon } from "lucide-react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { format } from "date-fns"; import { format } from "date-fns";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { detailSchedule, listSchedule, listScheduleNextPublic, listSchedulePrevPublic, listScheduleTodayPublic, searchSchedules } from "@/service/schedule/schedule"; import {
detailSchedule,
listSchedule,
listScheduleNextPublic,
listSchedulePrevPublic,
listScheduleTodayPublic,
searchSchedules,
} from "@/service/schedule/schedule";
import { usePathname, useRouter } from "@/i18n/routing"; import { usePathname, useRouter } from "@/i18n/routing";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import {
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -292,13 +324,21 @@ const Schedule = (props: any) => {
function getLastWeek(today: Date | undefined) { function getLastWeek(today: Date | undefined) {
if (today) { if (today) {
return new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7); return new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() - 7
);
} }
} }
function getNextWeek(today: Date | undefined) { function getNextWeek(today: Date | undefined) {
if (today) { if (today) {
return new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7); return new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 7
);
} }
} }
@ -423,13 +463,22 @@ const Schedule = (props: any) => {
}; };
function setItemSchedule(id: string, date: string) { function setItemSchedule(id: string, date: string) {
const itemFound: any = schedules?.filter((s: any) => s.dateInRange.includes(date) && s.timeIndex.split(",").includes(id)); const itemFound: any = schedules?.filter(
(s: any) =>
s.dateInRange.includes(date) && s.timeIndex.split(",").includes(id)
);
if (itemFound?.length > 0) { if (itemFound?.length > 0) {
if (itemFound?.length == 1) { if (itemFound?.length == 1) {
return ( return (
<a <a
className={`cursor-pointer text-center ${Number(itemFound[0]?.uploaderLevelNumber) == 1 ? "bg-yellow-300" : Number(itemFound[0]?.uploaderLevelNumber) == 2 ? "bg-blue-400" : "bg-gray-500"}`} className={`cursor-pointer text-center ${
Number(itemFound[0]?.uploaderLevelNumber) == 1
? "bg-yellow-300"
: Number(itemFound[0]?.uploaderLevelNumber) == 2
? "bg-blue-400"
: "bg-gray-500"
}`}
onClick={() => { onClick={() => {
getItem(itemFound[0]); getItem(itemFound[0]);
}} }}
@ -452,7 +501,9 @@ const Schedule = (props: any) => {
<b>{`${itemFound?.length} Jadwal Bersamaan`}</b> <b>{`${itemFound?.length} Jadwal Bersamaan`}</b>
</p> </p>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger className="font-bold text-blue-300">Lihat Jadwal</DropdownMenuTrigger> <DropdownMenuTrigger className="font-bold text-blue-300">
Lihat Jadwal
</DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
{itemFound?.map((list: any) => ( {itemFound?.map((list: any) => (
<DropdownMenuItem <DropdownMenuItem
@ -471,7 +522,10 @@ const Schedule = (props: any) => {
))} ))}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<div className="border-0 dropdown-menu schedule-list" aria-labelledby="view-schedule"></div> <div
className="border-0 dropdown-menu schedule-list"
aria-labelledby="view-schedule"
></div>
</div> </div>
); );
} }
@ -480,73 +534,121 @@ const Schedule = (props: any) => {
return ( return (
<> <>
{/* Awal Komponen Kiri */} {/* Awal Komponen Kiri */}
<div className="relative px-4 lg:px-10 lg:py-10 py-4 bg-[#f7f7f7] dark:bg-slate-800"> <div className="relative pl-4 lg:px-8 lg:py-10 py-4 bg-[#f7f7f7] dark:bg-slate-800">
<Popover> <div className="flex flex-row items-center">
<PopoverTrigger asChild>
<Button variant={"outline"} className={cn("w-[240px] py-4 justify-start text-left font-normal", !startDate && "text-muted-foreground")}>
<CalendarIcon />
{startDate ? format(startDate, "MMM yyyy") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={startDate}
onSelect={(e) => {
handleChangeDate(e);
}}
initialFocus
/>
</PopoverContent>
</Popover>
<div className="container relative py-4">
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<a className="text-black dark:text-white flex flex-row w-fit gap-2 py-4 items-center cursor-pointer"> <Button
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> variant={"outline"}
<path fill="currentColor" d="M20 3H4a1 1 0 0 0-1 1v2.227l.008.223a3 3 0 0 0 .772 1.795L8 12.886V21a1 1 0 0 0 1.316.949l6-2l.108-.043A1 1 0 0 0 16 19v-6.586l4.121-4.12A3 3 0 0 0 21 6.171V4a1 1 0 0 0-1-1" /> className={cn(
</svg> "w-[240px] py-3 justify-start text-left font-normal",
Filter !startDate && "text-muted-foreground"
<svg className="flex items-center justify-center" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> )}
<path fill="currentColor" fill-rule="evenodd" d="m6 7l6 6l6-6l2 2l-8 8l-8-8z" /> >
</svg> <CalendarIcon />
</a> {startDate ? (
format(startDate, "MMM yyyy")
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="start" className="flex p-0 rounded-md w-fit"> <PopoverContent className="w-auto p-0" align="start">
<div className="flex flex-col items-center justify-between gap-1.5 p-2 border-b text-default-600 rounded-none"> <Calendar
<div className="gap-6 flex flex-row justify-end"> mode="single"
<p className="font-semibold">Filter</p> selected={startDate}
<button className="text-blue-400" onClick={doFilter}> onSelect={(e) => {
{t("save")} handleChangeDate(e);
</button> }}
</div> initialFocus
<div className="border w-full border-t border-slate-500"></div> />
<div className="overflow-y-auto flex flex-col gap-2 h-[200px] ">
<p className="text-center font-semibold">Region Filter</p>
{city?.map((list) => (
<div className="mt-2 gap-2 flex flex-row">
{" "}
<input type="checkbox" className="" id={`filterCategory-${list.key}`} value={list.id} checked={regionFilter?.includes(list.id)} onChange={handleRegionFilter} />
<p>{list?.name}</p>
</div>
))}
</div>
</div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
<div className="flex flex-col lg:flex-row gap-3">
{regionName?.map((list: any) => ( <div className="container relative py-4 flex flex-row items-center gap-4">
<div className="text-left"> <Popover>
<button onClick={() => deleteFilterhandler(list.id)} key={list.key} id={list.id} className="text-black bg-yellow-300 w-fit p-3 flex justify-center items-center rounded-lg"> <PopoverTrigger asChild>
{list.name}&nbsp; <a className="text-black dark:text-white flex flex-row w-fit gap-3 py-2 px-3 items-center cursor-pointer border border-black rounded-md ">
<Icon icon="icon-park-outline:delete-two" className="items-center" /> <svg
</button> xmlns="http://www.w3.org/2000/svg"
</div> width="1em"
))} height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 3H4a1 1 0 0 0-1 1v2.227l.008.223a3 3 0 0 0 .772 1.795L8 12.886V21a1 1 0 0 0 1.316.949l6-2l.108-.043A1 1 0 0 0 16 19v-6.586l4.121-4.12A3 3 0 0 0 21 6.171V4a1 1 0 0 0-1-1"
/>
</svg>
Filter
<svg
className="flex items-center justify-center"
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fill-rule="evenodd"
d="m6 7l6 6l6-6l2 2l-8 8l-8-8z"
/>
</svg>
</a>
</PopoverTrigger>
<PopoverContent
align="start"
className="flex p-0 rounded-md w-fit"
>
<div className="flex flex-col items-center justify-between gap-1.5 p-2 border-b text-default-600 rounded-none">
<div className="gap-6 flex flex-row justify-end">
<p className="font-semibold">Filter</p>
<button className="text-blue-400" onClick={doFilter}>
{t("save")}
</button>
</div>
<div className="border w-full border-t border-slate-500"></div>
<div className="overflow-y-auto flex flex-col gap-2 h-[200px] ">
<p className="text-center font-semibold">Region Filter</p>
{city?.map((list) => (
<div className="mt-2 gap-2 flex flex-row">
{" "}
<input
type="checkbox"
className=""
id={`filterCategory-${list.key}`}
value={list.id}
checked={regionFilter?.includes(list.id)}
onChange={handleRegionFilter}
/>
<p>{list?.name}</p>
</div>
))}
</div>
</div>
</PopoverContent>
</Popover>
<div className="flex flex-col lg:flex-row gap-3">
{regionName?.map((list: any) => (
<div className="text-left">
<button
onClick={() => deleteFilterhandler(list.id)}
key={list.key}
id={list.id}
className="text-black bg-yellow-300 w-fit p-3 flex justify-center items-center rounded-lg"
>
{list.name}&nbsp;
<Icon
icon="icon-park-outline:delete-two"
className="items-center"
/>
</button>
</div>
))}
</div>
</div> </div>
</div> </div>
<div className="flex flex-col lg:flex-row gap-6"> <div className="flex flex-col lg:flex-row gap-1">
<div className="h-[500px] overflow-y-auto md:overflow-y-auto w-full lg:w-3/4 "> <div className="h-[500px] overflow-y-auto md:overflow-y-auto w-full lg:w-3/4 ">
<div className="container-fluid relative"> <div className="container-fluid relative">
<div className="grid grid-cols-1 mt-8"> <div className="grid grid-cols-1 mt-8">
@ -554,53 +656,125 @@ const Schedule = (props: any) => {
<table className="w-full text-sm text-start"> <table className="w-full text-sm text-start">
<thead className="text-md"> <thead className="text-md">
<tr className="h-full"> <tr className="h-full">
<th className="text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[120px]">{t("timeTable")}</th> <th className="text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[210px]">
{t("timeTable")}
</th>
<th className="text-center border border-r-0 border-gray-100 dark:border-gray-700 py-6 w-[20px]"> <th className="text-center border border-r-0 border-gray-100 dark:border-gray-700 py-6 w-[20px]">
<a onClick={() => changePrevWeek()} className="cursor-pointer h-fit self-center bottom-0"> <a
<svg xmlns="http://www.w3.org/2000/svg" width="40px" height="40px" viewBox="0 0 24 24"> onClick={() => changePrevWeek()}
<path fill="currentColor" d="M12.29 8.71L9.7 11.3a.996.996 0 0 0 0 1.41l2.59 2.59c.63.63 1.71.18 1.71-.71V9.41c0-.89-1.08-1.33-1.71-.7" /> className="cursor-pointer h-fit self-center bottom-0"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40px"
height="40px"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12.29 8.71L9.7 11.3a.996.996 0 0 0 0 1.41l2.59 2.59c.63.63 1.71.18 1.71-.71V9.41c0-.89-1.08-1.33-1.71-.7"
/>
</svg> </svg>
</a> </a>
</th> </th>
<th className={`text-center cursor-pointer border border-l-0 h-full border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[0] ? "bg-red-600 text-white" : ""}`}> <th
className={`text-center cursor-pointer border border-l-0 h-full border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
new Date().toISOString().slice(0, 10) ==
dateAWeek[0]
? "bg-red-600 text-white"
: ""
}`}
>
{/* <a className="cursor-pointer h-fit self-center bottom-0" > {/* <a className="cursor-pointer h-fit self-center bottom-0" >
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24">
<path fill="currentColor" d="M12.29 8.71L9.7 11.3a.996.996 0 0 0 0 1.41l2.59 2.59c.63.63 1.71.18 1.71-.71V9.41c0-.89-1.08-1.33-1.71-.7" /> <path fill="currentColor" d="M12.29 8.71L9.7 11.3a.996.996 0 0 0 0 1.41l2.59 2.59c.63.63 1.71.18 1.71-.71V9.41c0-.89-1.08-1.33-1.71-.7" />
</svg> </svg>
</a>{" "} */} </a>{" "} */}
<div className="flex flex-col "> <div className="flex flex-col ">
<p className="text-2xl">{dateAWeek[0]?.split("-")[2]}</p> <p className="text-2xl">
{dateAWeek[0]?.split("-")[2]}
</p>
<p>{t("monday")}</p> <p>{t("monday")}</p>
</div> </div>
</th> </th>
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[1] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}> <th
<div className="text-2xl">{dateAWeek[1]?.split("-")[2]}</div> className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
new Date().toISOString().slice(0, 10) ==
dateAWeek[1]
? "bg-[#BE0106] text-white rounded-lg"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[1]?.split("-")[2]}
</div>
{t("tuesday")} {t("tuesday")}
</th> </th>
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[2] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}> <th
<div className="text-2xl">{dateAWeek[2]?.split("-")[2]}</div> className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
new Date().toISOString().slice(0, 10) ==
dateAWeek[2]
? "bg-[#BE0106] text-white rounded-lg"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[2]?.split("-")[2]}
</div>
{t("wednesday")} {t("wednesday")}
</th> </th>
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[3] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}> <th
<div className="text-2xl">{dateAWeek[3]?.split("-")[2]}</div> className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
new Date().toISOString().slice(0, 10) ==
dateAWeek[3]
? "bg-[#BE0106] text-white rounded-lg"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[3]?.split("-")[2]}
</div>
{t("thursday")} {t("thursday")}
</th> </th>
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[4] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}> <th
<div className="text-2xl">{dateAWeek[4]?.split("-")[2]}</div> className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
new Date().toISOString().slice(0, 10) ==
dateAWeek[4]
? "bg-[#BE0106] text-white rounded-lg"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[4]?.split("-")[2]}
</div>
{t("friday")} {t("friday")}
</th> </th>
<th className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${new Date().toISOString().slice(0, 10) == dateAWeek[5] ? "bg-[#BE0106] text-white rounded-lg" : ""}`}> <th
<div className="text-2xl">{dateAWeek[5]?.split("-")[2]}</div> className={`text-center border border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
new Date().toISOString().slice(0, 10) ==
dateAWeek[5]
? "bg-[#BE0106] text-white rounded-lg"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[5]?.split("-")[2]}
</div>
{t("saturday")} {t("saturday")}
</th> </th>
<th <th
onClick={() => changeNextWeek()} onClick={() => changeNextWeek()}
className={`text-center border cursor-pointer border-r-0 border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${ className={`text-center border cursor-pointer border-r-0 border-gray-100 dark:border-gray-700 py-6 min-w-[100px] ${
new Date().toISOString().slice(0, 10) == dateAWeek[6] ? "bg-[#BE0106] text-white rounded-lg" : "" new Date().toISOString().slice(0, 10) ==
dateAWeek[6]
? "bg-[#BE0106] text-white rounded-lg"
: ""
}`} }`}
> >
<div className="flex flex-col "> <div className="flex flex-col ">
<p className="text-2xl">{dateAWeek[6]?.split("-")[2]}</p> <p className="text-2xl">
{dateAWeek[6]?.split("-")[2]}
</p>
<p>{t("sunday")}</p> <p>{t("sunday")}</p>
</div> </div>
{/* <a className="cursor-pointer h-fit p-0 m-0 self-center"> {/* <a className="cursor-pointer h-fit p-0 m-0 self-center">
@ -610,9 +784,20 @@ const Schedule = (props: any) => {
</a> */} </a> */}
</th> </th>
<th className="text-center border-l-0 border border-gray-100 dark:border-gray-700 py-6 w-[20px]"> <th className="text-center border-l-0 border border-gray-100 dark:border-gray-700 py-6 w-[20px]">
<a onClick={() => changeNextWeek()} className="cursor-pointer"> <a
<svg xmlns="http://www.w3.org/2000/svg" width="40px" height="40px" viewBox="0 0 24 24"> onClick={() => changeNextWeek()}
<path fill="currentColor" d="m11.71 15.29l2.59-2.59a.996.996 0 0 0 0-1.41L11.71 8.7c-.63-.62-1.71-.18-1.71.71v5.17c0 .9 1.08 1.34 1.71.71" /> className="cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40px"
height="40px"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m11.71 15.29l2.59-2.59a.996.996 0 0 0 0-1.41L11.71 8.7c-.63-.62-1.71-.18-1.71.71v5.17c0 .9 1.08 1.34 1.71.71"
/>
</svg> </svg>
</a> </a>
</th> </th>
@ -621,16 +806,34 @@ const Schedule = (props: any) => {
<tbody> <tbody>
{timeList.map((times) => ( {timeList.map((times) => (
<tr key={times.id}> <tr key={times.id}>
<th className="text-center border border-gray-100 dark:border-gray-700 py-5">{times.time}</th> <th className="text-center border border-gray-100 dark:border-gray-700 py-5">
<td colSpan={2} className="p-3 border border-gray-100 dark:border-gray-700 "> {times.time}
</th>
<td
colSpan={2}
className="p-3 border border-gray-100 dark:border-gray-700 "
>
{setItemSchedule(times.id, dateList[0])} {setItemSchedule(times.id, dateList[0])}
</td> </td>
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[1])}</td> <td className="border border-gray-100 dark:border-gray-700">
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[2])}</td> {setItemSchedule(times.id, dateList[1])}
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[3])}</td> </td>
<td className="p-3 border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[4])}</td> <td className="border border-gray-100 dark:border-gray-700">
<td className="border border-gray-100 dark:border-gray-700">{setItemSchedule(times.id, dateList[5])}</td> {setItemSchedule(times.id, dateList[2])}
<td colSpan={2} className="border border-gray-100 dark:border-gray-700"> </td>
<td className="border border-gray-100 dark:border-gray-700">
{setItemSchedule(times.id, dateList[3])}
</td>
<td className="p-3 border border-gray-100 dark:border-gray-700">
{setItemSchedule(times.id, dateList[4])}
</td>
<td className="border border-gray-100 dark:border-gray-700">
{setItemSchedule(times.id, dateList[5])}
</td>
<td
colSpan={2}
className="border border-gray-100 dark:border-gray-700"
>
{setItemSchedule(times.id, dateList[6])} {setItemSchedule(times.id, dateList[6])}
</td> </td>
</tr> </tr>
@ -654,10 +857,18 @@ const Schedule = (props: any) => {
className="pl-8 pr-4 py-1 w-full border rounded-full text-sm focus:outline-none" className="pl-8 pr-4 py-1 w-full border rounded-full text-sm focus:outline-none"
/> />
<span className="absolute left-2 top-1/2 transform -translate-y-1/2"> <span className="absolute left-2 top-1/2 transform -translate-y-1/2">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"> <svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<g fill="none" fill-rule="evenodd"> <g fill="none" fill-rule="evenodd">
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" /> <path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
<path fill="currentColor" d="M10.5 2a8.5 8.5 0 1 0 5.262 15.176l3.652 3.652a1 1 0 0 0 1.414-1.414l-3.652-3.652A8.5 8.5 0 0 0 10.5 2M4 10.5a6.5 6.5 0 1 1 13 0a6.5 6.5 0 0 1-13 0" /> <path
fill="currentColor"
d="M10.5 2a8.5 8.5 0 1 0 5.262 15.176l3.652 3.652a1 1 0 0 0 1.414-1.414l-3.652-3.652A8.5 8.5 0 0 0 10.5 2M4 10.5a6.5 6.5 0 1 1 13 0a6.5 6.5 0 0 1-13 0"
/>
</g> </g>
</svg> </svg>
</span> </span>
@ -700,8 +911,13 @@ const Schedule = (props: any) => {
<AccordionItem value="item-1"> <AccordionItem value="item-1">
<AccordionTrigger>{t("todaySchedule")}</AccordionTrigger> <AccordionTrigger>{t("todaySchedule")}</AccordionTrigger>
{todayList?.map((list: any) => ( {todayList?.map((list: any) => (
<AccordionContent key={list?.id} className="flex flex-row gap-3"> <AccordionContent
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div> key={list?.id}
className="flex flex-row gap-3"
>
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">
{new Date(list.startDate).getDate()}
</div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h3 className="font-bold">{list?.title}</h3> <h3 className="font-bold">{list?.title}</h3>
<p className="flex flex-row items-center gap-2"> <p className="flex flex-row items-center gap-2">
@ -725,8 +941,13 @@ const Schedule = (props: any) => {
<AccordionItem value="item-2"> <AccordionItem value="item-2">
<AccordionTrigger>{t("previousSchedule")}</AccordionTrigger> <AccordionTrigger>{t("previousSchedule")}</AccordionTrigger>
{prevdayList?.map((list: any) => ( {prevdayList?.map((list: any) => (
<AccordionContent key={list?.id} className="flex flex-row gap-3"> <AccordionContent
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div> key={list?.id}
className="flex flex-row gap-3"
>
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">
{new Date(list.startDate).getDate()}
</div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h3 className="font-bold">{list?.title}</h3> <h3 className="font-bold">{list?.title}</h3>
<p className="flex flex-row items-center gap-2"> <p className="flex flex-row items-center gap-2">
@ -750,8 +971,13 @@ const Schedule = (props: any) => {
<AccordionItem value="item-3"> <AccordionItem value="item-3">
<AccordionTrigger>{t("nextSchedule")}</AccordionTrigger> <AccordionTrigger>{t("nextSchedule")}</AccordionTrigger>
{nextdayList?.map((list: any) => ( {nextdayList?.map((list: any) => (
<AccordionContent key={list?.id} className="flex flex-row gap-3"> <AccordionContent
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">{new Date(list.startDate).getDate()}</div> key={list?.id}
className="flex flex-row gap-3"
>
<div className="border-l-4 border-red-700 pl-1 h-fit font-bold text-lg">
{new Date(list.startDate).getDate()}
</div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h3 className="font-bold">{list?.title}</h3> <h3 className="font-bold">{list?.title}</h3>
<p className="flex flex-row items-center gap-2"> <p className="flex flex-row items-center gap-2">
@ -846,13 +1072,17 @@ const Schedule = (props: any) => {
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle> <AlertDialogTitle>
<h1 className="my-4 font-light">JADWAL / {detail?.isYoutube == true ? "LIVE STREAMING" : "DETAIL"}</h1> <h1 className="my-4 font-light">
JADWAL /{" "}
{detail?.isYoutube == true ? "LIVE STREAMING" : "DETAIL"}
</h1>
<p className="font-bold">{detail?.title}</p> <p className="font-bold">{detail?.title}</p>
</AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
<p className="flex flex-row items-center gap-2"> <p className="flex flex-row items-center gap-2">
<Icon icon="iconamoon:clock-thin" /> <Icon icon="iconamoon:clock-thin" />
{detail?.date} {detail?.startTime} - {detail?.endTime} {detail?.timezone ? detail.timezone : "WIB"} {detail?.date} {detail?.startTime} - {detail?.endTime}{" "}
{detail?.timezone ? detail.timezone : "WIB"}
</p> </p>
</AlertDialogDescription> </AlertDialogDescription>
<AlertDialogDescription> <AlertDialogDescription>

View File

@ -25,11 +25,23 @@ const Login = ({ params: { locale } }: { params: { locale: string } }) => {
<div className="lg:block hidden flex-1 overflow-hidden text-[40px] leading-[48px] text-default-600 relative z-[1] bg-default-50"> <div className="lg:block hidden flex-1 overflow-hidden text-[40px] leading-[48px] text-default-600 relative z-[1] bg-default-50">
<div className="max-w-[520px] pt-16 ps-20 "> <div className="max-w-[520px] pt-16 ps-20 ">
<Link href="/" className="mb-6 inline-block"> <Link href="/" className="mb-6 inline-block">
<Image src="/assets/mediahub-logo.png" alt="" width={250} height={250} className="mb-10 w-full h-full" /> <Image
src="/assets/mediahub-logo.png"
alt=""
width={250}
height={250}
className="mb-10 w-full h-full"
/>
</Link> </Link>
</div> </div>
<div className="absolute left-0 2xl:bottom-[-160px] bottom-[-130px] h-full w-full z-[-1]"> <div className="absolute left-0 2xl:bottom-[-160px] bottom-[-130px] h-full w-full z-[-1]">
<Image src="/assets/vector-login.svg" alt="" width={300} height={300} className="mb-10 w-full h-full" /> <Image
src="/assets/vector-login.svg"
alt=""
width={300}
height={300}
className="mb-10 w-full h-full"
/>
</div> </div>
</div> </div>
<div className="flex-1 relative"> <div className="flex-1 relative">
@ -40,12 +52,12 @@ const Login = ({ params: { locale } }: { params: { locale: string } }) => {
<Logo /> <Logo />
</Link> </Link>
</div> */} </div> */}
<div className="text-left 2xl:mb-10 mb-4 mt-10"> {/* <div className="text-left 2xl:mb-10 mb-4 mt-10">
<h4 className="font-semibold text-3xl text-left">{t("logInPlease")}</h4> <h4 className="font-semibold text-3xl text-left">{t("logInPlease")}</h4>
<div className="text-default-500 text-base"> <div className="text-default-500 text-base">
{t("acc")} <span className="text-red-500">{t("reg")}</span> {t("acc")} <span className="text-red-500">{t("reg")}</span>
</div> </div>
</div> </div> */}
<LoginForm /> <LoginForm />
{/* <div className="relative border-b-[#9AA2AF] border-opacity-[16%] border-b pt-6"> {/* <div className="relative border-b-[#9AA2AF] border-opacity-[16%] border-b pt-6">
<div className="absolute inline-block bg-default-50 dark:bg-default-100 left-1/2 top-1/2 transform -translate-x-1/2 px-4 min-w-max text-sm text-default-500 font-normal"> <div className="absolute inline-block bg-default-50 dark:bg-default-100 left-1/2 top-1/2 transform -translate-x-1/2 px-4 min-w-max text-sm text-default-500 font-normal">

View File

@ -26,6 +26,7 @@ import {
SelectItem, SelectItem,
} from "@radix-ui/react-select"; } from "@radix-ui/react-select";
import { SelectGroup } from "@/components/ui/select"; import { SelectGroup } from "@/components/ui/select";
import dynamic from "next/dynamic";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -43,6 +44,13 @@ interface Option {
userLevelId: string; userLevelId: string;
} }
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormCollaboration() { export default function FormCollaboration() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -257,12 +265,7 @@ export default function FormCollaboration() {
control={control} control={control}
name="naration" name="naration"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor onChange={onChange} initialData={value} />
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
)} )}
/> />
{errors.naration?.message && ( {errors.naration?.message && (

View File

@ -26,6 +26,7 @@ import {
SelectItem, SelectItem,
} from "@radix-ui/react-select"; } from "@radix-ui/react-select";
import { SelectGroup } from "@/components/ui/select"; import { SelectGroup } from "@/components/ui/select";
import dynamic from "next/dynamic";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -43,6 +44,13 @@ interface Option {
userLevelId: string; userLevelId: string;
} }
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormInternal() { export default function FormInternal() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -253,12 +261,7 @@ export default function FormInternal() {
control={control} control={control}
name="naration" name="naration"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor onChange={onChange} initialData={value} />
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
)} )}
/> />
{errors.naration?.message && ( {errors.naration?.message && (

View File

@ -57,6 +57,7 @@ import { error } from "@/lib/swal";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import WavesurferPlayer from "@wavesurfer/react"; import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js"; import WaveSurfer from "wavesurfer.js";
import { useTranslations } from "next-intl";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -135,6 +136,7 @@ export default function FormAudioDetail() {
const [detailThumb, setDetailThumb] = useState<any>([]); const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null); const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const t = useTranslations("Form");
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [files, setFiles] = useState<FileType[]>([]); const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]); const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
@ -452,11 +454,11 @@ export default function FormAudioDetail() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Audio</p> <p className="text-lg font-semibold mb-3">{t("form-audio")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -477,8 +479,8 @@ export default function FormAudioDetail() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={detail?.category.name} // Nilai default berdasarkan detail value={detail?.category.name} // Nilai default berdasarkan detail
onValueChange={(id) => { onValueChange={(id) => {
@ -500,8 +502,8 @@ export default function FormAudioDetail() {
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -516,7 +518,7 @@ export default function FormAudioDetail() {
)} )}
</div> </div>
<Label className="text-xl text-black">File Mediaaa</Label> <Label className="text-xl space-y-2">{t("file-media")}</Label>
<div className="w-full"> <div className="w-full">
<div className={"container example"}> <div className={"container example"}>
{detailThumb?.map((url: any, index: number) => ( {detailThumb?.map((url: any, index: number) => (
@ -561,7 +563,7 @@ export default function FormAudioDetail() {
<Card className=" h-[600px]"> <Card className=" h-[600px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -585,7 +587,7 @@ export default function FormAudioDetail() {
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{detail?.tags {detail?.tags
?.split(",") ?.split(",")
@ -601,8 +603,8 @@ export default function FormAudioDetail() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id="5"
@ -639,10 +641,10 @@ export default function FormAudioDetail() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")}(0)</p>
</div> </div>
<div className="px-3 py-3 border mx-3"> <div className="px-3 py-3 border mx-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p> <p className="text-sm text-slate-400">{detail?.statusName}</p>
</div> </div>
{/* {detail?.isPublish == false ? ( {/* {detail?.isPublish == false ? (
@ -662,14 +664,15 @@ export default function FormAudioDetail() {
color="primary" color="primary"
type="button" type="button"
> >
<Icon icon="fa:check" className="mr-3" /> Setujui <Icon icon="fa:check" className="mr-3" /> {t("accept")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("3")} onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300" className="bg-orange-400 hover:bg-orange-300"
type="button" type="button"
> >
<Icon icon="fa:comment-o" className="mr-3" /> Revisi <Icon icon="fa:comment-o" className="mr-3" />{" "}
{t("revision")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("4")} onClick={() => actionApproval("4")}
@ -677,7 +680,7 @@ export default function FormAudioDetail() {
type="button" type="button"
> >
<Icon icon="fa:times" className="mr-3" /> <Icon icon="fa:times" className="mr-3" />
Tolak {t("reject")}
</Button> </Button>
</div> </div>
) )
@ -688,7 +691,7 @@ export default function FormAudioDetail() {
<Dialog open={modalOpen} onOpenChange={setModalOpen}> <Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent size="md"> <DialogContent size="md">
<DialogHeader> <DialogHeader>
<DialogTitle>Berikan Komentar</DialogTitle> <DialogTitle>{t("leave-comment")}</DialogTitle>
</DialogHeader> </DialogHeader>
{status == "2" {status == "2"
? files?.map((file, index) => ( ? files?.map((file, index) => (
@ -725,7 +728,7 @@ export default function FormAudioDetail() {
htmlFor="terms" htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
> >
Semua {t("all")}
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -870,7 +873,7 @@ export default function FormAudioDetail() {
color="primary" color="primary"
onClick={() => submit()} onClick={() => submit()}
> >
Submit {t("submit")}
</Button> </Button>
<Button <Button
type="button" type="button"
@ -879,7 +882,7 @@ export default function FormAudioDetail() {
setModalOpen(false); setModalOpen(false);
}} }}
> >
Cancel {t("cancel")}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -54,6 +54,7 @@ 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"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
interface FileWithPreview extends File { interface FileWithPreview extends File {
preview: string; preview: string;
@ -88,6 +89,7 @@ export default function FormAudio() {
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
@ -708,11 +710,11 @@ export default function FormAudio() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Audio</p> <p className="text-lg font-semibold mb-3">{t("form-audio")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -732,8 +734,8 @@ export default function FormAudio() {
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={selectedCategory} // Ensure selectedTarget is updated correctly value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => { onValueChange={(id) => {
@ -758,7 +760,7 @@ export default function FormAudio() {
</div> </div>
</div> </div>
<div className="flex flex-row items-center gap-3 py-2"> <div className="flex flex-row items-center gap-3 py-2">
<Label>Bantuan AI</Label> <Label>{t("ai-assistance")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch <Switch
defaultChecked={isSwitchOn} defaultChecked={isSwitchOn}
@ -774,7 +776,7 @@ export default function FormAudio() {
<div> <div>
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Bahasa</Label> <Label>{t("language")}</Label>
<Select onValueChange={setSelectedLanguage}> <Select onValueChange={setSelectedLanguage}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -786,7 +788,7 @@ export default function FormAudio() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Writing Style</Label> <Label>{t("writing-style")}</Label>
<Select onValueChange={setSelectedWritingStyle}> <Select onValueChange={setSelectedWritingStyle}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -805,7 +807,7 @@ export default function FormAudio() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Article Size</Label> <Label>{t("article-size")}</Label>
<Select onValueChange={setSelectedSize}> <Select onValueChange={setSelectedSize}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -826,7 +828,7 @@ export default function FormAudio() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Main Keyword</Label> <Label>{t("main-keyword")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -850,7 +852,7 @@ export default function FormAudio() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -872,7 +874,7 @@ export default function FormAudio() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>SEO</Label> <Label>{t("seo")}</Label>
<Button <Button
variant={"outline"} variant={"outline"}
color="primary" color="primary"
@ -883,15 +885,9 @@ export default function FormAudio() {
</Button> </Button>
</div> </div>
<p className="font-semibold"> <p className="font-semibold">
Kata kunci untuk disertakan dalam teks {t("Keywords to include in the text")}
</p>
<p className="text-sm">
JIka Anda tidak Memberikan kata kunci, kami akan secara
otomatis membuat kata kunci yang relevan dari kata kunci
utama untuk setiap bagian dan menggunakannya untuk membuat
artikel. Untuk menambahkan kata kunci baru, ketik &apos;,
+ kata kunci&apos;.
</p> </p>
<p className="text-sm">{t("title-key")}</p>
<div className="mt-3"> <div className="mt-3">
<Textarea <Textarea
value={selectedSEO} value={selectedSEO}
@ -901,7 +897,7 @@ export default function FormAudio() {
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Label>Instruksi Khusus (Optional)</Label> <Label>{t("special-instructions")} (Optional)</Label>
<div className="mt-3"> <div className="mt-3">
<Controller <Controller
control={control} control={control}
@ -959,7 +955,7 @@ export default function FormAudio() {
variant={"outline"} variant={"outline"}
color="primary" color="primary"
> >
Edit {t("update")}
</Button> </Button>
</Link> </Link>
)} )}
@ -968,8 +964,8 @@ export default function FormAudio() {
</div> </div>
</div> </div>
)} )}
<div className=""> <div className="space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -992,8 +988,8 @@ export default function FormAudio() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -1006,11 +1002,10 @@ export default function FormAudio() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan mp3 atau wav Ukuran maksimal {t("upload-file-audio-max")}
100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -1028,7 +1023,7 @@ export default function FormAudio() {
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={handleRemoveAllFiles}
> >
Remove All {t("remove-all")}
</Button> </Button>
</div> </div>
</Fragment> </Fragment>
@ -1042,7 +1037,7 @@ export default function FormAudio() {
<Card className=" h-[500px]"> <Card className=" h-[500px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -1063,8 +1058,8 @@ export default function FormAudio() {
)} )}
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3 space-y-2">
<Label htmlFor="tags">Tags</Label> <Label htmlFor="tags">{t("tags")}</Label>
<Input <Input
type="text" type="text"
@ -1092,8 +1087,8 @@ export default function FormAudio() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option) => ( {options.map((option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -1116,12 +1111,12 @@ export default function FormAudio() {
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
Cancel {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -48,6 +48,8 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
const audioSchema = z.object({ const audioSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -87,6 +89,13 @@ type Option = {
name: string; name: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormAudioUpdate() { export default function FormAudioUpdate() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -108,6 +117,7 @@ export default function FormAudioUpdate() {
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
@ -590,11 +600,11 @@ export default function FormAudioUpdate() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Audio</p> <p className="text-lg font-semibold mb-3">{t("form-audio")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -615,8 +625,8 @@ export default function FormAudioUpdate() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
onValueChange={(id) => { onValueChange={(id) => {
@ -638,17 +648,15 @@ export default function FormAudioUpdate() {
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={detail?.description}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.description || value}
/> />
)} )}
/> />
@ -658,8 +666,8 @@ export default function FormAudioUpdate() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -672,11 +680,10 @@ export default function FormAudioUpdate() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau .png. {t("upload-file-audio-max")}
Ukuran maksimal 100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -685,7 +692,7 @@ export default function FormAudioUpdate() {
<div>{fileList}</div> <div>{fileList}</div>
<div className=" flex justify-between gap-2"> <div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3"> <div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label> <Label>{t("watermark")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
@ -701,7 +708,10 @@ export default function FormAudioUpdate() {
) : null} ) : null}
{files.length > 0 && ( {files.length > 0 && (
<div className="mt-4"> <div className="mt-4">
<Label className="text-lg font-semibold">File</Label> <Label className="text-lg font-semibold">
{" "}
{t("file-media")}
</Label>
<div className="grid gap-4"> <div className="grid gap-4">
{files.map((file: any) => ( {files.map((file: any) => (
<div <div
@ -722,7 +732,7 @@ export default function FormAudioUpdate() {
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-blue-500 text-sm" className="text-blue-500 text-sm"
> >
Lihat File {t("view-file")}
</a> </a>
</div> </div>
<div> <div>
@ -740,7 +750,7 @@ export default function FormAudioUpdate() {
} }
className="form-checkbox" className="form-checkbox"
/> />
<span>Semua</span> <span>{t("all")}</span>
</Label> </Label>
</div> </div>
<div> <div>
@ -812,7 +822,7 @@ export default function FormAudioUpdate() {
<Card className=" h-[800px]"> <Card className=" h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -835,7 +845,7 @@ export default function FormAudioUpdate() {
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<Input <Input
type="text" type="text"
id="tags" id="tags"
@ -869,8 +879,8 @@ export default function FormAudioUpdate() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option: Option) => ( {options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -890,22 +900,22 @@ export default function FormAudioUpdate() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
{/* <p>{detail?.status}</p> */} {/* <p>{detail?.status}</p> */}
</div> </div>
</Card> </Card>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
Cancel {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -57,6 +57,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useRouter } from "@/i18n/routing"; import { useRouter } from "@/i18n/routing";
import { useTranslations } from "next-intl";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -119,6 +120,7 @@ export default function FormImageDetail() {
const editor = useRef(null); const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>; type ImageSchema = z.infer<typeof imageSchema>;
const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
@ -418,11 +420,11 @@ export default function FormImageDetail() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">{t("form-image")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")} </Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -443,8 +445,8 @@ export default function FormImageDetail() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={detail?.category.name} // Nilai default berdasarkan detail value={detail?.category.name} // Nilai default berdasarkan detail
onValueChange={(id) => { onValueChange={(id) => {
@ -466,8 +468,8 @@ export default function FormImageDetail() {
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -481,46 +483,47 @@ export default function FormImageDetail() {
</p> </p>
)} )}
</div> </div>
<div className="space-y-2">
<Label className="text-xl text-black">File Media</Label> <Label className="text-xl ">{t("file-media")}</Label>
<div className="w-full "> <div className="w-full ">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-fill h-full w-full rounded-md"
src={data}
alt={` ${data.id}`}
/>
</SwiperSlide>
))}
</Swiper>
<div className=" mt-2 ">
<Swiper <Swiper
onSwiper={setThumbsSwiper} thumbs={{ swiper: thumbsSwiper }}
slidesPerView={6} modules={[FreeMode, Navigation, Thumbs]}
spaceBetween={8} navigation={false}
pagination={{ className="w-full"
clickable: true,
}}
modules={[Pagination, Thumbs]}
// className="mySwiper2"
> >
{detailThumb?.map((data: any) => ( {detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}> <SwiperSlide key={data.id}>
<img <img
className="object-cover h-[60px] w-[80px]" className="object-fill h-full w-full rounded-md"
src={data} src={data}
alt={` ${data.id}`} alt={` ${data.id}`}
/> />
</SwiperSlide> </SwiperSlide>
))} ))}
</Swiper> </Swiper>
<div className=" mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
// className="mySwiper2"
>
{detailThumb?.map((data: any) => (
<SwiperSlide key={data.id}>
<img
className="object-cover h-[60px] w-[80px]"
src={data}
alt={` ${data.id}`}
/>
</SwiperSlide>
))}
</Swiper>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -530,7 +533,7 @@ export default function FormImageDetail() {
<Card className=" h-[1050px]"> <Card className=" h-[1050px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -551,8 +554,8 @@ export default function FormImageDetail() {
)} )}
</div> </div>
</div> </div>
<div className="mt-3 px-3"> <div className="mt-3 px-3 space-y-2">
<Label>Pratinjau Gambar Utama</Label> <Label>{t("preview")}</Label>
<Card className="mt-2"> <Card className="mt-2">
<img <img
src={detail.thumbnailLink} src={detail.thumbnailLink}
@ -563,7 +566,7 @@ export default function FormImageDetail() {
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{detail?.tags {detail?.tags
?.split(",") ?.split(",")
@ -579,8 +582,8 @@ export default function FormImageDetail() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id="5"
@ -617,10 +620,10 @@ export default function FormImageDetail() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3 border mx-3"> <div className="px-3 py-3 border mx-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p> <p className="text-sm text-slate-400">{detail?.statusName}</p>
</div> </div>
{/* {detail?.isPublish == false ? ( {/* {detail?.isPublish == false ? (
@ -672,14 +675,16 @@ export default function FormImageDetail() {
color="primary" color="primary"
type="button" type="button"
> >
<Icon icon="fa:check" className="mr-3" /> Setujui <Icon icon="fa:check" className="mr-3" />
{t("accept")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("3")} onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300" className="bg-orange-400 hover:bg-orange-300"
type="button" type="button"
> >
<Icon icon="fa:comment-o" className="mr-3" /> Revisi <Icon icon="fa:comment-o" className="mr-3" />{" "}
{t("revision")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("4")} onClick={() => actionApproval("4")}
@ -687,7 +692,7 @@ export default function FormImageDetail() {
type="button" type="button"
> >
<Icon icon="fa:times" className="mr-3" /> <Icon icon="fa:times" className="mr-3" />
Tolak {t("reject")}
</Button> </Button>
</div> </div>
) )
@ -698,7 +703,7 @@ export default function FormImageDetail() {
<Dialog open={modalOpen} onOpenChange={setModalOpen}> <Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent size="md"> <DialogContent size="md">
<DialogHeader> <DialogHeader>
<DialogTitle>Berikan Komentar</DialogTitle> <DialogTitle>{t("leave-comment")}</DialogTitle>
</DialogHeader> </DialogHeader>
{status == "2" {status == "2"
? files?.map((file, index) => ( ? files?.map((file, index) => (
@ -735,7 +740,7 @@ export default function FormImageDetail() {
htmlFor="terms" htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
> >
Semua {t("all")}
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -880,7 +885,7 @@ export default function FormImageDetail() {
color="primary" color="primary"
onClick={() => submit()} onClick={() => submit()}
> >
Submit {t("submit")}
</Button> </Button>
<Button <Button
type="button" type="button"
@ -889,7 +894,7 @@ export default function FormImageDetail() {
setModalOpen(false); setModalOpen(false);
}} }}
> >
Cancel {t("cancel")}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -57,6 +57,7 @@ import dynamic from "next/dynamic";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { request } from "http"; import { request } from "http";
import { useTranslations } from "next-intl";
interface FileWithPreview extends File { interface FileWithPreview extends File {
preview: string; preview: string;
@ -85,6 +86,7 @@ export default function FormImage() {
const editor = useRef(null); const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>; type ImageSchema = z.infer<typeof imageSchema>;
const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
@ -714,11 +716,11 @@ export default function FormImage() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">{t("form-image")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -738,8 +740,8 @@ export default function FormImage() {
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 space-y-2 w-full">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={selectedCategory} // Ensure selectedTarget is updated correctly value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => { onValueChange={(id) => {
@ -763,8 +765,8 @@ export default function FormImage() {
</Select> </Select>
</div> </div>
</div> </div>
<div className="flex flex-row items-center gap-3 py-2"> <div className="flex flex-row items-center gap-3 py-3 ">
<Label>Bantuan AI</Label> <Label>{t("ai-assistance")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch <Switch
defaultChecked={isSwitchOn} defaultChecked={isSwitchOn}
@ -780,7 +782,7 @@ export default function FormImage() {
<div> <div>
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Bahasa</Label> <Label>{t("language")}</Label>
<Select onValueChange={setSelectedLanguage}> <Select onValueChange={setSelectedLanguage}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -792,7 +794,7 @@ export default function FormImage() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Writing Style</Label> <Label>{t("writing-style")}</Label>
<Select onValueChange={setSelectedWritingStyle}> <Select onValueChange={setSelectedWritingStyle}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -811,7 +813,7 @@ export default function FormImage() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Article Size</Label> <Label>{t("article-size")}</Label>
<Select onValueChange={setSelectedSize}> <Select onValueChange={setSelectedSize}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -832,7 +834,7 @@ export default function FormImage() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Main Keyword</Label> <Label>{t("main-keyword")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -856,7 +858,7 @@ export default function FormImage() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -878,7 +880,7 @@ export default function FormImage() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>SEO</Label> <Label>{t("seo")}</Label>
<Button <Button
variant={"outline"} variant={"outline"}
color="primary" color="primary"
@ -889,15 +891,9 @@ export default function FormImage() {
</Button> </Button>
</div> </div>
<p className="font-semibold"> <p className="font-semibold">
Kata kunci untuk disertakan dalam teks {t("Keywords to include in the text")}
</p>
<p className="text-sm">
JIka Anda tidak Memberikan kata kunci, kami akan secara
otomatis membuat kata kunci yang relevan dari kata kunci
utama untuk setiap bagian dan menggunakannya untuk membuat
artikel. Untuk menambahkan kata kunci baru, ketik &apos;,
+ kata kunci&apos;.
</p> </p>
<p className="text-sm">{t("title-key")}</p>
<div className="mt-3"> <div className="mt-3">
<Textarea <Textarea
value={selectedSEO} value={selectedSEO}
@ -907,7 +903,7 @@ export default function FormImage() {
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Label>Instruksi Khusus (Optional)</Label> <Label>{t("special-instructions")}(Optional)</Label>
<div className="mt-3"> <div className="mt-3">
<Controller <Controller
control={control} control={control}
@ -964,7 +960,7 @@ export default function FormImage() {
variant={"outline"} variant={"outline"}
color="primary" color="primary"
> >
Edit {t("update")}
</Button> </Button>
</Link> </Link>
)} )}
@ -973,8 +969,8 @@ export default function FormImage() {
</div> </div>
</div> </div>
)} )}
<div className=""> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -997,8 +993,8 @@ export default function FormImage() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -1011,11 +1007,10 @@ export default function FormImage() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau .png. {t("upload-file-max")}
Ukuran maksimal 100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -1024,7 +1019,7 @@ export default function FormImage() {
<div>{fileList}</div> <div>{fileList}</div>
<div className=" flex justify-between gap-2"> <div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3"> <div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label> <Label>{t("watermark")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
@ -1033,7 +1028,7 @@ export default function FormImage() {
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={handleRemoveAllFiles}
> >
Remove All {t("remove-all")}
</Button> </Button>
</div> </div>
</Fragment> </Fragment>
@ -1049,7 +1044,7 @@ export default function FormImage() {
<Card className=" h-[500px]"> <Card className=" h-[500px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -1070,9 +1065,8 @@ export default function FormImage() {
)} )}
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3 space-y-2">
<Label htmlFor="tags">Tags</Label> <Label htmlFor="tags">{t("tags")}</Label>
<Input <Input
type="text" type="text"
id="tags" id="tags"
@ -1099,8 +1093,8 @@ export default function FormImage() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option) => ( {options.map((option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -1123,13 +1117,15 @@ export default function FormImage() {
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Link href={"/contributor/content/image"}>
Cancel <Button type="submit" color="primary" variant="outline">
</Button> {t("cancel")}
</Button>
</Link>
</div> </div>
</div> </div>
</div> </div>

View File

@ -47,6 +47,7 @@ import Image from "next/image";
import { error, loading } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { useTranslations } from "next-intl";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -114,7 +115,7 @@ export default function FormImageUpdate() {
let uploadPersen = 0; let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
@ -628,11 +629,11 @@ export default function FormImageUpdate() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">{t("form-image")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -653,8 +654,8 @@ export default function FormImageUpdate() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue defaultValue={detail?.category.id} // Gunakan ID sebagai defaultValue
onValueChange={(id) => { onValueChange={(id) => {
@ -679,8 +680,8 @@ export default function FormImageUpdate() {
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -697,8 +698,8 @@ export default function FormImageUpdate() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -711,11 +712,10 @@ export default function FormImageUpdate() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau .png. {t("upload-file-max")}
Ukuran maksimal 100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -724,7 +724,7 @@ export default function FormImageUpdate() {
<div>{fileList}</div> <div>{fileList}</div>
<div className=" flex justify-between gap-2"> <div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3"> <div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label> <Label>{t("watermark")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
@ -740,7 +740,10 @@ export default function FormImageUpdate() {
) : null} ) : null}
{files.length > 0 && ( {files.length > 0 && (
<div className="mt-4"> <div className="mt-4">
<Label className="text-lg font-semibold">File</Label> <Label className="text-lg font-semibold">
{" "}
{t("file-media")}
</Label>
<div className="grid gap-4"> <div className="grid gap-4">
{files.map((file: any) => ( {files.map((file: any) => (
<div <div
@ -761,7 +764,7 @@ export default function FormImageUpdate() {
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-blue-500 text-sm" className="text-blue-500 text-sm"
> >
Lihat File {t("view-file")}
</a> </a>
</div> </div>
<div> <div>
@ -779,7 +782,7 @@ export default function FormImageUpdate() {
} }
className="form-checkbox" className="form-checkbox"
/> />
<span>Semua</span> <span>{t("all")}</span>
</Label> </Label>
</div> </div>
<div> <div>
@ -851,7 +854,7 @@ export default function FormImageUpdate() {
<Card className="h-[900px] md:h-[1100px] lg:h-[800px]"> <Card className="h-[900px] md:h-[1100px] lg:h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -872,8 +875,8 @@ export default function FormImageUpdate() {
)} )}
</div> </div>
</div> </div>
<div className="mt-3 px-3"> <div className="mt-3 px-3 space-y-2">
<Label>Pratinjau Gambar Utama</Label> <Label>{t("preview")}</Label>
<Card className="mt-2"> <Card className="mt-2">
<img <img
src={detail.thumbnailLink} src={detail.thumbnailLink}
@ -884,7 +887,7 @@ export default function FormImageUpdate() {
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<Input <Input
type="text" type="text"
id="tags" id="tags"
@ -928,8 +931,8 @@ export default function FormImageUpdate() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option: Option) => ( {options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -949,22 +952,22 @@ export default function FormImageUpdate() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
{/* <p>{detail?.status}</p> */} {/* <p>{detail?.status}</p> */}
</div> </div>
</Card> </Card>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
Cancel {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -49,6 +49,7 @@ 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"; import { error } from "@/lib/swal";
import { useTranslations } from "next-intl";
const imageSchema = z.object({ const imageSchema = z.object({
contentTitle: z.string().min(1, { message: "Judul diperlukan" }), contentTitle: z.string().min(1, { message: "Judul diperlukan" }),
@ -144,6 +145,7 @@ export default function FormConvertSPIT() {
const [selectedArticleId, setSelectedArticleId] = useState<string | null>( const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null null
); );
const t = useTranslations("Form");
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
const [selectedFileType, setSelectedFileType] = useState("original"); const [selectedFileType, setSelectedFileType] = useState("original");
const [isLoadingData, setIsLoadingData] = useState<boolean>(false); const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
@ -616,11 +618,11 @@ export default function FormConvertSPIT() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Foto</p> <p className="text-lg font-semibold mb-3">{t("form-spit")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="contentTitle" name="contentTitle"
@ -641,8 +643,8 @@ export default function FormConvertSPIT() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
defaultValue={detail?.content?.name} defaultValue={detail?.content?.name}
onValueChange={(id) => { onValueChange={(id) => {
@ -678,8 +680,8 @@ export default function FormConvertSPIT() {
Select Original File Select Original File
</Label> </Label>
</div> </div>
<div className="py-3 "> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="contentDescription" name="contentDescription"
@ -733,11 +735,11 @@ export default function FormConvertSPIT() {
<div className="flex items-center space-x-2 mt-3"> <div className="flex items-center space-x-2 mt-3">
<RadioGroupItem value="rewrite" id="rewrite-file" /> <RadioGroupItem value="rewrite" id="rewrite-file" />
<Label htmlFor="rewrite-file"> <Label htmlFor="rewrite-file">
Select File Hasil Rewrite Select File Rewrite
</Label> </Label>
</div> </div>
<div className="py-3 "> <div className="py-3 space-y-2">
<Label>File hasil Rewrite</Label> <Label>{t("file-rewrite")}</Label>
<Controller <Controller
control={control} control={control}
name="contentRewriteDescription" name="contentRewriteDescription"
@ -766,8 +768,8 @@ export default function FormConvertSPIT() {
)} )}
</RadioGroup> </RadioGroup>
</div> </div>
<div> <div className="space-y-2">
<Label className="text-xl">File Media</Label> <Label className="text-xl">{t("file-media")}</Label>
<div className="w-full "> <div className="w-full ">
<Swiper <Swiper
thumbs={{ swiper: thumbsSwiper }} thumbs={{ swiper: thumbsSwiper }}
@ -810,10 +812,10 @@ export default function FormConvertSPIT() {
</div> </div>
</div> </div>
<div className="mt-3"> <div className="mt-3">
<Label className="text-xl">Penempatan file</Label> <Label className="text-xl">{t("file-placement")}</Label>
</div> </div>
{files?.length > 1 && ( {files?.length > 1 && (
<div className="flex flex-wrap gap-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2 justify-end mr-24 pr-2">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Checkbox <Checkbox
id="all-content" id="all-content"
@ -825,7 +827,7 @@ export default function FormConvertSPIT() {
htmlFor="all-content" htmlFor="all-content"
className="text-xs font-medium" className="text-xs font-medium"
> >
All {t("all")}
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -839,7 +841,7 @@ export default function FormConvertSPIT() {
htmlFor="all-nasional" htmlFor="all-nasional"
className="text-xs font-medium" className="text-xs font-medium"
> >
All Nasional {t("all")} Nasional
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -853,7 +855,7 @@ export default function FormConvertSPIT() {
htmlFor="all-wilayah" htmlFor="all-wilayah"
className="text-xs font-medium" className="text-xs font-medium"
> >
All Wilayah {t("all")} Wilayah
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -867,7 +869,7 @@ export default function FormConvertSPIT() {
htmlFor="all-international" htmlFor="all-international"
className="text-xs font-medium" className="text-xs font-medium"
> >
All Internasional {t("all")} Internasional
</label> </label>
</div> </div>
</div> </div>
@ -879,20 +881,12 @@ export default function FormConvertSPIT() {
> >
<img <img
src={file.contentFile} src={file.contentFile}
className="w-[150px] rounded-md" className="w-[180px] rounded-md"
/> />
<div className="flex flex-col gap-2 w-full"> <div className="flex flex-col gap-2 w-full pl-4">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
{file.fileName} {file.fileName}
{/* <a
onClick={() =>
handleDeleteFileApproval(file.id)
}
>
<Icon icon="humbleicons:times" color="red" />
</a> */}
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Checkbox <Checkbox
@ -907,7 +901,7 @@ export default function FormConvertSPIT() {
htmlFor="terms" htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
> >
Semua {t("all")}
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -969,7 +963,7 @@ export default function FormConvertSPIT() {
<Card className=" h-[800px]"> <Card className=" h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="contentCreator" name="contentCreator"
@ -977,7 +971,7 @@ export default function FormConvertSPIT() {
<Input <Input
size="md" size="md"
type="text" type="text"
defaultValue={detail?.contentCreator} value={detail?.contentCreator}
onChange={field.onChange} onChange={field.onChange}
placeholder="Enter Title" placeholder="Enter Title"
/> />
@ -991,7 +985,7 @@ export default function FormConvertSPIT() {
</div> </div>
</div> </div>
<div className="mt-3 px-3"> <div className="mt-3 px-3">
<Label>Pratinjau Gambar Utama</Label> <Label>{t("preview")}</Label>
<Card className="mt-2"> <Card className="mt-2">
<img <img
src={detail.contentThumbnail} src={detail.contentThumbnail}
@ -1002,7 +996,7 @@ export default function FormConvertSPIT() {
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{detail?.contentTag {detail?.contentTag
?.split(",") ?.split(",")
@ -1018,8 +1012,8 @@ export default function FormConvertSPIT() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option) => ( {options.map((option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -1040,17 +1034,17 @@ export default function FormConvertSPIT() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
{/* <p>{detail?.status}</p> */} {/* <p>{detail?.status}</p> */}
</div> </div>
</Card> </Card>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
@ -1059,7 +1053,7 @@ export default function FormConvertSPIT() {
className="bg-red-500 hover:bg-red-700" className="bg-red-500 hover:bg-red-700"
onClick={() => deleteSpitContent()} onClick={() => deleteSpitContent()}
> >
Delete {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -133,6 +134,7 @@ export default function FormTeksDetail() {
const [detailThumb, setDetailThumb] = useState<any>([]); const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null); const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const t = useTranslations("Form");
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [files, setFiles] = useState<FileType[]>([]); const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]); const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
@ -421,13 +423,11 @@ export default function FormTeksDetail() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3"> <p className="text-lg font-semibold mb-3">{t("form-text")}</p>
Form detail Konten Teks
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -448,8 +448,8 @@ export default function FormTeksDetail() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={detail?.category.name} // Nilai default berdasarkan detail value={detail?.category.name} // Nilai default berdasarkan detail
onValueChange={(id) => { onValueChange={(id) => {
@ -471,8 +471,8 @@ export default function FormTeksDetail() {
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -486,81 +486,85 @@ export default function FormTeksDetail() {
</p> </p>
)} )}
</div> </div>
<Label className="text-xl text-black">File Media</Label> <div className="space-y-2">
<div className="w-full"> <Label className="text-xl">{t("file-media")} </Label>
<Swiper <div className="w-full">
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailThumb?.map((data: any, index: number) => (
<SwiperSlide key={index}>
{[".jpg", ".jpeg", ".png", ".webp"].includes(
data.format
) ? (
// Menampilkan gambar
<img
className="object-fill h-full w-full rounded-md"
src={data.url}
alt={data.fileName || "File"}
/>
) : data.format === ".pdf" ? (
// Menampilkan PDF menggunakan iframe
<iframe
className="w-full h-96 rounded-md"
src={data.url}
title={data.fileName || "PDF File"}
/>
) : [".docx", ".ppt", ".pptx"].includes(data.format) ? (
// Menampilkan file dokumen menggunakan Office Viewer
<iframe
className="w-full h-96 rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
data.url
)}`}
title={data.fileName || "Document"}
/>
) : (
// Menampilkan link jika format tidak dikenali
<a
href={data.url}
target="_blank"
rel="noopener noreferrer"
className="block text-blue-500 underline"
>
View {data.fileName || "File"}
</a>
)}
</SwiperSlide>
))}
</Swiper>
<div className="mt-2 ">
<Swiper <Swiper
onSwiper={setThumbsSwiper} thumbs={{ swiper: thumbsSwiper }}
slidesPerView={8} modules={[FreeMode, Navigation, Thumbs]}
spaceBetween={8} navigation={false}
pagination={{ clickable: true }} className="w-full"
modules={[Pagination, Thumbs]}
> >
{detailThumb?.map((data: any, index: number) => ( {detailThumb?.map((data: any, index: number) => (
<SwiperSlide key={index}> <SwiperSlide key={index}>
{[".jpg", ".jpeg", ".png", ".webp"].includes( {[".jpg", ".jpeg", ".png", ".webp"].includes(
data.format data.format
) ? ( ) ? (
// Menampilkan gambar
<img <img
className="object-cover h-[60px] w-[80px]" className="object-fill h-full w-full rounded-md"
src={data.url} src={data.url}
alt={data.fileName} alt={data.fileName || "File"}
/>
) : data.format === ".pdf" ? (
// Menampilkan PDF menggunakan iframe
<iframe
className="w-full h-96 rounded-md"
src={data.url}
title={data.fileName || "PDF File"}
/>
) : [".docx", ".ppt", ".pptx"].includes(
data.format
) ? (
// Menampilkan file dokumen menggunakan Office Viewer
<iframe
className="w-full h-96 rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
data.url
)}`}
title={data.fileName || "Document"}
/> />
) : ( ) : (
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md"> // Menampilkan link jika format tidak dikenali
{data?.format?.replace(".", "").toUpperCase()} <a
</div> href={data.url}
target="_blank"
rel="noopener noreferrer"
className="block text-blue-500 underline"
>
View {data.fileName || "File"}
</a>
)} )}
</SwiperSlide> </SwiperSlide>
))} ))}
</Swiper> </Swiper>
<div className="mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={8}
spaceBetween={8}
pagination={{ clickable: true }}
modules={[Pagination, Thumbs]}
>
{detailThumb?.map((data: any, index: number) => (
<SwiperSlide key={index}>
{[".jpg", ".jpeg", ".png", ".webp"].includes(
data.format
) ? (
<img
className="object-cover h-[60px] w-[80px]"
src={data.url}
alt={data.fileName}
/>
) : (
<div className="h-[60px] w-[80px] flex items-center justify-center bg-gray-200 text-sm text-center text-gray-700 rounded-md">
{data?.format?.replace(".", "").toUpperCase()}
</div>
)}
</SwiperSlide>
))}
</Swiper>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -570,7 +574,7 @@ export default function FormTeksDetail() {
<Card className=" h-[800px]"> <Card className=" h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -603,7 +607,7 @@ export default function FormTeksDetail() {
</div> */} </div> */}
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{detail?.tags {detail?.tags
?.split(",") ?.split(",")
@ -619,8 +623,8 @@ export default function FormTeksDetail() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id="5"
@ -657,10 +661,10 @@ export default function FormTeksDetail() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3 border mx-3"> <div className="px-3 py-3 border mx-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p> <p className="text-sm text-slate-400">{detail?.statusName}</p>
</div> </div>
{/* {detail?.isPublish == false ? ( {/* {detail?.isPublish == false ? (
@ -680,14 +684,15 @@ export default function FormTeksDetail() {
color="primary" color="primary"
type="button" type="button"
> >
<Icon icon="fa:check" className="mr-3" /> Setujui <Icon icon="fa:check" className="mr-3" /> {t("accept")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("3")} onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300" className="bg-orange-400 hover:bg-orange-300"
type="button" type="button"
> >
<Icon icon="fa:comment-o" className="mr-3" /> Revisi <Icon icon="fa:comment-o" className="mr-3" />{" "}
{t("revision")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("4")} onClick={() => actionApproval("4")}
@ -695,7 +700,7 @@ export default function FormTeksDetail() {
type="button" type="button"
> >
<Icon icon="fa:times" className="mr-3" /> <Icon icon="fa:times" className="mr-3" />
Tolak {t("reject")}
</Button> </Button>
</div> </div>
) )
@ -706,7 +711,7 @@ export default function FormTeksDetail() {
<Dialog open={modalOpen} onOpenChange={setModalOpen}> <Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent size="md"> <DialogContent size="md">
<DialogHeader> <DialogHeader>
<DialogTitle>Berikan Komentar</DialogTitle> <DialogTitle>{t("leave-comment")}</DialogTitle>
</DialogHeader> </DialogHeader>
{status == "2" {status == "2"
? files?.map((file, index) => ( ? files?.map((file, index) => (
@ -743,7 +748,7 @@ export default function FormTeksDetail() {
htmlFor="terms" htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
> >
Semua {t("all")}
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -888,7 +893,7 @@ export default function FormTeksDetail() {
color="primary" color="primary"
onClick={() => submit()} onClick={() => submit()}
> >
Submit {t("submit")}
</Button> </Button>
<Button <Button
type="button" type="button"
@ -897,7 +902,7 @@ export default function FormTeksDetail() {
setModalOpen(false); setModalOpen(false);
}} }}
> >
Cancel {t("cancel")}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -54,6 +54,7 @@ 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"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
interface FileWithPreview extends File { interface FileWithPreview extends File {
preview: string; preview: string;
@ -115,7 +116,7 @@ export default function FormTeks() {
const [articleImages, setArticleImages] = useState<string[]>([]); const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false); const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const t = useTranslations("Form");
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
allUnit: false, allUnit: false,
@ -707,11 +708,11 @@ export default function FormTeks() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Teks</p> <p className="text-lg font-semibold mb-3">{t("form-text")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -731,8 +732,8 @@ export default function FormTeks() {
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={selectedCategory} // Ensure selectedTarget is updated correctly value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => { onValueChange={(id) => {
@ -757,7 +758,7 @@ export default function FormTeks() {
</div> </div>
</div> </div>
<div className="flex flex-row items-center gap-3 py-2"> <div className="flex flex-row items-center gap-3 py-2">
<Label>Bantuan AI</Label> <Label>{t("ai-assistance")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch <Switch
defaultChecked={isSwitchOn} defaultChecked={isSwitchOn}
@ -773,7 +774,7 @@ export default function FormTeks() {
<div> <div>
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Bahasa</Label> <Label>{t("language")}</Label>
<Select onValueChange={setSelectedLanguage}> <Select onValueChange={setSelectedLanguage}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -785,7 +786,7 @@ export default function FormTeks() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Writing Style</Label> <Label>{t("writing-style")}</Label>
<Select onValueChange={setSelectedWritingStyle}> <Select onValueChange={setSelectedWritingStyle}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -804,7 +805,7 @@ export default function FormTeks() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Article Size</Label> <Label>{t("article-size")}</Label>
<Select onValueChange={setSelectedSize}> <Select onValueChange={setSelectedSize}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -825,7 +826,7 @@ export default function FormTeks() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Main Keyword</Label> <Label>{t("main-keyword")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -849,7 +850,7 @@ export default function FormTeks() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -871,7 +872,7 @@ export default function FormTeks() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>SEO</Label> <Label>{t("seo")}</Label>
<Button <Button
variant={"outline"} variant={"outline"}
color="primary" color="primary"
@ -882,15 +883,9 @@ export default function FormTeks() {
</Button> </Button>
</div> </div>
<p className="font-semibold"> <p className="font-semibold">
Kata kunci untuk disertakan dalam teks {t("Keywords to include in the text")}
</p>
<p className="text-sm">
JIka Anda tidak Memberikan kata kunci, kami akan secara
otomatis membuat kata kunci yang relevan dari kata kunci
utama untuk setiap bagian dan menggunakannya untuk membuat
artikel. Untuk menambahkan kata kunci baru, ketik &apos;,
+ kata kunci&apos;.
</p> </p>
<p className="text-sm">{t("title-key")}</p>
<div className="mt-3"> <div className="mt-3">
<Textarea <Textarea
value={selectedSEO} value={selectedSEO}
@ -900,7 +895,7 @@ export default function FormTeks() {
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Label>Instruksi Khusus (Optional)</Label> <Label>{t("special-instructions")} (Optional)</Label>
<div className="mt-3"> <div className="mt-3">
<Controller <Controller
control={control} control={control}
@ -957,7 +952,7 @@ export default function FormTeks() {
variant={"outline"} variant={"outline"}
color="primary" color="primary"
> >
Edit {t("update")}
</Button> </Button>
</Link> </Link>
)} )}
@ -966,8 +961,8 @@ export default function FormTeks() {
</div> </div>
</div> </div>
)} )}
<div className=""> <div className="space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -990,8 +985,8 @@ export default function FormTeks() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -1004,11 +999,10 @@ export default function FormTeks() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan format .doc, .docx, .pdf, .ppt, {t("upload-file-text-max")}
atau .pptx Ukuran maksimal 100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -1017,7 +1011,7 @@ export default function FormTeks() {
<div>{fileList}</div> <div>{fileList}</div>
<div className=" flex justify-between gap-2"> <div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3"> <div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label> <Label>{t("watermark")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
@ -1026,7 +1020,7 @@ export default function FormTeks() {
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={handleRemoveAllFiles}
> >
Remove All {t("remove-all")}
</Button> </Button>
</div> </div>
</Fragment> </Fragment>
@ -1042,7 +1036,7 @@ export default function FormTeks() {
<Card className=" h-[500px]"> <Card className=" h-[500px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -1063,8 +1057,8 @@ export default function FormTeks() {
)} )}
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3 space-y-2">
<Label htmlFor="tags">Tags</Label> <Label htmlFor="tags">{t("tags")}</Label>
<Input <Input
type="text" type="text"
@ -1092,8 +1086,8 @@ export default function FormTeks() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option) => ( {options.map((option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -1116,12 +1110,12 @@ export default function FormTeks() {
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
Cancel {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -44,6 +44,8 @@ import Image from "next/image";
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 { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
const teksSchema = z.object({ const teksSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -87,6 +89,13 @@ type Option = {
label: string; label: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormTeksUpdate() { export default function FormTeksUpdate() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -108,6 +117,7 @@ export default function FormTeksUpdate() {
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
@ -555,13 +565,11 @@ export default function FormTeksUpdate() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3"> <p className="text-lg font-semibold mb-3">{t("form-text")}</p>
Form Update Konten Teks
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -582,8 +590,8 @@ export default function FormTeksUpdate() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
onValueChange={(id) => { onValueChange={(id) => {
@ -604,17 +612,15 @@ export default function FormTeksUpdate() {
</Select> </Select>
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={detail?.description}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.description || value}
/> />
)} )}
/> />
@ -624,8 +630,8 @@ export default function FormTeksUpdate() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -638,11 +644,10 @@ export default function FormTeksUpdate() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan format .doc, .docx, .pdf, .ppt, {t("upload-file-text-max")}
atau .pptx Ukuran maksimal 100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -651,7 +656,7 @@ export default function FormTeksUpdate() {
<div>{fileList}</div> <div>{fileList}</div>
<div className=" flex justify-between gap-2"> <div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3"> <div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label> <Label>{t("watermark")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
@ -660,14 +665,17 @@ export default function FormTeksUpdate() {
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={handleRemoveAllFiles}
> >
Remove All {t("remove-all")}
</Button> </Button>
</div> </div>
</Fragment> </Fragment>
) : null} ) : null}
{files.length > 0 && ( {files.length > 0 && (
<div className="mt-4"> <div className="mt-4 space-y-2">
<Label className="text-lg font-semibold">File</Label> <Label className="text-lg font-semibold">
{" "}
{t("file-media")}
</Label>
<div className="grid gap-4"> <div className="grid gap-4">
{files.map((file: any) => ( {files.map((file: any) => (
<div <div
@ -688,7 +696,7 @@ export default function FormTeksUpdate() {
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-blue-500 text-sm" className="text-blue-500 text-sm"
> >
Lihat File {t("view-file")}
</a> </a>
</div> </div>
<div> <div>
@ -706,7 +714,7 @@ export default function FormTeksUpdate() {
} }
className="form-checkbox" className="form-checkbox"
/> />
<span>Semua</span> <span>{t("all")}</span>
</Label> </Label>
</div> </div>
<div> <div>
@ -778,7 +786,7 @@ export default function FormTeksUpdate() {
<Card className=" h-[800px]"> <Card className=" h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -811,7 +819,7 @@ export default function FormTeksUpdate() {
</div> */} </div> */}
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<Input <Input
type="text" type="text"
id="tags" id="tags"
@ -846,7 +854,7 @@ export default function FormTeksUpdate() {
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option) => ( {options.map((option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -867,22 +875,22 @@ export default function FormTeksUpdate() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
{/* <p>{detail?.status}</p> */} {/* <p>{detail?.status}</p> */}
</div> </div>
</Card> </Card>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
Cancel {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -56,6 +56,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal"; import { error } from "@/lib/swal";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useRouter } from "@/i18n/routing"; import { useRouter } from "@/i18n/routing";
import { useTranslations } from "next-intl";
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -135,6 +136,7 @@ export default function FormVideoDetail() {
const [detailVideo, setDetailVideo] = useState<any>([]); const [detailVideo, setDetailVideo] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null); const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const t = useTranslations("Form");
const [filePlacements, setFilePlacements] = useState<string[][]>([]); const [filePlacements, setFilePlacements] = useState<string[][]>([]);
const [selectedTarget, setSelectedTarget] = useState(""); const [selectedTarget, setSelectedTarget] = useState("");
const [files, setFiles] = useState<FileType[]>([]); const [files, setFiles] = useState<FileType[]>([]);
@ -412,13 +414,11 @@ export default function FormVideoDetail() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3"> <p className="text-lg font-semibold mb-3">{t("form-video")}</p>
Form Detail Konten Video
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -439,8 +439,8 @@ export default function FormVideoDetail() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={detail?.category.name} // Nilai default berdasarkan detail value={detail?.category.name} // Nilai default berdasarkan detail
onValueChange={(id) => { onValueChange={(id) => {
@ -462,8 +462,8 @@ export default function FormVideoDetail() {
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -477,48 +477,49 @@ export default function FormVideoDetail() {
</p> </p>
)} )}
</div> </div>
<div className="space-y-2">
<Label className="text-xl text-black">File Mediaa</Label> <Label className="text-xl "> {t("file-media")}</Label>
<div className="w-full "> <div className="w-full ">
<Swiper
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
navigation={false}
className="w-full"
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<video
className="object-fill h-full w-full"
src={data}
controls
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
</SwiperSlide>
))}
</Swiper>
<div className=" mt-2 ">
<Swiper <Swiper
onSwiper={setThumbsSwiper} thumbs={{ swiper: thumbsSwiper }}
slidesPerView={6} modules={[FreeMode, Navigation, Thumbs]}
spaceBetween={8} navigation={false}
pagination={{ className="w-full"
clickable: true,
}}
modules={[Pagination, Thumbs]}
// className="mySwiper2"
> >
{detailVideo?.map((data: any) => ( {detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}> <SwiperSlide key={data.id}>
<video <video
className="object-cover h-[60px] w-[80px]" className="object-fill h-full w-full"
src={data} src={data}
muted controls
title={`Video ${data.id}`} // Mengganti alt dengan title title={`Video ${data.id}`} // Mengganti alt dengan title
/> />
</SwiperSlide> </SwiperSlide>
))} ))}
</Swiper> </Swiper>
<div className=" mt-2 ">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={6}
spaceBetween={8}
pagination={{
clickable: true,
}}
modules={[Pagination, Thumbs]}
// className="mySwiper2"
>
{detailVideo?.map((data: any) => (
<SwiperSlide key={data.id}>
<video
className="object-cover h-[60px] w-[80px]"
src={data}
muted
title={`Video ${data.id}`} // Mengganti alt dengan title
/>
</SwiperSlide>
))}
</Swiper>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -528,7 +529,7 @@ export default function FormVideoDetail() {
<Card className=" h-[800px]"> <Card className=" h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -549,8 +550,8 @@ export default function FormVideoDetail() {
)} )}
</div> </div>
</div> </div>
<div className="mt-3 px-3"> <div className="mt-3 px-3 space-y-2">
<Label>Pratinjau Gambar Utama</Label> <Label>{t("preview")}</Label>
<Card className="mt-2"> <Card className="mt-2">
<img <img
src={detail.thumbnailLink} src={detail.thumbnailLink}
@ -561,7 +562,7 @@ export default function FormVideoDetail() {
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{detail?.tags {detail?.tags
?.split(",") ?.split(",")
@ -577,8 +578,8 @@ export default function FormVideoDetail() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="5" id="5"
@ -615,10 +616,10 @@ export default function FormVideoDetail() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3 border mx-3"> <div className="px-3 py-3 border mx-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
<p className="text-sm text-slate-400">{detail?.statusName}</p> <p className="text-sm text-slate-400">{detail?.statusName}</p>
</div> </div>
{/* {detail?.isPublish == false ? ( {/* {detail?.isPublish == false ? (
@ -638,14 +639,15 @@ export default function FormVideoDetail() {
color="primary" color="primary"
type="button" type="button"
> >
<Icon icon="fa:check" className="mr-3" /> Setujui <Icon icon="fa:check" className="mr-3" /> {t("accept")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("3")} onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300" className="bg-orange-400 hover:bg-orange-300"
type="button" type="button"
> >
<Icon icon="fa:comment-o" className="mr-3" /> Revisi <Icon icon="fa:comment-o" className="mr-3" />{" "}
{t("revision")}
</Button> </Button>
<Button <Button
onClick={() => actionApproval("4")} onClick={() => actionApproval("4")}
@ -653,7 +655,7 @@ export default function FormVideoDetail() {
type="button" type="button"
> >
<Icon icon="fa:times" className="mr-3" /> <Icon icon="fa:times" className="mr-3" />
Tolak {t("reject")}
</Button> </Button>
</div> </div>
) )
@ -664,7 +666,7 @@ export default function FormVideoDetail() {
<Dialog open={modalOpen} onOpenChange={setModalOpen}> <Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent size="md"> <DialogContent size="md">
<DialogHeader> <DialogHeader>
<DialogTitle>Berikan Komentar</DialogTitle> <DialogTitle>{t("leave-comment")}</DialogTitle>
</DialogHeader> </DialogHeader>
{status == "2" {status == "2"
? files?.map((file, index) => ( ? files?.map((file, index) => (
@ -701,7 +703,7 @@ export default function FormVideoDetail() {
htmlFor="terms" htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
> >
Semua {t("all")}
</label> </label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
@ -846,14 +848,14 @@ export default function FormVideoDetail() {
color="primary" color="primary"
onClick={() => submit()} onClick={() => submit()}
> >
Submit {t("submit")}
</Button> </Button>
<Button <Button
type="button" type="button"
color="destructive" color="destructive"
onClick={() => setModalOpen(false)} onClick={() => setModalOpen(false)}
> >
Cancel {t("cancel")}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>

View File

@ -54,6 +54,7 @@ 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"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
const CustomEditor = dynamic( const CustomEditor = dynamic(
() => { () => {
@ -88,6 +89,7 @@ export default function FormVideo() {
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const t = useTranslations("Form");
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
@ -706,10 +708,10 @@ export default function FormVideo() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Konten Video</p> <p className="text-lg font-semibold mb-3">{t("form-video")}</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -729,8 +731,8 @@ export default function FormVideo() {
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
value={selectedCategory} // Ensure selectedTarget is updated correctly value={selectedCategory} // Ensure selectedTarget is updated correctly
onValueChange={(id) => { onValueChange={(id) => {
@ -755,7 +757,7 @@ export default function FormVideo() {
</div> </div>
</div> </div>
<div className="flex flex-row items-center gap-3 py-2"> <div className="flex flex-row items-center gap-3 py-2">
<Label>Bantuan AI</Label> <Label>{t("ai-assistance")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch <Switch
defaultChecked={isSwitchOn} defaultChecked={isSwitchOn}
@ -771,7 +773,7 @@ export default function FormVideo() {
<div> <div>
<div className="flex flex-row gap-3"> <div className="flex flex-row gap-3">
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Bahasa</Label> <Label>{t("language")}</Label>
<Select onValueChange={setSelectedLanguage}> <Select onValueChange={setSelectedLanguage}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -783,7 +785,7 @@ export default function FormVideo() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Writing Style</Label> <Label>{t("writing-style")}</Label>
<Select onValueChange={setSelectedWritingStyle}> <Select onValueChange={setSelectedWritingStyle}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -802,7 +804,7 @@ export default function FormVideo() {
</Select> </Select>
</div> </div>
<div className="space-y-2 py-3 w-4/12"> <div className="space-y-2 py-3 w-4/12">
<Label>Article Size</Label> <Label>{t("article-size")}</Label>
<Select onValueChange={setSelectedSize}> <Select onValueChange={setSelectedSize}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Pilih" />
@ -823,7 +825,7 @@ export default function FormVideo() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Main Keyword</Label> <Label>{t("main-keyword")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -847,7 +849,7 @@ export default function FormVideo() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Button <Button
variant="outline" variant="outline"
color="primary" color="primary"
@ -869,7 +871,7 @@ export default function FormVideo() {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<div className="flex flex-row items-center gap-3 mb-3"> <div className="flex flex-row items-center gap-3 mb-3">
<Label>SEO</Label> <Label>{t("seo")}</Label>
<Button <Button
variant={"outline"} variant={"outline"}
color="primary" color="primary"
@ -880,15 +882,9 @@ export default function FormVideo() {
</Button> </Button>
</div> </div>
<p className="font-semibold"> <p className="font-semibold">
Kata kunci untuk disertakan dalam teks {t("Keywords to include in the text")}
</p>
<p className="text-sm">
JIka Anda tidak Memberikan kata kunci, kami akan secara
otomatis membuat kata kunci yang relevan dari kata kunci
utama untuk setiap bagian dan menggunakannya untuk membuat
artikel. Untuk menambahkan kata kunci baru, ketik &apos;,
+ kata kunci&apos;.
</p> </p>
<p className="text-sm">{t("title-key")}</p>
<div className="mt-3"> <div className="mt-3">
<Textarea <Textarea
value={selectedSEO} value={selectedSEO}
@ -898,7 +894,7 @@ export default function FormVideo() {
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Label>Instruksi Khusus (Optional)</Label> <Label>{t("special-instructions")} (Optional)</Label>
<div className="mt-3"> <div className="mt-3">
<Controller <Controller
control={control} control={control}
@ -955,7 +951,7 @@ export default function FormVideo() {
variant={"outline"} variant={"outline"}
color="primary" color="primary"
> >
Edit {t("update")}
</Button> </Button>
</Link> </Link>
)} )}
@ -964,8 +960,8 @@ export default function FormVideo() {
</div> </div>
</div> </div>
)} )}
<div className=""> <div className="space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -988,8 +984,8 @@ export default function FormVideo() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -1002,11 +998,10 @@ export default function FormVideo() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan mp4 atau mov Ukuran maksimal {t("upload-file-video-max")}
100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -1015,7 +1010,7 @@ export default function FormVideo() {
<div>{fileList}</div> <div>{fileList}</div>
<div className=" flex justify-between gap-2"> <div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3"> <div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label> <Label>{t("watermark")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
@ -1024,7 +1019,7 @@ export default function FormVideo() {
color="destructive" color="destructive"
onClick={handleRemoveAllFiles} onClick={handleRemoveAllFiles}
> >
Remove All {t("remove-all")}
</Button> </Button>
</div> </div>
</Fragment> </Fragment>
@ -1040,7 +1035,7 @@ export default function FormVideo() {
<Card className=" h-[800px]"> <Card className=" h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -1074,8 +1069,8 @@ export default function FormVideo() {
/> />
</div> </div>
)} )}
<div className="px-3 py-3"> <div className="px-3 py-3 space-y-2">
<Label htmlFor="tags">Tags</Label> <Label htmlFor="tags">{t("tags")}</Label>
<Input <Input
type="text" type="text"
@ -1103,8 +1098,8 @@ export default function FormVideo() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option) => ( {options.map((option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -1127,12 +1122,12 @@ export default function FormVideo() {
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
Cancel {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -55,6 +55,8 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { error, loading } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
const videoSchema = z.object({ const videoSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -94,6 +96,13 @@ type Option = {
name: string; name: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormVideoUpdate() { export default function FormVideoUpdate() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -110,6 +119,7 @@ export default function FormVideoUpdate() {
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0); const [counterProgress, setCounterProgress] = useState(0);
const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId"); const scheduleId = Cookies.get("scheduleId");
@ -609,13 +619,11 @@ export default function FormVideoUpdate() {
<div className="flex flex-col lg:flex-row gap-10"> <div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12"> <Card className="w-full lg:w-8/12">
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3"> <p className="text-lg font-semibold mb-3">{t("form-video")}</p>
Form Update Konten Video
</p>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -636,8 +644,8 @@ export default function FormVideoUpdate() {
)} )}
</div> </div>
<div className="flex items-center"> <div className="flex items-center">
<div className="py-3 w-full"> <div className="py-3 w-full space-y-2">
<Label>Kategori</Label> <Label>{t("category")}</Label>
<Select <Select
defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail defaultValue={detail?.categoryId.name} // Nilai default berdasarkan detail
onValueChange={(id) => { onValueChange={(id) => {
@ -659,17 +667,15 @@ export default function FormVideoUpdate() {
</div> </div>
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Deskripsi</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={detail?.description}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.description || value}
/> />
)} )}
/> />
@ -679,8 +685,8 @@ export default function FormVideoUpdate() {
</p> </p>
)} )}
</div> </div>
<div className="py-3"> <div className="py-3 space-y-2">
<Label>Pilih File</Label> <Label>{t("select-file")}</Label>
{/* <Input {/* <Input
id="fileInput" id="fileInput"
type="file" type="file"
@ -693,11 +699,10 @@ export default function FormVideoUpdate() {
<CloudUpload className="text-default-300 w-10 h-10" /> <CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80"> <h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */} {/* Drop files here or click to upload. */}
Tarik file disini atau klik untuk upload. {t("drag-file")}
</h4> </h4>
<div className=" text-xs text-muted-foreground"> <div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau .png. {t("upload-file-video-max")}
Ukuran maksimal 100mb.)
</div> </div>
</div> </div>
</div> </div>
@ -706,7 +711,7 @@ export default function FormVideoUpdate() {
<div>{fileList}</div> <div>{fileList}</div>
<div className=" flex justify-between gap-2"> <div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3"> <div className="flex flex-row items-center gap-3 py-3">
<Label>Gunakan Watermark</Label> <Label>{t("watermark")}</Label>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" /> <Switch defaultChecked color="primary" id="c2" />
</div> </div>
@ -721,8 +726,11 @@ export default function FormVideoUpdate() {
</Fragment> </Fragment>
) : null} ) : null}
{files.length > 0 && ( {files.length > 0 && (
<div className="mt-4"> <div className="mt-4 space-y-2">
<Label className="text-lg font-semibold">File</Label> <Label className="text-lg font-semibold">
{" "}
{t("file-media")}
</Label>
<div className="grid gap-4"> <div className="grid gap-4">
{files.map((file: any) => ( {files.map((file: any) => (
<div <div
@ -743,7 +751,7 @@ export default function FormVideoUpdate() {
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-blue-500 text-sm" className="text-blue-500 text-sm"
> >
Lihat File {t("view-file")}
</a> </a>
</div> </div>
<div> <div>
@ -761,7 +769,7 @@ export default function FormVideoUpdate() {
} }
className="form-checkbox" className="form-checkbox"
/> />
<span>Semua</span> <span>{t("all")}</span>
</Label> </Label>
</div> </div>
<div> <div>
@ -833,7 +841,7 @@ export default function FormVideoUpdate() {
<Card className=" h-[800px]"> <Card className=" h-[800px]">
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kreator</Label> <Label>{t("creator")}</Label>
<Controller <Controller
control={control} control={control}
name="creatorName" name="creatorName"
@ -854,8 +862,8 @@ export default function FormVideoUpdate() {
)} )}
</div> </div>
</div> </div>
<div className="mt-3 px-3"> <div className="mt-3 px-3 space-y-2">
<Label>Pratinjau Gambar Utama</Label> <Label>{t("preview")}</Label>
<Card className="mt-2"> <Card className="mt-2">
<img <img
src={detail.thumbnailLink} src={detail.thumbnailLink}
@ -866,7 +874,7 @@ export default function FormVideoUpdate() {
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Tag</Label> <Label>{t("tags")}</Label>
<Input <Input
type="text" type="text"
id="tags" id="tags"
@ -900,8 +908,8 @@ export default function FormVideoUpdate() {
</div> </div>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6 space-y-2">
<Label>Target Publish</Label> <Label>{t("publish-target")}</Label>
{options.map((option: Option) => ( {options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center"> <div key={option.id} className="flex gap-2 items-center">
<Checkbox <Checkbox
@ -921,22 +929,22 @@ export default function FormVideoUpdate() {
</div> </div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm"> <div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon /> <MailIcon />
<p className="">Kotak Saran (0)</p> <p className="">{t("suggestion-box")} (0)</p>
</div> </div>
<div className="px-3 py-3"> <div className="px-3 py-3">
<p>Keterangan:</p> <p>{t("information")}:</p>
{/* <p>{detail?.status}</p> */} {/* <p>{detail?.status}</p> */}
</div> </div>
</Card> </Card>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
Cancel {t("cancel")}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -33,7 +33,7 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { cn, getCookiesDecrypt } from "@/lib/utils"; import { cn, getCookiesDecrypt } from "@/lib/utils";
import { CalendarIcon, ChevronDown, ChevronUp } from "lucide-react"; import { CalendarIcon, ChevronDown, ChevronUp, Trash2 } from "lucide-react";
import { format, parseISO } from "date-fns"; import { format, parseISO } from "date-fns";
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { DateRange } from "react-day-picker"; import { DateRange } from "react-day-picker";
@ -898,10 +898,10 @@ export default function FormContestDetail() {
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4"> <div className="mt-4">
<h2 className="text-lg font-bold">Link Berita</h2> <Label className="">Link Berita</Label>
{links.map((link, index) => ( {links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2"> <div key={index} className="flex items-center gap-2 mt-2">
<input <Input
type="url" type="url"
className="border rounded p-2 w-full" className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${index + 1}`} placeholder={`Masukkan link berita ${index + 1}`}
@ -916,18 +916,19 @@ export default function FormContestDetail() {
className="bg-red-500 text-white px-3 py-1 rounded" className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)} onClick={() => handleRemoveRow(index)}
> >
Hapus <Trash2 className="h-4 w-4" />
</button> </button>
)} )}
</div> </div>
))} ))}
<button <Button
type="button" type="button"
size="md"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded" className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow} onClick={handleAddRow}
> >
Tambah Link Tambah Link
</button> </Button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -37,6 +37,8 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { AlertDialogHeader } from "@/components/ui/alert-dialog"; import { AlertDialogHeader } from "@/components/ui/alert-dialog";
import { Description } from "@radix-ui/react-toast"; import { Description } from "@radix-ui/react-toast";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -61,6 +63,13 @@ export type mediahubDetail = {
is_active: string; is_active: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function PublishMediahub() { export default function PublishMediahub() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -75,6 +84,7 @@ export default function PublishMediahub() {
image: false, image: false,
text: false, text: false,
}); });
const t = useTranslations("Form");
const [mainType, setMainType] = useState<number>(1); const [mainType, setMainType] = useState<number>(1);
const [taskType, setTaskType] = useState<string>("atensi-khusus"); const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
@ -304,13 +314,13 @@ export default function PublishMediahub() {
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p> <p className="text-lg font-semibold mb-3">{t("planning-mediahub")}</p>
{detail !== undefined ? ( {detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2"> <div className="space-y-2">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -328,8 +338,8 @@ export default function PublishMediahub() {
<p className="text-red-400 text-sm">{errors.title.message}</p> <p className="text-red-400 text-sm">{errors.title.message}</p>
)} )}
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Output Tugas</Label> <Label>{t("output-tugas")}</Label>
<div className="flex flex-wrap gap-3 mt-1"> <div className="flex flex-wrap gap-3 mt-1">
{Object.keys(taskOutput).map((key) => ( {Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
@ -347,8 +357,8 @@ export default function PublishMediahub() {
))} ))}
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Pelaksana Tugas</Label> <Label>{t("executive-task")}</Label>
</div> </div>
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div className=""> <div className="">
@ -451,8 +461,8 @@ export default function PublishMediahub() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Jenis Penugasan</Label> <Label>{t("assignment-type")}</Label>
<RadioGroup <RadioGroup
value={detail?.assignmentType?.id.toString()} value={detail?.assignmentType?.id.toString()}
onValueChange={(value) => setType(value)} onValueChange={(value) => setType(value)}
@ -473,8 +483,8 @@ export default function PublishMediahub() {
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6"> <div className="mt-6">
<div className="flex flex-col"> <div className="flex flex-col space-y-2">
<Label>Date</Label> <Label>{t("date")}</Label>
<div> <div>
<Button <Button
defaultValue={detail?.date} defaultValue={detail?.date}
@ -495,17 +505,15 @@ export default function PublishMediahub() {
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Narasi Penugasan</Label> <Label>{t("description-task")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={detail?.description}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.description || value}
/> />
)} )}
/> />
@ -520,7 +528,7 @@ export default function PublishMediahub() {
{/* Submit Button */} {/* Submit Button */}
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -33,6 +33,8 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
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,13 @@ export type medsosDetail = {
is_active: string; is_active: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function PublishMedsos() { export default function PublishMedsos() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -69,6 +78,7 @@ export default function PublishMedsos() {
image: false, image: false,
text: false, text: false,
}); });
const t = useTranslations("Form");
const [mainType, setMainType] = useState<number>(1); const [mainType, setMainType] = useState<number>(1);
const [taskType, setTaskType] = useState<string>("atensi-khusus"); const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
@ -297,13 +307,13 @@ export default function PublishMedsos() {
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Perencanaan Mediahub</p> <p className="text-lg font-semibold mb-3">{t("planning-medsos")}</p>
{detail !== undefined ? ( {detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2"> <div className="space-y-2">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -322,7 +332,7 @@ export default function PublishMedsos() {
)} )}
</div> </div>
<div className="mt-6"> <div className="mt-6">
<Label>Output Tugas</Label> <Label>{t("output-tugas")}</Label>
<div className="flex flex-wrap gap-3 mt-1"> <div className="flex flex-wrap gap-3 mt-1">
{Object.keys(taskOutput).map((key) => ( {Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
@ -340,8 +350,8 @@ export default function PublishMedsos() {
))} ))}
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Pelaksana Tugas</Label> <Label>{t("executive-task")}</Label>
</div> </div>
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<div> <div>
@ -367,7 +377,7 @@ export default function PublishMedsos() {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary"> <Button variant="soft" size="sm" color="primary">
[Kustom] [{t("custom")}]
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
@ -444,8 +454,8 @@ export default function PublishMedsos() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Jenis Penugasan</Label> <Label>{t("assignment-type")}</Label>
<RadioGroup <RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
@ -465,9 +475,9 @@ export default function PublishMedsos() {
</div> </div>
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<div className="flex flex-col"> <div className="flex flex-col">
<Label>Date</Label> <Label>{t("date")}</Label>
<div> <div>
<Button <Button
value={detail.date} value={detail.date}
@ -488,17 +498,15 @@ export default function PublishMedsos() {
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Narasi Penugasan</Label> <Label>{t("description-task")}</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={detail?.description}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.description || value}
/> />
)} )}
/> />
@ -513,7 +521,7 @@ export default function PublishMedsos() {
{/* Submit Button */} {/* Submit Button */}
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -237,7 +237,7 @@ export default function FormEventDetail() {
)} )}
<div className="flex flex-col lg:flex-row mt-6 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mt-6 items-start lg:items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -245,11 +245,11 @@ export default function FormEventDetail() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -276,7 +276,7 @@ export default function FormEventDetail() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div> <div>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -326,7 +326,7 @@ export default function FormEventDetail() {
DI SAMPAIKAN OLEH DI SAMPAIKAN OLEH
</p> </p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -349,7 +349,7 @@ export default function FormEventDetail() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -234,7 +234,7 @@ export default function FormEvent() {
)} )}
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
<div className="flex flex-col "> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -242,11 +242,11 @@ export default function FormEvent() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -273,7 +273,7 @@ export default function FormEvent() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div className=""> <div className="">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -321,7 +321,7 @@ export default function FormEvent() {
</div> </div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p> <p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -344,7 +344,7 @@ export default function FormEvent() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -250,7 +250,7 @@ export default function FormEventUpdate() {
)} )}
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -258,11 +258,11 @@ export default function FormEventUpdate() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -289,7 +289,7 @@ export default function FormEventUpdate() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div> <div>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -337,7 +337,7 @@ export default function FormEventUpdate() {
</div> </div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p> <p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -360,7 +360,7 @@ export default function FormEventUpdate() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -247,7 +247,7 @@ export default function FormDetailPressRillis() {
)} )}
<div className="flex flex-col lg:flex-row mt-6 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mt-6 items-start lg:items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -255,11 +255,11 @@ export default function FormDetailPressRillis() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -286,7 +286,7 @@ export default function FormDetailPressRillis() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div> <div>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -332,7 +332,7 @@ export default function FormDetailPressRillis() {
{errors.location?.message} {errors.location?.message}
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Invitation</Label> <Label>Invitation</Label>
<Select onValueChange={setSelectedTarget}> <Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md"> <SelectTrigger size="md">
@ -353,7 +353,7 @@ export default function FormDetailPressRillis() {
DI SAMPAIKAN OLEH DI SAMPAIKAN OLEH
</p> </p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -376,7 +376,7 @@ export default function FormDetailPressRillis() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -257,7 +257,7 @@ export default function FormUpdatePressRelease() {
)} )}
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -265,11 +265,11 @@ export default function FormUpdatePressRelease() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -296,7 +296,7 @@ export default function FormUpdatePressRelease() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div> <div>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -342,7 +342,7 @@ export default function FormUpdatePressRelease() {
{errors.location?.message} {errors.location?.message}
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Invitation</Label> <Label>Invitation</Label>
<Select onValueChange={setSelectedTarget}> <Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md"> <SelectTrigger size="md">
@ -361,7 +361,7 @@ export default function FormUpdatePressRelease() {
</div> </div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p> <p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -384,7 +384,7 @@ export default function FormUpdatePressRelease() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -234,7 +234,7 @@ export default function FormPressRelease() {
)} )}
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
<div className="flex flex-col "> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -242,11 +242,11 @@ export default function FormPressRelease() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -273,7 +273,7 @@ export default function FormPressRelease() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div className=""> <div className="">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -319,7 +319,7 @@ export default function FormPressRelease() {
{errors.location?.message} {errors.location?.message}
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Invitation</Label> <Label>Invitation</Label>
<Select onValueChange={setSelectedTarget}> <Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md"> <SelectTrigger size="md">
@ -338,7 +338,7 @@ export default function FormPressRelease() {
</div> </div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p> <p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -361,7 +361,7 @@ export default function FormPressRelease() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -194,7 +194,7 @@ export default function FormDetailPressConference() {
)} )}
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mt-6"> <div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mt-6">
<div className="flex flex-col"> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -202,11 +202,11 @@ export default function FormDetailPressConference() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -233,7 +233,7 @@ export default function FormDetailPressConference() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div> <div>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -283,7 +283,7 @@ export default function FormDetailPressConference() {
DI SAMPAIKAN OLEH DI SAMPAIKAN OLEH
</p> </p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -306,7 +306,7 @@ export default function FormDetailPressConference() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -207,20 +207,20 @@ export default function FormPressConference() {
)} )}
<div className="flex flex-col lg:flex-row mb-4 mt-2 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mb-4 mt-2 items-start lg:items-center justify-between">
<div className="flex flex-col "> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild className="px-0">
<Button <Button
size="md" size="md"
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300", "w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300 px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -247,7 +247,7 @@ export default function FormPressConference() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div className=""> <div className="">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -295,7 +295,7 @@ export default function FormPressConference() {
</div> </div>
<p className="text-sm mt-4 font-semibold">DI SAMPAIKAN OLEH</p> <p className="text-sm mt-4 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -318,7 +318,7 @@ export default function FormPressConference() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

View File

@ -254,7 +254,7 @@ export default function FormUpdatePressConference() {
)} )}
<div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between"> <div className="flex flex-col lg:flex-row mt-3 items-start lg:items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
@ -262,11 +262,11 @@ export default function FormUpdatePressConference() {
id="date" id="date"
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[300px] justify-start text-left font-normal", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground" !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon /> <CalendarIcon size={15} className="mr-3" />
{date?.from ? ( {date?.from ? (
date.to ? ( date.to ? (
<> <>
@ -293,7 +293,7 @@ export default function FormUpdatePressConference() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div> <div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label> <Label htmlFor="title">Rentang Waktu</Label>
<div> <div>
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
@ -343,7 +343,7 @@ export default function FormUpdatePressConference() {
</div> </div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p> <p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label> <Label>Nama Pangkat</Label>
<Controller <Controller
control={control} control={control}
@ -366,7 +366,7 @@ export default function FormUpdatePressConference() {
</div> </div>
</div> </div>
<div className="flex flex-col my-3"> <div className="flex flex-col my-3">
<div className="mt-1"> <div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label> <Label>Nama Lengkap</Label>
<Controller <Controller
control={control} control={control}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,944 @@
"use client";
import React, { 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 } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import JoditEditor from "jodit-react";
import {
createTask,
createTaskTa,
getTask,
getUserLevelForAssignments,
} from "@/service/task";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { ChevronDown, ChevronUp, Trash2 } from "lucide-react";
import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client";
import { error } from "@/config/swal";
import { getCsrfToken } from "@/service/auth";
import { loading } from "@/lib/swal";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
// url: z.string().min(1, { message: "Judul diperlukan" }),
});
interface FileWithPreview extends File {
preview: string;
}
export type taskDetail = {
id: number;
title: string;
fileTypeOutput: string;
assignedToTopLevel: string;
assignedToLevel: string;
assignmentType: {
id: number;
name: string;
};
assignmentMainType: {
id: number;
name: string;
};
attachmentUrl: string;
taskType: string;
broadcastType: string;
narration: string;
is_active: string;
};
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormTaskTa() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);
type TaskSchema = z.infer<typeof taskSchema>;
const { id } = useParams() as { id: string };
console.log(id);
// State for various form fields
const [expertise, setExpertiseOutput] = useState({
semua: false,
komunikasi: false,
hukum: false,
bahasa: false,
ekonomi: false,
politik: false,
sosiologi: false,
ilmuadministrasipemerintah: false,
ti: false,
});
const [expert, setExpertOutput] = useState({
semua: false,
});
// const [assignmentType, setAssignmentType] = useState("mediahub");
// const [assignmentCategory, setAssignmentCategory] = useState("publication");
const [mainType, setMainType] = useState<string>("1");
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("");
const [type, setType] = useState<string>("1");
const [selectedTarget, setSelectedTarget] = useState("3,4");
const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false);
const [listDest, setListDest] = useState([]);
const [checkedLevels, setCheckedLevels] = useState(new Set());
const [expandedPolda, setExpandedPolda] = useState([{}]);
const [isLoading, setIsLoading] = useState(false);
const [audioFile, setAudioFile] = useState<File | null>(null);
const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120);
const t = useTranslations("Form");
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const [voiceNoteLink, setVoiceNoteLink] = useState("");
const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({
semua: false,
mabes: false,
polda: false,
polres: false,
satker: false,
});
const [links, setLinks] = useState<string[]>([""]);
const {
register,
control,
setValue,
handleSubmit,
formState: { errors },
} = useForm<TaskSchema>({
resolver: zodResolver(taskSchema),
mode: "all",
});
// const handleRadioChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// const selectedValue = Number(event.target.value);
// setMainType(selectedValue);
// setPlatformTypeVisible(selectedValue === 2);
useEffect(() => {
async function fetchPoldaPolres() {
setIsLoading(true);
try {
const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list);
console.log("polda", response?.data?.data?.list);
const initialExpandedState = response?.data?.data.list.reduce(
(acc: any, polda: any) => {
acc[polda.id] = false;
return acc;
},
{}
);
setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState);
} catch (error) {
console.error("Error fetching Polda/Polres data:", error);
} finally {
setIsLoading(false);
}
}
fetchPoldaPolres();
}, []);
// };
const handleCheckboxChange = (levelId: number) => {
setCheckedLevels((prev) => {
const updatedLevels = new Set(prev);
if (updatedLevels.has(levelId)) {
updatedLevels.delete(levelId);
} else {
updatedLevels.add(levelId);
}
return updatedLevels;
});
};
const handlePoldaPolresChange = () => {
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
};
const handleUnitChange = (
key: keyof typeof unitSelection,
value: boolean
) => {
if (key === "semua") {
const newState = {
semua: value,
mabes: value,
polda: value,
polres: value,
satker: value,
};
setUnitSelection(newState);
} else {
const updatedSelection = {
...unitSelection,
[key]: value,
};
const allChecked = ["mabes", "polda", "polres", "satker"].every(
(k) => updatedSelection[k as keyof typeof unitSelection]
);
updatedSelection.semua = allChecked;
setUnitSelection(updatedSelection);
}
};
const handleExpertiseOutputChange = (
key: keyof typeof expertise,
value: boolean
) => {
if (key === "semua") {
const newState = {
semua: value,
komunikasi: value,
hukum: value,
bahasa: value,
ekonomi: value,
politik: value,
sosiologi: value,
ilmuadministrasipemerintah: value,
ti: value,
};
setExpertiseOutput(newState);
} else {
const updated = {
...expertise,
[key]: value,
};
const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof expertise]
);
updated.semua = allChecked;
setExpertiseOutput(updated);
}
};
const handleExpertOutputChange = (
key: keyof typeof expert,
value: boolean
) => {
if (key === "semua") {
const newState = {
semua: value,
};
setExpertOutput(newState);
} else {
const updated = {
...expert,
[key]: value,
};
const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof expert]
);
updated.semua = allChecked;
setExpertOutput(updated);
}
};
const save = async (data: TaskSchema) => {
const fileTypeMapping = {
all: "1",
video: "2",
audio: "4",
image: "3",
text: "5",
};
const unitMapping = {
allUnit: "0",
mabes: "1",
polda: "2",
polres: "3",
satker: "4",
};
const assignmentPurposeString = Object.keys(unitSelection)
.filter((key) => unitSelection[key as keyof typeof unitSelection])
.map((key) => unitMapping[key as keyof typeof unitMapping])
.join(",");
const selectedOutputs = Object.keys(expertise)
.filter((key) => expertise[key as keyof typeof expertise])
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping])
.join(",");
const requestData: {
id?: number;
title: string;
assignedToLevel: any;
assignedToUsers: any;
assignmentTypeId: string;
fileTypeOutput: string;
narration: string;
platformType: string | null;
assignmentMainTypeId: any;
assignmentType: string;
assignedToRole: string;
broadcastType: string;
expertCompetencies: string;
attachmentUrl: string[];
} = {
...data,
// assignmentType,
// assignmentCategory,
assignedToLevel: handlePoldaPolresChange(),
assignedToUsers: assignmentPurposeString,
assignedToRole: selectedTarget,
assignmentType: taskType,
broadcastType: broadcastType,
assignmentMainTypeId: mainType,
assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
narration: data.naration,
platformType: "",
expertCompetencies: "1,2,3",
title: data.title,
attachmentUrl: links,
};
const response = await createTaskTa(requestData);
console.log("Form Data Submitted:", requestData);
console.log("response", response);
const id = response?.data?.data.id;
loading();
if (imageFiles?.length == 0) {
setIsImageUploadFinish(true);
}
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0");
});
if (videoFiles?.length == 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0");
});
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0");
});
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
}
audioFiles.map(async (item: FileWithPreview, index: number) => {
await uploadResumableFile(
index,
String(id),
item, // Use .file to access the actual File object
"4",
"0" // Optional: Replace with actual duration if available
);
});
};
const onSubmit = (data: TaskSchema) => {
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 toggleExpand = (poldaId: any) => {
setExpandedPolda((prev: any) => ({
...prev,
[poldaId]: !prev[poldaId],
}));
};
const onRecordingStart = () => {
setIsRecording(true);
const countdown = setInterval(() => {
setTimer((prevTimer) => {
if (prevTimer <= 1) {
clearInterval(countdown);
return 0;
}
return prevTimer - 1;
});
}, 1000);
setTimeout(() => {
if (isRecording) {
handleStopRecording();
}
}, 120000);
};
const handleStopRecording = () => {
setIsRecording(false);
setTimer(120); // Reset the timer to 2 minutes for the next recording
};
const addAudioElement = (blob: Blob) => {
const url = URL.createObjectURL(blob);
const audio = document.createElement("audio");
audio.src = url;
audio.controls = true;
document.body.appendChild(audio);
// Convert Blob to File and add preview
const fileWithPreview: FileWithPreview = Object.assign(
new File([blob], "voiceNote.webm", { type: "audio/webm" }),
{ preview: url }
);
// Add to state
setAudioFile(fileWithPreview);
setAudioFiles((prev) => [...prev, fileWithPreview]);
};
const handleDeleteAudio = (index: number) => {
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
};
async function uploadResumableFile(
idx: number,
id: string,
file: any,
fileTypeId: string,
duration: string
) {
console.log(idx, id, file, fileTypeId, duration);
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}/assignment/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
assignmentId: id,
filename: file.name,
contentType: file.type,
fileTypeId: fileTypeId,
duration,
},
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();
if (fileTypeId == "1") {
setIsImageUploadFinish(true);
} else if (fileTypeId == "2") {
setIsVideoUploadFinish(true);
}
if (fileTypeId == "3") {
setIsTextUploadFinish(true);
}
if (fileTypeId == "4") {
setIsAudioUploadFinish(true);
}
},
});
upload.start();
}
useEffect(() => {
successTodo();
}, [
isImageUploadFinish,
isVideoUploadFinish,
isAudioUploadFinish,
isTextUploadFinish,
]);
function successTodo() {
if (
isImageUploadFinish &&
isVideoUploadFinish &&
isAudioUploadFinish &&
isTextUploadFinish
) {
successSubmit("/in/contributor/task");
}
}
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
const handleLinkChange = (index: number, value: string) => {
const updatedLinks = [...links];
updatedLinks[index] = value;
setLinks(updatedLinks);
};
const handleAddRow = () => {
setLinks([...links, ""]);
};
// Remove a specific link row
const handleRemoveRow = (index: number) => {
const updatedLinks = links.filter((_: any, i: any) => i !== index);
setLinks(updatedLinks);
};
return (
<Card>
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">{t("form-task")}</p>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={detail?.title}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">{errors.title.message}</p>
)}
</div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-5 space-y-2">
<Label>{t("assignment-selection")}</Label>
<Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md">
<SelectValue placeholder="Choose" />
</SelectTrigger>
<SelectContent>
<SelectItem value="3,4">Semua Pengguna</SelectItem>
<SelectItem value="4">Kontributor</SelectItem>
<SelectItem value="3">Approver</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap gap-3 mt-5 lg:pt-7 lg:ml-3 ">
{Object.keys(unitSelection).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={unitSelection[key as keyof typeof unitSelection]}
onCheckedChange={(value) =>
handleUnitChange(
key as keyof typeof unitSelection,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
<div className="mt-6 lg:pt-6 lg:pl-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[{t("custom")}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>Daftar Wilayah Polda dan Polres</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listDest.map((polda: any) => (
<div key={polda.id} className="border p-2">
<Label className="flex items-center">
<Checkbox
checked={checkedLevels.has(polda.id)}
onCheckedChange={() =>
handleCheckboxChange(polda.id)
}
className="mr-3"
/>
{polda.name}
<button
onClick={() => toggleExpand(polda.id)}
className="ml-2 focus:outline-none"
>
{expandedPolda[polda.id] ? (
<ChevronUp size={16} />
) : (
<ChevronDown size={16} />
)}
</button>
</Label>
{expandedPolda[polda.id] && (
<div className="ml-6 mt-2">
<Label className="block">
<Checkbox
checked={polda?.subDestination?.every(
(polres: any) =>
checkedLevels.has(polres.id)
)}
onCheckedChange={(isChecked) => {
const updatedLevels = new Set(
checkedLevels
);
polda?.subDestination?.forEach(
(polres: any) => {
if (isChecked) {
updatedLevels.add(polres.id);
} else {
updatedLevels.delete(polres.id);
}
}
);
setCheckedLevels(updatedLevels);
}}
className="mr-2"
/>
Pilih Semua Polres
</Label>
{polda?.subDestination?.map((polres: any) => (
<Label key={polres.id} className="block mt-1">
<Checkbox
checked={checkedLevels.has(polres.id)}
onCheckedChange={() =>
handleCheckboxChange(polres.id)
}
className="mr-2"
/>
{polres.name}
</Label>
))}
</div>
)}
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
</div>
<div className="mt-5 space-y-2">
<Label>{t("assigment-type")} </Label>
<RadioGroup
value={taskType}
onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3"
>
<RadioGroupItem value="atensi-khusus" id="khusus" />
<Label htmlFor="atensi-khusus">Atensi Khusus</Label>
<RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor="tugas-harian">Tugas Harian</Label>
</RadioGroup>
</div>
<div className="mt-5 space-y-2">
<Label>{t("areas-expertise")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expertise).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={expertise[key as keyof typeof expertise]}
onCheckedChange={(value) =>
handleExpertiseOutputChange(
key as keyof typeof expertise,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
<div className="mt-5 space-y-2">
<Label>{t("choose-expert")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(expert).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={expert[key as keyof typeof expert]}
onCheckedChange={(value) =>
handleExpertOutputChange(
key as keyof typeof expert,
value as boolean
)
}
/>
<Label htmlFor={key}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</Label>
</div>
))}
</div>
</div>
{/* <div className="mt-5">
<Label>Broadcast </Label>
<RadioGroup
value={broadcastType} // Nilai terpilih diambil dari state broadcastType
onValueChange={(value) => setBroadcastType(value)} // Mengatur nilai saat radio berubah
className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="all" id="all" />
<Label htmlFor="all">Semua</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="email" id="email" />
<Label htmlFor="email">Email Blast</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="whatsapp" id="whatsapp" />
<Label htmlFor="whatsapp">WhatsApp Blast</Label>
</div>
</RadioGroup>
</div> */}
<div className="mt-5 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<CustomEditor onChange={onChange} initialData={value} />
)}
/>
{errors.naration?.message && (
<p className="text-red-400 text-sm">
{errors.naration.message}
</p>
)}
</div>
<div className="space-y-2.5 mt-5">
<Label htmlFor="attachments">{t("attachment")}</Label>
<div className="space-y-3">
<div>
<Label>{t("audio-visual")}</Label>
<FileUploader
accept={{
"mp4/*": [],
"mov/*": [],
}}
maxSize={100}
label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setVideoFiles(files)}
/>
</div>
<div className="space-y-2">
<Label>{t("image")}</Label>
<FileUploader
accept={{
"image/*": [],
}}
maxSize={100}
label="Upload file dengan format .png, .jpg, atau .jpeg."
onDrop={(files) => setImageFiles(files)}
/>
</div>
<div className="space-y-2">
<Label>{t("text")}</Label>
<FileUploader
accept={{
"pdf/*": [],
}}
maxSize={100}
label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)}
/>
</div>
<div className="space-y-2">
<Label>{t("audio")}</Label>
<AudioRecorder
onRecordingComplete={addAudioElement}
audioTrackConstraints={{
noiseSuppression: true,
echoCancellation: true,
}}
downloadOnSavePress={true}
downloadFileExtension="webm"
/>
<FileUploader
accept={{
"mp3/*": [],
"wav/*": [],
}}
maxSize={100}
label="Upload file dengan format .mp3 atau .wav."
onDrop={(files) =>
setAudioFiles((prev) => [...prev, ...files])
}
className="mt-2"
/>
</div>
{audioFiles?.map((audio: any, idx: any) => (
<div
key={idx}
className="flex flex-row justify-between items-center"
>
<p>{t("voice-note")}</p>
<Button
type="button"
onClick={() => handleDeleteAudio(idx)}
size="sm"
color="destructive"
>
X
</Button>
</div>
))}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */}
<div className="mt-4 space-y-2">
<Label className="">{t("news-links")}</Label>
{links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2">
<Input
type="url"
className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${index + 1}`}
value={link}
onChange={(e) =>
handleLinkChange(index, e.target.value)
}
/>
{links.length > 1 && (
<button
type="button"
className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)}
>
<Trash2 className="h-4 w-4" />
</button>
)}
</div>
))}
<Button
type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
size="sm"
>
{t("add-links")}
</Button>
</div>
</div>
</div>
</div>
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
{t("submit")}
</Button>
</div>
</form>
</div>
</Card>
);
}

View File

@ -66,6 +66,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import WavesurferPlayer from "@wavesurfer/react"; import WavesurferPlayer from "@wavesurfer/react";
import WaveSurfer from "wavesurfer.js"; import WaveSurfer from "wavesurfer.js";
import { InputGroup, InputGroupText } from "@/components/ui/input-group"; import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { useTranslations } from "next-intl";
const taskSchema = z.object({ const taskSchema = z.object({
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -206,6 +207,7 @@ export default function FormTaskDetail() {
text: false, text: false,
}); });
const t = useTranslations("Form");
const [uploadResults, setUploadResults] = useState<UploadResult[]>([]); const [uploadResults, setUploadResults] = useState<UploadResult[]>([]);
const [isTableResult, setIsTableResult] = useState(false); const [isTableResult, setIsTableResult] = useState(false);
const [isSentResult] = useState(false); const [isSentResult] = useState(false);
@ -812,7 +814,7 @@ export default function FormTaskDetail() {
{detail !== undefined ? ( {detail !== undefined ? (
<div className="px-6 py-6"> <div className="px-6 py-6">
<div className="flex flex-col sm:flex-row lg:flex-row justify-between"> <div className="flex flex-col sm:flex-row lg:flex-row justify-between">
<p className="text-lg font-semibold mb-3">Detail Penugasan</p> <p className="text-lg font-semibold mb-3">{t("detail-task")}</p>
<div <div
className="flex gap-3" className="flex gap-3"
style={ style={
@ -830,7 +832,7 @@ export default function FormTaskDetail() {
color="primary" color="primary"
onClick={() => setModalType("terkirim")} onClick={() => setModalType("terkirim")}
> >
{sentAcceptance?.length} Terkirim {sentAcceptance?.length} {t("sent")}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
@ -840,13 +842,15 @@ export default function FormTaskDetail() {
onClick={() => setModalType("diterima")} onClick={() => setModalType("diterima")}
className="ml-3" className="ml-3"
> >
{acceptAcceptance?.length} Diterima {acceptAcceptance?.length} {t("accepted")}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
<DialogHeader> <DialogHeader>
<DialogTitle>Detail Status Penugasan</DialogTitle> <DialogTitle>
{t("assignment-status-details")}
</DialogTitle>
</DialogHeader> </DialogHeader>
{modalType === "terkirim" && getModalContent("terkirim")} {modalType === "terkirim" && getModalContent("terkirim")}
@ -860,7 +864,7 @@ export default function FormTaskDetail() {
<form> <form>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
<div className="space-y-2"> <div className="space-y-2">
<Label>Kode Unik</Label> <Label>{t("unique-code")}</Label>
<Controller <Controller
control={control} control={control}
name="uniqueCode" name="uniqueCode"
@ -877,7 +881,7 @@ export default function FormTaskDetail() {
/> />
</div> </div>
<div className="space-y-2 mt-6"> <div className="space-y-2 mt-6">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -896,8 +900,8 @@ export default function FormTaskDetail() {
)} )}
</div> </div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center"> <div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Tujuan Pemilihan Tugas</Label> <Label>{t("assignment-selection")}</Label>
<Select <Select
onValueChange={setSelectedTarget} onValueChange={setSelectedTarget}
value={detail.assignedToRole} value={detail.assignedToRole}
@ -935,7 +939,7 @@ export default function FormTaskDetail() {
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary"> <Button variant="soft" size="sm" color="primary">
[Kustom] [{t("custom")}]
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
@ -1018,8 +1022,8 @@ export default function FormTaskDetail() {
</Dialog> </Dialog>
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Tipe Penugasan</Label> <Label>{t("type-task")}</Label>
<RadioGroup <RadioGroup
value={detail.assignmentMainType.id.toString()} value={detail.assignmentMainType.id.toString()}
onValueChange={(value) => setMainType(value)} onValueChange={(value) => setMainType(value)}
@ -1033,8 +1037,8 @@ export default function FormTaskDetail() {
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label> <Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Jenis Tugas </Label> <Label>{t("assigment-type")} </Label>
<RadioGroup <RadioGroup
value={detail.taskType.toString()} value={detail.taskType.toString()}
onValueChange={(value) => setTaskType(String(value))} onValueChange={(value) => setTaskType(String(value))}
@ -1047,8 +1051,8 @@ export default function FormTaskDetail() {
</RadioGroup> </RadioGroup>
</div> </div>
{/* RadioGroup Assignment Category */} {/* RadioGroup Assignment Category */}
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Jenis Penugasan</Label> <Label>{t("type-of-task")}</Label>
<RadioGroup <RadioGroup
value={detail.assignmentType.id.toString()} value={detail.assignmentType.id.toString()}
onValueChange={(value) => setType(value)} onValueChange={(value) => setType(value)}
@ -1068,8 +1072,8 @@ export default function FormTaskDetail() {
</div> </div>
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Output Tugas</Label> <Label>{t("output-task")}</Label>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{Object.keys(taskOutput).map((key) => ( {Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
@ -1089,8 +1093,8 @@ export default function FormTaskDetail() {
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-6 space-y-2">
<Label>Narasi Penugasan</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="naration" name="naration"
@ -1104,11 +1108,13 @@ export default function FormTaskDetail() {
</p> </p>
)} */} )} */}
</div> </div>
<div className="space-y-1.5 mt-5"> <div className=" mt-5 space-y-2">
<Label htmlFor="attachment">Lampiran</Label> <Label htmlFor="attachment">{t("attachment")}</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
{videoUploadedFiles?.length > 0 && <Label>Video</Label>} {videoUploadedFiles?.length > 0 && (
<Label>{t("audio-visual")}</Label>
)}
<div> <div>
{selectedVideo && ( {selectedVideo && (
<Card className="mt-2"> <Card className="mt-2">
@ -1154,7 +1160,9 @@ export default function FormTaskDetail() {
</div> </div>
</div> </div>
<div> <div>
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>} {imageUploadedFiles?.length > 0 && (
<Label>{t("image")}</Label>
)}
<div> <div>
{selectedImage && ( {selectedImage && (
<Card className="mt-2"> <Card className="mt-2">
@ -1200,7 +1208,9 @@ export default function FormTaskDetail() {
</div> </div>
</div> </div>
<div> <div>
{textUploadedFiles?.length > 0 && <Label>Teks</Label>} {textUploadedFiles?.length > 0 && (
<Label>{t("text")}</Label>
)}
<div> <div>
{selectedText && ( {selectedText && (
<Card className="mt-2"> <Card className="mt-2">
@ -1247,7 +1257,9 @@ export default function FormTaskDetail() {
</div> </div>
</div> </div>
<div> <div>
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>} {audioUploadedFiles?.length > 0 && (
<Label>{t("audio")}</Label>
)}
<div> <div>
{selectedAudio && ( {selectedAudio && (
<Card className="mt-2"> <Card className="mt-2">

View File

@ -48,6 +48,8 @@ import WavesurferPlayer from "@wavesurfer/react";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { error, loading } from "@/lib/swal"; import { error, loading } from "@/lib/swal";
import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
const taskSchema = z.object({ const taskSchema = z.object({
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), // uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -93,6 +95,13 @@ type Url = {
attachmentUrl: string; attachmentUrl: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormTaskEdit() { export default function FormTaskEdit() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -110,6 +119,7 @@ export default function FormTaskEdit() {
text: false, text: false,
}); });
const t = useTranslations("Form");
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]); const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]); const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]); const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
@ -294,6 +304,63 @@ export default function FormTaskEdit() {
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
}; };
const handleUnitChange = (
key: keyof typeof unitSelection,
value: boolean
) => {
if (key === "allUnit") {
const newState = {
allUnit: value,
mabes: value,
polda: value,
polres: value,
satker: value,
};
setUnitSelection(newState);
} else {
const updatedSelection = {
...unitSelection,
[key]: value,
};
const allChecked = ["mabes", "polda", "polres", "satker"].every(
(k) => updatedSelection[k as keyof typeof unitSelection]
);
updatedSelection.allUnit = allChecked;
setUnitSelection(updatedSelection);
}
};
const handleTaskOutputChange = (
key: keyof typeof taskOutput,
value: boolean
) => {
if (key === "all") {
const newState = {
all: value,
video: value,
audio: value,
image: value,
text: value,
};
setTaskOutput(newState);
} else {
const updated = {
...taskOutput,
[key]: value,
};
const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof taskOutput]
);
updated.all = allChecked;
setTaskOutput(updated);
}
};
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
const fileTypeMapping = { const fileTypeMapping = {
all: "1", all: "1",
@ -624,7 +691,7 @@ export default function FormTaskEdit() {
{detail !== undefined ? ( {detail !== undefined ? (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
<div className="space-y-2 mt-6"> <div className="space-y-2">
<Label>Judul</Label> <Label>Judul</Label>
<Controller <Controller
control={control} control={control}
@ -644,7 +711,7 @@ export default function FormTaskEdit() {
)} )}
</div> </div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center"> <div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-6"> <div className="mt-5 space-y-2">
<Label>Tujuan Pemilihan Tugas</Label> <Label>Tujuan Pemilihan Tugas</Label>
<Select <Select
onValueChange={setSelectedTarget} onValueChange={setSelectedTarget}
@ -660,7 +727,7 @@ export default function FormTaskEdit() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="flex flex-wrap gap-3 mt-6 lg:pt-5 lg:ml-3"> <div className="flex flex-wrap gap-3 mt-6 lg:pt-7 lg:ml-3">
{Object.keys(unitSelection).map((key) => ( {Object.keys(unitSelection).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
<Checkbox <Checkbox
@ -669,7 +736,10 @@ export default function FormTaskEdit() {
unitSelection[key as keyof typeof unitSelection] unitSelection[key as keyof typeof unitSelection]
} }
onCheckedChange={(value) => onCheckedChange={(value) =>
setUnitSelection({ ...unitSelection, [key]: value }) handleUnitChange(
key as keyof typeof unitSelection,
value as boolean
)
} }
/> />
<Label htmlFor={key}> <Label htmlFor={key}>
@ -678,7 +748,7 @@ export default function FormTaskEdit() {
</div> </div>
))} ))}
</div> </div>
<div className="mt-6 lg:pt-5 lg:pl-3"> <div className="mt-6 lg:pt-6 lg:pl-3">
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary"> <Button variant="soft" size="sm" color="primary">
@ -762,8 +832,8 @@ export default function FormTaskEdit() {
</Dialog> </Dialog>
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-5 space-y-2">
<Label>Tipe Penugasan</Label> <Label>{t("type-task")}</Label>
<RadioGroup <RadioGroup
defaultValue={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup defaultValue={detail.assignmentMainType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setMainType(value)} onValueChange={(value) => setMainType(value)}
@ -777,8 +847,8 @@ export default function FormTaskEdit() {
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label> <Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6"> <div className="mt-5 space-y-2">
<Label>Jenis Tugas </Label> <Label>{t("assigment-type")} </Label>
<RadioGroup <RadioGroup
defaultValue={detail.taskType.toString()} defaultValue={detail.taskType.toString()}
onValueChange={(value) => setTaskType(String(value))} onValueChange={(value) => setTaskType(String(value))}
@ -791,8 +861,8 @@ export default function FormTaskEdit() {
</RadioGroup> </RadioGroup>
</div> </div>
{/* RadioGroup Assignment Category */} {/* RadioGroup Assignment Category */}
<div className="mt-6"> <div className="mt-5 space-y-2">
<Label>Jenis Penugasan</Label> <Label>{t("type-of-task")}</Label>
<RadioGroup <RadioGroup
defaultValue={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup defaultValue={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
@ -812,16 +882,19 @@ export default function FormTaskEdit() {
</div> </div>
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6"> <div className="mt-5 space-y-2">
<Label>Output Tugas</Label> <Label>{t("output-task")}</Label>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-4">
{Object.keys(taskOutput).map((key) => ( {Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
<Checkbox <Checkbox
id={key} id={key}
checked={taskOutput[key as keyof typeof taskOutput]} checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) => onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value }) handleTaskOutputChange(
key as keyof typeof taskOutput,
value as boolean
)
} }
/> />
<Label htmlFor={key}> <Label htmlFor={key}>
@ -831,17 +904,15 @@ export default function FormTaskEdit() {
))} ))}
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-5 space-y-2">
<Label>Narasi Penugasan</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="naration" name="naration"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor
ref={editor}
value={detail?.narration}
onChange={onChange} onChange={onChange}
className="dark:text-black" initialData={detail?.narration || value}
/> />
)} )}
/> />
@ -851,11 +922,11 @@ export default function FormTaskEdit() {
</p> </p>
)} )}
</div> </div>
<div className="space-y-1.5 mt-5"> <div className="space-y-2.5 mt-5">
<Label htmlFor="attachments">Lampiran</Label> <Label htmlFor="attachments">{t("attachment")}</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div className="space-y-2">
<Label>Video</Label> <Label>{t("audio-visual")}</Label>
<FileUploader <FileUploader
accept={{ accept={{
"mp4/*": [], "mp4/*": [],
@ -895,8 +966,8 @@ export default function FormTaskEdit() {
</div> </div>
))} ))}
</div> </div>
<div> <div className="space-y-2">
<Label>Foto</Label> <Label>{t("image")}</Label>
<FileUploader <FileUploader
accept={{ accept={{
"image/*": [], "image/*": [],
@ -935,8 +1006,8 @@ export default function FormTaskEdit() {
</div> </div>
))} ))}
</div> </div>
<div> <div className="space-y-2">
<Label>Teks</Label> <Label>{t("text")}</Label>
<FileUploader <FileUploader
accept={{ accept={{
"pdf/*": [], "pdf/*": [],
@ -975,8 +1046,8 @@ export default function FormTaskEdit() {
</div> </div>
))} ))}
</div> </div>
<div> <div className="space-y-2">
<Label>Audio</Label> <Label>{t("audio")}</Label>
<AudioRecorder <AudioRecorder
onRecordingComplete={addAudioElement} onRecordingComplete={addAudioElement}
audioTrackConstraints={{ audioTrackConstraints={{
@ -1030,7 +1101,7 @@ export default function FormTaskEdit() {
</div> </div>
{audioFile && ( {audioFile && (
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
<p>Voice Note</p> <p>{t("voice-note")}</p>
<Button <Button
type="button" type="button"
onClick={handleDeleteAudio} onClick={handleDeleteAudio}
@ -1043,8 +1114,8 @@ export default function FormTaskEdit() {
)} )}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4"> <div className="mt-4 space-y-2">
<h2 className="text-lg font-bold">Link Berita</h2> <h2 className="text-lg font-bold">{t("news-links")}</h2>
{urlInputs.map((url: any, index: any) => ( {urlInputs.map((url: any, index: any) => (
<div <div
key={url.id} key={url.id}
@ -1066,7 +1137,7 @@ export default function FormTaskEdit() {
className="mt-4 bg-green-500 text-white px-4 py-2 rounded" className="mt-4 bg-green-500 text-white px-4 py-2 rounded"
onClick={handleAddLink} onClick={handleAddLink}
> >
Tambah Link {t("add-links")}
</button> </button>
</div> </div>
</div> </div>

View File

@ -32,13 +32,15 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { ChevronDown, ChevronUp } from "lucide-react"; import { ChevronDown, ChevronUp, Trash2 } from "lucide-react";
import { AudioRecorder } from "react-audio-voice-recorder"; import { AudioRecorder } from "react-audio-voice-recorder";
import FileUploader from "@/components/form/shared/file-uploader"; import FileUploader from "@/components/form/shared/file-uploader";
import { Upload } from "tus-js-client"; import { Upload } from "tus-js-client";
import { error } from "@/config/swal"; import { error } from "@/config/swal";
import { getCsrfToken } from "@/service/auth"; import { getCsrfToken } from "@/service/auth";
import { loading } from "@/lib/swal"; import { loading } from "@/lib/swal";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
const taskSchema = z.object({ const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }), title: z.string().min(1, { message: "Judul diperlukan" }),
@ -73,6 +75,13 @@ export type taskDetail = {
is_active: string; is_active: string;
}; };
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormTask() { export default function FormTask() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
@ -107,6 +116,7 @@ export default function FormTask() {
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
const t = useTranslations("Form");
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]); const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]); const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]); const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
@ -186,6 +196,63 @@ export default function FormTask() {
return Array.from(checkedLevels).join(","); // Mengonversi Set ke string return Array.from(checkedLevels).join(","); // Mengonversi Set ke string
}; };
const handleUnitChange = (
key: keyof typeof unitSelection,
value: boolean
) => {
if (key === "allUnit") {
const newState = {
allUnit: value,
mabes: value,
polda: value,
polres: value,
satker: value,
};
setUnitSelection(newState);
} else {
const updatedSelection = {
...unitSelection,
[key]: value,
};
const allChecked = ["mabes", "polda", "polres", "satker"].every(
(k) => updatedSelection[k as keyof typeof unitSelection]
);
updatedSelection.allUnit = allChecked;
setUnitSelection(updatedSelection);
}
};
const handleTaskOutputChange = (
key: keyof typeof taskOutput,
value: boolean
) => {
if (key === "all") {
const newState = {
all: value,
video: value,
audio: value,
image: value,
text: value,
};
setTaskOutput(newState);
} else {
const updated = {
...taskOutput,
[key]: value,
};
const allChecked = ["video", "audio", "image", "text"].every(
(k) => updated[k as keyof typeof taskOutput]
);
updated.all = allChecked;
setTaskOutput(updated);
}
};
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
const fileTypeMapping = { const fileTypeMapping = {
all: "1", all: "1",
@ -478,13 +545,13 @@ export default function FormTask() {
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">Form Penugasan</p> <p className="text-lg font-semibold mb-3">{t("form-task")}</p>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
{/* Input Title */} {/* Input Title */}
<div className="space-y-2"> <div className="space-y-2">
<Label>Judul</Label> <Label>{t("title")}</Label>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -503,11 +570,11 @@ export default function FormTask() {
)} )}
</div> </div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center"> <div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Tujuan Pemilihan Tugas</Label> <Label>{t("assignment-selection")}</Label>
<Select onValueChange={setSelectedTarget}> <Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Pilih" /> <SelectValue placeholder="Choose" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="3,4">Semua Pengguna</SelectItem> <SelectItem value="3,4">Semua Pengguna</SelectItem>
@ -516,14 +583,17 @@ export default function FormTask() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="flex flex-wrap gap-3 mt-5 lg:pt-5 lg:ml-3"> <div className="flex flex-wrap gap-3 mt-5 lg:pt-7 lg:ml-3 ">
{Object.keys(unitSelection).map((key) => ( {Object.keys(unitSelection).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
<Checkbox <Checkbox
id={key} id={key}
checked={unitSelection[key as keyof typeof unitSelection]} checked={unitSelection[key as keyof typeof unitSelection]}
onCheckedChange={(value) => onCheckedChange={(value) =>
setUnitSelection({ ...unitSelection, [key]: value }) handleUnitChange(
key as keyof typeof unitSelection,
value as boolean
)
} }
/> />
<Label htmlFor={key}> <Label htmlFor={key}>
@ -532,11 +602,11 @@ export default function FormTask() {
</div> </div>
))} ))}
</div> </div>
<div className="mt-6 lg:pt-5 lg:pl-3"> <div className="mt-6 lg:pt-6 lg:pl-3">
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary"> <Button variant="soft" size="sm" color="primary">
[Kustom] [{t("custom")}]
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
@ -614,10 +684,10 @@ export default function FormTask() {
</Dialog> </Dialog>
</div> </div>
</div> </div>
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Tipe Penugasan</Label> <Label>{t("type-task")}</Label>
<RadioGroup <RadioGroup
value={mainType} // State yang dipetakan ke value RadioGroup value={mainType}
onValueChange={(value) => setMainType(value)} onValueChange={(value) => setMainType(value)}
// value={String(mainType)} // value={String(mainType)}
// onValueChange={(value) => setMainType(Number(value))} // onValueChange={(value) => setMainType(Number(value))}
@ -629,8 +699,8 @@ export default function FormTask() {
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label> <Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Jenis Tugas </Label> <Label>{t("assigment-type")} </Label>
<RadioGroup <RadioGroup
value={taskType} value={taskType}
onValueChange={(value) => setTaskType(String(value))} onValueChange={(value) => setTaskType(String(value))}
@ -643,8 +713,8 @@ export default function FormTask() {
</RadioGroup> </RadioGroup>
</div> </div>
{/* RadioGroup Assignment Category */} {/* RadioGroup Assignment Category */}
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Jenis Penugasan</Label> <Label>{t("type-of-task")}</Label>
<RadioGroup <RadioGroup
value={type} // State yang dipetakan ke value RadioGroup value={type} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
@ -664,16 +734,19 @@ export default function FormTask() {
</div> </div>
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Output Tugas</Label> <Label>{t("output-task")}</Label>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-4">
{Object.keys(taskOutput).map((key) => ( {Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
<Checkbox <Checkbox
id={key} id={key}
checked={taskOutput[key as keyof typeof taskOutput]} checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) => onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value }) handleTaskOutputChange(
key as keyof typeof taskOutput,
value as boolean
)
} }
/> />
<Label htmlFor={key}> <Label htmlFor={key}>
@ -704,18 +777,13 @@ export default function FormTask() {
</div> </div>
</RadioGroup> </RadioGroup>
</div> */} </div> */}
<div className="mt-5"> <div className="mt-5 space-y-2">
<Label>Narasi Penugasan</Label> <Label>{t("description")}</Label>
<Controller <Controller
control={control} control={control}
name="naration" name="naration"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<JoditEditor <CustomEditor onChange={onChange} initialData={value} />
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
)} )}
/> />
{errors.naration?.message && ( {errors.naration?.message && (
@ -724,11 +792,11 @@ export default function FormTask() {
</p> </p>
)} )}
</div> </div>
<div className="space-y-1.5 mt-5"> <div className="space-y-2.5 mt-5">
<Label htmlFor="attachments">Lampiran</Label> <Label htmlFor="attachments">{t("attachment")}</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label>Video</Label> <Label>{t("audio-visual")}</Label>
<FileUploader <FileUploader
accept={{ accept={{
"mp4/*": [], "mp4/*": [],
@ -739,8 +807,8 @@ export default function FormTask() {
onDrop={(files) => setVideoFiles(files)} onDrop={(files) => setVideoFiles(files)}
/> />
</div> </div>
<div> <div className="space-y-2">
<Label>Foto</Label> <Label>{t("image")}</Label>
<FileUploader <FileUploader
accept={{ accept={{
"image/*": [], "image/*": [],
@ -750,8 +818,8 @@ export default function FormTask() {
onDrop={(files) => setImageFiles(files)} onDrop={(files) => setImageFiles(files)}
/> />
</div> </div>
<div> <div className="space-y-2">
<Label>Teks</Label> <Label>{t("text")}</Label>
<FileUploader <FileUploader
accept={{ accept={{
"pdf/*": [], "pdf/*": [],
@ -761,8 +829,8 @@ export default function FormTask() {
onDrop={(files) => setTextFiles(files)} onDrop={(files) => setTextFiles(files)}
/> />
</div> </div>
<div> <div className="space-y-2">
<Label>Audio</Label> <Label>{t("audio")}</Label>
<AudioRecorder <AudioRecorder
onRecordingComplete={addAudioElement} onRecordingComplete={addAudioElement}
audioTrackConstraints={{ audioTrackConstraints={{
@ -790,7 +858,7 @@ export default function FormTask() {
key={idx} key={idx}
className="flex flex-row justify-between items-center" className="flex flex-row justify-between items-center"
> >
<p>Voice Note</p> <p>{t("voice-note")}</p>
<Button <Button
type="button" type="button"
onClick={() => handleDeleteAudio(idx)} onClick={() => handleDeleteAudio(idx)}
@ -803,11 +871,11 @@ export default function FormTask() {
))} ))}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4"> <div className="mt-4 space-y-2">
<h2 className="text-lg font-bold">Link Berita</h2> <Label className="">{t("news-links")}</Label>
{links.map((link, index) => ( {links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2"> <div key={index} className="flex items-center gap-2 mt-2">
<input <Input
type="url" type="url"
className="border rounded p-2 w-full" className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${index + 1}`} placeholder={`Masukkan link berita ${index + 1}`}
@ -822,18 +890,19 @@ export default function FormTask() {
className="bg-red-500 text-white px-3 py-1 rounded" className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)} onClick={() => handleRemoveRow(index)}
> >
Hapus <Trash2 className="h-4 w-4" />
</button> </button>
)} )}
</div> </div>
))} ))}
<button <Button
type="button" type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded" className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow} onClick={handleAddRow}
size="sm"
> >
Tambah Link {t("add-links")}
</button> </Button>
</div> </div>
</div> </div>
</div> </div>
@ -842,7 +911,7 @@ export default function FormTask() {
{/* Submit Button */} {/* Submit Button */}
<div className="mt-4"> <div className="mt-4">
<Button type="submit" color="primary"> <Button type="submit" color="primary">
Submit {t("submit")}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -1,4 +1,7 @@
import { getCategoryData, getPublicCategoryData } from "@/service/landing/landing"; import {
getCategoryData,
getPublicCategoryData,
} from "@/service/landing/landing";
import Link from "next/link"; import Link from "next/link";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
@ -21,7 +24,15 @@ const ContentCategory = (props: { group?: string }) => {
}, []); }, []);
const initFetch = async () => { const initFetch = async () => {
const response = await getPublicCategoryData( const response = await getPublicCategoryData(
props.group == "mabes" ? "" : props.group == "polda" && poldaName && String(poldaName)?.length > 1 ? poldaName : props.group == "satker" && satkerName && String(satkerName)?.length > 1 ? "satker-" + satkerName : "", props.group == "mabes"
? ""
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
? poldaName
: props.group == "satker" &&
satkerName &&
String(satkerName)?.length > 1
? "satker-" + satkerName
: "",
"", "",
locale == "en" ? true : false locale == "en" ? true : false
); );
@ -47,7 +58,10 @@ const ContentCategory = (props: { group?: string }) => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" /> <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`; </svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str)); const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return ( return (
<div className="px-4 lg:px-24 py-10"> <div className="px-4 lg:px-24 py-10">
@ -84,10 +98,16 @@ const ContentCategory = (props: { group?: string }) => {
// <h3 className="text-sm font-semibold truncate">{category?.name}</h3> // <h3 className="text-sm font-semibold truncate">{category?.name}</h3>
// </div> // </div>
// </Link> // </Link>
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"> <Link
key={category?.id}
href={`all/filter?category=${category?.id}`}
className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"
>
{/* Gambar */} {/* Gambar */}
<Image <Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
alt="category" alt="category"
width={2560} width={2560}
height={1440} height={1440}
@ -99,8 +119,10 @@ const ContentCategory = (props: { group?: string }) => {
<div className="absolute inset-0 bg-black bg-opacity-25 group-hover:bg-opacity-35 transition-all duration-300 rounded-md"></div> <div className="absolute inset-0 bg-black bg-opacity-25 group-hover:bg-opacity-35 transition-all duration-300 rounded-md"></div>
{/* Judul */} {/* Judul */}
<div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10"> <div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10 group-hover:scale-x-150 origin-left">
<h3 className="text-sm font-semibold truncate">{category?.name}</h3> <h3 className="text-sm font-semibold truncate">
{category?.name}
</h3>
</div> </div>
</Link> </Link>
) : ( ) : (
@ -120,10 +142,16 @@ const ContentCategory = (props: { group?: string }) => {
// <h3 className="text-sm font-semibold truncate">{category?.name}</h3> // <h3 className="text-sm font-semibold truncate">{category?.name}</h3>
// </div> // </div>
// </Link> // </Link>
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"> <Link
key={category?.id}
href={`all/filter?category=${category?.id}`}
className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg"
>
{/* Gambar */} {/* Gambar */}
<Image <Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
alt="category" alt="category"
width={2560} width={2560}
height={1440} height={1440}
@ -136,14 +164,19 @@ const ContentCategory = (props: { group?: string }) => {
{/* Judul */} {/* Judul */}
<div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10"> <div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10">
<h3 className="text-sm font-semibold truncate">{category?.name}</h3> <h3 className="text-sm font-semibold truncate">
{category?.name}
</h3>
</div> </div>
</Link> </Link>
) )
)} )}
</div> </div>
<div className="flex items-center flex-row justify-center"> <div className="flex items-center flex-row justify-center">
<Button onClick={() => setSeeAllValue(!seeAllValue)} className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]"> <Button
onClick={() => setSeeAllValue(!seeAllValue)}
className="bg-white hover:bg-[#bb3523] text-[#bb3523] hover:text-white border-2 border-[#bb3523]"
>
{seeAllValue ? t("seeLess") : t("seeMore")} {seeAllValue ? t("seeLess") : t("seeMore")}
</Button> </Button>
</div> </div>

View File

@ -28,23 +28,33 @@ const Footer = () => {
return ( return (
<footer className="bg-[#bb3523] text-white text-xs lg:text-sm py-4 space-y-3 lg:space-y-0 h-"> <footer className="bg-[#bb3523] text-white text-xs lg:text-sm py-4 space-y-3 lg:space-y-0 h-">
<div className="mx-auto flex flex-col md:flex-row justify-between items-center px-4"> <div className="mx-auto flex flex-col md:flex-row justify-between items-center px-4 gap-3">
{/* Hak Cipta */} {/* Hak Cipta */}
<div className="text-center md:text-left"> <div className="text-center md:text-left">
{t("copyright")} &copy; {new Date().getFullYear()} {t("publicRelation")} {t("reserved")} {t("copyright")} &copy; {new Date().getFullYear()}{" "}
{t("publicRelation")} {t("reserved")}
</div> </div>
{/* Menu Links */} {/* Menu Links */}
<div className="flex flex-wrap justify-center items-center space-x-3"> <div className="flex flex-wrap justify-center items-center space-x-3">
<Link href={generateLocalizedPath("/feedback", String(locale))} className="hover:underline"> <Link
href={generateLocalizedPath("/feedback", String(locale))}
className="hover:underline"
>
{t("feedback")} {t("feedback")}
</Link> </Link>
<span className="hidden md:inline-block ">|</span> <span className="hidden md:inline-block ">|</span>
<Link href={generateLocalizedPath("/contact", String(locale))} className="hover:underline"> <Link
href={generateLocalizedPath("/contact", String(locale))}
className="hover:underline"
>
{t("contact")} {t("contact")}
</Link> </Link>
<span className="hidden md:inline-block">|</span> <span className="hidden md:inline-block">|</span>
<Link href={generateLocalizedPath("/faqs", String(locale))} className="hover:underline"> <Link
href={generateLocalizedPath("/faqs", String(locale))}
className="hover:underline"
>
FAQ FAQ
</Link> </Link>
@ -55,12 +65,17 @@ const Footer = () => {
{t("privacy")} {t("privacy")}
</a> </a>
</DialogTrigger> </DialogTrigger>
<DialogContent className="flex flex-col overflow-y-scroll h-[80%]" size="md"> <DialogContent
className="flex flex-col overflow-y-scroll h-[80%]"
size="md"
>
<div className="flex flex-row items-center justify-center gap-4"> <div className="flex flex-row items-center justify-center gap-4">
<img src="/assets/icon-privacy.png" alt="Privacy" /> <img src="/assets/icon-privacy.png" alt="Privacy" />
<p className="font-semibold text-lg">{t("privacy")}</p> <p className="font-semibold text-lg">{t("privacy")}</p>
</div> </div>
<div className="container text-black dark:text-white space-y-2">{parse(String(privacy))}</div> <div className="container text-black dark:text-white space-y-2">
{parse(String(privacy))}
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
@ -69,13 +84,25 @@ const Footer = () => {
<div className="flex justify-center items-center space-x-3"> <div className="flex justify-center items-center space-x-3">
<span className="text-sm">Follow Us:</span> <span className="text-sm">Follow Us:</span>
<a href="#" aria-label="Facebook"> <a href="#" aria-label="Facebook">
<img src="/assets/facebook.svg" alt="Facebook" className="w-5 h-5" /> <img
src="/assets/facebook.svg"
alt="Facebook"
className="w-5 h-5"
/>
</a> </a>
<a href="#" aria-label="Instagram"> <a href="#" aria-label="Instagram">
<img src="/assets/instagram.svg" alt="Instagram" className="w-5 h-5" /> <img
src="/assets/instagram.svg"
alt="Instagram"
className="w-5 h-5"
/>
</a> </a>
<a href="#" aria-label="Twitter"> <a href="#" aria-label="Twitter">
<img src="/assets/twitter.svg" alt="Instagram" className="w-5 h-5" /> <img
src="/assets/twitter.svg"
alt="Instagram"
className="w-5 h-5"
/>
</a> </a>
<a href="#" aria-label="TikTok"> <a href="#" aria-label="TikTok">
<img src="/assets/tiktok.svg" alt="TikTok" className="w-5 h-5" /> <img src="/assets/tiktok.svg" alt="TikTok" className="w-5 h-5" />

View File

@ -5,9 +5,372 @@ import "swiper/css/navigation";
import { getHeroData } from "@/service/landing/landing"; import { getHeroData } from "@/service/landing/landing";
import Link from "next/link"; import Link from "next/link";
import { useParams, usePathname, useRouter } from "next/navigation"; import { useParams, usePathname, useRouter } from "next/navigation";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
import Image from "next/image"; import Image from "next/image";
import Cookies from "js-cookie";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../ui/card";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Textarea } from "../ui/textarea";
import { Checkbox } from "../ui/checkbox";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { Autoplay, Pagination } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/pagination";
const HeroModal = ({ onClose }: { onClose: () => void }) => {
const [heroData, setHeroData] = useState<any>();
const params = useParams();
const locale = params?.locale;
useEffect(() => {
async function fetchCategories() {
const url = "https://netidhub.com/api/csrf";
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch error: ", error);
}
}
fetchCategories();
initFetch();
}, []);
const initFetch = async () => {
const response = await getHeroData();
console.log(response);
setHeroData(response?.data?.data?.content);
};
return (
<div className="fixed inset-0 flex items-center justify-center backdrop-brightness-50 z-50">
<div className="relative dark:bg-gray-900 rounded-lg w-[90%] md:w-[600px] p-4 shadow-none">
<Swiper
pagination={{ dynamicBullets: true }}
modules={[Pagination, Autoplay]}
className="mySwiper w-full"
>
{heroData?.map((list: any, index: number) => (
<SwiperSlide key={list?.id}>
<div className="relative h-[310px] lg:h-[420px]">
<button
onClick={onClose}
className="absolute top-3 right-3 text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-white"
>
</button>
<Image
src={list?.thumbnailLink}
alt="gambar-utama"
width={1920}
height={1080}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-black/30 backdrop-brightness-50 text-white pb-4 px-4 pt-8 rounded-bl-2xl rounded-tr-2xl mx-3 mb-2">
<div className="absolute top-0 left-0 bottom-0 w-2 bg-[#bb3523] rounded-bl-lg"></div>
<span className="absolute top-0 left-0 mt-2 mb-3 mx-3 bg-[#bb3523] text-white text-xs font-semibold uppercase px-2 py-1 rounded">
{list?.categoryName || "Liputan Kegiatan"}
</span>
<Link href={`${locale}/image/detail/${list?.slug}`}>
<h2 className="text-lg leading-tight">{list?.title}</h2>
</Link>
<p className="text-xs flex items-center gap-1 mt-2 opacity-80">
{formatDateToIndonesian(new Date(list?.createdAt))}{" "}
{list?.timezone || "WIB"} |{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
className="inline-block"
>
<path
fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/>
</svg>
{list?.clickCount}
</p>
</div>
</div>
</SwiperSlide>
))}
<style jsx global>{`
.swiper-pagination-bullet {
background: white !important;
opacity: 0.7;
}
.swiper-pagination-bullet-active {
background: white !important;
opacity: 1;
}
`}</style>
</Swiper>
</div>
</div>
);
};
const SurveyIntroModal = ({ onNext }: { onNext: () => void }) => {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="relative bg-white rounded-xl p-6 w-[90%] max-w-md text-center">
<button
onClick={onNext}
className="absolute top-3 right-3 text-gray-500 hover:text-black"
>
</button>
<Image
src="/assets/survey.jpg"
alt="Survey Illustration"
width={300}
height={200}
className="mx-auto my-4"
/>
<button
onClick={onNext}
className="mt-4 bg-red-600 hover:bg-red-700 text-white font-semibold py-3 px-6 rounded w-full"
>
Lihat Selengkapnya
</button>
</div>
</div>
);
};
const options = {
q1: [
"Setiap hari",
"Beberapa kali seminggu",
"Beberapa kali dalam sebulan",
"Baru pertama kali",
],
q2a: ["Sangat baik", "Baik", "Cukup", "Kurang", "Buruk"],
q2b: ["Sangat mudah", "Mudah", "Cukup", "Sulit", "Sangat sulit"],
q2c: ["Sangat cepat", "Cepat", "Cukup", "Lambat", "Sangat lambat"],
q3a: ["Sangat puas", "Puas", "Cukup", "Kurang puas", "Tidak puas"],
q3b: [
"Sangat lengkap",
"Lengkap",
"Cukup",
"Kurang lengkap",
"Tidak lengkap",
],
q4: [
"Sangat membantu",
"Membantu",
"Cukup membantu",
"Kurang membantu",
"Tidak membantu",
],
};
const SurveyFormModal = ({ onClose }: { onClose: () => void }) => {
useEffect(() => {
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "";
};
}, []);
return (
<Dialog
open
onOpenChange={(open) => {
if (!open) onClose();
}}
>
<DialogContent className="z-50 min-w-max h-[600px] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-lg font-bold">
SURVEI KEPUASAN PENGGUNA MEDIAHUB POLRI
</DialogTitle>
<DialogDescription className="text-sm">
Kami menghargai pendapat Anda! Survei ini bertujuan untuk
meningkatkan kualitas layanan MediaHub Polri. Mohon luangkan waktu
beberapa menit untuk mengisi survei ini.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 mt-4">
{/* 1 */}
<div>
<p className="font-medium">
1. Seberapa sering Anda mengakses MediaHub Polri?
</p>
<div className="grid grid-cols-2 gap-2 mt-2">
{options.q1.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q1-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
{/* 2 */}
<div>
<p className="font-medium">
2. Bagaimana pengalaman Anda dalam mengakses website ini?
</p>
<div className="mt-2 space-y-3">
<div>
<p className="text-sm font-medium">
a) Tampilan dan desain website
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q2a.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q2a-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
<div>
<p className="text-sm font-medium">
b) Kemudahan navigasi (pencarian informasi, menu, dll)
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q2b.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q2b-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
<div>
<p className="text-sm font-medium">
c) Kecepatan akses website
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q2c.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q2c-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
</div>
</div>
{/* 3 */}
<div>
<p className="font-medium">
3. Seberapa puas Anda dengan informasi yang tersedia di MediaHub
Polri?
</p>
<div className="mt-2 space-y-3">
<div>
<p className="text-sm font-medium">a) Akurat dan terpercaya</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q3a.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q3a-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
<div>
<p className="text-sm font-medium">
b) Kelengkapan berita dan informasi
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
{options.q3b.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q3b-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
</div>
</div>
{/* 4 */}
<div>
<p className="font-medium">
5. Apakah Anda merasa website ini membantu dalam mendapatkan
informasi terkait Polri?
</p>
<div className="grid grid-cols-2 gap-2 mt-2">
{options.q4.map((item, i) => (
<label key={i} className="flex items-center space-x-2">
<Checkbox id={`q4-${i}`} />
<span>{item}</span>
</label>
))}
</div>
</div>
{/* 5 */}
<div>
<p className="font-medium">
6. Apa saran atau masukan Anda untuk meningkatkan layanan MediaHub
Polri?
</p>
<Textarea placeholder="Tulis pesan Anda" />
</div>
</div>
<div className="flex justify-end gap-2 mt-6">
<DialogClose asChild>
<Button variant="outline">Batal</Button>
</DialogClose>
<Button>Kirim</Button>
</div>
</DialogContent>
</Dialog>
);
};
const ONE_MONTH = 30 * 24 * 60 * 60 * 1000;
const Hero: React.FC = () => { const Hero: React.FC = () => {
const router = useRouter(); const router = useRouter();
@ -16,6 +379,9 @@ const Hero: React.FC = () => {
const locale = params?.locale; const locale = params?.locale;
const [isLoading, setIsLoading] = useState<any>(true); const [isLoading, setIsLoading] = useState<any>(true);
const [heroData, setHeroData] = useState<any>(); const [heroData, setHeroData] = useState<any>();
const [showModal, setShowModal] = useState(false);
const [showSurveyModal, setShowSurveyModal] = useState(false);
const [showFormModal, setShowFormModal] = useState(false);
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
@ -25,6 +391,29 @@ const Hero: React.FC = () => {
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, []); }, []);
useEffect(() => {
const roleId = Cookies.get("urie");
if (!roleId) {
setShowModal(true);
}
initFetch();
}, []);
useEffect(() => {
const roleId = Cookies.get("urie");
const lastShown = Cookies.get("surveyLastShown");
const now = new Date().getTime();
if (roleId && roleId !== "2") {
if (!lastShown || now - parseInt(lastShown) > ONE_MONTH) {
setShowSurveyModal(true);
Cookies.set("surveyLastShown", now.toString(), { expires: 30 });
}
}
initFetch();
}, []);
useEffect(() => { useEffect(() => {
async function fetchCategories() { async function fetchCategories() {
const url = "https://netidhub.com/api/csrf"; const url = "https://netidhub.com/api/csrf";
@ -37,7 +426,7 @@ const Hero: React.FC = () => {
} }
const data = await response.json(); const data = await response.json();
return data; // Menampilkan data yang diterima dari API return data;
} catch (error) { } catch (error) {
console.error("Fetch error: ", error); console.error("Fetch error: ", error);
} }
@ -67,11 +456,28 @@ const Hero: React.FC = () => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" /> <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`; </svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str)); const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return ( return (
<div className="flex flex-col lg:flex-row items-start justify-center gap-8 px-4 lg:px-20 py-4 mx-auto w-auto mt-6"> <div className="flex flex-col lg:flex-row items-start justify-center gap-8 px-4 lg:px-20 py-4 mx-auto w-auto mt-6">
{/* Section Gambar Utama */} <div className="relative">
{showModal && <HeroModal onClose={() => setShowModal(false)} />}
{showSurveyModal && !showFormModal && (
<SurveyIntroModal
onNext={() => {
setShowSurveyModal(false);
setShowFormModal(true);
}}
/>
)}
{showFormModal && (
<SurveyFormModal onClose={() => setShowFormModal(false)} />
)}
</div>
{isLoading ? ( {isLoading ? (
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3"> <div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
<Skeleton className="h-[310px] lg:h-[420px] rounded-xl" /> <Skeleton className="h-[310px] lg:h-[420px] rounded-xl" />
@ -85,30 +491,44 @@ const Hero: React.FC = () => {
<CarouselContent> <CarouselContent>
{heroData?.map((list: any) => ( {heroData?.map((list: any) => (
<CarouselItem key={list?.id}> <CarouselItem key={list?.id}>
<div className="relative h-[310px] lg:h-[420px]"> <div className="relative h-[310px] lg:h-[460px] mt-1">
<Image <Image
src={list?.thumbnailLink} src={list?.thumbnailLink}
alt="gambar-utama" alt="gambar-utama"
width={1920} width={1920}
height={1080} height={1080}
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} placeholder={`data:image/svg+xml;base64,${toBase64(
className="w-full h-[310px] lg:h-[420px] rounded-lg object-cover" shimmer(700, 475)
)}`}
className="w-full h-[320px] lg:h-[460px] rounded-lg object-cover"
/> />
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-black dark:text-white p-4 rounded-b-lg"> <div className="absolute bottom-0 left-0 right-0 bg-black/30 backdrop-brightness-50 text-white pb-4 px-4 pt-8 rounded-bl-2xl rounded-tr-2xl mx-3 mb-2">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{list?.categoryName}</span> <div className="absolute top-0 left-0 bottom-0 w-2 bg-[#bb3523] rounded-bl-lg"></div>
<span className="absolute top-0 left-0 mt-2 mb-3 mx-3 bg-[#bb3523] text-white text-xs font-semibold uppercase px-2 py-1 rounded">
{list?.categoryName || "Liputan Kegiatan"}
</span>
<Link href={`${locale}/image/detail/${list?.slug}`}> <Link href={`${locale}/image/detail/${list?.slug}`}>
<h2 className="text-lg text-slate-500 dark:text-white font-bold mt-2">{list?.title}</h2> <h2 className="text-lg leading-tight">{list?.title}</h2>
</Link> </Link>
<p className="text-xs flex flex-row items-center text-slate-500 dark:text-white gap-1 mt-1">
{formatDateToIndonesian(new Date(list?.createdAt))} {list?.timezone ? list?.timezone : "WIB"}|{" "} <p className="text-xs flex items-center gap-1 mt-2 opacity-80">
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"> {formatDateToIndonesian(new Date(list?.createdAt))}{" "}
{list?.timezone || "WIB"} |{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
className="inline-block"
>
<path <path
fill="currentColor" fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9" d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/> />
</svg>{" "} </svg>
{list?.clickCount}{" "} {list?.clickCount}
</p> </p>
</div> </div>
</div> </div>
@ -120,7 +540,6 @@ const Hero: React.FC = () => {
</Carousel> </Carousel>
)} )}
{/* Section Kanan */}
<div> <div>
{isLoading ? ( {isLoading ? (
<> <>
@ -161,30 +580,157 @@ const Hero: React.FC = () => {
</div> </div>
</> </>
) : ( ) : (
<ul className="py-4 lg:py-0 flex flex-row lg:flex-col gap-4 flex-nowrap w-[95vw] lg:w-auto overflow-x-auto"> <ul className="py-4 lg:py-0 flex flex-row lg:flex-col gap-4 flex-nowrap w-[95vw] lg:w-auto">
{heroData?.map((item: any) => ( <Tabs defaultValue="national" className="w-[350px]">
<li key={item?.id} className="flex gap-4 flex-row lg:w-full "> <TabsList className="grid w-full grid-cols-3 border">
<div className="flex-shrink-0 w-24 rounded-lg"> <TabsTrigger value="national">Nasional</TabsTrigger>
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={720} height={480} src={item?.thumbnailLink} alt={item?.title} className="w-full h-[73px] object-cover rounded-lg" /> <TabsTrigger value="polda">Polda</TabsTrigger>
</div> <TabsTrigger value="satker">Satker</TabsTrigger>
<div className="w-[280px] lg:w-auto"> </TabsList>
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">{item?.categoryName}</span> <TabsContent value="national">
<Link href={`${locale}/image/detail/${item?.slug}`}> {heroData?.map((item: any) => (
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{item?.title}</h3> <li
</Link> key={item?.id}
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1"> className="flex gap-4 flex-row lg:w-full mx-2"
{formatDateToIndonesian(new Date(item?.createdAt))} {item?.timezone ? item?.timezone : "WIB"} |{" "} >
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"> <div className="flex-shrink-0 w-24 rounded-lg">
<path <Image
fill="currentColor" placeholder={`data:image/svg+xml;base64,${toBase64(
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9" shimmer(700, 475)
)}`}
width={720}
height={480}
src={item?.thumbnailLink}
alt={item?.title}
className="w-full h-[73px] object-cover rounded-lg"
/> />
</svg>{" "} </div>
{item?.clickCount} <div className="w-[280px] lg:w-auto">
</p> <span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
</div> {item?.categoryName}
</li> </span>
))} <Link href={`${locale}/image/detail/${item?.slug}`}>
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
{item?.title}
</h3>
</Link>
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
{formatDateToIndonesian(new Date(item?.createdAt))}{" "}
{item?.timezone ? item?.timezone : "WIB"} |{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/>
</svg>{" "}
{item?.clickCount}
</p>
</div>
</li>
))}
</TabsContent>
<TabsContent value="polda">
{heroData
?.filter((item: any) => item.isPublishOnPolda === true)
.map((item: any, index: any) => (
<li
key={item?.id}
className="flex gap-4 flex-row lg:w-full mx-2"
>
<div className="flex-shrink-0 w-24 rounded-lg">
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={720}
height={480}
src={item?.thumbnailLink}
alt={item?.title}
className="w-full h-[73px] object-cover rounded-lg"
/>
</div>
<div className="w-[280px] lg:w-auto">
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
{item?.categoryName}
</span>
<Link href={`${locale}/image/detail/${item?.slug}`}>
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
{item?.title}
</h3>
</Link>
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
{formatDateToIndonesian(new Date(item?.createdAt))}{" "}
{item?.timezone ? item?.timezone : "WIB"} |{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/>
</svg>{" "}
{item?.clickCount}
</p>
</div>
</li>
))}
</TabsContent>
<TabsContent value="satker">
{heroData?.map((item: any) => (
<li
key={item?.id}
className="flex gap-4 flex-row lg:w-full mx-2"
>
<div className="flex-shrink-0 w-24 rounded-lg">
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={720}
height={480}
src={item?.thumbnailLink}
alt={item?.title}
className="w-full h-[73px] object-cover rounded-lg"
/>
</div>
<div className="w-[280px] lg:w-auto">
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">
{item?.categoryName}
</span>
<Link href={`${locale}/image/detail/${item?.slug}`}>
<h3 className="text-base font-bold mt-2 h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
{item?.title}
</h3>
</Link>
<p className="text-[10px] flex flex-row items-center gap-1 text-gray-500 mt-1">
{formatDateToIndonesian(new Date(item?.createdAt))}{" "}
{item?.timezone ? item?.timezone : "WIB"} |{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/>
</svg>{" "}
{item?.clickCount}
</p>
</div>
</li>
))}
</TabsContent>
</Tabs>
</ul> </ul>
)} )}
</div> </div>

View File

@ -75,7 +75,7 @@ const HeaderBannerKaltara = () => {
<> <>
<Reveal> <Reveal>
{/* Header Left */} {/* Header Left */}
<div className="flex flex-col lg:flex-row items-start justify-center gap-[25px] px-4 lg:px-18 py-4 w-auto mt-6"> <div className=" flex flex-col lg:flex-row items-start justify-center gap-[25px] px-4 lg:px-18 py-4 w-auto mt-6">
{isBannerLoading ? ( {isBannerLoading ? (
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3"> <div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
<Skeleton className="h-[310px] lg:h-[420px] rounded-xl" /> <Skeleton className="h-[310px] lg:h-[420px] rounded-xl" />

Some files were not shown because too many files have changed in this diff Show More