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
# Install dependencies
RUN pnpm install --frozen-lockfile
RUN pnpm install
# RUN pnpm install --frozen-lockfile
# Menyalin source code aplikasi
COPY . .

View File

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

View File

@ -24,7 +24,13 @@ import {
SelectValue,
} from "@/components/ui/select";
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";
const FormSchema = z.object({
@ -58,7 +64,7 @@ export type Placements = {
index: number;
roleId?: string;
userLevelId?: number;
}
};
export default function AddExpertForm() {
const MySwal = withReactContent(Swal);
@ -67,7 +73,9 @@ export default function AddExpertForm() {
resolver: zodResolver(FormSchema),
});
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 [userExperiences, setUserExperiences] = useState<any>();
const [userLevels, setUserLevels] = useState<any>();
@ -80,7 +88,7 @@ export default function AddExpertForm() {
{
id: "12",
name: "Kurator",
}
},
];
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
@ -113,12 +121,12 @@ export default function AddExpertForm() {
userCompetencyId: data.skills,
userExperienceId: data.experiences,
companyName: data.company,
}
};
loading();
const res = await saveUserInternal(dataReq);
const resData = res?.data?.data;
const userProfileId = resData.id;
const userProfileId = resData?.id;
var placementArr: any[] = [];
placementRows.forEach((row: any) => {
@ -131,15 +139,14 @@ export default function AddExpertForm() {
const dataReq2 = {
userId: userProfileId,
placements: placementArr
}
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>',
@ -180,7 +187,6 @@ export default function AddExpertForm() {
setUserLevels(levelsArr);
}
function successSubmit() {
MySwal.fire({
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) =>
prevRows.map((row) =>
row.index === index ? { ...row, [type]: value } : row
@ -202,7 +212,6 @@ export default function AddExpertForm() {
);
};
const handleRemoveRow = (index: number) => {
console.log(index);
console.log(placementRows);
@ -212,7 +221,10 @@ export default function AddExpertForm() {
};
const handleAddRow = () => {
setPlacementRows((prevRows: any) => [...prevRows, { index: incrementId, roleId: "", userLevelId: 0 }]);
setPlacementRows((prevRows: any) => [
...prevRows,
{ index: incrementId, roleId: "", userLevelId: 0 },
]);
setIncrementId((prevId) => prevId + 1);
};
@ -372,10 +384,14 @@ export default function AddExpertForm() {
/>
<div className="mt-4">
<FormLabel>Penempatan</FormLabel>
<FormLabel>Posisi</FormLabel>
{placementRows?.map((row: any) => (
<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>
<SelectTrigger>
<SelectValue placeholder="Pilih Role" />
@ -389,7 +405,11 @@ export default function AddExpertForm() {
))}
</SelectContent>
</Select>
<Select onValueChange={(e) => handleSelectionChange(row.index, "userLevelId", e)}>
<Select
onValueChange={(e) =>
handleSelectionChange(row.index, "userLevelId", e)
}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih User Level" />
@ -415,11 +435,7 @@ export default function AddExpertForm() {
)}
</div>
))}
<Button
type="button"
size="md"
onClick={() => handleAddRow()}
>
<Button type="button" size="md" onClick={() => handleAddRow()}>
Tambah
</Button>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -243,7 +243,10 @@ export default function CreateCategoryModal() {
{t("add-category")}
</Button>
</DialogTrigger>
<DialogContent size="md">
<DialogContent
size="md"
className="sm:h-[300px] md:h-[300px] lg:h-[500px] overflow-y-auto"
>
<DialogHeader>
<DialogTitle> {t("add-category")}</DialogTitle>
</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 ? (
<Button
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" />
{t("addEvent")}
@ -593,8 +593,8 @@ const CalendarView = ({ categories }: CalendarViewProps) => {
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
{roleId === 3 && userLevelId === 216 ? (
<Button className="dark:bg-background dark:text-foreground w-[250px]">
<Book className="w-4 h-4" />
<Button className="dark:bg-background dark:text-foreground w-full">
<Book size={15} className="w-4 h-4 mr-3" />
{t("bag-pa-monitoring-results")}
</Button>
) : null}

View File

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

View File

@ -38,6 +38,8 @@ import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
@ -60,9 +62,10 @@ const BlogTable = () => {
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: 10,
pageSize: Number(showData),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
@ -106,12 +109,12 @@ const BlogTable = () => {
React.useEffect(() => {
fetchData();
getCategories();
}, [categoryFilter, statusFilter, page, limit, search]);
}, [categoryFilter, statusFilter, page, showData, search]);
async function fetchData() {
try {
const res = await paginationBlog(
limit,
showData,
page - 1,
search,
categoryFilter,
@ -120,7 +123,7 @@ const BlogTable = () => {
const data = res?.data?.data;
const contentData = data?.content;
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);
@ -183,7 +186,7 @@ const BlogTable = () => {
<div className="flex-none">
<Link href={"/contributor/blog/create"}>
<Button fullWidth color="primary">
<Plus className="w-6 h-6 me-1.5" />
<Plus size={18} className=" me-1.5" />
{t("create-indeks")}
</Button>
</Link>
@ -210,6 +213,34 @@ const BlogTable = () => {
</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" size="md">

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
@ -76,9 +78,10 @@ const TableAudio = () => {
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: 10,
pageSize: Number(showData),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
@ -132,7 +135,15 @@ const TableAudio = () => {
React.useEffect(() => {
fetchData();
getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
}, [
categoryFilter,
statusFilter,
page,
showData,
search,
startDate,
endDate,
]);
async function getCategories() {
const category = await listEnableCategory("4");
@ -171,7 +182,7 @@ const TableAudio = () => {
try {
const isForSelf = Number(roleId) === 4;
const res = await listDataAudio(
limit,
showData,
page - 1,
isForSelf,
!isForSelf,
@ -189,7 +200,7 @@ const TableAudio = () => {
const data = res?.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * limit + index + 1;
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
@ -248,6 +259,34 @@ const TableAudio = () => {
</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">
{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>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">

View File

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

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
@ -81,13 +83,13 @@ const TableImage = () => {
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: 10,
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("");
const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
@ -96,6 +98,7 @@ const TableImage = () => {
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
[]
);
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
const [startDate, setStartDate] = React.useState("");
@ -135,10 +138,17 @@ const TableImage = () => {
}, [searchParams]);
React.useEffect(() => {
// Panggil fetchData saat filter kategori berubah
fetchData();
getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
}, [
categoryFilter,
statusFilter,
page,
showData,
search,
startDate,
endDate,
]);
async function getCategories() {
const category = await listEnableCategory("1");
@ -177,7 +187,7 @@ const TableImage = () => {
try {
const isForSelf = Number(roleId) === 4;
const res = await listDataImage(
limit,
showData,
page - 1,
isForSelf,
!isForSelf,
@ -195,7 +205,7 @@ const TableImage = () => {
const data = res?.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * limit + index + 1;
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
@ -254,6 +264,34 @@ const TableImage = () => {
</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">
{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>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">

View File

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

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
@ -75,9 +77,10 @@ const TableTeks = () => {
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: 10,
pageSize: Number(showData),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
@ -131,7 +134,15 @@ const TableTeks = () => {
React.useEffect(() => {
fetchData();
getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
}, [
categoryFilter,
statusFilter,
page,
showData,
search,
startDate,
endDate,
]);
async function getCategories() {
const category = await listEnableCategory("3");
@ -170,7 +181,7 @@ const TableTeks = () => {
try {
const isForSelf = Number(roleId) === 4;
const res = await listDataTeks(
limit,
showData,
page - 1,
isForSelf,
!isForSelf,
@ -188,7 +199,7 @@ const TableTeks = () => {
const data = res?.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * limit + index + 1;
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
@ -247,6 +258,34 @@ const TableTeks = () => {
</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">
{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>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">

View File

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

View File

@ -43,6 +43,8 @@ import {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
@ -75,9 +77,10 @@ const TableVideo = () => {
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: 10,
pageSize: Number(showData),
});
const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1);
@ -131,7 +134,16 @@ const TableVideo = () => {
React.useEffect(() => {
fetchData();
getCategories();
}, [categoryFilter, statusFilter, page, limit, search, startDate, endDate]);
}, [
categoryFilter,
statusFilter,
page,
showData,
,
search,
startDate,
endDate,
]);
async function getCategories() {
const category = await listEnableCategory("2");
@ -170,7 +182,7 @@ const TableVideo = () => {
try {
const isForSelf = Number(roleId) === 4;
const res = await listDataVideo(
limit,
showData,
page - 1,
isForSelf,
!isForSelf,
@ -188,7 +200,7 @@ const TableVideo = () => {
const data = res?.data?.data;
const contentData = data?.content;
contentData.forEach((item: any, index: number) => {
item.no = (page - 1) * limit + index + 1;
item.no = (page - 1) * Number(showData) + index + 1;
});
setDataTable(contentData);
@ -248,6 +260,34 @@ const TableVideo = () => {
<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">
{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>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto" size="md">

View File

@ -64,7 +64,7 @@ const ReactTableVideoPage = () => {
<div className="flex-none">
<Link href={"/contributor/content/video/create"}>
<Button color="primary" className="text-white">
<UploadIcon />
<UploadIcon size={18} className="mr-2" />
{t("create-video")}
</Button>
</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,
TableRow,
} from "@/components/ui/table";
import { Search, UploadIcon } from "lucide-react";
import { ChevronDown, Search, UploadIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { useRouter, useSearchParams } from "next/navigation";
@ -34,6 +34,14 @@ import { CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "@/i18n/routing";
import { Button } from "@/components/ui/button";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const EventTable = () => {
const router = useRouter();
@ -48,14 +56,16 @@ const EventTable = () => {
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: 10,
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 [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns();
const table = useReactTable({
data: dataTable,
@ -87,15 +97,21 @@ const EventTable = () => {
React.useEffect(() => {
fetchData();
}, [page, limit, search]);
}, [page, showData, search, statusFilter]);
async function fetchData() {
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 contentData = data?.content;
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);
@ -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>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -124,7 +148,7 @@ const EventTable = () => {
<div className="flex-none">
<Link href={"/contributor/schedule/event/create"}>
<Button color="primary" className="text-white">
<UploadIcon />
<UploadIcon size={18} className="mr-2" />
{t("create-schedule")}
</Button>
</Link>
@ -133,8 +157,8 @@ const EventTable = () => {
</CardTitle>
</CardHeader>
<div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5 gap-2">
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<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-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
@ -148,17 +172,78 @@ const EventTable = () => {
/>
</InputGroup>
</div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<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 className="flex flex-row">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div 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>
<Table className="overflow-hidden mt-3">

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -47,6 +48,14 @@ import { CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const PressConferenceTable = () => {
const router = useRouter();
@ -61,14 +70,16 @@ const PressConferenceTable = () => {
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: 10,
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 [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns();
const table = useReactTable({
data: dataTable,
@ -100,15 +111,21 @@ const PressConferenceTable = () => {
React.useEffect(() => {
fetchData();
}, [page, limit, search]);
}, [page, showData, search, statusFilter]);
async function fetchData() {
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 contentData = data?.content;
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);
@ -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>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -137,7 +162,7 @@ const PressConferenceTable = () => {
<div className="flex-none">
<Link href={"/contributor/schedule/press-conference/create"}>
<Button color="primary" className="text-white">
<UploadIcon />
<UploadIcon size={18} className="mr-2" />
{t("create-schedule")}
</Button>
</Link>
@ -146,8 +171,8 @@ const PressConferenceTable = () => {
</CardTitle>
</CardHeader>
<div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5 gap-2">
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<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-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
@ -161,17 +186,78 @@ const PressConferenceTable = () => {
/>
</InputGroup>
</div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<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 className="flex flex-row gap-3">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div 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>
<Table className="overflow-hidden mt-3">

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -48,6 +49,14 @@ import { useTranslations } from "next-intl";
import { CardHeader, CardTitle } from "@/components/ui/card";
import { Link } from "@/i18n/routing";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const PressReleaseTable = () => {
const router = useRouter();
@ -62,14 +71,16 @@ const PressReleaseTable = () => {
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: 10,
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 [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns();
const table = useReactTable({
data: dataTable,
@ -101,15 +112,21 @@ const PressReleaseTable = () => {
React.useEffect(() => {
fetchData();
}, [page, limit, search]);
}, [page, showData, search, statusFilter]);
async function fetchData() {
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 contentData = data?.content;
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);
@ -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>) => {
setSearch(e.target.value); // Perbarui state search
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -138,7 +163,7 @@ const PressReleaseTable = () => {
<div className="flex-none">
<Link href={"/contributor/schedule/press-release/create"}>
<Button color="primary" className="text-white">
<UploadIcon />
<UploadIcon size={18} className="mr-2" />
{t("create-schedule")}
</Button>
</Link>
@ -147,8 +172,8 @@ const PressReleaseTable = () => {
</CardTitle>
</CardHeader>
<div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5 gap-2">
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<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-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
@ -162,17 +187,78 @@ const PressReleaseTable = () => {
/>
</InputGroup>
</div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<Input
placeholder="Filter Status..."
value={
(table.getColumn("status")?.getFilterValue() as string) ?? ""
}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
table.getColumn("status")?.setFilterValue(event.target.value)
}
className="w-full "
/>
<div className="flex flex-row">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div 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>
<Table className="overflow-hidden mt-3">
@ -228,7 +314,6 @@ const PressReleaseTable = () => {
totalPage={totalPage}
/>
</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,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
@ -71,9 +73,10 @@ const TaskTable = () => {
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: 10,
pageSize: Number(showData),
});
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const [dateFilter, setDateFilter] = React.useState("");
@ -117,7 +120,7 @@ const TaskTable = () => {
fetchData();
}, [
page,
limit,
showData,
isSpecificAttention,
search,
dateFilter,
@ -133,7 +136,7 @@ const TaskTable = () => {
const res = await listTask(
page - 1,
search,
limit,
showData,
filterByCode,
formattedStartDate,
isSpecificAttention ? "atensi-khusus" : "tugas-harian",
@ -153,7 +156,7 @@ const TaskTable = () => {
// });
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);
@ -241,36 +244,64 @@ const TaskTable = () => {
/>
</InputGroup>
</div>
<div className="w-full sm:w-[100px] items-center gap-2">
<div className=" gap-3">
<div className="flex items-center py-4">
<div className=" flex flex-row items-center gap-3">
<div className="flex items-center py-4">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="ml-auto w-full sm:w-[100px]"
size="md"
>
Filter <ChevronDown />
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[200px] overflow-y-auto"
<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"
>
<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">
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..."
@ -279,36 +310,36 @@ const TaskTable = () => {
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>
<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 className="flex-none">
</div>
{/* <div className="flex-none">
<Input
placeholder="Filter Status..."
value={
@ -320,7 +351,6 @@ const TaskTable = () => {
className="max-w-sm "
/>
</div> */}
</div>
</div>
<Table className="overflow-hidden mt-3">
<TableHeader>

View File

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

View File

@ -168,8 +168,8 @@ export default function ExecutiveDashboard() {
const [ticket6, setTicket6] = useState("");
const [isInternational, setIsInternational] = useState([false, false, false]);
const baseUrl = "https://db-mediahub.polri.go.id/";
const url = "https://db-mediahub.polri.go.id/trusted/";
const baseUrl = "https://analytic.sitani.info/";
const url = "https://analytic.sitani.info/trusted/";
const view1 =
levelName == "MABES POLRI"
@ -182,15 +182,15 @@ export default function ExecutiveDashboard() {
levelName == "MABES POLRI"
? isInternational[1]
? "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_Rev202/db-konten-publisher-polda-executive?provinsi-polda=${poldaState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-published-produksi-executive?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-konten-publisher-polda-executive?provinsi-polda=${poldaState}&`;
const view3 =
levelName == "MABES POLRI"
? isInternational[2]
? "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_Rev202/db-waktu-akses-pengguna-polda-executive?provinsi-polda=${poldaState}&`;
: "views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-executive?"
: `views/2023_04_MediaHUB-Viz-POLDA_Rev200/db-waktu-akses-pengguna-polda-executive?provinsi-polda=${poldaState}&`;
const view4 =
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 RecentActivity from "./routine-task/components/recent-activity";
import { Link } from "@/components/navigation";
import ReportTable from "../contributor/report/components/report-table";
const DashboardPage = () => {
const t = useTranslations("AnalyticsDashboard");
@ -59,6 +60,12 @@ const DashboardPage = () => {
>
{t("indeks")}
</TabsTrigger>
<TabsTrigger
value="report"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
>
{t("report")}
</TabsTrigger>
</TabsList>
</Card>
<TabsContent value="routine-task">
@ -107,7 +114,7 @@ const DashboardPage = () => {
<Card>
<CardHeader className="flex flex-row items-center">
<CardTitle className="flex-1">{t("tabel")}</CardTitle>
<DashboardDropdown />
{/* <DashboardDropdown /> */}
</CardHeader>
<CardContent className="p-0">
<ContentTable />
@ -157,7 +164,7 @@ const DashboardPage = () => {
<div className="grid grid-cols-12 gap-5">
<div className="lg:col-span-12 col-span-12">
<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-1 text-xl font-medium text-default-900 mb-2">
Table Indeks
@ -171,7 +178,7 @@ const DashboardPage = () => {
</Link>
</div>
</div>
</Card>
</Card> */}
<CardContent className="p-0 mt-3">
<BlogTable />
</CardContent>
@ -179,6 +186,17 @@ const DashboardPage = () => {
</div>
</div>
</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>
</div>
</div>

View File

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

View File

@ -26,6 +26,7 @@ import {
} from "@/components/ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
ChevronDown,
ChevronLeft,
ChevronRight,
Eye,
@ -43,6 +44,14 @@ import TablePagination from "@/components/table/table-pagination";
import columns from "./columns";
import { listContest } from "@/service/contest/contest";
import useTableColumns from "./columns";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
const TaskTable = () => {
const router = useRouter();
@ -56,14 +65,16 @@ const TaskTable = () => {
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: 10,
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 [statusFilter, setStatusFilter] = React.useState<number[]>([]);
const columns = useTableColumns();
const table = useReactTable({
data: dataTable,
@ -95,15 +106,30 @@ const TaskTable = () => {
React.useEffect(() => {
fetchData();
}, [page, limit, search]);
}, [page, showData, search, statusFilter]);
async function fetchData() {
try {
const res = await listContest(search, limit, page - 1);
const res = await listContest(search, showData, page - 1);
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) => {
item.no = (page - 1) * limit + index + 1;
item.no = (page - 1) * Number(showData) + index + 1;
});
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>) => {
setSearch(e.target.value);
table.getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
@ -123,8 +157,8 @@ const TaskTable = () => {
return (
<div className="w-full overflow-x-auto">
<div className="flex justify-between items-center px-5">
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<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-full sm:w-[150px] md:w-[250px] lg:w-[250px]">
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className=" h-4 w-4 dark:text-white" />
@ -138,17 +172,90 @@ const TaskTable = () => {
/>
</InputGroup>
</div>
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
<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 className="flex flex-row">
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
1 - {showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={setShowData}
>
<DropdownMenuRadioItem value="10">
1 - 10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
1 - 50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
1 - 100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
1 - 250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div 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>
<Table className="overflow-hidden mt-3">

View File

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

View File

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

View File

@ -1,15 +1,7 @@
import * as React from "react";
import {
ColumnDef,
} from "@tanstack/react-table";
import { ColumnDef } from "@tanstack/react-table";
import {
Eye,
MoreVertical,
SquarePen,
Trash2,
} from "lucide-react";
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import {
DropdownMenu,
@ -27,50 +19,60 @@ const columns: ColumnDef<any>[] = [
cell: ({ row }) => <span>{row.getValue("no")}</span>,
},
{
accessorKey: "question",
header: "Question",
cell: ({ row }) => <span>{row.getValue("question")}</span>,
accessorKey: "report",
header: "Pelapor",
cell: ({ row }) => <span>{row.getValue("report")}</span>,
},
{
accessorKey: "answer",
header: "Answer",
cell: ({ row }) => <span>{row.getValue("answer")}</span>,
accessorKey: "reportDetail",
header: "Detail Laporan",
cell: ({ row }) => <span>{row.getValue("reportDetail")}</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>
);
},
accessorKey: "reportAccount",
header: "Terlapor",
cell: ({ row }) => <span>{row.getValue("reportAccount")}</span>,
},
{
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 columns from "./column";
const FaqTable = () => {
const ReportTable = () => {
const router = useRouter();
const searchParams = useSearchParams();
@ -94,11 +94,11 @@ const FaqTable = () => {
});
React.useEffect(() => {
const pageFromUrl = searchParams?.get('page');
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) {
setPage(Number(pageFromUrl));
}
}, [searchParams]);
}, [searchParams]);
React.useEffect(() => {
fetchData();
@ -191,9 +191,14 @@ const FaqTable = () => {
)}
</TableBody>
</Table>
<TablePagination table={table} totalData={totalData} totalPage={totalPage} visiblePageCount={5} />
<TablePagination
table={table}
totalData={totalData}
totalPage={totalPage}
visiblePageCount={5}
/>
</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 { Button } from "@/components/ui/button";
import { Plus } from "lucide-react";
import { StatisticsBlock } from "@/components/blocks/statistics-block";
import ReportTable from "./components/table";
const FaqPage = async () => {
return (
<div>
<SiteBreadcrumb />
<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>
<CardHeader className="border-b border-solid border-default-200 mb-6">
<CardTitle>
<div className="flex items-center">
<div className="flex-1 text-xl font-medium text-default-900">
FAQ Data
</div>
<div className="flex-none">
<Button
fullWidth
size="md"
>
<Plus className="w-6 h-6 me-1.5"/>
New FAQ
</Button>
</div>
</div>
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<FaqTable />
<CardContent className="p-4">
<div className="grid md:grid-cols-3 gap-4">
<StatisticsBlock
total="64"
title="Total Pelaporan"
className=" border shadow-none"
/>
<StatisticsBlock
title="Total Disetujui"
total="564"
className=" border shadow-none"
chartColor="#FB8F65"
/>
<StatisticsBlock
title="Pelaporan Pending"
total="+5.0%"
className=" border shadow-none"
chartColor="#2563eb"
/>
</div>
</CardContent>
</Card>
<ReportTable />
</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 { getCookiesDecrypt } from "@/lib/utils";
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 { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
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 Swal from "sweetalert2";
import parse from "html-react-parser";
@ -22,7 +38,6 @@ import { Skeleton } from "@/components/ui/skeleton";
import { useTranslations } from "next-intl";
import Image from "next/image";
interface Size {
label: string;
value: string;
@ -64,8 +79,11 @@ const DetailInfo = () => {
const poldaName = params?.polda_name;
const satkerName = params?.satker_name;
let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/";
let prefixPath = poldaName
? `/polda/${poldaName}`
: satkerName
? `/satker/${satkerName}`
: "/";
useEffect(() => {
const timer = setTimeout(() => {
@ -178,14 +196,14 @@ const DetailInfo = () => {
const sizes: Size[] = Object.entries(scaleFactors).map(([label, factor]) => {
const width = Number(main?.widthPixel);
const height = Number(main?.heightPixel);
if (isNaN(width) || isNaN(height) || width === 0) {
return { label, value: "Invalid size" };
}
const newWidth = Math.round(width * factor);
const newHeight = Math.round((width * factor) / (width / height));
return { label, value: `${newWidth} x ${newHeight} px` };
});
@ -263,7 +281,8 @@ const DetailInfo = () => {
xhr.addEventListener("readystatechange", () => {
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 filename = `${name}.${extension}`;
@ -297,7 +316,11 @@ const DetailInfo = () => {
if (type == "wa" && width <= 768) {
window.open(`whatsapp://send?${url}`, "_blank");
} 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 {
window.open(url);
}
@ -386,7 +409,9 @@ const DetailInfo = () => {
}
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) {
loading();
@ -399,7 +424,9 @@ const DetailInfo = () => {
console.log(data);
const response = await createPublicSuggestion(data);
console.log(response);
const responseGet: any = await getPublicSuggestionList(slug?.split("-")?.[0]);
const responseGet: any = await getPublicSuggestionList(
slug?.split("-")?.[0]
);
console.log(responseGet.data?.data);
setListSuggestion(responseGet.data?.data);
@ -477,9 +504,11 @@ const DetailInfo = () => {
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return (
<>
@ -494,7 +523,16 @@ const DetailInfo = () => {
</div>
) : (
<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>
)}
@ -510,7 +548,16 @@ const DetailInfo = () => {
<div className="py-4 flex flex-row gap-3">
{detailDataImage?.files?.map((file: any, index: number) => (
<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>
))}
</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="flex flex-col lg:flex-row items-center mt-3 lg:justify-between">
<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 className="text-xs lg:text-sm">
&nbsp;|&nbsp;{t("updatedOn")}
@ -528,7 +578,10 @@ const DetailInfo = () => {
</p> */}
<p className="text-xs lg:text-sm">
&nbsp;|&nbsp;{t("updatedOn")}&nbsp;
{formatDateToIndonesian(new Date(detailDataImage?.updatedAt))} {"WIB"}
{formatDateToIndonesian(
new Date(detailDataImage?.updatedAt)
)}{" "}
{"WIB"}
</p>
<p className="text-xs lg:text-sm flex justify-center items-center">
&nbsp;|&nbsp;
@ -546,51 +599,86 @@ const DetailInfo = () => {
{/* Keterangan */}
<div className="w-full">
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">{detailDataImage?.title}</h1>
<div className="font-light text-justify mb-5 space-y-4 lg:mb-0" dangerouslySetInnerHTML={{ __html: detailDataImage?.htmlDescription }} />
<h1 className="flex flex-row font-bold text-lg lg:text-2xl my-8">
{detailDataImage?.title}
</h1>
<div
className="font-light text-justify mb-5 space-y-4 lg:mb-0"
dangerouslySetInnerHTML={{
__html: detailDataImage?.htmlDescription,
}}
/>
</div>
</div>
{/* Bagian Kanan */}
<div className="md:w-1/4 p-4 bg-[#f7f7f7] dark:bg-slate-600 h-fit rounded-lg mx-4">
{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} />
<p className="text-base lg:text-lg">{t("delete")}</p>
</a>
) : (
<a onClick={() => doBookmark()} className="flex flex-col mb-3 items-center justify-center cursor-pointer">
<Icon icon="material-symbols:bookmark-outline" width={40} />
<p className="text-base lg:text-lg">{t("save")}</p>
<a
onClick={() => doBookmark()}
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>
)}
{/* garis */}
<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">
{detailDataImage?.category?.name}
</Link>
<div className="flex justify-center flex-wrap gap-2 mb-4">
{detailDataImage?.tags?.split(",").map((tag: string) => (
<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}
</a>
))}
<div className="flex justify-center flex-wrap gap-2 mb-4">
{detailDataImage?.tags?.split(",").map((tag: string) => (
<a
onClick={() => router.push(`/all/filter?tag=${tag}`)}
key={tag}
className="bg-gray-200 text-gray-700 text-xs px-3 py-3 font-semibold rounded-full cursor-pointer hover:bg-gray-500"
>
{tag}
</a>
))}
</div>
</div>
<div className="border-t border-black my-4"></div>
{/* 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="space-y-2">
{sizes.map((size: any) => (
<div className="flex flex-row justify-between">
<div 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
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>
<div className="text-sm">{size?.value}</div>
@ -601,15 +689,30 @@ const DetailInfo = () => {
{/* Download Semua */}
<div className="mt-4">
<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>
</label>
</div>
{/* 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">
<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" />
<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"
>
<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>
{t("download")}
</button>
@ -617,29 +720,90 @@ const DetailInfo = () => {
{/* Tombol Bagikan */}
<div className="flex flex-row mt-5 justify-center">
<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}`)}>
<Icon icon="brandico:facebook" height="20" className="px-auto text-red-600 text-center" />
<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}`
)
}
>
<Icon
icon="brandico:facebook"
height="20"
className="px-auto text-red-600 text-center"
/>
</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}`)}>
<Icon icon="mdi:twitter" width="23" className="text-red-600 text-center" />
<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}`
)
}
>
<Icon
icon="mdi:twitter"
width="23"
className="text-red-600 text-center"
/>
</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}`)}>
<Icon icon="ri:whatsapp-fill" width="23" className="text-red-600 text-center" />
<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}`
)
}
>
<Icon
icon="ri:whatsapp-fill"
width="23"
className="text-red-600 text-center"
/>
</a>
<Popover>
<PopoverTrigger className="flex justify-end gap-1 cursor-pointer" 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" />
<PopoverTrigger
className="flex justify-end gap-1 cursor-pointer"
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>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col">
<h1 className="mb-2">{t("shareTo")}</h1>
<div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1">{t("destinationEmail")}</p>
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter")} />
<p className="text-base font-semibold mb-1">
{t("destinationEmail")}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter")}
/>
</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")}
</Button>
</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="gap-5 flex flex-col px-4 lg:px-14">
<p className="flex items-start text-lg">{t("comment")}</p>
<Textarea 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">
<Textarea
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")}
</button>
</div>
@ -667,13 +838,29 @@ const DetailInfo = () => {
{listSuggestion?.map((data: any) => (
<div className="flex flex-col">
<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">
<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))}
</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>
<a
style={
@ -686,11 +873,16 @@ const DetailInfo = () => {
onClick={() => showInput(`comment-id-${data.id}`)}
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>
{Number(data.suggestionFrom?.id) == Number(userId) || Number(userRoleId) == 2 ? (
{Number(data.suggestionFrom?.id) == Number(userId) ||
Number(userRoleId) == 2 ? (
<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>
) : (
""
@ -699,14 +891,25 @@ const DetailInfo = () => {
</div>
</div>
{visibleInput === `comment-id-${data.id}` && (
<div 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
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">
<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 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>
</div>
</div>
@ -716,30 +919,56 @@ const DetailInfo = () => {
? data.children?.map((child1: any) => (
<div className="flex flex-col">
<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">
<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>{" "}
{getPublicLocaleTimestamp(new Date(child1.createdAt))}
<b>
{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 className="text-slate-500 text-[13px] lg:text-sm mb-4">{parse(String(child1?.message))}</p>
<div>
<a
style={
Number(child1.suggestionFrom?.id) == Number(userId)
Number(child1.suggestionFrom?.id) ==
Number(userId)
? {
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
style={
Number(child1.suggestionFrom?.id) == Number(userId)
Number(child1.suggestionFrom?.id) ==
Number(userId)
? {}
: {
display: "none",
@ -747,21 +976,39 @@ const DetailInfo = () => {
}
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>
</div>
</div>
</div>
{visibleInput === `comment-id-${child1.id}` && (
<div 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
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">
<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 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
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>
</div>
</div>
@ -771,30 +1018,62 @@ const DetailInfo = () => {
? child1.children?.map((child2: any) => (
<div className="">
<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">
<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>{" "}
{getPublicLocaleTimestamp(new Date(child2.createdAt))}
<b>
{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 className="text-slate-500 text-sm mb-4">{parse(String(child2?.message))}</p>
<div>
<a
style={
Number(child2.suggestionFrom?.id) == Number(userId)
Number(child2.suggestionFrom?.id) ==
Number(userId)
? {
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
style={
Number(child2.suggestionFrom?.id) == Number(userId)
Number(child2.suggestionFrom?.id) ==
Number(userId)
? {}
: {
display: "none",
@ -802,20 +1081,40 @@ const DetailInfo = () => {
}
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>
</div>
</div>
</div>
{visibleInput === `comment-id-${child2.id}` && (
<div 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
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">
<a 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
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 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
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>
</div>
</div>

View File

@ -3,10 +3,30 @@ import React, { useEffect, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
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 { getUserLevelListByParent, listCategory, listData, listDataRegional } from "@/service/landing/landing";
import { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
import {
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 { Reveal } from "@/components/landing-page/Reveal";
import { Link, useRouter } from "@/i18n/routing";
@ -38,8 +58,11 @@ const FilterPage = () => {
const [totalData, setTotalData] = React.useState<number>(1);
const [totalPage, setTotalPage] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
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,
@ -60,7 +83,9 @@ const FilterPage = () => {
const [categoryFilter, setCategoryFilter] = useState<any>([]);
const [monthYearFilter, setMonthYearFilter] = useState<any>();
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 isSatker = asPath?.includes("satker");
const [formatFilter, setFormatFilter] = useState<any>([]);
@ -73,6 +98,7 @@ const FilterPage = () => {
const [categories, setCategories] = useState([]);
const [userLevels, setUserLevels] = useState([]);
const t = useTranslations("FilterPage");
const [isFilterOpen, setIsFilterOpen] = useState(true);
// const [startDate, endDate] = dateRange;
@ -105,8 +131,14 @@ const FilterPage = () => {
useEffect(() => {
if (categorie) {
setCategoryFilter(categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
console.log("Kategori", categorie, categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]);
setCategoryFilter(
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
);
console.log(
"Kategori",
categorie,
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
);
}
}, [categorie]);
@ -124,7 +156,19 @@ const FilterPage = () => {
}
console.log(monthYearFilter, "monthFilter");
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() {
const category = await listCategory("1");
@ -147,7 +191,10 @@ const FilterPage = () => {
async function getDataAll() {
if (asPath?.includes("/polda/") == true) {
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 format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -165,8 +212,14 @@ const FilterPage = () => {
filterGroup,
startDateString,
endDateString,
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)
?.split("/")[0]
?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
locale == "en" ? true : false
);
close();
@ -181,7 +234,10 @@ const FilterPage = () => {
setTotalContent(response?.data?.data?.totalElements);
}
} 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 format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -198,8 +254,12 @@ const FilterPage = () => {
"",
startDateString,
endDateString,
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
locale == "en" ? true : false
);
close();
@ -250,7 +310,10 @@ const FilterPage = () => {
};
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 format = formatFilter == undefined ? "" : formatFilter?.join(",");
@ -263,8 +326,12 @@ const FilterPage = () => {
"",
startDateString,
endDateString,
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") : "",
monthYearFilter ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] : "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
: "",
monthYearFilter
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
: "",
12,
pages,
sortByOpt
@ -381,16 +448,20 @@ const FilterPage = () => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return (
<div className="flex flex-col">
{/* 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>
{" "}
{t("image")} {">"} <span className="font-bold">{t("allImage")}</span>
<span className="font-bold">{t("allImage")}</span>
</p>
<p className="font-bold">|</p>
<p>{`${t("thereIs")} ${totalContent} ${t("downloadableImage")}`}</p>
@ -398,106 +469,182 @@ const FilterPage = () => {
{/* Left */}
<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">
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
<Icon icon="stash:filter-light" fontSize={30} />
Filter
</h2>
<div className="border-t border-black my-4 dark:border-white"></div>
<div className="space-y-6">
<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 className="lg:hidden flex justify-end mb-2">
<button
onClick={() => setIsFilterOpen(!isFilterOpen)}
className="text-sm text-white bg-[#bb3523] px-4 py-1 rounded-md shadow"
>
{isFilterOpen ? "Hide Filter" : "Show Filter"}
</button>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-white">{t("monthYear")}</label>
<ReactDatePicker
selected={monthYearFilter}
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
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)}
{isFilterOpen && (
<div className="lg:w-[25%] h-fit w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
<Icon icon="stash:filter-light" fontSize={30} />
Filter
</h2>
<div className="border-t border-black my-4 dark:border-white"></div>
<div className="space-y-6">
<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 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>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-white">
{t("monthYear")}
</label>
<ReactDatePicker
selected={monthYearFilter}
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
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>
</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>
</li>
<li>
<label className="inline-flex items-center">
<Checkbox id="jpeg" value="jpeg" checked={formatFilter.includes("jpeg")} onCheckedChange={(e) => handleFormatFilter(Boolean(e), "jpeg")} />
<span className="ml-2 text-gray-700 dark:text-white">JPEG</span>
</label>
</li>
<li>
<label className="inline-flex items-center">
<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>
</li>
</ul>
</div>
<div className="border-t border-black dark:border-white my-4"></div>
<div className="text-center">
<a onClick={cleanCheckbox} className="text-[#bb3523] cursor-pointer">
<b>Reset Filter</b>
</a>
<li>
<label className="inline-flex items-center">
<Checkbox
id="jpeg"
value="jpeg"
checked={formatFilter.includes("jpeg")}
onCheckedChange={(e) =>
handleFormatFilter(Boolean(e), "jpeg")
}
/>
<span className="ml-2 text-gray-700 dark:text-white">
JPEG
</span>
</label>
</li>
<li>
<label className="inline-flex items-center">
<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>
</li>
</ul>
</div>
<div className="border-t border-black dark:border-white my-4"></div>
<div className="text-center">
<a
onClick={cleanCheckbox}
className="text-[#bb3523] cursor-pointer"
>
<b>Reset Filter</b>
</a>
</div>
</div>
</div>
</div>
)}
{/* Right */}
<div className="w-full lg:w-[75%]">
@ -505,7 +652,11 @@ const FilterPage = () => {
<div className="w-full">
<div className="flex flex-col items-end mb-4">
<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="popular">{t("mostPopular")}</option>
</select>
@ -529,24 +680,50 @@ const FilterPage = () => {
{imageData?.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{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">
<Link href={`/image/detail/${image?.slug}`}>
{/* <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">
<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 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}{" "}
<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
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"
/>
</svg>{" "}
</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>
</CardContent>
</Card>
@ -554,12 +731,24 @@ const FilterPage = () => {
</div>
) : (
<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>
)}
</>
)}
{totalData > 1 && <LandingPagination table={table} totalData={totalData} totalPage={totalPage} />}
{totalData > 1 && (
<LandingPagination
table={table}
totalData={totalData}
totalPage={totalPage}
/>
)}
</div>
</Reveal>
</div>

View File

@ -2,9 +2,22 @@
import { Textarea } from "@/components/ui/textarea";
import { useParams, usePathname, useSearchParams } from "next/navigation";
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 { 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 { Icon } from "@iconify/react/dist/iconify.js";
import { getCookiesDecrypt } from "@/lib/utils";
@ -94,7 +107,9 @@ const IndeksDetail = () => {
// }
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) {
loading();
@ -166,32 +181,54 @@ const IndeksDetail = () => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return (
<>
<div className="p-4 lg:px-60 lg:p-12">
<div className="p-4 lg:px-28 lg:p-12">
{/* Judul */}
<div className="flex flex-col mb-5">
<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>
{/* Gambar Utama */}
<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>
{/* Footer Informasi */}
<div className="text-gray-500 flex border-t mt-4">
<div className="flex mt-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>
</div>
</div>
{/* Keterangan */}
<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>
@ -199,9 +236,19 @@ const IndeksDetail = () => {
<div className="w-full">
<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">
<p className="flex items-start text-bases lg:text-lg">{t("comment")}</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()}>
<p className="flex items-start text-bases lg:text-lg">
{t("comment")}
</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")}
</button>
</div>
@ -212,15 +259,40 @@ const IndeksDetail = () => {
{listComments?.map((data: any) => (
<div className="flex flex-col">
<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">
<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>
{`${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()}`}
<b>
{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 className="text-slate-500 text-sm lg:text-base mb-4">{data.message}</p>
<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")}
</a>
<a
@ -240,9 +312,21 @@ const IndeksDetail = () => {
</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}`}>
<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)}>
<div
className="flex flex-row px-4 pl-[55px] lg:px-14 lg:pl-[135px] mt-2"
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")}
</a>
</div>
@ -250,24 +334,54 @@ const IndeksDetail = () => {
? data.children?.map((child1: any) => (
<div className="flex flex-col">
<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">
<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>
{`${new Date(child1.createdAt).getDate()}/${new Date(child1.createdAt).getMonth() + 1}/${new Date(child1.createdAt).getFullYear()} ${new Date(child1.createdAt).getHours()}:${new Date(
<b>
{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
).getMinutes()}`}
</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">
<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")}
</a>
<a
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"
style={
Number(child1.commentFrom?.id) == Number(userId)
Number(child1.commentFrom?.id) ==
Number(userId)
? {}
: {
display: "none",
@ -280,9 +394,21 @@ const IndeksDetail = () => {
</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}`}>
<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)}>
<div
className="flex flex-row justify-center px-4 pl-[87px] lg:px-14 lg:pl-[205px] mt-2"
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")}
</a>
</div>
@ -290,24 +416,59 @@ const IndeksDetail = () => {
? child1.children?.map((child2: any) => (
<div className="flex flex-col">
<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">
<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>
{`${new Date(child2.createdAt).getDate()}/${new Date(child2.createdAt).getMonth() + 1}/${new Date(child2.createdAt).getFullYear()} ${new Date(child2.createdAt).getHours()}:${new Date(
<b>
{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
).getMinutes()}`}
</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>
<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")}
</a>
<a
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"
style={
Number(child2.commentFrom?.id) == Number(userId)
Number(child2.commentFrom?.id) ==
Number(userId)
? {}
: {
display: "none",
@ -320,9 +481,21 @@ const IndeksDetail = () => {
</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}`}>
<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)}>
<div
className="flex flex-row px-4 pl-[120px] lg:px-14 lg:pl-[270px] mt-2"
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")}
</a>
</div>
@ -339,14 +512,25 @@ const IndeksDetail = () => {
{/* Konten Serupa */}
<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>
<CarouselContent className="w-full max-w-7xl">
{indexData?.map((relate: any) => (
<CarouselItem 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">
<CarouselItem
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
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
alt=""
width={2560}
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"
/>
<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>
<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>
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">
{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">
{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>
</div>
</Link>

View File

@ -3,7 +3,7 @@
import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "@/i18n/routing";
import { getIndeksData } from "@/service/landing/landing";
import { formatDateToIndonesian } from "@/utils/globals";
import { formatDateToIndonesian, htmlToString } from "@/utils/globals";
import Image from "next/image";
import { usePathname } from "next/navigation";
import React, { useEffect, useState } from "react";
@ -53,9 +53,11 @@ const Indeks: React.FC = () => {
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return (
<div className="px-4 lg:px-14">
@ -70,22 +72,44 @@ const Indeks: React.FC = () => {
{indeksData?.map(
(indeks: any, index: number) =>
index == count && (
<div key={indeks?.id} className="relative h-[310px] lg:h-[435px]">
<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-transparent backdrop-blur-sm 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>
<div
key={indeks?.id}
className="relative h-[310px] lg:h-[435px]"
>
<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}`}>
<h2 className="text-2xl font-bold mt-2">{indeks?.title}</h2>
<h2 className="text-2xl font-bold mt-2">
{indeks?.title}
</h2>
</Link>
<p className="text-xs flex flex-row items-center gap-1 mt-1">
{formatDateToIndonesian(new Date(indeks?.createdAt))} {indeks?.timezone ? indeks?.timezone : "WIB"} |{" "}
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
{formatDateToIndonesian(new Date(indeks?.createdAt))}{" "}
{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
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>{" "}
{indeks?.clickCount}
</svg>{" "} */}
{/* {indeks?.clickCount} */}
</p>
</div>
</div>
@ -108,22 +132,49 @@ const Indeks: React.FC = () => {
{indeksData?.map(
(indeksRight: any, index: number) =>
(index == count + 1 || index == count + 2) && (
<div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]">
<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-transparent backdrop-blur-sm 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>
<div
key={indeksRight?.id}
className="relative h-[310px] lg:h-[215px]"
>
<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}`}>
<h2 className="text-xl font-bold mt-2">{indeksRight?.title}</h2>
<h2 className="text-xl font-bold mt-2">
{indeksRight?.title}
</h2>
</Link>
<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"}|{" "}
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
{formatDateToIndonesian(
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
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>{" "}
{indeksRight?.clickCount}
{indeksRight?.clickCount} */}
</p>
</div>
</div>
@ -165,14 +216,31 @@ const Indeks: React.FC = () => {
{indeksData?.map(
(indeksBottom: any, index: number) =>
index < 3 && (
<div 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={2560} height={1440} src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" />
<div
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">
<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}
</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>
)

View File

@ -1,18 +1,50 @@
"use client";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Popover, PopoverArrow, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Popover,
PopoverArrow,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { CalendarIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { format } from "date-fns";
import { cn } from "@/lib/utils";
import { Checkbox } from "@/components/ui/checkbox";
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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import {
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 { useTranslations } from "next-intl";
import { Input } from "@/components/ui/input";
@ -292,13 +324,21 @@ const Schedule = (props: any) => {
function getLastWeek(today: Date | undefined) {
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) {
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) {
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 == 1) {
return (
<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={() => {
getItem(itemFound[0]);
}}
@ -452,7 +501,9 @@ const Schedule = (props: any) => {
<b>{`${itemFound?.length} Jadwal Bersamaan`}</b>
</p>
<DropdownMenu>
<DropdownMenuTrigger className="font-bold text-blue-300">Lihat Jadwal</DropdownMenuTrigger>
<DropdownMenuTrigger className="font-bold text-blue-300">
Lihat Jadwal
</DropdownMenuTrigger>
<DropdownMenuContent>
{itemFound?.map((list: any) => (
<DropdownMenuItem
@ -471,7 +522,10 @@ const Schedule = (props: any) => {
))}
</DropdownMenuContent>
</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>
);
}
@ -480,73 +534,121 @@ const Schedule = (props: any) => {
return (
<>
{/* Awal Komponen Kiri */}
<div className="relative px-4 lg:px-10 lg:py-10 py-4 bg-[#f7f7f7] dark:bg-slate-800">
<Popover>
<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">
<div className="relative pl-4 lg:px-8 lg:py-10 py-4 bg-[#f7f7f7] dark:bg-slate-800">
<div className="flex flex-row items-center">
<Popover>
<PopoverTrigger asChild>
<a className="text-black dark:text-white flex flex-row w-fit gap-2 py-4 items-center cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" 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>
<Button
variant={"outline"}
className={cn(
"w-[240px] py-3 justify-start text-left font-normal",
!startDate && "text-muted-foreground"
)}
>
<CalendarIcon />
{startDate ? (
format(startDate, "MMM yyyy")
) : (
<span>Pick a date</span>
)}
</Button>
</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 className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={startDate}
onSelect={(e) => {
handleChangeDate(e);
}}
initialFocus
/>
</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 className="container relative py-4 flex flex-row items-center gap-4">
<Popover>
<PopoverTrigger asChild>
<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 ">
<svg
xmlns="http://www.w3.org/2000/svg"
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 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="container-fluid relative">
<div className="grid grid-cols-1 mt-8">
@ -554,53 +656,125 @@ const Schedule = (props: any) => {
<table className="w-full text-sm text-start">
<thead className="text-md">
<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]">
<a onClick={() => changePrevWeek()} 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" />
<a
onClick={() => changePrevWeek()}
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>
</a>
</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" >
<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" />
</svg>
</a>{" "} */}
<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>
</div>
</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" : ""}`}>
<div className="text-2xl">{dateAWeek[1]?.split("-")[2]}</div>
<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"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[1]?.split("-")[2]}
</div>
{t("tuesday")}
</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" : ""}`}>
<div className="text-2xl">{dateAWeek[2]?.split("-")[2]}</div>
<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"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[2]?.split("-")[2]}
</div>
{t("wednesday")}
</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" : ""}`}>
<div className="text-2xl">{dateAWeek[3]?.split("-")[2]}</div>
<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"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[3]?.split("-")[2]}
</div>
{t("thursday")}
</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" : ""}`}>
<div className="text-2xl">{dateAWeek[4]?.split("-")[2]}</div>
<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"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[4]?.split("-")[2]}
</div>
{t("friday")}
</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" : ""}`}>
<div className="text-2xl">{dateAWeek[5]?.split("-")[2]}</div>
<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"
: ""
}`}
>
<div className="text-2xl">
{dateAWeek[5]?.split("-")[2]}
</div>
{t("saturday")}
</th>
<th
onClick={() => changeNextWeek()}
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 ">
<p className="text-2xl">{dateAWeek[6]?.split("-")[2]}</p>
<p className="text-2xl">
{dateAWeek[6]?.split("-")[2]}
</p>
<p>{t("sunday")}</p>
</div>
{/* <a className="cursor-pointer h-fit p-0 m-0 self-center">
@ -610,9 +784,20 @@ const Schedule = (props: any) => {
</a> */}
</th>
<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">
<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" />
<a
onClick={() => changeNextWeek()}
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>
</a>
</th>
@ -621,16 +806,34 @@ const Schedule = (props: any) => {
<tbody>
{timeList.map((times) => (
<tr key={times.id}>
<th className="text-center border border-gray-100 dark:border-gray-700 py-5">{times.time}</th>
<td colSpan={2} className="p-3 border border-gray-100 dark:border-gray-700 ">
<th className="text-center border border-gray-100 dark:border-gray-700 py-5">
{times.time}
</th>
<td
colSpan={2}
className="p-3 border border-gray-100 dark:border-gray-700 "
>
{setItemSchedule(times.id, dateList[0])}
</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">{setItemSchedule(times.id, dateList[2])}</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">
<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">
{setItemSchedule(times.id, dateList[2])}
</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])}
</td>
</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"
/>
<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">
<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>
</svg>
</span>
@ -700,8 +911,13 @@ const Schedule = (props: any) => {
<AccordionItem value="item-1">
<AccordionTrigger>{t("todaySchedule")}</AccordionTrigger>
{todayList?.map((list: any) => (
<AccordionContent 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>
<AccordionContent
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">
<h3 className="font-bold">{list?.title}</h3>
<p className="flex flex-row items-center gap-2">
@ -725,8 +941,13 @@ const Schedule = (props: any) => {
<AccordionItem value="item-2">
<AccordionTrigger>{t("previousSchedule")}</AccordionTrigger>
{prevdayList?.map((list: any) => (
<AccordionContent 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>
<AccordionContent
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">
<h3 className="font-bold">{list?.title}</h3>
<p className="flex flex-row items-center gap-2">
@ -750,8 +971,13 @@ const Schedule = (props: any) => {
<AccordionItem value="item-3">
<AccordionTrigger>{t("nextSchedule")}</AccordionTrigger>
{nextdayList?.map((list: any) => (
<AccordionContent 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>
<AccordionContent
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">
<h3 className="font-bold">{list?.title}</h3>
<p className="flex flex-row items-center gap-2">
@ -846,13 +1072,17 @@ const Schedule = (props: any) => {
<AlertDialogContent>
<AlertDialogHeader>
<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>
</AlertDialogTitle>
<AlertDialogDescription>
<p className="flex flex-row items-center gap-2">
<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>
</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="max-w-[520px] pt-16 ps-20 ">
<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>
</div>
<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 className="flex-1 relative">
@ -40,12 +52,12 @@ const Login = ({ params: { locale } }: { params: { locale: string } }) => {
<Logo />
</Link>
</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>
<div className="text-default-500 text-base">
{t("acc")} <span className="text-red-500">{t("reg")}</span>
</div>
</div>
</div> */}
<LoginForm />
{/* <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">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,7 @@ import { getCookiesDecrypt } from "@/lib/utils";
import { Icon } from "@iconify/react/dist/iconify.js";
import { error } from "@/lib/swal";
import dynamic from "next/dynamic";
import { useTranslations } from "next-intl";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -133,6 +134,7 @@ export default function FormTeksDetail() {
const [detailThumb, setDetailThumb] = useState<any>([]);
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
const t = useTranslations("Form");
const [selectedTarget, setSelectedTarget] = useState("");
const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
@ -421,13 +423,11 @@ export default function FormTeksDetail() {
<div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">
Form detail Konten Teks
</p>
<p className="text-lg font-semibold mb-3">{t("form-text")}</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
<Label>Judul</Label>
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
@ -448,8 +448,8 @@ export default function FormTeksDetail() {
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full">
<Label>Kategori</Label>
<div className="py-3 w-full space-y-2">
<Label>{t("category")}</Label>
<Select
value={detail?.category.name} // Nilai default berdasarkan detail
onValueChange={(id) => {
@ -471,8 +471,8 @@ export default function FormTeksDetail() {
</div>
</div>
<div className="py-3">
<Label>Deskripsi</Label>
<div className="py-3 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="description"
@ -486,81 +486,85 @@ export default function FormTeksDetail() {
</p>
)}
</div>
<Label className="text-xl text-black">File Media</Label>
<div className="w-full">
<Swiper
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 ">
<div className="space-y-2">
<Label className="text-xl">{t("file-media")} </Label>
<div className="w-full">
<Swiper
onSwiper={setThumbsSwiper}
slidesPerView={8}
spaceBetween={8}
pagination={{ clickable: true }}
modules={[Pagination, Thumbs]}
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-cover h-[60px] w-[80px]"
className="object-fill h-full w-full rounded-md"
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">
{data?.format?.replace(".", "").toUpperCase()}
</div>
// 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
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>
@ -570,7 +574,7 @@ export default function FormTeksDetail() {
<Card className=" h-[800px]">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Kreator</Label>
<Label>{t("creator")}</Label>
<Controller
control={control}
name="creatorName"
@ -603,7 +607,7 @@ export default function FormTeksDetail() {
</div> */}
<div className="px-3 py-3">
<div className="space-y-2">
<Label>Tag</Label>
<Label>{t("tags")}</Label>
<div className="flex flex-wrap gap-2">
{detail?.tags
?.split(",")
@ -619,8 +623,8 @@ export default function FormTeksDetail() {
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-6">
<Label>Target Publish</Label>
<div className="flex flex-col gap-6 space-y-2">
<Label>{t("publish-target")}</Label>
<div className="flex gap-2 items-center">
<Checkbox
id="5"
@ -657,10 +661,10 @@ export default function FormTeksDetail() {
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon />
<p className="">Kotak Saran (0)</p>
<p className="">{t("suggestion-box")} (0)</p>
</div>
<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>
</div>
{/* {detail?.isPublish == false ? (
@ -680,14 +684,15 @@ export default function FormTeksDetail() {
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" /> Setujui
<Icon icon="fa:check" className="mr-3" /> {t("accept")}
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revisi
<Icon icon="fa:comment-o" className="mr-3" />{" "}
{t("revision")}
</Button>
<Button
onClick={() => actionApproval("4")}
@ -695,7 +700,7 @@ export default function FormTeksDetail() {
type="button"
>
<Icon icon="fa:times" className="mr-3" />
Tolak
{t("reject")}
</Button>
</div>
)
@ -706,7 +711,7 @@ export default function FormTeksDetail() {
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>Berikan Komentar</DialogTitle>
<DialogTitle>{t("leave-comment")}</DialogTitle>
</DialogHeader>
{status == "2"
? files?.map((file, index) => (
@ -743,7 +748,7 @@ export default function FormTeksDetail() {
htmlFor="terms"
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Semua
{t("all")}
</label>
</div>
<div className="flex items-center space-x-2">
@ -888,7 +893,7 @@ export default function FormTeksDetail() {
color="primary"
onClick={() => submit()}
>
Submit
{t("submit")}
</Button>
<Button
type="button"
@ -897,7 +902,7 @@ export default function FormTeksDetail() {
setModalOpen(false);
}}
>
Cancel
{t("cancel")}
</Button>
</DialogFooter>
</DialogContent>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
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 { Calendar } from "@/components/ui/calendar";
import { DateRange } from "react-day-picker";
@ -898,10 +898,10 @@ export default function FormContestDetail() {
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */}
<div className="mt-4">
<h2 className="text-lg font-bold">Link Berita</h2>
<Label className="">Link Berita</Label>
{links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2">
<input
<Input
type="url"
className="border rounded p-2 w-full"
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"
onClick={() => handleRemoveRow(index)}
>
Hapus
<Trash2 className="h-4 w-4" />
</button>
)}
</div>
))}
<button
<Button
type="button"
size="md"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
>
Tambah Link
</button>
</Button>
</div>
</div>
</div>

View File

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

View File

@ -33,6 +33,8 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useTranslations } from "next-intl";
import dynamic from "next/dynamic";
const taskSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
@ -55,6 +57,13 @@ export type medsosDetail = {
is_active: string;
};
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function PublishMedsos() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -69,6 +78,7 @@ export default function PublishMedsos() {
image: false,
text: false,
});
const t = useTranslations("Form");
const [mainType, setMainType] = useState<number>(1);
const [taskType, setTaskType] = useState<string>("atensi-khusus");
const [broadcastType, setBroadcastType] = useState<string>("all"); // untuk Tipe Penugasan
@ -297,13 +307,13 @@ export default function PublishMedsos() {
return (
<Card>
<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 ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>Judul</Label>
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
@ -322,7 +332,7 @@ export default function PublishMedsos() {
)}
</div>
<div className="mt-6">
<Label>Output Tugas</Label>
<Label>{t("output-tugas")}</Label>
<div className="flex flex-wrap gap-3 mt-1">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
@ -340,8 +350,8 @@ export default function PublishMedsos() {
))}
</div>
</div>
<div className="mt-6">
<Label>Pelaksana Tugas</Label>
<div className="mt-6 space-y-2">
<Label>{t("executive-task")}</Label>
</div>
<div className="flex flex-row items-center gap-3">
<div>
@ -367,7 +377,7 @@ export default function PublishMedsos() {
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Kustom]
[{t("custom")}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
@ -444,8 +454,8 @@ export default function PublishMedsos() {
</DialogContent>
</Dialog>
</div>
<div className="mt-6">
<Label>Jenis Penugasan</Label>
<div className="mt-6 space-y-2">
<Label>{t("assignment-type")}</Label>
<RadioGroup
value={detail.assignmentType.id.toString()} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
@ -465,9 +475,9 @@ export default function PublishMedsos() {
</div>
</RadioGroup>
</div>
<div className="mt-6">
<div className="mt-6 space-y-2">
<div className="flex flex-col">
<Label>Date</Label>
<Label>{t("date")}</Label>
<div>
<Button
value={detail.date}
@ -488,17 +498,15 @@ export default function PublishMedsos() {
</div>
</div>
</div>
<div className="mt-6">
<Label>Narasi Penugasan</Label>
<div className="mt-6 space-y-2">
<Label>{t("description-task")}</Label>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={detail?.description}
<CustomEditor
onChange={onChange}
className="dark:text-black"
initialData={detail?.description || value}
/>
)}
/>
@ -513,7 +521,7 @@ export default function PublishMedsos() {
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
Submit
{t("submit")}
</Button>
</div>
</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">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -245,11 +245,11 @@ export default function FormEventDetail() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -276,7 +276,7 @@ export default function FormEventDetail() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div>
<div className="flex flex-row items-center">
@ -326,7 +326,7 @@ export default function FormEventDetail() {
DI SAMPAIKAN OLEH
</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -349,7 +349,7 @@ export default function FormEventDetail() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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 ">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -242,11 +242,11 @@ export default function FormEvent() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -273,7 +273,7 @@ export default function FormEvent() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div className="">
<div className="flex flex-row items-center">
@ -321,7 +321,7 @@ export default function FormEvent() {
</div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -344,7 +344,7 @@ export default function FormEvent() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -258,11 +258,11 @@ export default function FormEventUpdate() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -289,7 +289,7 @@ export default function FormEventUpdate() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div>
<div className="flex flex-row items-center">
@ -337,7 +337,7 @@ export default function FormEventUpdate() {
</div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -360,7 +360,7 @@ export default function FormEventUpdate() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -255,11 +255,11 @@ export default function FormDetailPressRillis() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -286,7 +286,7 @@ export default function FormDetailPressRillis() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div>
<div className="flex flex-row items-center">
@ -332,7 +332,7 @@ export default function FormDetailPressRillis() {
{errors.location?.message}
</div>
</div>
<div className="mt-6">
<div className="mt-6 space-y-2">
<Label>Invitation</Label>
<Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md">
@ -353,7 +353,7 @@ export default function FormDetailPressRillis() {
DI SAMPAIKAN OLEH
</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -376,7 +376,7 @@ export default function FormDetailPressRillis() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -265,11 +265,11 @@ export default function FormUpdatePressRelease() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -296,7 +296,7 @@ export default function FormUpdatePressRelease() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div>
<div className="flex flex-row items-center">
@ -342,7 +342,7 @@ export default function FormUpdatePressRelease() {
{errors.location?.message}
</div>
</div>
<div className="mt-5">
<div className="mt-5 space-y-2">
<Label>Invitation</Label>
<Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md">
@ -361,7 +361,7 @@ export default function FormUpdatePressRelease() {
</div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -384,7 +384,7 @@ export default function FormUpdatePressRelease() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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 ">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -242,11 +242,11 @@ export default function FormPressRelease() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -273,7 +273,7 @@ export default function FormPressRelease() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div className="">
<div className="flex flex-row items-center">
@ -319,7 +319,7 @@ export default function FormPressRelease() {
{errors.location?.message}
</div>
</div>
<div className="mt-5">
<div className="mt-5 space-y-2">
<Label>Invitation</Label>
<Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md">
@ -338,7 +338,7 @@ export default function FormPressRelease() {
</div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -361,7 +361,7 @@ export default function FormPressRelease() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -202,11 +202,11 @@ export default function FormDetailPressConference() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -233,7 +233,7 @@ export default function FormDetailPressConference() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div>
<div className="flex flex-row items-center">
@ -283,7 +283,7 @@ export default function FormDetailPressConference() {
DI SAMPAIKAN OLEH
</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -306,7 +306,7 @@ export default function FormDetailPressConference() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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 ">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
<PopoverTrigger asChild className="px-0">
<Button
size="md"
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -247,7 +247,7 @@ export default function FormPressConference() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div className="">
<div className="flex flex-row items-center">
@ -295,7 +295,7 @@ export default function FormPressConference() {
</div>
<p className="text-sm mt-4 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -318,7 +318,7 @@ export default function FormPressConference() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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">
<div className="flex flex-col space-y-2">
<Label className="mr-3 mb-1">Tanggal</Label>
<Popover>
<PopoverTrigger asChild>
@ -262,11 +262,11 @@ export default function FormUpdatePressConference() {
id="date"
variant={"outline"}
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"
)}
>
<CalendarIcon />
<CalendarIcon size={15} className="mr-3" />
{date?.from ? (
date.to ? (
<>
@ -293,7 +293,7 @@ export default function FormUpdatePressConference() {
</PopoverContent>
</Popover>
</div>
<div>
<div className="space-y-2">
<Label htmlFor="title">Rentang Waktu</Label>
<div>
<div className="flex flex-row items-center">
@ -343,7 +343,7 @@ export default function FormUpdatePressConference() {
</div>
<p className="text-sm my-2 font-semibold">DI SAMPAIKAN OLEH</p>
<div className="flex flex-col ">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Pangkat</Label>
<Controller
control={control}
@ -366,7 +366,7 @@ export default function FormUpdatePressConference() {
</div>
</div>
<div className="flex flex-col my-3">
<div className="mt-1">
<div className="mt-1 space-y-2">
<Label>Nama Lengkap</Label>
<Controller
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 WaveSurfer from "wavesurfer.js";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import { useTranslations } from "next-intl";
const taskSchema = z.object({
uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
@ -206,6 +207,7 @@ export default function FormTaskDetail() {
text: false,
});
const t = useTranslations("Form");
const [uploadResults, setUploadResults] = useState<UploadResult[]>([]);
const [isTableResult, setIsTableResult] = useState(false);
const [isSentResult] = useState(false);
@ -812,7 +814,7 @@ export default function FormTaskDetail() {
{detail !== undefined ? (
<div className="px-6 py-6">
<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
className="flex gap-3"
style={
@ -830,7 +832,7 @@ export default function FormTaskDetail() {
color="primary"
onClick={() => setModalType("terkirim")}
>
{sentAcceptance?.length} Terkirim
{sentAcceptance?.length} {t("sent")}
</Button>
</DialogTrigger>
@ -840,13 +842,15 @@ export default function FormTaskDetail() {
onClick={() => setModalType("diterima")}
className="ml-3"
>
{acceptAcceptance?.length} Diterima
{acceptAcceptance?.length} {t("accepted")}
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
<DialogHeader>
<DialogTitle>Detail Status Penugasan</DialogTitle>
<DialogTitle>
{t("assignment-status-details")}
</DialogTitle>
</DialogHeader>
{modalType === "terkirim" && getModalContent("terkirim")}
@ -860,7 +864,7 @@ export default function FormTaskDetail() {
<form>
<div className="gap-5 mb-5">
<div className="space-y-2">
<Label>Kode Unik</Label>
<Label>{t("unique-code")}</Label>
<Controller
control={control}
name="uniqueCode"
@ -877,7 +881,7 @@ export default function FormTaskDetail() {
/>
</div>
<div className="space-y-2 mt-6">
<Label>Judul</Label>
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
@ -896,8 +900,8 @@ export default function FormTaskDetail() {
)}
</div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-6">
<Label>Tujuan Pemilihan Tugas</Label>
<div className="mt-6 space-y-2">
<Label>{t("assignment-selection")}</Label>
<Select
onValueChange={setSelectedTarget}
value={detail.assignedToRole}
@ -935,7 +939,7 @@ export default function FormTaskDetail() {
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Kustom]
[{t("custom")}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
@ -1018,8 +1022,8 @@ export default function FormTaskDetail() {
</Dialog>
</div>
</div>
<div className="mt-6">
<Label>Tipe Penugasan</Label>
<div className="mt-6 space-y-2">
<Label>{t("type-task")}</Label>
<RadioGroup
value={detail.assignmentMainType.id.toString()}
onValueChange={(value) => setMainType(value)}
@ -1033,8 +1037,8 @@ export default function FormTaskDetail() {
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup>
</div>
<div className="mt-6">
<Label>Jenis Tugas </Label>
<div className="mt-6 space-y-2">
<Label>{t("assigment-type")} </Label>
<RadioGroup
value={detail.taskType.toString()}
onValueChange={(value) => setTaskType(String(value))}
@ -1047,8 +1051,8 @@ export default function FormTaskDetail() {
</RadioGroup>
</div>
{/* RadioGroup Assignment Category */}
<div className="mt-6">
<Label>Jenis Penugasan</Label>
<div className="mt-6 space-y-2">
<Label>{t("type-of-task")}</Label>
<RadioGroup
value={detail.assignmentType.id.toString()}
onValueChange={(value) => setType(value)}
@ -1068,8 +1072,8 @@ export default function FormTaskDetail() {
</div>
</RadioGroup>
</div>
<div className="mt-6">
<Label>Output Tugas</Label>
<div className="mt-6 space-y-2">
<Label>{t("output-task")}</Label>
<div className="flex flex-wrap gap-3">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
@ -1089,8 +1093,8 @@ export default function FormTaskDetail() {
</div>
</div>
<div className="mt-6">
<Label>Narasi Penugasan</Label>
<div className="mt-6 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="naration"
@ -1104,11 +1108,13 @@ export default function FormTaskDetail() {
</p>
)} */}
</div>
<div className="space-y-1.5 mt-5">
<Label htmlFor="attachment">Lampiran</Label>
<div className=" mt-5 space-y-2">
<Label htmlFor="attachment">{t("attachment")}</Label>
<div className="space-y-3">
<div>
{videoUploadedFiles?.length > 0 && <Label>Video</Label>}
{videoUploadedFiles?.length > 0 && (
<Label>{t("audio-visual")}</Label>
)}
<div>
{selectedVideo && (
<Card className="mt-2">
@ -1154,7 +1160,9 @@ export default function FormTaskDetail() {
</div>
</div>
<div>
{imageUploadedFiles?.length > 0 && <Label>Foto</Label>}
{imageUploadedFiles?.length > 0 && (
<Label>{t("image")}</Label>
)}
<div>
{selectedImage && (
<Card className="mt-2">
@ -1200,7 +1208,9 @@ export default function FormTaskDetail() {
</div>
</div>
<div>
{textUploadedFiles?.length > 0 && <Label>Teks</Label>}
{textUploadedFiles?.length > 0 && (
<Label>{t("text")}</Label>
)}
<div>
{selectedText && (
<Card className="mt-2">
@ -1247,7 +1257,9 @@ export default function FormTaskDetail() {
</div>
</div>
<div>
{audioUploadedFiles?.length > 0 && <Label>Audio</Label>}
{audioUploadedFiles?.length > 0 && (
<Label>{t("audio")}</Label>
)}
<div>
{selectedAudio && (
<Card className="mt-2">

View File

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

View File

@ -32,13 +32,15 @@ import {
DialogTitle,
DialogTrigger,
} 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 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" }),
@ -73,6 +75,13 @@ export type taskDetail = {
is_active: string;
};
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
export default function FormTask() {
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -107,6 +116,7 @@ export default function FormTask() {
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[]>([]);
@ -186,6 +196,63 @@ export default function FormTask() {
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 fileTypeMapping = {
all: "1",
@ -478,13 +545,13 @@ export default function FormTask() {
return (
<Card>
<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)}>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2">
<Label>Judul</Label>
<Label>{t("title")}</Label>
<Controller
control={control}
name="title"
@ -503,11 +570,11 @@ export default function FormTask() {
)}
</div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-5">
<Label>Tujuan Pemilihan Tugas</Label>
<div className="mt-5 space-y-2">
<Label>{t("assignment-selection")}</Label>
<Select onValueChange={setSelectedTarget}>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
<SelectValue placeholder="Choose" />
</SelectTrigger>
<SelectContent>
<SelectItem value="3,4">Semua Pengguna</SelectItem>
@ -516,14 +583,17 @@ export default function FormTask() {
</SelectContent>
</Select>
</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) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={unitSelection[key as keyof typeof unitSelection]}
onCheckedChange={(value) =>
setUnitSelection({ ...unitSelection, [key]: value })
handleUnitChange(
key as keyof typeof unitSelection,
value as boolean
)
}
/>
<Label htmlFor={key}>
@ -532,11 +602,11 @@ export default function FormTask() {
</div>
))}
</div>
<div className="mt-6 lg:pt-5 lg:pl-3">
<div className="mt-6 lg:pt-6 lg:pl-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[Kustom]
[{t("custom")}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
@ -614,10 +684,10 @@ export default function FormTask() {
</Dialog>
</div>
</div>
<div className="mt-5">
<Label>Tipe Penugasan</Label>
<div className="mt-5 space-y-2">
<Label>{t("type-task")}</Label>
<RadioGroup
value={mainType} // State yang dipetakan ke value RadioGroup
value={mainType}
onValueChange={(value) => setMainType(value)}
// value={String(mainType)}
// onValueChange={(value) => setMainType(Number(value))}
@ -629,8 +699,8 @@ export default function FormTask() {
<Label htmlFor="medsos-mediahub">Medsos Mediahub</Label>
</RadioGroup>
</div>
<div className="mt-5">
<Label>Jenis Tugas </Label>
<div className="mt-5 space-y-2">
<Label>{t("assigment-type")} </Label>
<RadioGroup
value={taskType}
onValueChange={(value) => setTaskType(String(value))}
@ -643,8 +713,8 @@ export default function FormTask() {
</RadioGroup>
</div>
{/* RadioGroup Assignment Category */}
<div className="mt-5">
<Label>Jenis Penugasan</Label>
<div className="mt-5 space-y-2">
<Label>{t("type-of-task")}</Label>
<RadioGroup
value={type} // State yang dipetakan ke value RadioGroup
onValueChange={(value) => setType(value)} // Mengubah nilai state ketika pilihan berubah
@ -664,16 +734,19 @@ export default function FormTask() {
</div>
</RadioGroup>
</div>
<div className="mt-5">
<Label>Output Tugas</Label>
<div className="flex flex-wrap gap-3">
<div className="mt-5 space-y-2">
<Label>{t("output-task")}</Label>
<div className="flex flex-wrap gap-4">
{Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}>
<Checkbox
id={key}
checked={taskOutput[key as keyof typeof taskOutput]}
onCheckedChange={(value) =>
setTaskOutput({ ...taskOutput, [key]: value })
handleTaskOutputChange(
key as keyof typeof taskOutput,
value as boolean
)
}
/>
<Label htmlFor={key}>
@ -704,18 +777,13 @@ export default function FormTask() {
</div>
</RadioGroup>
</div> */}
<div className="mt-5">
<Label>Narasi Penugasan</Label>
<div className="mt-5 space-y-2">
<Label>{t("description")}</Label>
<Controller
control={control}
name="naration"
render={({ field: { onChange, value } }) => (
<JoditEditor
ref={editor}
value={value}
onChange={onChange}
className="dark:text-black"
/>
<CustomEditor onChange={onChange} initialData={value} />
)}
/>
{errors.naration?.message && (
@ -724,11 +792,11 @@ export default function FormTask() {
</p>
)}
</div>
<div className="space-y-1.5 mt-5">
<Label htmlFor="attachments">Lampiran</Label>
<div className="space-y-2.5 mt-5">
<Label htmlFor="attachments">{t("attachment")}</Label>
<div className="space-y-3">
<div>
<Label>Video</Label>
<Label>{t("audio-visual")}</Label>
<FileUploader
accept={{
"mp4/*": [],
@ -739,8 +807,8 @@ export default function FormTask() {
onDrop={(files) => setVideoFiles(files)}
/>
</div>
<div>
<Label>Foto</Label>
<div className="space-y-2">
<Label>{t("image")}</Label>
<FileUploader
accept={{
"image/*": [],
@ -750,8 +818,8 @@ export default function FormTask() {
onDrop={(files) => setImageFiles(files)}
/>
</div>
<div>
<Label>Teks</Label>
<div className="space-y-2">
<Label>{t("text")}</Label>
<FileUploader
accept={{
"pdf/*": [],
@ -761,8 +829,8 @@ export default function FormTask() {
onDrop={(files) => setTextFiles(files)}
/>
</div>
<div>
<Label>Audio</Label>
<div className="space-y-2">
<Label>{t("audio")}</Label>
<AudioRecorder
onRecordingComplete={addAudioElement}
audioTrackConstraints={{
@ -790,7 +858,7 @@ export default function FormTask() {
key={idx}
className="flex flex-row justify-between items-center"
>
<p>Voice Note</p>
<p>{t("voice-note")}</p>
<Button
type="button"
onClick={() => handleDeleteAudio(idx)}
@ -803,11 +871,11 @@ export default function FormTask() {
))}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */}
<div className="mt-4">
<h2 className="text-lg font-bold">Link Berita</h2>
<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
<Input
type="url"
className="border rounded p-2 w-full"
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"
onClick={() => handleRemoveRow(index)}
>
Hapus
<Trash2 className="h-4 w-4" />
</button>
)}
</div>
))}
<button
<Button
type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
size="sm"
>
Tambah Link
</button>
{t("add-links")}
</Button>
</div>
</div>
</div>
@ -842,7 +911,7 @@ export default function FormTask() {
{/* Submit Button */}
<div className="mt-4">
<Button type="submit" color="primary">
Submit
{t("submit")}
</Button>
</div>
</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 React, { useEffect, useState } from "react";
import { Button } from "../ui/button";
@ -21,7 +24,15 @@ const ContentCategory = (props: { group?: string }) => {
}, []);
const initFetch = async () => {
const response = await getPublicCategoryData(
props.group == "mabes" ? "" : props.group == "polda" && poldaName && String(poldaName)?.length > 1 ? poldaName : props.group == "satker" && satkerName && String(satkerName)?.length > 1 ? "satker-" + satkerName : "",
props.group == "mabes"
? ""
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
? poldaName
: props.group == "satker" &&
satkerName &&
String(satkerName)?.length > 1
? "satker-" + satkerName
: "",
"",
locale == "en" ? true : false
);
@ -47,7 +58,10 @@ const ContentCategory = (props: { group?: string }) => {
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return (
<div className="px-4 lg:px-24 py-10">
@ -84,10 +98,16 @@ const ContentCategory = (props: { group?: string }) => {
// <h3 className="text-sm font-semibold truncate">{category?.name}</h3>
// </div>
// </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 */}
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
alt="category"
width={2560}
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>
{/* 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">
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
<div className="absolute bottom-5 left-0 right-16 bg-transparent backdrop-blur-md text-white p-4 border-l-2 border-[#bb3523] z-10 group-hover:scale-x-150 origin-left">
<h3 className="text-sm font-semibold truncate">
{category?.name}
</h3>
</div>
</Link>
) : (
@ -120,10 +142,16 @@ const ContentCategory = (props: { group?: string }) => {
// <h3 className="text-sm font-semibold truncate">{category?.name}</h3>
// </div>
// </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 */}
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
alt="category"
width={2560}
height={1440}
@ -136,14 +164,19 @@ const ContentCategory = (props: { group?: string }) => {
{/* 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">
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
<h3 className="text-sm font-semibold truncate">
{category?.name}
</h3>
</div>
</Link>
)
)}
</div>
<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")}
</Button>
</div>

View File

@ -28,23 +28,33 @@ const Footer = () => {
return (
<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 */}
<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>
{/* Menu Links */}
<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")}
</Link>
<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")}
</Link>
<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
</Link>
@ -55,12 +65,17 @@ const Footer = () => {
{t("privacy")}
</a>
</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">
<img src="/assets/icon-privacy.png" alt="Privacy" />
<p className="font-semibold text-lg">{t("privacy")}</p>
</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>
</Dialog>
</div>
@ -69,13 +84,25 @@ const Footer = () => {
<div className="flex justify-center items-center space-x-3">
<span className="text-sm">Follow Us:</span>
<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 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 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 href="#" aria-label="TikTok">
<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 Link from "next/link";
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 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 router = useRouter();
@ -16,6 +379,9 @@ const Hero: React.FC = () => {
const locale = params?.locale;
const [isLoading, setIsLoading] = useState<any>(true);
const [heroData, setHeroData] = useState<any>();
const [showModal, setShowModal] = useState(false);
const [showSurveyModal, setShowSurveyModal] = useState(false);
const [showFormModal, setShowFormModal] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
@ -25,6 +391,29 @@ const Hero: React.FC = () => {
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(() => {
async function fetchCategories() {
const url = "https://netidhub.com/api/csrf";
@ -37,7 +426,7 @@ const Hero: React.FC = () => {
}
const data = await response.json();
return data; // Menampilkan data yang diterima dari API
return data;
} catch (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" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
return (
<div className="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 ? (
<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" />
@ -85,30 +491,44 @@ const Hero: React.FC = () => {
<CarouselContent>
{heroData?.map((list: any) => (
<CarouselItem key={list?.id}>
<div className="relative h-[310px] lg:h-[420px]">
<div className="relative h-[310px] lg:h-[460px] mt-1">
<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"
placeholder={`data:image/svg+xml;base64,${toBase64(
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">
<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 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 text-slate-500 dark:text-white font-bold mt-2">{list?.title}</h2>
<h2 className="text-lg leading-tight">{list?.title}</h2>
</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"}|{" "}
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24">
<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}{" "}
</svg>
{list?.clickCount}
</p>
</div>
</div>
@ -120,7 +540,6 @@ const Hero: React.FC = () => {
</Carousel>
)}
{/* Section Kanan */}
<div>
{isLoading ? (
<>
@ -161,30 +580,157 @@ const Hero: React.FC = () => {
</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">
{heroData?.map((item: any) => (
<li key={item?.id} className="flex gap-4 flex-row lg:w-full ">
<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"
<ul className="py-4 lg:py-0 flex flex-row lg:flex-col gap-4 flex-nowrap w-[95vw] lg:w-auto">
<Tabs defaultValue="national" className="w-[350px]">
<TabsList className="grid w-full grid-cols-3 border">
<TabsTrigger value="national">Nasional</TabsTrigger>
<TabsTrigger value="polda">Polda</TabsTrigger>
<TabsTrigger value="satker">Satker</TabsTrigger>
</TabsList>
<TabsContent value="national">
{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"
/>
</svg>{" "}
{item?.clickCount}
</p>
</div>
</li>
))}
</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="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>
)}
</div>

View File

@ -75,7 +75,7 @@ const HeaderBannerKaltara = () => {
<>
<Reveal>
{/* 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 ? (
<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" />

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