Compare commits
118 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9223122e66 | |
|
|
9e286ffd06 | |
|
|
18d6f32536 | |
|
|
9c2b3612c6 | |
|
|
6bd83237c4 | |
|
|
61df236e13 | |
|
|
7f9166b866 | |
|
|
2876e4eb30 | |
|
|
292cde6ed0 | |
|
|
3c5e644668 | |
|
|
d80adaa133 | |
|
|
eefa43a5b0 | |
|
|
277f1cc805 | |
|
|
f357a4e8ce | |
|
|
bb91379058 | |
|
|
740d73d689 | |
|
|
76c4ed9238 | |
|
|
44c6cc6d9d | |
|
|
2b8f7b724e | |
|
|
10694547d1 | |
|
|
e5e32496ab | |
|
|
78d1fc0d93 | |
|
|
c99e1a5c7d | |
|
|
274fd022e1 | |
|
|
933a672280 | |
|
|
ce7c343808 | |
|
|
cba94cf0a9 | |
|
|
7c721dcc96 | |
|
|
78ddd461df | |
|
|
ec2d7eb203 | |
|
|
ad5d048e78 | |
|
|
5614b5ada5 | |
|
|
f1b59ee537 | |
|
|
0ad1acee09 | |
|
|
2d09af2e8b | |
|
|
edafc223db | |
|
|
933cdb1100 | |
|
|
280ea508e9 | |
|
|
1a0d53bb11 | |
|
|
be1c799629 | |
|
|
3d23d75e05 | |
|
|
1ee379df71 | |
|
|
9a1b41e90b | |
|
|
fcfa24b06c | |
|
|
cf91ea33ec | |
|
|
1cc7786dee | |
|
|
f1ecebfb0f | |
|
|
76b7b4b86e | |
|
|
3698996c0c | |
|
|
3349450cb9 | |
|
|
80d07d358f | |
|
|
f9417b63ae | |
|
|
8f494739c6 | |
|
|
dd94afc81d | |
|
|
4c32b76ea3 | |
|
|
d21d377e17 | |
|
|
f90c9f73e1 | |
|
|
3c14fd2358 | |
|
|
6951ccd137 | |
|
|
cd822a7b5d | |
|
|
68b9439f23 | |
|
|
6afd7d1183 | |
|
|
1f7b912b52 | |
|
|
ae61d19a61 | |
|
|
3b0bf64e39 | |
|
|
0c5a7f86bb | |
|
|
15a395ccd2 | |
|
|
c71532cdd5 | |
|
|
eaa9001c98 | |
|
|
a08841b44c | |
|
|
e5704c6bec | |
|
|
5b6ffd0863 | |
|
|
eaa1628fde | |
|
|
dbabd36687 | |
|
|
618e5f4edb | |
|
|
5c7b8b7a34 | |
|
|
78518f038e | |
|
|
9bbbf4e45e | |
|
|
d730cc9cf9 | |
|
|
30235c240a | |
|
|
f7b1b79c08 | |
|
|
a69d3afa72 | |
|
|
f5802ff248 | |
|
|
16b5bb9fa0 | |
|
|
6b72b3e606 | |
|
|
abb5e0370f | |
|
|
6a76f2c0c2 | |
|
|
2f7bccfea9 | |
|
|
83cf910a34 | |
|
|
587f221fe1 | |
|
|
c662f3c866 | |
|
|
0d1f8041a9 | |
|
|
0757ec6519 | |
|
|
ecf4a64b03 | |
|
|
a60e661a6e | |
|
|
942b00521c | |
|
|
fd6ffda5a3 | |
|
|
d7e4200f3c | |
|
|
c6e0e123c3 | |
|
|
92811802cc | |
|
|
838b492768 | |
|
|
3e920e9108 | |
|
|
405455ea1f | |
|
|
38897c1cc1 | |
|
|
ff44a3b837 | |
|
|
4a5bce9c24 | |
|
|
5343e67f7d | |
|
|
399eef865f | |
|
|
92e300a0cf | |
|
|
b163084dfe | |
|
|
745c0b72f2 | |
|
|
c65afe8ced | |
|
|
94535fa64e | |
|
|
a96ab8590a | |
|
|
5e82ee8085 | |
|
|
b34a3d684b | |
|
|
d647025a42 | |
|
|
c661f8bbd2 |
|
|
@ -0,0 +1,45 @@
|
|||
kind: pipeline
|
||||
type: ssh
|
||||
name: mediahub-fe-build-deploy
|
||||
|
||||
server:
|
||||
host:
|
||||
from_secret: ssh_host
|
||||
user:
|
||||
from_secret: ssh_user
|
||||
ssh_key:
|
||||
from_secret: ssh_key
|
||||
|
||||
steps:
|
||||
- name: prepare repo
|
||||
when:
|
||||
branch:
|
||||
- dev-sabda-v2
|
||||
- main
|
||||
- prod
|
||||
commands:
|
||||
- rm -rf /opt/build/mediahub-fe
|
||||
- mkdir -p /opt/build
|
||||
- cd /opt/build
|
||||
- git clone http://38.47.180.165:3000/mediahub/mediahub-fe.git
|
||||
|
||||
- name: build image
|
||||
when:
|
||||
branch:
|
||||
- dev-sabda-v2
|
||||
- prod
|
||||
commands:
|
||||
- docker login 38.47.180.165:3000 -u administrator -p HarborDockerImageRep0
|
||||
- cd /opt/build/mediahub-fe
|
||||
- docker build -t 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH .
|
||||
- docker push 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH
|
||||
|
||||
- name: deploy
|
||||
when:
|
||||
branch:
|
||||
- prod
|
||||
commands:
|
||||
- docker pull 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH
|
||||
- docker stop new-mediahub-fe || true
|
||||
- docker rm new-mediahub-fe || true
|
||||
- docker run -dt -p 4200:3000 --restart always --name new-mediahub-fe 38.47.180.165:3000/mediahub/mediahub-fe:$DRONE_BRANCH
|
||||
4
.env
4
.env
|
|
@ -1,3 +1,3 @@
|
|||
NEXT_PUBLIC_API=https://netidhub.com/api
|
||||
NEXT_PUBLIC=https://netidhub.com
|
||||
NEXT_PUBLIC_API=https://new.netidhub.com/api
|
||||
NEXT_PUBLIC=https://new.netidhub.com
|
||||
NEXT_PUBLIC_TINYMCE_API_KEY=bhteuja26yz5p0aubxry9b95hs33amgn65kjv5km0fd5iuev
|
||||
|
|
@ -1,22 +1,23 @@
|
|||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
|
||||
build-dev:
|
||||
stage: build
|
||||
when: on_success
|
||||
only:
|
||||
- main
|
||||
- dev-landing-v2
|
||||
image: docker:stable
|
||||
image:
|
||||
name: docker:25.0.3-cli
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--insecure-registry=103.82.242.92:8900"]
|
||||
- name: docker:25.0.3-dind
|
||||
command: ["--insecure-registry=38.47.185.86:8900"]
|
||||
script:
|
||||
- docker logout
|
||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
|
||||
- docker build -t 103.82.242.92:8900/mediahub/new-mediahub-fe:dev .
|
||||
- docker push 103.82.242.92:8900/mediahub/new-mediahub-fe:dev
|
||||
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900
|
||||
- docker build -t 38.47.185.86:8900/mediahub/new-mediahub-fe:dev .
|
||||
- docker push 38.47.185.86:8900/mediahub/new-mediahub-fe:dev
|
||||
|
||||
auto-deploy:
|
||||
stage: deploy
|
||||
|
|
@ -25,7 +26,5 @@ auto-deploy:
|
|||
- main
|
||||
- dev-landing-v2
|
||||
image: curlimages/curl:latest
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- curl --user admin:$JENKINS_PWD http://38.47.180.165:8080/job/auto-deploy-new-mediahub-fe/build?token=autodeploynewmediahub
|
||||
- curl --user admin:$JENKINS_PWD http://38.47.185.86:8080/job/auto-deploy-new-mediahub-fe/build?token=autodeploynewmediahub
|
||||
|
|
|
|||
39
Dockerfile
39
Dockerfile
|
|
@ -32,3 +32,42 @@ EXPOSE 3000
|
|||
|
||||
# Perintah untuk menjalankan aplikasi
|
||||
CMD ["pnpm", "run", "start"]
|
||||
|
||||
|
||||
# # Gunakan base image Node.js Alpine yang ringan
|
||||
# FROM node:23.5.0-alpine
|
||||
|
||||
# # Atur environment
|
||||
# ENV PORT=3000
|
||||
# ENV NODE_ENV=production
|
||||
# ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
# # Install dependencies global
|
||||
# RUN npm install -g pnpm pm2
|
||||
|
||||
# # Set working directory
|
||||
# WORKDIR /usr/src/app
|
||||
|
||||
# # Salin file penting untuk caching dependencies
|
||||
# COPY package.json pnpm-lock.yaml* ./
|
||||
|
||||
# # Salin vendor jika diperlukan (ckeditor misalnya)
|
||||
# COPY vendor/ckeditor5 ./vendor/ckeditor5
|
||||
|
||||
# # Install dependencies
|
||||
# RUN pnpm install --frozen-lockfile
|
||||
|
||||
# # Salin semua source code
|
||||
# COPY . .
|
||||
|
||||
# # Salin ecosystem config
|
||||
# COPY ecosystem.config.js ./
|
||||
|
||||
# # Build Next.js
|
||||
# RUN pnpm run build
|
||||
|
||||
# # Expose port
|
||||
# EXPOSE 3000
|
||||
|
||||
# # Jalankan Next.js dalam mode cluster
|
||||
# CMD ["pm2-runtime", "start", "ecosystem.config.js"]
|
||||
|
|
@ -18,7 +18,7 @@ import { useRouter } from "next/navigation";
|
|||
import { deleteUser } from "@/service/management-user/management-user";
|
||||
import { stringify } from "querystring";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[] => [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
|
|
@ -30,11 +30,13 @@ const columns: ColumnDef<any>[] = [
|
|||
header: "Nama",
|
||||
cell: ({ row }) => <span>{row.getValue("fullname")}</span>,
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "address",
|
||||
header: "Wilayah",
|
||||
cell: ({ row }) => <span>MABES</span>,
|
||||
cell: () => <span>MABES</span>,
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "userRolePlacements",
|
||||
header: "Posisi",
|
||||
|
|
@ -52,6 +54,7 @@ const columns: ColumnDef<any>[] = [
|
|||
return <span>{posisi}</span>;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "role.name",
|
||||
header: "Bidang Keahlian",
|
||||
|
|
@ -81,72 +84,77 @@ const columns: ColumnDef<any>[] = [
|
|||
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const { toast } = useToast();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
||||
const doDelete = async (id: number) => {
|
||||
Swal.fire({
|
||||
title: "Menghapus user...",
|
||||
text: "Mohon tunggu",
|
||||
allowOutsideClick: false,
|
||||
didOpen: () => Swal.showLoading(),
|
||||
});
|
||||
|
||||
const response = await deleteUser(id);
|
||||
|
||||
Swal.close();
|
||||
|
||||
if (response?.error) {
|
||||
toast({
|
||||
title: stringify(response?.message),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
toast({
|
||||
title: "Success delete",
|
||||
});
|
||||
|
||||
router.push("?dataChange=true");
|
||||
toast({ title: "Berhasil menghapus user" });
|
||||
|
||||
// ⬅️ INI YANG PENTING → REFRESH TABLE TANPA RELOAD
|
||||
onRefresh();
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Apakah anda ingin menghapus data user?",
|
||||
title: "Hapus user ini?",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#dc3545",
|
||||
confirmButtonText: "Iya",
|
||||
cancelButtonText: "Tidak",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDelete(id);
|
||||
}
|
||||
}).then((res) => {
|
||||
if (res.isConfirmed) doDelete(id);
|
||||
});
|
||||
};
|
||||
|
||||
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 size="icon" variant="ghost">
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<Link href={`/admin/add-experts/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
|
||||
<DropdownMenuContent align="end">
|
||||
|
||||
<Link href={`/admin/add-experts/detail/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
||||
<Eye className="w-4 h-4 me-1.5" /> View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
<Link href={`/admin/add-experts/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
|
||||
<Link href={`/admin/add-experts/update/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
||||
<SquarePen className="w-4 h-4 me-1.5" /> Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDelete(row.original.userKeycloakId)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
className="text-red-600 cursor-pointer hover:bg-red-300"
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
<Trash2 className="w-4 h-4 me-1.5" /> Delete
|
||||
</DropdownMenuItem>
|
||||
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
|
@ -154,4 +162,4 @@ const columns: ColumnDef<any>[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export default columns;
|
||||
export default getColumns;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import * as React from "react";
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
PaginationState,
|
||||
SortingState,
|
||||
|
|
@ -15,7 +14,6 @@ import {
|
|||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -25,7 +23,6 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { UserIcon } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -35,43 +32,14 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
|
||||
import { paginationBlog } from "@/service/blog/blog";
|
||||
import { ticketingPagination } from "@/service/ticketing/ticketing";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./column";
|
||||
import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { listDataMedia } from "@/service/broadcast/broadcast";
|
||||
// import columns from "./column";
|
||||
import getColumns from "./column";
|
||||
import { listEnableCategory } from "@/service/content/content";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { listDataExperts } from "@/service/experts/experts";
|
||||
|
||||
const dummyData = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Prof. Dr. Ravi",
|
||||
region: "Nasional",
|
||||
skills: "Komunikasi",
|
||||
experience: "Akademisi",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Prof. Dr. Novan",
|
||||
region: "DKI Jakarta",
|
||||
skills: "Hukum",
|
||||
experience: "Akademisi + Praktisi",
|
||||
},
|
||||
];
|
||||
|
||||
const AddExpertTable = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
|
@ -97,7 +65,8 @@ const AddExpertTable = () => {
|
|||
const [limit, setLimit] = React.useState(10);
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
// columns,
|
||||
columns: getColumns({ onRefresh: fetchData }),
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
|
|
@ -283,7 +252,11 @@ const AddExpertTable = () => {
|
|||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
<TableCell
|
||||
// colSpan={columns.length}
|
||||
colSpan={table.getAllLeafColumns().length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -35,32 +35,61 @@ import {
|
|||
import { error, loading } from "@/config/swal";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
|
||||
// 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",
|
||||
// }),
|
||||
// });
|
||||
|
||||
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",
|
||||
}),
|
||||
name: z.string({ required_error: "Required" }),
|
||||
username: z
|
||||
.string({ required_error: "Required" })
|
||||
.refine((val) => !/\s/.test(val), {
|
||||
message: "Username tidak boleh mengandung spasi",
|
||||
}),
|
||||
// .transform((val) => val.toLowerCase()),
|
||||
|
||||
password: z
|
||||
.string({ required_error: "Required" })
|
||||
.min(8, "Minimal 8 karakter")
|
||||
.regex(/[A-Z]/, "Harus mengandung huruf besar (A-Z)")
|
||||
.regex(/[0-9]/, "Harus mengandung angka (0-9)")
|
||||
.regex(/[^A-Za-z0-9]/, "Harus mengandung karakter spesial (!@#$%^&*)"),
|
||||
|
||||
// confirmPassword: 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" }),
|
||||
});
|
||||
// .refine((data) => data.password === data.confirmPassword, {
|
||||
// path: ["confirmPassword"],
|
||||
// message: "Konfirmasi password tidak sama",
|
||||
// });
|
||||
|
||||
export type Placements = {
|
||||
index: number;
|
||||
|
|
@ -74,6 +103,7 @@ export default function AddExpertForm() {
|
|||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
const [passwordStrength, setPasswordStrength] = useState("");
|
||||
const [incrementId, setIncrementId] = useState(1);
|
||||
const [placementRows, setPlacementRows] = useState<Placements[]>([
|
||||
{ index: 0, roleId: "", userLevelId: 0 },
|
||||
|
|
@ -135,7 +165,7 @@ export default function AddExpertForm() {
|
|||
};
|
||||
|
||||
loading();
|
||||
|
||||
|
||||
// check availability first
|
||||
var placementArr: any[] = [];
|
||||
placementRows.forEach((row: any) => {
|
||||
|
|
@ -261,6 +291,19 @@ export default function AddExpertForm() {
|
|||
}
|
||||
};
|
||||
|
||||
const computeStrength = (password: string) => {
|
||||
let score = 0;
|
||||
if (password.length >= 8) score++;
|
||||
if (/[A-Z]/.test(password)) score++;
|
||||
if (/[0-9]/.test(password)) score++;
|
||||
if (/[^A-Za-z0-9]/.test(password)) score++;
|
||||
|
||||
if (score <= 1) return "weak";
|
||||
if (score === 2 || score === 3) return "medium";
|
||||
if (score === 4) return "strong";
|
||||
return "";
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
|
|
@ -288,6 +331,39 @@ export default function AddExpertForm() {
|
|||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username (huruf kecil, tanpa spasi)</FormLabel>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
value={field.value}
|
||||
placeholder="masukkan username"
|
||||
onChange={(e) => {
|
||||
let value = e.target.value;
|
||||
|
||||
// Hapus spasi otomatis
|
||||
value = value.replace(/\s+/g, "");
|
||||
|
||||
// Jadikan lowercase otomatis
|
||||
value = value.toLowerCase();
|
||||
|
||||
field.onChange(value);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Info tambahan */}
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Username otomatis menjadi huruf kecil tanpa spasi.
|
||||
</p>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
|
|
@ -303,7 +379,7 @@ export default function AddExpertForm() {
|
|||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="phoneNumber"
|
||||
|
|
@ -349,6 +425,69 @@ export default function AddExpertForm() {
|
|||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={field.value}
|
||||
placeholder="Masukkan Password"
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
setPasswordStrength(computeStrength(e.target.value));
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-default-500"
|
||||
>
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
<FormLabel className="text-gray-400 text-[12px]">
|
||||
Password harus memiliki minimal 8 karakter, special karakter,
|
||||
angka dan huruf kapital
|
||||
</FormLabel>
|
||||
|
||||
{/* Strength meter */}
|
||||
{field.value && (
|
||||
<div className="mt-2">
|
||||
<div
|
||||
className={`h-2 rounded transition-all ${
|
||||
passwordStrength === "weak"
|
||||
? "bg-red-500 w-1/4"
|
||||
: passwordStrength === "medium"
|
||||
? "bg-yellow-500 w-2/4"
|
||||
: "bg-green-500 w-full"
|
||||
}`}
|
||||
/>
|
||||
|
||||
<p
|
||||
className={`text-xs mt-1 ${
|
||||
passwordStrength === "weak"
|
||||
? "text-red-500"
|
||||
: passwordStrength === "medium"
|
||||
? "text-yellow-600"
|
||||
: "text-green-600"
|
||||
}`}
|
||||
>
|
||||
{passwordStrength === "weak" && "Weak Password"}
|
||||
{passwordStrength === "medium" && "Medium Password"}
|
||||
{passwordStrength === "strong" && "Strong Password"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
|
|
@ -373,7 +512,7 @@ export default function AddExpertForm() {
|
|||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="skills"
|
||||
|
|
|
|||
|
|
@ -37,32 +37,43 @@ import { Eye, EyeOff } from "lucide-react";
|
|||
import { useParams } from "next/navigation";
|
||||
|
||||
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",
|
||||
}),
|
||||
name: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
phoneNumber: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
skills: z.string().optional(),
|
||||
experiences: z.string().optional(),
|
||||
company: z.string().optional(),
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
|
@ -96,6 +107,10 @@ interface Detail {
|
|||
createdAt: string;
|
||||
};
|
||||
};
|
||||
userRolePlacements?: {
|
||||
roleId: number;
|
||||
userLevelId: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export default function UpdateExpertForm() {
|
||||
|
|
@ -149,6 +164,39 @@ export default function UpdateExpertForm() {
|
|||
initState();
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!detail) return;
|
||||
|
||||
// Isi semua form field
|
||||
form.reset({
|
||||
name: detail.fullname || "",
|
||||
username: detail.username || "",
|
||||
phoneNumber: detail.phoneNumber || "",
|
||||
email: detail.email || "",
|
||||
password: "",
|
||||
skills: detail?.userProfilesAdditional?.userCompetency?.id
|
||||
? String(detail.userProfilesAdditional.userCompetency.id)
|
||||
: "",
|
||||
experiences: detail?.userProfilesAdditional?.userExperienceId
|
||||
? String(detail.userProfilesAdditional.userExperienceId)
|
||||
: "",
|
||||
company: detail?.userProfilesAdditional?.companyName || "",
|
||||
});
|
||||
|
||||
// 🔥 Masukkan posisi existing
|
||||
if (detail.userRolePlacements && detail.userRolePlacements.length > 0) {
|
||||
const mapped = detail.userRolePlacements.map(
|
||||
(item: any, idx: number) => ({
|
||||
index: idx,
|
||||
roleId: String(item.roleId),
|
||||
userLevelId: Number(item.userLevelId),
|
||||
})
|
||||
);
|
||||
|
||||
setPlacementRows(mapped);
|
||||
}
|
||||
}, [detail]);
|
||||
|
||||
if (!detail) return <div>Loading...</div>;
|
||||
|
||||
const togglePasswordType = () => {
|
||||
|
|
@ -189,18 +237,35 @@ export default function UpdateExpertForm() {
|
|||
|
||||
const dataReq = {
|
||||
id: detail?.id,
|
||||
firstName: data.name,
|
||||
username: data.username,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
firstName: data.name || detail.fullname,
|
||||
username: data.username || detail.username,
|
||||
email: data.email || detail.email,
|
||||
password: data.password || undefined,
|
||||
address: "",
|
||||
roleId: "EXP-ID",
|
||||
phoneNumber: data.phoneNumber,
|
||||
userCompetencyId: data.skills,
|
||||
userExperienceId: data.experiences,
|
||||
companyName: data.company,
|
||||
phoneNumber: data.phoneNumber || detail.phoneNumber,
|
||||
userCompetencyId:
|
||||
data.skills || detail.userProfilesAdditional?.userCompetency?.id,
|
||||
userExperienceId:
|
||||
data.experiences || detail.userProfilesAdditional?.userExperienceId,
|
||||
companyName: data.company || detail.userProfilesAdditional?.companyName,
|
||||
isAdmin: true,
|
||||
};
|
||||
|
||||
// const dataReq = {
|
||||
// id: detail?.id,
|
||||
// firstName: data.name,
|
||||
// username: data.username,
|
||||
// email: data.email,
|
||||
// password: data.password,
|
||||
// address: "",
|
||||
// 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;
|
||||
|
|
@ -322,10 +387,15 @@ export default function UpdateExpertForm() {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama Lengkap</FormLabel>
|
||||
<Input
|
||||
{/* <Input
|
||||
defaultValue={detail?.fullname}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
onChange={field.onChange}
|
||||
/> */}
|
||||
<Input
|
||||
{...field}
|
||||
defaultValue={detail?.fullname}
|
||||
placeholder="Masukkan Nama Lengkap"
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
|
|
@ -333,18 +403,24 @@ export default function UpdateExpertForm() {
|
|||
)}
|
||||
/>
|
||||
<FormField
|
||||
disabled
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<Input
|
||||
{/* <Input
|
||||
type="text"
|
||||
defaultValue={detail?.username}
|
||||
placeholder="Masukkan"
|
||||
onChange={field.onChange}
|
||||
/> */}
|
||||
<Input
|
||||
{...field}
|
||||
type="text"
|
||||
defaultValue={detail?.username}
|
||||
placeholder="Masukkan"
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
|
@ -355,11 +431,17 @@ export default function UpdateExpertForm() {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>No. HP</FormLabel>
|
||||
<Input
|
||||
{/* <Input
|
||||
type="number"
|
||||
defaultValue={detail?.phoneNumber}
|
||||
placeholder="Masukkan No.Hp"
|
||||
onChange={field.onChange}
|
||||
/> */}
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
defaultValue={detail?.phoneNumber}
|
||||
placeholder="Masukkan"
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
|
@ -371,17 +453,46 @@ export default function UpdateExpertForm() {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<Input
|
||||
{/* <Input
|
||||
type="email"
|
||||
defaultValue={detail?.email}
|
||||
placeholder="Masukkan email"
|
||||
onChange={field.onChange}
|
||||
/> */}
|
||||
<Input
|
||||
{...field}
|
||||
type="email"
|
||||
defaultValue={detail?.email}
|
||||
placeholder="Masukkan email"
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password (Opsional)</FormLabel>
|
||||
<div className="relative">
|
||||
<Input
|
||||
{...field}
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="Kosongkan jika tidak ingin mengubah password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/> */}
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
|
|
@ -406,7 +517,7 @@ export default function UpdateExpertForm() {
|
|||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="skills"
|
||||
|
|
@ -481,12 +592,21 @@ export default function UpdateExpertForm() {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama Institusi/Perusahaan</FormLabel>
|
||||
<Input
|
||||
{/* <Input
|
||||
type="text"
|
||||
value={detail?.userProfilesAdditional?.companyName || ""}
|
||||
placeholder="Nama Institusi/Perusahaan"
|
||||
onChange={field.onChange}
|
||||
/> */}
|
||||
<Input
|
||||
{...field}
|
||||
type="text"
|
||||
defaultValue={
|
||||
detail?.userProfilesAdditional?.companyName || ""
|
||||
}
|
||||
placeholder="Nama Institusi/Perusahaan"
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
|
@ -497,6 +617,7 @@ export default function UpdateExpertForm() {
|
|||
{placementRows?.map((row: any) => (
|
||||
<div key={row.index} className="flex items-center gap-2 my-2">
|
||||
<Select
|
||||
value={row.roleId}
|
||||
onValueChange={(e) =>
|
||||
handleSelectionChange(row.index, "roleId", e)
|
||||
}
|
||||
|
|
@ -533,6 +654,7 @@ export default function UpdateExpertForm() {
|
|||
</SelectContent>
|
||||
</Select> */}
|
||||
<Select
|
||||
value={row.userLevelId}
|
||||
onValueChange={(e) =>
|
||||
handleSelectionChange(row.index, "userLevelId", e)
|
||||
}
|
||||
|
|
@ -562,7 +684,7 @@ export default function UpdateExpertForm() {
|
|||
type="button"
|
||||
size="md"
|
||||
onClick={handleAddRow}
|
||||
disabled={placementRows.length >= 2} // optional: disable button if already 1 row added
|
||||
disabled={placementRows.length >= 2}
|
||||
>
|
||||
Tambah
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ const columns: ColumnDef<any>[] = [
|
|||
<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/account-list/edit/${row.original.id}`}
|
||||
href={`/admin/broadcast/campaign-list/account-list/edit/${row.original.mediaBlastAccountId}`}
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -59,8 +59,12 @@ import {
|
|||
deleteMediaBlastCampaignAccount,
|
||||
saveMediaBlastCampaignAccountBulk,
|
||||
} from "@/service/broadcast/broadcast";
|
||||
import { AdministrationUserList } from "@/service/management-user/management-user";
|
||||
import {
|
||||
AdministrationUserList,
|
||||
getUserListAll,
|
||||
} from "@/service/management-user/management-user";
|
||||
import { close, loading, error, success, successCallback } from "@/config/swal";
|
||||
import { Link } from "@/i18n/routing";
|
||||
|
||||
// Mock data for available accounts - replace with actual API call
|
||||
const availableAccounts = [
|
||||
|
|
@ -98,7 +102,8 @@ const AccountListTable = () => {
|
|||
const [accountCategory, setAccountCategory] = React.useState<string>("");
|
||||
const [selectedAccount, setSelectedAccount] = React.useState<any[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = React.useState<string>("");
|
||||
const [availableAccountsList, setAvailableAccountsList] = React.useState<any[]>(availableAccounts);
|
||||
const [availableAccountsList, setAvailableAccountsList] =
|
||||
React.useState<any[]>(availableAccounts);
|
||||
const [usersList, setUsersList] = React.useState<any[]>([]);
|
||||
|
||||
const table = useReactTable({
|
||||
|
|
@ -171,7 +176,7 @@ const AccountListTable = () => {
|
|||
async function saveCampaignAccount() {
|
||||
try {
|
||||
loading();
|
||||
|
||||
|
||||
if (accountCategory === "all-account") {
|
||||
// Handle all accounts - send only campaignId and category "all"
|
||||
const request = {
|
||||
|
|
@ -202,7 +207,7 @@ const AccountListTable = () => {
|
|||
default:
|
||||
roleId = "5";
|
||||
}
|
||||
|
||||
|
||||
const request = {
|
||||
mediaBlastCampaignId: campaignId,
|
||||
mediaBlastAccountCategory: `role-${roleId}`,
|
||||
|
|
@ -216,7 +221,7 @@ const AccountListTable = () => {
|
|||
// Handle custom selection - send campaignId and selected user IDs
|
||||
const request = {
|
||||
mediaBlastCampaignId: campaignId,
|
||||
mediaBlastAccountIds: selectedAccount.map(acc => acc.id),
|
||||
mediaBlastAccountIds: selectedAccount.map((acc) => acc.id),
|
||||
};
|
||||
const response = await saveMediaBlastCampaignAccountBulk(request);
|
||||
if (response?.error) {
|
||||
|
|
@ -224,7 +229,7 @@ const AccountListTable = () => {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
close();
|
||||
successCallback("Akun berhasil ditambahkan ke campaign!");
|
||||
resetDialogState();
|
||||
|
|
@ -246,15 +251,8 @@ const AccountListTable = () => {
|
|||
const fetchUsersList = async () => {
|
||||
try {
|
||||
loading();
|
||||
const response = await AdministrationUserList(
|
||||
"1", // levelId
|
||||
0, // page
|
||||
"", // name
|
||||
"100", // size
|
||||
"1", // featureId
|
||||
"" // role
|
||||
);
|
||||
|
||||
const response = await getUserListAll();
|
||||
|
||||
if (response?.data?.data?.content) {
|
||||
setUsersList(response.data.data.content);
|
||||
}
|
||||
|
|
@ -272,15 +270,15 @@ const AccountListTable = () => {
|
|||
setFiltered(temp);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const removeSelectedAccount = (accountId: string) => {
|
||||
setSelectedAccount(selectedAccount.filter(acc => acc.id !== accountId));
|
||||
setSelectedAccount(selectedAccount.filter((acc) => acc.id !== accountId));
|
||||
};
|
||||
|
||||
const getFilteredAccounts = () => {
|
||||
if (accountCategory === "kategori" && selectedCategory) {
|
||||
return availableAccountsList.filter(acc => acc.category === selectedCategory);
|
||||
return availableAccountsList.filter(
|
||||
(acc) => acc.category === selectedCategory
|
||||
);
|
||||
}
|
||||
return availableAccountsList;
|
||||
};
|
||||
|
|
@ -298,7 +296,10 @@ const AccountListTable = () => {
|
|||
Pilih Akun
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size="md" className="max-w-xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogContent
|
||||
size="md"
|
||||
className="max-w-xl max-h-[80vh] overflow-y-auto"
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Pilih Akun Untuk Campaign Ini</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
|
@ -357,15 +358,17 @@ const AccountListTable = () => {
|
|||
options={usersList.map((user: any) => ({
|
||||
value: user.id,
|
||||
label: `${user.fullname} (${user.role?.name})`,
|
||||
user: user
|
||||
user: user,
|
||||
}))}
|
||||
value={selectedAccount.map((acc: any) => ({
|
||||
value: acc.id,
|
||||
label: `${acc.fullname} (${acc.role?.name})`,
|
||||
user: acc
|
||||
user: acc,
|
||||
}))}
|
||||
onChange={(selectedOptions: any) => {
|
||||
const selectedUsers = selectedOptions ? selectedOptions.map((option: any) => option.user) : [];
|
||||
const selectedUsers = selectedOptions
|
||||
? selectedOptions.map((option: any) => option.user)
|
||||
: [];
|
||||
setSelectedAccount(selectedUsers);
|
||||
}}
|
||||
placeholder="Cari dan pilih user..."
|
||||
|
|
@ -376,14 +379,17 @@ const AccountListTable = () => {
|
|||
className="react-select"
|
||||
classNamePrefix="select"
|
||||
/>
|
||||
|
||||
|
||||
{/* Selected Accounts Display */}
|
||||
{selectedAccount.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<Label>User Terpilih ({selectedAccount.length}):</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedAccount.map((acc) => (
|
||||
<Badge key={acc.id} className="flex items-center gap-1">
|
||||
<Badge
|
||||
key={acc.id}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{acc.fullname}
|
||||
<X
|
||||
className="h-3 w-3 cursor-pointer"
|
||||
|
|
@ -410,7 +416,8 @@ const AccountListTable = () => {
|
|||
{accountCategory === "kategori" && selectedCategory && (
|
||||
<div className="p-3 bg-green-50 rounded-md">
|
||||
<p className="text-sm text-green-700">
|
||||
Semua akun dengan role "{selectedCategory.toUpperCase()}" akan ditambahkan.
|
||||
Semua akun dengan role "{selectedCategory.toUpperCase()}"
|
||||
akan ditambahkan.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -419,7 +426,8 @@ const AccountListTable = () => {
|
|||
{accountCategory === "custom" && (
|
||||
<div className="p-3 bg-purple-50 rounded-md">
|
||||
<p className="text-sm text-purple-700">
|
||||
{selectedAccount.length} user terpilih akan ditambahkan ke campaign ini.
|
||||
{selectedAccount.length} user terpilih akan ditambahkan ke
|
||||
campaign ini.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -430,7 +438,8 @@ const AccountListTable = () => {
|
|||
onClick={saveCampaignAccount}
|
||||
disabled={
|
||||
!accountCategory ||
|
||||
(accountCategory === "custom" && selectedAccount.length < 1) ||
|
||||
(accountCategory === "custom" &&
|
||||
selectedAccount.length < 1) ||
|
||||
(accountCategory === "kategori" && !selectedCategory)
|
||||
}
|
||||
>
|
||||
|
|
@ -448,7 +457,47 @@ const AccountListTable = () => {
|
|||
</div>
|
||||
|
||||
{/* === Filter Akun === */}
|
||||
<div className="flex justify-end">
|
||||
<div className="flex flex-row justify-end">
|
||||
{/* <div className="flex flex-row gap-4">
|
||||
<Link href="/admin/broadcast/campaign-list/account-list/create">
|
||||
<Button variant="default" className="bg-[#3f37c9] gap-2">
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M17 13h-4v4h-2v-4H7v-2h4V7h2v4h4m2-8H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
Tambahkan Akun
|
||||
</Button>
|
||||
</Link>
|
||||
<Button variant="default" className="bg-[#3f37c9] gap-2">
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<g fill="none">
|
||||
<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="M12 2v6.5a1.5 1.5 0 0 0 1.5 1.5H20v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-1h3.414l-1.121 1.121a1 1 0 1 0 1.414 1.415l2.829-2.829a1 1 0 0 0 0-1.414l-2.829-2.828a1 1 0 1 0-1.414 1.414L7.414 17H4V4a2 2 0 0 1 2-2zM4 17v2H3a1 1 0 1 1 0-2zM14 2.043a2 2 0 0 1 1 .543L19.414 7a2 2 0 0 1 .543 1H14z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
Import Akun
|
||||
</Button>
|
||||
</div> */}
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button size="md" variant="outline">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
|
|
@ -14,55 +13,80 @@ import {
|
|||
} from "@/components/ui/form";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { getOnlyDate } from "@/utils/globals";
|
||||
import {
|
||||
getMediaBlastCampaignPage,
|
||||
saveMediaBlastAccount,
|
||||
saveMediaBlastCampaign,
|
||||
} from "@/service/broadcast/broadcast";
|
||||
import { error } from "@/config/swal";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// ----------------------------
|
||||
// ZOD SCHEMA (dinamis)
|
||||
// ----------------------------
|
||||
|
||||
const FormSchema = z.object({
|
||||
name: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
name: z.string({ required_error: "Required" }),
|
||||
|
||||
accountType: z
|
||||
.array(z.string())
|
||||
.refine((value) => value.some((item) => item), {
|
||||
message: "Required",
|
||||
}),
|
||||
accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
|
||||
required_error: "Required",
|
||||
}),
|
||||
email: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
whatsapp: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
});
|
||||
.min(1, "Pilih minimal satu tipe akun"),
|
||||
|
||||
email: z.string().optional(),
|
||||
whatsapp: z.string().optional(),
|
||||
|
||||
campaignId: z.string({ required_error: "Required" }),
|
||||
}).refine(
|
||||
(data) => {
|
||||
if (data.accountType.includes("email") && !data.email) return false;
|
||||
return true;
|
||||
},
|
||||
{ message: "Email wajib diisi", path: ["email"] }
|
||||
).refine(
|
||||
(data) => {
|
||||
if (data.accountType.includes("wa") && !data.whatsapp) return false;
|
||||
return true;
|
||||
},
|
||||
{ message: "Whatsapp wajib diisi", path: ["whatsapp"] }
|
||||
);
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// COMPONENT
|
||||
// ----------------------------
|
||||
|
||||
export default function CreateAccountForBroadcast() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: { accountType: [] },
|
||||
defaultValues: {
|
||||
accountType: [],
|
||||
email: "",
|
||||
whatsapp: "",
|
||||
},
|
||||
});
|
||||
|
||||
const selectedTypes = form.watch("accountType");
|
||||
const [campaigns, setCampaigns] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCampaignList();
|
||||
}, []);
|
||||
|
||||
async function fetchCampaignList() {
|
||||
try {
|
||||
const res = await getMediaBlastCampaignPage(0);
|
||||
setCampaigns(res?.data?.data?.content ?? []);
|
||||
} catch (e) {
|
||||
console.log("Error fetch campaign:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||
MySwal.fire({
|
||||
title: "Simpan Data",
|
||||
|
|
@ -85,10 +109,8 @@ export default function CreateAccountForBroadcast() {
|
|||
icon: "success",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
router.push("/admin/broadcast/campaign-list/account-list");
|
||||
}
|
||||
}).then(() => {
|
||||
router.push("/admin/broadcast/campaign-list/account-list");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -96,20 +118,21 @@ export default function CreateAccountForBroadcast() {
|
|||
const reqData = {
|
||||
accountName: data.name,
|
||||
accountType: data.accountType.join(","),
|
||||
accountCategory: data.accountCategory,
|
||||
emailAddress: data.email,
|
||||
whatsappNumber: data.whatsapp,
|
||||
emailAddress: data.email ?? "",
|
||||
whatsappNumber: data.whatsapp ?? "",
|
||||
campaignId: data.campaignId,
|
||||
};
|
||||
console.log("data", data);
|
||||
|
||||
console.log("REQ:", reqData);
|
||||
|
||||
const response = await saveMediaBlastAccount(reqData);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
successSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
|
|
@ -118,7 +141,9 @@ export default function CreateAccountForBroadcast() {
|
|||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-3 bg-white rounded-sm p-4"
|
||||
>
|
||||
<p className="fonnt-semibold">Account</p>
|
||||
<p className="font-semibold">Account</p>
|
||||
|
||||
{/* NAMA */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
|
|
@ -130,172 +155,125 @@ export default function CreateAccountForBroadcast() {
|
|||
placeholder="Masukkan nama"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* CHECKBOX TIPE AKUN */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accountType"
|
||||
render={() => (
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tipe Akun</FormLabel>
|
||||
<div className="flex flex-row gap-2">
|
||||
{" "}
|
||||
<FormField
|
||||
key="wa"
|
||||
control={form.control}
|
||||
name="accountType"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key="wa"
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes("wa")}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, "wa"])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== "wa"
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">
|
||||
Whatsapp
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
key="email"
|
||||
control={form.control}
|
||||
name="accountType"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key="email"
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes("email")}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, "email"])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== "email"
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">Email</FormLabel>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-4">
|
||||
{/* WA */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={field.value.includes("wa")}
|
||||
onCheckedChange={(checked) =>
|
||||
checked
|
||||
? field.onChange([...field.value, "wa"])
|
||||
: field.onChange(field.value.filter((v) => v !== "wa"))
|
||||
}
|
||||
/>
|
||||
<label>Whatsapp</label>
|
||||
</div>
|
||||
|
||||
{/* EMAIL */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={field.value.includes("email")}
|
||||
onCheckedChange={(checked) =>
|
||||
checked
|
||||
? field.onChange([...field.value, "email"])
|
||||
: field.onChange(
|
||||
field.value.filter((v) => v !== "email")
|
||||
)
|
||||
}
|
||||
/>
|
||||
<label>Email</label>
|
||||
</div>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* FORM WHATSAPP */}
|
||||
{selectedTypes.includes("wa") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="whatsapp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Whatsapp</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Masukkan nomor Whatsapp"
|
||||
{...field}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* FORM EMAIL */}
|
||||
{selectedTypes.includes("email") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Masukkan email"
|
||||
{...field}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* CAMPAIGN */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accountCategory"
|
||||
name="campaignId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>Kategori</FormLabel>
|
||||
<FormItem>
|
||||
<FormLabel>Campaign</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex flex-row gap-2"
|
||||
<select
|
||||
className="w-full border rounded-md p-2 text-sm"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="polri" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">POLRI</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="jurnalis" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">JURNALIS</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="umum" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">UMUM</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="ksp" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">KSP</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
<option value="" className="text-slate-400">
|
||||
Pilih campaign
|
||||
</option>
|
||||
|
||||
{campaigns.map((c: any) => (
|
||||
<option key={c.id} value={c.id}>
|
||||
{c.title || `Campaign ${c.id}`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
value={field.value}
|
||||
placeholder="Masukkan email"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="whatsapp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value}
|
||||
placeholder="Masukkan whatsapp"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* BUTTON */}
|
||||
<div className="flex flex-row gap-2 mt-4 pt-4">
|
||||
<Button
|
||||
size="md"
|
||||
type="button"
|
||||
variant="outline"
|
||||
color="destructive"
|
||||
className="text-xs"
|
||||
>
|
||||
<Button type="button" variant="outline" color="destructive">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="md" type="submit" color="primary" className="text-xs">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -304,3 +282,380 @@ export default function CreateAccountForBroadcast() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// "use client";
|
||||
// import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
// import { z } from "zod";
|
||||
// import { useForm } from "react-hook-form";
|
||||
// import { zodResolver } from "@hookform/resolvers/zod";
|
||||
// import {
|
||||
// Form,
|
||||
// FormControl,
|
||||
// FormField,
|
||||
// FormItem,
|
||||
// FormLabel,
|
||||
// FormMessage,
|
||||
// } from "@/components/ui/form";
|
||||
// import withReactContent from "sweetalert2-react-content";
|
||||
// import Swal from "sweetalert2";
|
||||
// import { Input } from "@/components/ui/input";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import {
|
||||
// getMediaBlastCampaignPage,
|
||||
// saveMediaBlastAccount,
|
||||
// } from "@/service/broadcast/broadcast";
|
||||
// import { error } from "@/config/swal";
|
||||
// import { useRouter } from "@/i18n/routing";
|
||||
// import { Checkbox } from "@/components/ui/checkbox";
|
||||
// import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
// import { useEffect, useState } from "react";
|
||||
|
||||
// // const FormSchema = z.object({
|
||||
// // name: z.string({
|
||||
// // required_error: "Required",
|
||||
// // }),
|
||||
// // accountType: z
|
||||
// // .array(z.string())
|
||||
// // .refine((value) => value.some((item) => item), {
|
||||
// // message: "Required",
|
||||
// // }),
|
||||
// // accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
|
||||
// // required_error: "Required",
|
||||
// // }),
|
||||
// // email: z.string({
|
||||
// // required_error: "Required",
|
||||
// // }),
|
||||
// // whatsapp: z.string({
|
||||
// // required_error: "Required",
|
||||
// // }),
|
||||
// // campaignId: z.string({ required_error: "Required" }),
|
||||
// // });
|
||||
// const FormSchema = z
|
||||
// .object({
|
||||
// name: z.string().min(1, "Required"),
|
||||
|
||||
// accountType: z.array(z.string()).refine((value) => value.length > 0, {
|
||||
// message: "Pilih minimal satu tipe akun",
|
||||
// }),
|
||||
|
||||
// accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
|
||||
// email: z.string().optional(),
|
||||
// whatsapp: z.string().optional(),
|
||||
|
||||
// campaignId: z.string().min(1, "Required"),
|
||||
// })
|
||||
// .refine(
|
||||
// (data) => {
|
||||
// if (data.accountType.includes("email")) {
|
||||
// return !!data.email && data.email.trim() !== "";
|
||||
// }
|
||||
// return true;
|
||||
// },
|
||||
// { path: ["email"], message: "Email wajib diisi" }
|
||||
// )
|
||||
// .refine(
|
||||
// (data) => {
|
||||
// if (data.accountType.includes("wa")) {
|
||||
// return !!data.whatsapp && data.whatsapp.trim() !== "";
|
||||
// }
|
||||
// return true;
|
||||
// },
|
||||
// { path: ["whatsapp"], message: "Whatsapp wajib diisi" }
|
||||
// );
|
||||
|
||||
// export default function CreateAccountForBroadcast() {
|
||||
// const MySwal = withReactContent(Swal);
|
||||
// const router = useRouter();
|
||||
// const form = useForm<z.infer<typeof FormSchema>>({
|
||||
// resolver: zodResolver(FormSchema),
|
||||
// defaultValues: { accountType: [] },
|
||||
// });
|
||||
// const selectedTypes = form.watch("accountType");
|
||||
// const [campaigns, setCampaigns] = useState<any[]>([]);
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchCampaignList();
|
||||
// }, []);
|
||||
|
||||
// async function fetchCampaignList() {
|
||||
// try {
|
||||
// const res = await getMediaBlastCampaignPage(0);
|
||||
// setCampaigns(res?.data?.data?.content ?? []);
|
||||
// } catch (e) {
|
||||
// console.log("Error fetch campaign:", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// function successSubmit() {
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// router.push("/admin/broadcast/campaign-list/account-list");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// const save = async (data: z.infer<typeof FormSchema>) => {
|
||||
// const reqData = {
|
||||
// accountName: data.name,
|
||||
// accountType: data.accountType.join(","),
|
||||
// accountCategory: data.accountCategory,
|
||||
// emailAddress: data.email ?? "",
|
||||
// whatsappNumber: data.whatsapp ?? "",
|
||||
// campaignId: data.campaignId,
|
||||
// };
|
||||
// console.log("data", data);
|
||||
|
||||
// const response = await saveMediaBlastAccount(reqData);
|
||||
// if (response?.error) {
|
||||
// error(response.message);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// successSubmit();
|
||||
// };
|
||||
// return (
|
||||
// <div>
|
||||
// <SiteBreadcrumb />
|
||||
// <Form {...form}>
|
||||
// <form
|
||||
// onSubmit={form.handleSubmit(onSubmit)}
|
||||
// className="space-y-3 bg-white rounded-sm p-4"
|
||||
// >
|
||||
// <p className="fonnt-semibold">Account</p>
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="name"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Nama</FormLabel>
|
||||
// <Input
|
||||
// value={field.value}
|
||||
// placeholder="Masukkan nama"
|
||||
// onChange={field.onChange}
|
||||
// />
|
||||
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="accountType"
|
||||
// render={() => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Tipe Akun</FormLabel>
|
||||
// <div className="flex flex-row gap-2">
|
||||
// {" "}
|
||||
// <FormField
|
||||
// key="wa"
|
||||
// control={form.control}
|
||||
// name="accountType"
|
||||
// render={({ field }) => {
|
||||
// return (
|
||||
// <FormItem
|
||||
// key="wa"
|
||||
// className="flex flex-row items-start space-x-3 space-y-0"
|
||||
// >
|
||||
// <FormControl>
|
||||
// <Checkbox
|
||||
// checked={field.value?.includes("wa")}
|
||||
// onCheckedChange={(checked) => {
|
||||
// return checked
|
||||
// ? field.onChange([...field.value, "wa"])
|
||||
// : field.onChange(
|
||||
// field.value?.filter(
|
||||
// (value) => value !== "wa"
|
||||
// )
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
// </FormControl>
|
||||
// <FormLabel className="font-normal">
|
||||
// Whatsapp
|
||||
// </FormLabel>
|
||||
// </FormItem>
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
// <FormField
|
||||
// key="email"
|
||||
// control={form.control}
|
||||
// name="accountType"
|
||||
// render={({ field }) => {
|
||||
// return (
|
||||
// <FormItem
|
||||
// key="email"
|
||||
// className="flex flex-row items-start space-x-3 space-y-0"
|
||||
// >
|
||||
// <FormControl>
|
||||
// <Checkbox
|
||||
// checked={field.value?.includes("email")}
|
||||
// onCheckedChange={(checked) => {
|
||||
// return checked
|
||||
// ? field.onChange([...field.value, "email"])
|
||||
// : field.onChange(
|
||||
// field.value?.filter(
|
||||
// (value) => value !== "email"
|
||||
// )
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
// </FormControl>
|
||||
// <FormLabel className="font-normal">Email</FormLabel>
|
||||
// </FormItem>
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
// </div>
|
||||
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// {/* <FormField
|
||||
// control={form.control}
|
||||
// name="accountCategory"
|
||||
// render={({ field }) => (
|
||||
// <FormItem className="space-y-3">
|
||||
// <FormLabel>Kategori</FormLabel>
|
||||
// <FormControl>
|
||||
// <RadioGroup
|
||||
// onValueChange={field.onChange}
|
||||
// defaultValue={field.value}
|
||||
// className="flex flex-row gap-2"
|
||||
// >
|
||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
||||
// <FormControl>
|
||||
// <RadioGroupItem value="polri" />
|
||||
// </FormControl>
|
||||
// <FormLabel className="font-normal">POLRI</FormLabel>
|
||||
// </FormItem>
|
||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
||||
// <FormControl>
|
||||
// <RadioGroupItem value="jurnalis" />
|
||||
// </FormControl>
|
||||
// <FormLabel className="font-normal">JURNALIS</FormLabel>
|
||||
// </FormItem>
|
||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
||||
// <FormControl>
|
||||
// <RadioGroupItem value="umum" />
|
||||
// </FormControl>
|
||||
// <FormLabel className="font-normal">UMUM</FormLabel>
|
||||
// </FormItem>
|
||||
// <FormItem className="flex items-center space-x-3 space-y-0">
|
||||
// <FormControl>
|
||||
// <RadioGroupItem value="ksp" />
|
||||
// </FormControl>
|
||||
// <FormLabel className="font-normal">KSP</FormLabel>
|
||||
// </FormItem>
|
||||
// </RadioGroup>
|
||||
// </FormControl>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// /> */}
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="email"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Email</FormLabel>
|
||||
// <Input
|
||||
// type="email"
|
||||
// value={field.value}
|
||||
// placeholder="Masukkan email"
|
||||
// onChange={field.onChange}
|
||||
// />
|
||||
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="whatsapp"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Nama</FormLabel>
|
||||
// <Input
|
||||
// type="number"
|
||||
// value={field.value}
|
||||
// placeholder="Masukkan whatsapp"
|
||||
// onChange={field.onChange}
|
||||
// />
|
||||
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="campaignId"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Campaign</FormLabel>
|
||||
// <FormControl>
|
||||
// <select
|
||||
// className="w-full border rounded-md p-2"
|
||||
// value={field.value}
|
||||
// onChange={field.onChange}
|
||||
// >
|
||||
// <option value="" className="text-slate-400">
|
||||
// Pilih campaign
|
||||
// </option>
|
||||
|
||||
// {campaigns.map((c: any) => (
|
||||
// <option key={c.id} value={c.id}>
|
||||
// {c.title || `Campaign ${c.id}`}
|
||||
// </option>
|
||||
// ))}
|
||||
// </select>
|
||||
// </FormControl>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <div className="flex flex-row gap-2 mt-4 pt-4">
|
||||
// <Button
|
||||
// size="md"
|
||||
// type="button"
|
||||
// variant="outline"
|
||||
// color="destructive"
|
||||
// className="text-xs"
|
||||
// >
|
||||
// Cancel
|
||||
// </Button>
|
||||
// <Button size="md" type="submit" color="primary" className="text-xs">
|
||||
// Submit
|
||||
// </Button>
|
||||
// </div>
|
||||
// </form>
|
||||
// </Form>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -37,46 +37,74 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
getUserById,
|
||||
saveUserInternal,
|
||||
} from "@/service/management-user/management-user";
|
||||
|
||||
// const FormSchema = z.object({
|
||||
// fullname: z.string({
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
// // accountType: z
|
||||
// // .array(z.string())
|
||||
// // .refine((value) => value.some((item) => item), {
|
||||
// // message: "Required",
|
||||
// // }),
|
||||
// // accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
|
||||
// // required_error: "Required",
|
||||
// // }),
|
||||
// email: z.string({
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
// phoneNumber: z.string({
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
// });
|
||||
|
||||
const FormSchema = z.object({
|
||||
name: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
accountType: z
|
||||
.array(z.string())
|
||||
.refine((value) => value.some((item) => item), {
|
||||
message: "Required",
|
||||
}),
|
||||
accountCategory: z.enum(["polri", "jurnalis", "umumu", "ksp"], {
|
||||
required_error: "Required",
|
||||
}),
|
||||
email: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
whatsapp: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
fullname: z.string({ required_error: "Required" }),
|
||||
email: z.string({ required_error: "Required" }),
|
||||
phoneNumber: z.string({ required_error: "Required" }),
|
||||
username: z.string().optional(),
|
||||
role: z.string().optional(),
|
||||
level: z.string().optional(),
|
||||
nrp: z.string().optional(),
|
||||
address: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
confirmPassword: z.string().optional(),
|
||||
});
|
||||
|
||||
export default function EditAccountForBroadcast() {
|
||||
const id = useParams()?.id;
|
||||
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: { accountType: [] },
|
||||
// defaultValues: { accountType: [] },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function getDetailData() {
|
||||
const response = await getMediaBlastAccount(String(id));
|
||||
const response = await getUserById(String(id));
|
||||
const details = response?.data?.data;
|
||||
console.log("new", details);
|
||||
form.setValue("name", details.accountName);
|
||||
form.setValue("email", details?.emailAddress);
|
||||
form.setValue("whatsapp", details?.whatsappNumber);
|
||||
form.setValue("accountCategory", details?.accountCategory);
|
||||
form.setValue("accountType", details?.accountType.split(","));
|
||||
console.log("Response full:", response);
|
||||
form.setValue("fullname", details?.fullname);
|
||||
form.setValue("username", details?.username);
|
||||
form.setValue("phoneNumber", details?.phoneNumber);
|
||||
// form.setValue("nrp", details?.memberIdentity);
|
||||
// form.setValue("address", details?.address);
|
||||
form.setValue("email", details?.email);
|
||||
form.setValue("role", details?.role?.code);
|
||||
// form.setValue("level", String(details?.userLevelId));
|
||||
// form.setValue("name", details?.accountName);
|
||||
// form.setValue("fullname", details?.fullname);
|
||||
// form.setValue("email", details?.email);
|
||||
// form.setValue("phoneNumber", details?.phoneNumber);
|
||||
// form.setValue("whatsapp", details?.whatsappNumber);
|
||||
// form.setValue("accountCategory", details?.accountCategory);
|
||||
// form.setValue("accountType", details?.accountType.split(","));
|
||||
}
|
||||
|
||||
getDetailData();
|
||||
|
|
@ -106,23 +134,32 @@ export default function EditAccountForBroadcast() {
|
|||
confirmButtonText: "OK",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
router.push("/admin/broadcast/campaign-list/account-list");
|
||||
router.push("/admin/broadcast/campaign-list");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const save = async (data: z.infer<typeof FormSchema>) => {
|
||||
const reqData = {
|
||||
id: String(id),
|
||||
accountName: data.name,
|
||||
accountType: data.accountType.join(","),
|
||||
accountCategory: data.accountCategory,
|
||||
emailAddress: data.email,
|
||||
whatsappNumber: data.whatsapp,
|
||||
id: Number(id),
|
||||
// accountName: data.fullname,
|
||||
// accountType: data.accountType.join(","),
|
||||
// accountCategory: data.accountCategory,
|
||||
// emailAddress: data.email,
|
||||
phoneNumber: data.phoneNumber,
|
||||
firstName: data.fullname,
|
||||
username: data.username,
|
||||
roleId: data.role,
|
||||
// userLevelId: Number(data.level),
|
||||
// memberIdentity: data.nrp,
|
||||
// address: data.address,
|
||||
email: data.email,
|
||||
isDefault: false,
|
||||
isAdmin: true,
|
||||
};
|
||||
console.log("data", data);
|
||||
|
||||
const response = await saveMediaBlastAccount(reqData);
|
||||
const response = await saveUserInternal(reqData);
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
|
|
@ -141,7 +178,7 @@ export default function EditAccountForBroadcast() {
|
|||
<p className="fonnt-semibold">Account</p>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
name="fullname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama</FormLabel>
|
||||
|
|
@ -155,7 +192,7 @@ export default function EditAccountForBroadcast() {
|
|||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="accountType"
|
||||
render={() => (
|
||||
|
|
@ -227,8 +264,8 @@ export default function EditAccountForBroadcast() {
|
|||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
/> */}
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="accountCategory"
|
||||
render={({ field }) => (
|
||||
|
|
@ -269,13 +306,13 @@ export default function EditAccountForBroadcast() {
|
|||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama</FormLabel>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
value={field.value}
|
||||
|
|
@ -289,14 +326,14 @@ export default function EditAccountForBroadcast() {
|
|||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="whatsapp"
|
||||
name="phoneNumber"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Nama</FormLabel>
|
||||
<FormLabel>Nomor Whatsapp</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value}
|
||||
placeholder="Masukkan whatsapp"
|
||||
placeholder="Masukkan nomor whatsapp"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { close, error, loading, success } from "@/config/swal";
|
||||
import { deleteMediaBlastCampaign } from "@/service/broadcast/broadcast";
|
||||
|
|
@ -62,38 +61,47 @@ const columns: ColumnDef<any>[] = [
|
|||
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: "Actions",
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
cell: ({ row, onDeleteSuccess }: any) => {
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
const handleDelete = (id: any) => {
|
||||
const handleDelete = (id: number) => {
|
||||
MySwal.fire({
|
||||
title: "Apakah anda ingin menghapus data?",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#dc3545",
|
||||
confirmButtonText: "Iya",
|
||||
cancelButtonText: "Tidak",
|
||||
}).then((result: any) => {
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
doDeleteAccount(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function doDeleteAccount(id: any) {
|
||||
async function doDeleteAccount(id: number) {
|
||||
loading();
|
||||
const response = await deleteMediaBlastCampaign(id);
|
||||
close();
|
||||
|
||||
if (response.error) {
|
||||
error(response.message);
|
||||
return false;
|
||||
}
|
||||
// success();
|
||||
console.log("Delete response:", response);
|
||||
|
||||
MySwal.fire({
|
||||
icon: "success",
|
||||
title: "Berhasil!",
|
||||
text: "Data berhasil dihapus.",
|
||||
confirmButtonColor: "#3085d6",
|
||||
timer: 2000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
// ✅ panggil callback dari parent
|
||||
onDeleteSuccess?.(id);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -119,14 +127,99 @@ const columns: ColumnDef<any>[] = [
|
|||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
||||
<a>Delete</a>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDelete(row.original.id)}
|
||||
className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer"
|
||||
>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
// {
|
||||
// id: "actions",
|
||||
// accessorKey: "action",
|
||||
// header: "Actions",
|
||||
// enableHiding: false,
|
||||
// cell: ({ row, onDeleteSuccess }: any) => {
|
||||
// const MySwal = withReactContent(Swal);
|
||||
|
||||
// const handleDelete = (id: any) => {
|
||||
// MySwal.fire({
|
||||
// title: "Apakah anda ingin menghapus data?",
|
||||
// showCancelButton: true,
|
||||
// confirmButtonColor: "#dc3545",
|
||||
// confirmButtonText: "Iya",
|
||||
// cancelButtonText: "Tidak",
|
||||
// }).then((result: any) => {
|
||||
// if (result.isConfirmed) {
|
||||
// doDeleteAccount(id);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// async function doDeleteAccount(id: any) {
|
||||
// loading();
|
||||
// const response = await deleteMediaBlastCampaign(id);
|
||||
// close();
|
||||
|
||||
// if (response.error) {
|
||||
// error(response.message);
|
||||
// return false;
|
||||
// }
|
||||
// console.log("Delete response:", response);
|
||||
|
||||
// MySwal.fire({
|
||||
// icon: "success",
|
||||
// title: "Berhasil!",
|
||||
// text: "Data berhasil dihapus.",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// timer: 2000,
|
||||
// timerProgressBar: true,
|
||||
// });
|
||||
// // ✅ langsung hapus dari state
|
||||
// onDeleteSuccess?.(id);
|
||||
// }
|
||||
|
||||
// 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">
|
||||
// <Link
|
||||
// href={`/admin/broadcast/campaign-list/detail/${row.original.id}`}
|
||||
// >
|
||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
||||
// Detail
|
||||
// </DropdownMenuItem>
|
||||
// </Link>
|
||||
// <Link
|
||||
// href={`//admin/broadcast/campaign-list/edit/${row.original.id}`}
|
||||
// >
|
||||
// <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
||||
// Edit
|
||||
// </DropdownMenuItem>
|
||||
// </Link>
|
||||
// <DropdownMenuItem
|
||||
// onClick={() => handleDelete(row.original.id)}
|
||||
// className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer"
|
||||
// >
|
||||
// Delete
|
||||
// </DropdownMenuItem>
|
||||
// </DropdownMenuContent>
|
||||
// </DropdownMenu>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
||||
export default columns;
|
||||
|
|
|
|||
|
|
@ -89,6 +89,11 @@ const CampaignListTable = () => {
|
|||
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [totalPage, setTotalPage] = React.useState(1);
|
||||
|
||||
function handleDeleteSuccess(id: number) {
|
||||
setDataTable((prev) => prev.filter((item) => item.id !== id));
|
||||
}
|
||||
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
|
|
@ -147,12 +152,12 @@ const CampaignListTable = () => {
|
|||
<div className="flex justify-between mb-10 items-center">
|
||||
<p className="text-xl font-medium text-default-900">Daftar Campaign</p>
|
||||
<div className="flex flex-row gap-2">
|
||||
<Link href="/admin/broadcast/campaign-list/account-list">
|
||||
{/* <Link href="/admin/broadcast/campaign-list/account-list">
|
||||
<Button color="primary" size="md" className="text-sm">
|
||||
<UserIcon />
|
||||
Daftar Akun
|
||||
</Button>
|
||||
</Link>
|
||||
</Link> */}
|
||||
<Link href="/admin/broadcast/campaign-list/create">
|
||||
<Button color="primary" size="md" className="text-sm">
|
||||
<NewCampaignIcon size={23} />
|
||||
|
|
@ -189,7 +194,14 @@ const CampaignListTable = () => {
|
|||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
{/* {flexRender(cell.column.columnDef.cell, cell.getContext())} */}
|
||||
{flexRender(cell.column.columnDef.cell, {
|
||||
...cell.getContext(),
|
||||
onDeleteSuccess: (id: number) => {
|
||||
// setDataTable((prev) => prev.filter((item) => item.id !== id)
|
||||
fetchData()
|
||||
},
|
||||
})}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default function CreateEmailBlast() {
|
|||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<ContentBlast type="email" />
|
||||
<ContentBlast />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -90,16 +90,16 @@ const columns: ColumnDef<any>[] = [
|
|||
Detail
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link href={`/admin/broadcast/email/${row.original.id}`}>
|
||||
<Link href={`/admin/broadcast/create/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
||||
Email Blast
|
||||
Email & Whatsapp Blast
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
|
||||
{/* <Link href={`/admin/broadcast/whatsapp/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
|
||||
Whatsapp Blast
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
</Link> */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||
import BroadcastTable from "./email/component/table";
|
||||
import BroadcastTable from "./create/component/table";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
import EscalationTable from "../../shared/communication/escalation/components/escalation-table";
|
||||
|
|
@ -8,7 +8,7 @@ import InternalTable from "../../shared/communication/internal/components/intern
|
|||
import { useState } from "react";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import BroadcastEmailTable from "./email/component/table";
|
||||
import BroadcastEmailTable from "./create/component/table";
|
||||
import BroadcastWhatsAppTable from "./whatsapp/component/table";
|
||||
|
||||
export default function AdminBroadcast() {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default function CreateWABlast() {
|
|||
return (
|
||||
<div>
|
||||
<SiteBreadcrumb />
|
||||
<ContentBlast type="wa" />
|
||||
{/* <ContentBlast /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||
import { Check, ChevronsUpDown, Eye, EyeOff } from "lucide-react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -290,7 +289,17 @@ export default function CreateUserForm() {
|
|||
};
|
||||
|
||||
if (data.role == "OPT-ID") {
|
||||
req.handledSocialMedia = data?.sns ? data.sns.join(",") : "";
|
||||
// req.handledSocialMedia = data?.sns ? data.sns.join(",") : "";
|
||||
if (data.role == "OPT-ID") {
|
||||
let snsValue = data?.sns ? data.sns.join(",") : "";
|
||||
|
||||
// ✅ Jika hanya 1 value → tambahkan koma agar backend tidak error
|
||||
if (data?.sns && data.sns.length === 1) {
|
||||
snsValue = snsValue + ",";
|
||||
}
|
||||
|
||||
req.handledSocialMedia = snsValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.role == "KUR-ID") {
|
||||
|
|
|
|||
|
|
@ -221,7 +221,6 @@ export default function EditUserForm() {
|
|||
form.setValue("level", String(res?.userLevelId));
|
||||
} else {
|
||||
initFetch();
|
||||
console.log("sadad", res?.role?.code);
|
||||
form.setValue("fullname", res?.fullname);
|
||||
form.setValue("username", res?.username);
|
||||
form.setValue("phoneNumber", res?.phoneNumber);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import {
|
|||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||
import { validateMediaLink } from "@/service/media-tracking/media-tracking";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -52,12 +54,132 @@ const columns: ColumnDef<any>[] = [
|
|||
<span className="normal-case">{row.getValue("title")}</span>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// accessorKey: "link",
|
||||
// header: "Link Berita",
|
||||
// cell: ({ row }) => (
|
||||
// <span className="normal-case">{row.getValue("link")}</span>
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
accessorKey: "link",
|
||||
header: "Link Berita",
|
||||
cell: ({ row }) => (
|
||||
<span className="normal-case">{row.getValue("link")}</span>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const link = row.getValue<string>("link");
|
||||
|
||||
if (!link) {
|
||||
return <span className="text-muted-foreground">-</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 underline hover:text-blue-800 break-all"
|
||||
>
|
||||
{link}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "validation",
|
||||
header: "Validasi",
|
||||
cell: ({ row, table }) => {
|
||||
const original = row.original;
|
||||
|
||||
// const isValid = original.isValid;
|
||||
const isRelevant = original.isRelevant;
|
||||
const link = original.link;
|
||||
|
||||
const updateRow = (data: Partial<any>) => {
|
||||
table.options.meta?.updateData(row.index, data);
|
||||
};
|
||||
|
||||
const handleValid = async () => {
|
||||
try {
|
||||
await validateMediaLink(original.id, true);
|
||||
updateRow({
|
||||
isRelevant: true,
|
||||
});
|
||||
table.options.meta?.refetchData?.();
|
||||
} catch (err: any) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInvalid = async () => {
|
||||
try {
|
||||
await validateMediaLink(original.id, false);
|
||||
|
||||
updateRow({
|
||||
isRelevant: false,
|
||||
});
|
||||
table.options.meta?.refetchData?.();
|
||||
} catch (err: any) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
if (!link) {
|
||||
return <span className="text-muted-foreground">-</span>;
|
||||
}
|
||||
|
||||
if (isRelevant === true) {
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
disabled
|
||||
>
|
||||
Relevan
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleValid}
|
||||
className="flex items-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18.7 7.2c-.4-.4-1-.4-1.4 0l-7.5 7.5l-3.1-3.1c-.4-.4-1-.4-1.4 0s-.4 1 0 1.4l3.8 3.8c.2.2.4.3.7.3s.5-.1.7-.3l8.2-8.2c.4-.4.4-1 0-1.4"
|
||||
/>
|
||||
</svg>
|
||||
Relevan
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={handleInvalid} className="flex text-center items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M6.758 17.243L12.001 12m5.243-5.243L12 12m0 0L6.758 6.757M12.001 12l5.243 5.243"
|
||||
/>
|
||||
</svg>
|
||||
Tidak Relevan
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,18 @@ const NewsDetailTable = () => {
|
|||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onPaginationChange: setPagination,
|
||||
meta: {
|
||||
updateData: (rowIndex: number, value: Partial<any>) => {
|
||||
setDataTable((old) =>
|
||||
old.map((row, index) =>
|
||||
index === rowIndex ? { ...row, ...value } : row
|
||||
)
|
||||
);
|
||||
},
|
||||
refetchData: () => {
|
||||
fetchData();
|
||||
},
|
||||
},
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
|
|
@ -154,7 +166,7 @@ const NewsDetailTable = () => {
|
|||
pageIndex: 0,
|
||||
pageSize: Number(showData),
|
||||
});
|
||||
}, [page, showData]);
|
||||
}, [page, showData, id]);
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
import * as React from "react";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
|
||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||
import { exportMediaTrackingToExcel } from "@/utils/export-media-tracking";
|
||||
import { loading, close } from "@/config/swal";
|
||||
import { error } from "@/lib/swal";
|
||||
import {
|
||||
DownloadIcon,
|
||||
Eye,
|
||||
MoreVertical,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -45,19 +53,101 @@ const columns: ColumnDef<any>[] = [
|
|||
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "link",
|
||||
header: "Jumlah Amplifikasi",
|
||||
cell: ({ row }) => <span>{row.getValue("link")}</span>,
|
||||
accessorKey: "resultTotal",
|
||||
header: () => <div className="text-center w-full">Total Artikel</div>,
|
||||
cell: ({ row }) => {
|
||||
const value = row.getValue("resultTotal") as number | string | null;
|
||||
|
||||
const finalValue =
|
||||
value === null || value === undefined || value === ""
|
||||
? 0
|
||||
: Number(value);
|
||||
|
||||
return <div className="text-center w-full">{finalValue}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
cell: ({ row }) => <span>{row.getValue("status")}</span>,
|
||||
accessorKey: "amplification",
|
||||
header: () => <div className="text-center w-full">Jumlah Amplifikasi</div>,
|
||||
cell: ({ row }) => {
|
||||
const raw = row.getValue("amplification") as string | null;
|
||||
|
||||
let total = 0;
|
||||
let invalidTotal = 0;
|
||||
|
||||
if (raw && typeof raw === "string") {
|
||||
const parts = raw.split("/").map((v) => v.trim());
|
||||
total = Number(parts[0]) || 0;
|
||||
invalidTotal = Number(parts[1]) || 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-center w-full font-medium">
|
||||
{total}
|
||||
<span className="text-muted-foreground">/{invalidTotal}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
// {
|
||||
// accessorKey: "status",
|
||||
// header: "Status",
|
||||
// cell: ({ row }) => <span>{row.getValue("status")}</span>,
|
||||
// },
|
||||
// {
|
||||
// accessorKey: "isProcessing",
|
||||
// header: () => <div className="text-center">Status</div>,
|
||||
// cell: ({ row }) => {
|
||||
// const raw = row.getValue("isProcessing");
|
||||
// var status = "Sedang Diproses"
|
||||
// if (Boolean(raw) == true) {
|
||||
// status = "Selesai Diproses";
|
||||
// }
|
||||
// return <div className="text-center">{status}</div>;
|
||||
// },
|
||||
// },
|
||||
{
|
||||
accessorKey: "date",
|
||||
header: "Tanggal Penarikan",
|
||||
cell: ({ row }) => <span>{row.getValue("date")}</span>,
|
||||
accessorKey: "isProcessing",
|
||||
header: () => <div className="text-center">Status</div>,
|
||||
cell: ({ row }) => {
|
||||
const raw = Boolean(row.getValue("isProcessing"));
|
||||
const statusText = raw ? "Sedang Diproses" : "Sudah Selesai";
|
||||
const colorClass = raw
|
||||
? "bg-yellow-100 text-yellow-700 border border-yellow-300"
|
||||
: "bg-green-100 text-green-700 border border-green-300";
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs font-medium inline-block ${colorClass}`}
|
||||
>
|
||||
{statusText}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "createdAt",
|
||||
header: () => <div className="text-center">Tanggal Penarikan</div>,
|
||||
cell: ({ row }) => {
|
||||
const raw = row.getValue("createdAt");
|
||||
if (!raw || typeof raw !== "string")
|
||||
return <div className="text-center">-</div>;
|
||||
|
||||
const date = new Date(raw);
|
||||
if (isNaN(date.getTime())) return <div className="text-center">-</div>;
|
||||
|
||||
const formatted = date.toLocaleDateString("id-ID", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
return <div className="text-center">{formatted}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
|
|
@ -78,13 +168,31 @@ const columns: ColumnDef<any>[] = [
|
|||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
<Link href={`/admin/media-tracking/detail/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||
<DropdownMenuItem className="p-2 border-b cursor-pointer text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
View
|
||||
{row.original.mediaUpload.fileType.secondaryName &&
|
||||
row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
className="p-2 border-b cursor-pointer text-default-700 group rounded-none focus:bg-default focus:text-primary-foreground "
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
await exportMediaTrackingToExcel({
|
||||
mediaTrackingId: row.original.id,
|
||||
});
|
||||
close();
|
||||
} catch (e: any) {
|
||||
close();
|
||||
error(e.message || "Gagal export data");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DownloadIcon className="w-4 h-4 me-1.5" />
|
||||
<p>Download</p>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default function TrackingBeritaCard() {
|
|||
|
||||
const initFecth = async () => {
|
||||
loading();
|
||||
const response = await listDataTracking(showData, page - 1);
|
||||
const response = await listDataTracking(Number(showData), page - 1, search);
|
||||
const data = response?.data?.data;
|
||||
const newData = data?.content;
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
|
|
@ -56,23 +56,85 @@ export default function TrackingBeritaCard() {
|
|||
setContent(response?.data?.data?.content || []);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setSearch(value);
|
||||
|
||||
if (value.trim() === "") {
|
||||
initFecth();
|
||||
} else {
|
||||
fecthAll(value);
|
||||
}
|
||||
const response = await listDataTracking(Number(showData), 0, value);
|
||||
setContent(response?.data?.data?.content || []);
|
||||
};
|
||||
|
||||
// const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// const value = e.target.value;
|
||||
// setSearch(value);
|
||||
|
||||
// if (value.trim() === "") {
|
||||
// initFecth();
|
||||
// } else {
|
||||
// fecthAll(value);
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleSelect = (id: number) => {
|
||||
setSelectedItems((prev) =>
|
||||
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
const doSave = async () => {
|
||||
if (selectedItems.length === 0) {
|
||||
MySwal.fire(
|
||||
"Peringatan",
|
||||
"Pilih minimal 1 berita untuk disimpan.",
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading();
|
||||
|
||||
const promises = selectedItems.map(async (id) => {
|
||||
const res = await mediaTrackingSave({
|
||||
mediaUploadId: id,
|
||||
duration: 24,
|
||||
scrapingPeriod: 3,
|
||||
});
|
||||
|
||||
// cek pesan API
|
||||
if (!res?.data?.success) {
|
||||
throw new Error(
|
||||
res?.data?.message ||
|
||||
"Limit media tracking per hari sudah tercapai. Maksimal 5 tracking per hari."
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
close();
|
||||
|
||||
await MySwal.fire({
|
||||
icon: "success",
|
||||
title: "Berhasil!",
|
||||
text: "Tracking berita berhasil ditambahkan.",
|
||||
confirmButtonColor: "#2563eb",
|
||||
});
|
||||
|
||||
setSelectedItems([]);
|
||||
initFecth();
|
||||
} catch (err: any) {
|
||||
close();
|
||||
MySwal.fire({
|
||||
icon: "error",
|
||||
title: "Gagal!",
|
||||
text: err?.message || "Terjadi kesalahan saat menyimpan data.",
|
||||
confirmButtonColor: "#dc2626",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// const doSave = async () => {
|
||||
// if (selectedItems.length === 0) {
|
||||
// toast("Pilih minimal 1 berita untuk disimpan.");
|
||||
|
|
@ -99,48 +161,63 @@ export default function TrackingBeritaCard() {
|
|||
// }
|
||||
// };
|
||||
|
||||
const doSave = async () => {
|
||||
if (selectedItems.length === 0) {
|
||||
MySwal.fire(
|
||||
"Peringatan",
|
||||
"Pilih minimal 1 berita untuk disimpan.",
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
// const doSave = async () => {
|
||||
// if (selectedItems.length === 0) {
|
||||
// MySwal.fire(
|
||||
// "Peringatan",
|
||||
// "Pilih minimal 1 berita untuk disimpan.",
|
||||
// "warning"
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
loading();
|
||||
// try {
|
||||
// loading();
|
||||
|
||||
const promises = selectedItems.map((id) =>
|
||||
mediaTrackingSave({
|
||||
mediaUploadId: id,
|
||||
duration: 24,
|
||||
scrapingPeriod: 3,
|
||||
})
|
||||
);
|
||||
await Promise.all(promises);
|
||||
// const promises = selectedItems.map((id) =>
|
||||
// mediaTrackingSave({
|
||||
// mediaUploadId: id,
|
||||
// duration: 24,
|
||||
// scrapingPeriod: 3,
|
||||
// })
|
||||
// );
|
||||
// await Promise.all(promises);
|
||||
|
||||
close();
|
||||
// close();
|
||||
|
||||
await MySwal.fire({
|
||||
icon: "success",
|
||||
title: "Berhasil!",
|
||||
text: "Tracking berita berhasil ditambahkan.",
|
||||
confirmButtonColor: "#2563eb",
|
||||
});
|
||||
// await MySwal.fire({
|
||||
// icon: "success",
|
||||
// title: "Berhasil!",
|
||||
// text: "Tracking berita berhasil ditambahkan.",
|
||||
// confirmButtonColor: "#2563eb",
|
||||
// });
|
||||
|
||||
setSelectedItems([]);
|
||||
initFecth();
|
||||
} catch (err: any) {
|
||||
close();
|
||||
MySwal.fire({
|
||||
icon: "error",
|
||||
title: "Gagal!",
|
||||
text: err?.message || "Terjadi kesalahan saat menyimpan data.",
|
||||
confirmButtonColor: "#dc2626",
|
||||
});
|
||||
}
|
||||
// setSelectedItems([]);
|
||||
// initFecth();
|
||||
// } catch (err: any) {
|
||||
// close();
|
||||
// MySwal.fire({
|
||||
// icon: "error",
|
||||
// title: "Gagal!",
|
||||
// text: err?.message || "Terjadi kesalahan saat menyimpan data.",
|
||||
// confirmButtonColor: "#dc2626",
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
const slugify = (text: string) => {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/(^-|-$)+/g, "");
|
||||
};
|
||||
|
||||
const goToDetail = (item: any) => {
|
||||
const type = item.type || "image";
|
||||
const slug = slugify(item.title || "");
|
||||
const url = `/in/${type}/detail/${item.id}-${slug}`;
|
||||
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -188,7 +265,7 @@ export default function TrackingBeritaCard() {
|
|||
<div className="text-sm text-blue-600 font-medium">
|
||||
{selectedItems.length} Item Terpilih{" "}
|
||||
<span className="text-black">
|
||||
/ Tracking Berita tersisa {29 - selectedItems.length}
|
||||
/ Tracking Berita tersisa {5 - selectedItems.length}
|
||||
</span>
|
||||
</div>
|
||||
<Button className="bg-blue-600 text-white" onClick={doSave}>
|
||||
|
|
@ -198,6 +275,48 @@ export default function TrackingBeritaCard() {
|
|||
)}
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{content?.length > 0 &&
|
||||
content.map((item: any) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
className="relative overflow-hidden shadow-sm border rounded-lg"
|
||||
>
|
||||
{/* KLIK GAMBAR = CHECKLIST */}
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSelect(item.id)}
|
||||
>
|
||||
<img
|
||||
src={item.thumbnailLink}
|
||||
alt={item.title}
|
||||
className="w-full h-[300px] object-cover"
|
||||
/>
|
||||
|
||||
{/* CHECKBOX */}
|
||||
<div className="absolute top-2 left-2">
|
||||
<div className="w-5 h-5 border-2 border-white bg-white rounded-sm flex items-center justify-center">
|
||||
{selectedItems.includes(item.id) && (
|
||||
<div className="w-3 h-3 bg-blue-600 rounded-sm" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KLIK JUDUL = DETAIL */}
|
||||
<p
|
||||
className="p-2 text-sm font-medium hover:underline cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
goToDetail(item);
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{content?.length > 1 &&
|
||||
content.map((item: any) => (
|
||||
<Card
|
||||
|
|
@ -222,7 +341,7 @@ export default function TrackingBeritaCard() {
|
|||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="mt-3">
|
||||
{content && content?.length > 0 ? (
|
||||
<CustomPagination
|
||||
|
|
|
|||
|
|
@ -10,15 +10,34 @@ export default function StatusToogle(props: {
|
|||
}) {
|
||||
const { id, initValue } = props;
|
||||
const router = useRouter();
|
||||
// const publishCategory = async (id: number, status: string) => {
|
||||
// const response = await publishUnpublishCategory(id, status);
|
||||
// console.log(response);
|
||||
// if (response?.error) {
|
||||
// error(response.message);
|
||||
// return false;
|
||||
// }
|
||||
// router.push("/admin/settings/category?dataChange=true");
|
||||
// };
|
||||
const publishCategory = async (id: number, status: string) => {
|
||||
const response = await publishUnpublishCategory(id, status);
|
||||
console.log(response);
|
||||
console.log("API Response:", response);
|
||||
|
||||
// cek error interceptor
|
||||
if (response?.error) {
|
||||
error(response.message);
|
||||
error(response.message || "Terjadi kesalahan");
|
||||
return false;
|
||||
}
|
||||
|
||||
// cek flag success asli dari backend
|
||||
if (response?.data?.success === false) {
|
||||
error(response?.data?.message || "Terjadi kesalahan");
|
||||
return false;
|
||||
}
|
||||
|
||||
router.push("/admin/settings/category?dataChange=true");
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch
|
||||
id={String(id)}
|
||||
|
|
|
|||
|
|
@ -28,21 +28,10 @@ import {
|
|||
import { useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./column";
|
||||
|
||||
import { listEnableCategory } from "@/service/content/content";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { NewCampaignIcon } from "@/components/icon";
|
||||
import { getCategories } from "@/service/settings/settings";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import CreateCategoryModal from "./create";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -27,22 +27,10 @@ import {
|
|||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import TablePagination from "@/components/table/table-pagination";
|
||||
import columns from "./column";
|
||||
|
||||
import { listEnableCategory } from "@/service/content/content";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { NewCampaignIcon } from "@/components/icon";
|
||||
import { getCategories } from "@/service/settings/settings";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
Popover,
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ const EventModal = ({
|
|||
const pathname = usePathname();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
|
||||
const [expandedPolda, setExpandedPolda] = useState<Record<number, boolean>>({});
|
||||
const [expandedPolda, setExpandedPolda] = useState<Record<number, boolean>>(
|
||||
{}
|
||||
);
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [timer, setTimer] = useState<number>(120);
|
||||
|
|
@ -153,9 +155,11 @@ const EventModal = ({
|
|||
});
|
||||
|
||||
// State untuk melacak apakah perubahan berasal dari checkbox Jenis Agenda
|
||||
const [isUpdatingFromJenisAgenda, setIsUpdatingFromJenisAgenda] = useState(false);
|
||||
const [isUpdatingFromJenisAgenda, setIsUpdatingFromJenisAgenda] =
|
||||
useState(false);
|
||||
// State untuk melacak jenis perubahan spesifik
|
||||
const [jenisAgendaChangeType, setJenisAgendaChangeType] = useState<string>("");
|
||||
const [jenisAgendaChangeType, setJenisAgendaChangeType] =
|
||||
useState<string>("");
|
||||
|
||||
const levelNumber = Number(getCookiesDecrypt("ulne")) || 0;
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
|
|
@ -261,7 +265,11 @@ const EventModal = ({
|
|||
|
||||
// useEffect untuk sinkronisasi checkbox modal dengan Jenis Agenda
|
||||
useEffect(() => {
|
||||
if (listDest.length > 0 && isUpdatingFromJenisAgenda && jenisAgendaChangeType) {
|
||||
if (
|
||||
listDest.length > 0 &&
|
||||
isUpdatingFromJenisAgenda &&
|
||||
jenisAgendaChangeType
|
||||
) {
|
||||
syncModalWithJenisAgenda();
|
||||
}
|
||||
}, [isUpdatingFromJenisAgenda, jenisAgendaChangeType]);
|
||||
|
|
@ -273,44 +281,55 @@ const EventModal = ({
|
|||
}
|
||||
}, [checkedLevels, isUpdatingFromJenisAgenda]);
|
||||
|
||||
// Fungsi untuk update wilayahPublish berdasarkan checkbox modal
|
||||
// Fungsi untuk update wilayahPublish berdasarkan checkbox modal
|
||||
const updateWilayahPublishFromModal = () => {
|
||||
// Hanya update jika tidak sedang dalam proses update dari Jenis Agenda
|
||||
if (!isUpdatingFromJenisAgenda && listDest.length > 0) {
|
||||
// Hitung item yang dipilih berdasarkan checkedLevels
|
||||
const checkedPoldaCount = listDest.filter((item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
checkedLevels.has(Number(item.id))
|
||||
const checkedPoldaCount = listDest.filter(
|
||||
(item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
checkedLevels.has(Number(item.id))
|
||||
).length;
|
||||
|
||||
|
||||
const checkedPolresCount = listDest.reduce((total: number, item: any) => {
|
||||
if (item.subDestination) {
|
||||
return total + item.subDestination.filter((sub: any) => checkedLevels.has(Number(sub.id))).length;
|
||||
return (
|
||||
total +
|
||||
item.subDestination.filter((sub: any) =>
|
||||
checkedLevels.has(Number(sub.id))
|
||||
).length
|
||||
);
|
||||
}
|
||||
return total;
|
||||
}, 0);
|
||||
|
||||
const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
|
||||
const checkedSatkerCount = satkerItem ? (
|
||||
(checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
|
||||
(satkerItem.subDestination?.filter((sub: any) => checkedLevels.has(Number(sub.id))).length || 0)
|
||||
) : 0;
|
||||
|
||||
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
);
|
||||
const checkedSatkerCount = satkerItem
|
||||
? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
|
||||
(satkerItem.subDestination?.filter((sub: any) =>
|
||||
checkedLevels.has(Number(sub.id))
|
||||
).length || 0)
|
||||
: 0;
|
||||
|
||||
// Checkbox aktif jika ADA item yang dipilih dalam kategori tersebut
|
||||
const hasSelectedPolda = checkedPoldaCount > 0;
|
||||
const hasSelectedPolres = checkedPolresCount > 0;
|
||||
const hasSelectedSatker = checkedSatkerCount > 0;
|
||||
|
||||
|
||||
// Update arrays untuk backend
|
||||
const newSelectedPolda = listDest
|
||||
.filter((item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
checkedLevels.has(Number(item.id))
|
||||
.filter(
|
||||
(item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
checkedLevels.has(Number(item.id))
|
||||
)
|
||||
.map((item: any) => String(item.id));
|
||||
|
||||
|
||||
const newSelectedPolres: string[] = [];
|
||||
listDest.forEach((item: any) => {
|
||||
if (item.subDestination) {
|
||||
|
|
@ -321,7 +340,7 @@ const EventModal = ({
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const newSelectedSatker: string[] = [];
|
||||
if (satkerItem) {
|
||||
if (checkedLevels.has(Number(satkerItem.id))) {
|
||||
|
|
@ -335,51 +354,56 @@ const EventModal = ({
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update state arrays
|
||||
setSelectedPolda(newSelectedPolda);
|
||||
setSelectedPolres(newSelectedPolres);
|
||||
setSelectedSatker(newSelectedSatker);
|
||||
|
||||
|
||||
// Update wilayahPublish berdasarkan yang dipilih di modal
|
||||
setWilayahPublish(prev => {
|
||||
setWilayahPublish((prev) => {
|
||||
const newState = { ...prev };
|
||||
|
||||
|
||||
// Update individual checkboxes
|
||||
newState.polda = hasSelectedPolda;
|
||||
newState.polres = hasSelectedPolres;
|
||||
newState.satker = hasSelectedSatker;
|
||||
|
||||
|
||||
// Update checkbox "semua" berdasarkan level user
|
||||
if (levelNumber === 1) {
|
||||
// Level 1: semua checkbox harus aktif (nasional, polda, polres, satker, international)
|
||||
newState.semua = newState.nasional && hasSelectedPolda && hasSelectedPolres && hasSelectedSatker && newState.international;
|
||||
newState.semua =
|
||||
newState.nasional &&
|
||||
hasSelectedPolda &&
|
||||
hasSelectedPolres &&
|
||||
hasSelectedSatker &&
|
||||
newState.international;
|
||||
} else if (levelNumber === 2) {
|
||||
// Level 2: hanya polres yang perlu aktif
|
||||
newState.semua = hasSelectedPolres;
|
||||
} else {
|
||||
newState.semua = false;
|
||||
}
|
||||
|
||||
|
||||
return newState;
|
||||
});
|
||||
|
||||
|
||||
// Update agendaType berdasarkan checkbox yang aktif
|
||||
const selectedKeys = [];
|
||||
if (hasSelectedPolda) selectedKeys.push(wilayahValueMap.polda);
|
||||
if (hasSelectedPolres) selectedKeys.push(wilayahValueMap.polres);
|
||||
if (hasSelectedSatker) selectedKeys.push(wilayahValueMap.satker);
|
||||
|
||||
|
||||
setAgendaType(selectedKeys.join(","));
|
||||
}
|
||||
};
|
||||
|
||||
// Fungsi untuk sinkronisasi checkbox modal dengan Jenis Agenda
|
||||
// Fungsi untuk sinkronisasi checkbox modal dengan Jenis Agenda
|
||||
const syncModalWithJenisAgenda = () => {
|
||||
// Hanya jalankan sinkronisasi jika perubahan berasal dari checkbox Jenis Agenda
|
||||
if (isUpdatingFromJenisAgenda) {
|
||||
const newCheckedLevels = new Set(checkedLevels);
|
||||
|
||||
|
||||
// Handle checklist actions - menambahkan semua item ke modal
|
||||
if (jenisAgendaChangeType === "polda_checked") {
|
||||
// Checklist semua polda
|
||||
|
|
@ -391,7 +415,11 @@ const EventModal = ({
|
|||
} else if (jenisAgendaChangeType === "polres_checked") {
|
||||
// Checklist semua polres, tapi hanya yang poldanya sudah di-checklist
|
||||
listDest.forEach((item: any) => {
|
||||
if (item.levelNumber === 2 && item.name !== "SATKER POLRI" && newCheckedLevels.has(Number(item.id))) {
|
||||
if (
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
newCheckedLevels.has(Number(item.id))
|
||||
) {
|
||||
if (item.subDestination) {
|
||||
item.subDestination.forEach((polres: any) => {
|
||||
newCheckedLevels.add(Number(polres.id));
|
||||
|
|
@ -401,7 +429,9 @@ const EventModal = ({
|
|||
});
|
||||
} else if (jenisAgendaChangeType === "satker_checked") {
|
||||
// Checklist satker
|
||||
const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
);
|
||||
if (satkerItem) {
|
||||
newCheckedLevels.add(Number(satkerItem.id));
|
||||
if (satkerItem.subDestination) {
|
||||
|
|
@ -434,10 +464,12 @@ const EventModal = ({
|
|||
}
|
||||
}
|
||||
});
|
||||
setWilayahPublish(prev => ({ ...prev, polres: false }));
|
||||
setWilayahPublish((prev) => ({ ...prev, polres: false }));
|
||||
} else if (jenisAgendaChangeType === "satker_unchecked") {
|
||||
// Clear satker dari checkedLevels
|
||||
const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
);
|
||||
if (satkerItem) {
|
||||
newCheckedLevels.delete(Number(satkerItem.id));
|
||||
if (satkerItem.subDestination) {
|
||||
|
|
@ -447,9 +479,9 @@ const EventModal = ({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setCheckedLevels(newCheckedLevels);
|
||||
|
||||
|
||||
// Reset flag setelah sinkronisasi selesai
|
||||
setIsUpdatingFromJenisAgenda(false);
|
||||
setJenisAgendaChangeType("");
|
||||
|
|
@ -484,12 +516,14 @@ const EventModal = ({
|
|||
setCheckedLevels((prev) => {
|
||||
const updatedLevels = new Set(prev);
|
||||
const isCurrentlyChecked = updatedLevels.has(levelId);
|
||||
|
||||
|
||||
if (isCurrentlyChecked) {
|
||||
updatedLevels.delete(levelId);
|
||||
|
||||
|
||||
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
|
||||
const poldaItem = listDest.find((item: any) => Number(item.id) === levelId) as any;
|
||||
const poldaItem = listDest.find(
|
||||
(item: any) => Number(item.id) === levelId
|
||||
) as any;
|
||||
if (poldaItem && poldaItem.subDestination) {
|
||||
poldaItem.subDestination.forEach((polres: any) => {
|
||||
updatedLevels.delete(Number(polres.id));
|
||||
|
|
@ -514,7 +548,12 @@ const EventModal = ({
|
|||
const toggleWilayah = (key: string) => {
|
||||
// Set flag bahwa perubahan berasal dari checkbox Jenis Agenda
|
||||
setIsUpdatingFromJenisAgenda(true);
|
||||
setJenisAgendaChangeType(key + (wilayahPublish[key as keyof typeof wilayahPublish] ? "_unchecked" : "_checked"));
|
||||
setJenisAgendaChangeType(
|
||||
key +
|
||||
(wilayahPublish[key as keyof typeof wilayahPublish]
|
||||
? "_unchecked"
|
||||
: "_checked")
|
||||
);
|
||||
|
||||
setWilayahPublish((prev: any) => {
|
||||
let newState = { ...prev };
|
||||
|
|
@ -551,18 +590,21 @@ const EventModal = ({
|
|||
return newState;
|
||||
}
|
||||
|
||||
// Validasi khusus untuk POLRES
|
||||
// Validasi khusus untuk POLRES
|
||||
if (key === "polres" && !prev[key]) {
|
||||
// Cek apakah ada POLDA yang sudah dipilih di modal
|
||||
const hasSelectedPolda = listDest.some((item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
checkedLevels.has(Number(item.id))
|
||||
const hasSelectedPolda = listDest.some(
|
||||
(item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
checkedLevels.has(Number(item.id))
|
||||
);
|
||||
|
||||
|
||||
if (!hasSelectedPolda) {
|
||||
// Jika tidak ada POLDA yang dipilih, tampilkan peringatan dan batalkan
|
||||
alert("Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES.");
|
||||
alert(
|
||||
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."
|
||||
);
|
||||
// Reset flag karena perubahan dibatalkan
|
||||
setIsUpdatingFromJenisAgenda(false);
|
||||
setJenisAgendaChangeType("");
|
||||
|
|
@ -598,7 +640,9 @@ const EventModal = ({
|
|||
});
|
||||
} else if (key === "satker") {
|
||||
// Clear satker
|
||||
const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
);
|
||||
if (satkerItem) {
|
||||
newCheckedLevels.delete(Number(satkerItem.id));
|
||||
if (satkerItem.subDestination) {
|
||||
|
|
@ -613,9 +657,14 @@ const EventModal = ({
|
|||
|
||||
// Update checkbox "semua" berdasarkan status semua checkbox lainnya
|
||||
// Untuk level 1: semua, nasional, polda, polres, satker, international harus aktif
|
||||
// Untuk level 2: semua, polres harus aktif
|
||||
// Untuk level 2: semua, polres harus aktif
|
||||
if (levelNumber === 1) {
|
||||
newState.semua = newState.nasional && newState.polda && newState.polres && newState.satker && newState.international;
|
||||
newState.semua =
|
||||
newState.nasional &&
|
||||
newState.polda &&
|
||||
newState.polres &&
|
||||
newState.satker &&
|
||||
newState.international;
|
||||
} else if (levelNumber === 2) {
|
||||
newState.semua = newState.polres;
|
||||
} else {
|
||||
|
|
@ -770,7 +819,7 @@ const EventModal = ({
|
|||
|
||||
const onDeleteEventAction = async () => {
|
||||
try {
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const handleOpenDeleteModal = (eventId: string) => {
|
||||
|
|
@ -845,7 +894,6 @@ const EventModal = ({
|
|||
|
||||
const resCsrf = await getCsrfToken();
|
||||
const csrfToken = resCsrf?.data?.token;
|
||||
console.log("CSRF TOKEN : ", csrfToken);
|
||||
const headers = {
|
||||
"X-XSRF-TOKEN": csrfToken,
|
||||
};
|
||||
|
|
@ -950,7 +998,7 @@ const EventModal = ({
|
|||
);
|
||||
};
|
||||
|
||||
const handleRemoveFile = (id: number) => { };
|
||||
const handleRemoveFile = (id: number) => {};
|
||||
|
||||
async function doDelete(id: any) {
|
||||
loading();
|
||||
|
|
@ -1106,40 +1154,42 @@ const EventModal = ({
|
|||
<div className="space-y-1.5">
|
||||
<Label htmlFor="wilayahPublish">Jenis Agenda</Label>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
|
||||
<div>
|
||||
<Checkbox
|
||||
id="semua"
|
||||
checked={wilayahPublish.semua}
|
||||
onCheckedChange={() => toggleWilayah("semua")}
|
||||
/>
|
||||
<label htmlFor="semua" className="ml-2 text-sm">
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{levelNumber === 1 && (
|
||||
<>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="nasional"
|
||||
checked={wilayahPublish.nasional}
|
||||
onCheckedChange={() => toggleWilayah("nasional")}
|
||||
/>
|
||||
<label htmlFor="nasional" className="ml-2 text-sm mr-2">
|
||||
Nasional
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="polda"
|
||||
checked={wilayahPublish.polda}
|
||||
onCheckedChange={() => toggleWilayah("polda")}
|
||||
id="semua"
|
||||
checked={wilayahPublish.semua}
|
||||
onCheckedChange={() => toggleWilayah("semua")}
|
||||
/>
|
||||
<label htmlFor="polda" className="mx-2 text-sm mr-2">
|
||||
Polda
|
||||
<label htmlFor="semua" className="ml-2 text-sm">
|
||||
Semua
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{levelNumber === 1 && (
|
||||
<>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="nasional"
|
||||
checked={wilayahPublish.nasional}
|
||||
onCheckedChange={() => toggleWilayah("nasional")}
|
||||
/>
|
||||
<label
|
||||
htmlFor="nasional"
|
||||
className="ml-2 text-sm mr-2"
|
||||
>
|
||||
Nasional
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="polda"
|
||||
checked={wilayahPublish.polda}
|
||||
onCheckedChange={() => toggleWilayah("polda")}
|
||||
/>
|
||||
<label htmlFor="polda" className="mx-2 text-sm mr-2">
|
||||
Polda
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
@ -1151,39 +1201,41 @@ const EventModal = ({
|
|||
onCheckedChange={() => toggleWilayah("polres")}
|
||||
/>
|
||||
<label htmlFor="polres" className="ml-2 text-sm mr-2">
|
||||
Polres
|
||||
Polres
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{levelNumber === 1 && (
|
||||
<>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="satker"
|
||||
checked={wilayahPublish.satker}
|
||||
onCheckedChange={() => toggleWilayah("satker")}
|
||||
/>
|
||||
<label htmlFor="satker" className="mx-2 text-sm mr-2">
|
||||
Satker
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="international"
|
||||
checked={wilayahPublish.international}
|
||||
onCheckedChange={() => toggleWilayah("international")}
|
||||
/>
|
||||
<label
|
||||
htmlFor="international"
|
||||
className="ml-2 text-sm mr-2"
|
||||
>
|
||||
Internasional
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="satker"
|
||||
checked={wilayahPublish.satker}
|
||||
onCheckedChange={() => toggleWilayah("satker")}
|
||||
/>
|
||||
<label htmlFor="satker" className="mx-2 text-sm mr-2">
|
||||
Satker
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
id="international"
|
||||
checked={wilayahPublish.international}
|
||||
onCheckedChange={() =>
|
||||
toggleWilayah("international")
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="international"
|
||||
className="ml-2 text-sm mr-2"
|
||||
>
|
||||
Internasional
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
<div className="pl-1">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
|
|
@ -1202,7 +1254,9 @@ const EventModal = ({
|
|||
<div key={polda.id} className="border p-2">
|
||||
<Label className="flex items-center">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(Number(polda.id))}
|
||||
checked={checkedLevels.has(
|
||||
Number(polda.id)
|
||||
)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(Number(polda.id))
|
||||
}
|
||||
|
|
@ -1240,9 +1294,13 @@ const EventModal = ({
|
|||
polda?.subDestination?.forEach(
|
||||
(polres: any) => {
|
||||
if (isChecked) {
|
||||
updatedLevels.add(Number(polres.id));
|
||||
updatedLevels.add(
|
||||
Number(polres.id)
|
||||
);
|
||||
} else {
|
||||
updatedLevels.delete(Number(polres.id));
|
||||
updatedLevels.delete(
|
||||
Number(polres.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -1252,18 +1310,27 @@ const EventModal = ({
|
|||
/>
|
||||
Pilih Semua
|
||||
</Label>
|
||||
{polda?.subDestination?.map((polres: any) => (
|
||||
<Label key={polres.id} className="block mt-1">
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(Number(polres.id))}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(Number(polres.id))
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
{polres.name}
|
||||
</Label>
|
||||
))}
|
||||
{polda?.subDestination?.map(
|
||||
(polres: any) => (
|
||||
<Label
|
||||
key={polres.id}
|
||||
className="block mt-1"
|
||||
>
|
||||
<Checkbox
|
||||
checked={checkedLevels.has(
|
||||
Number(polres.id)
|
||||
)}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
Number(polres.id)
|
||||
)
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
{polres.name}
|
||||
</Label>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1304,8 +1371,7 @@ const EventModal = ({
|
|||
<Label>Video</Label>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp4/*": [],
|
||||
"mov/*": [],
|
||||
"video/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp4 atau .mov."
|
||||
|
|
@ -1317,7 +1383,7 @@ const EventModal = ({
|
|||
className="object-fill h-full w-full rounded-md"
|
||||
src={file.url}
|
||||
controls
|
||||
title={`Video ${file.id}`} // Mengganti alt dengan title
|
||||
title={`Video ${file.id}`}
|
||||
/>
|
||||
<div
|
||||
key={index}
|
||||
|
|
@ -1396,10 +1462,9 @@ const EventModal = ({
|
|||
</div>
|
||||
<div>
|
||||
<Label>Teks</Label>
|
||||
|
||||
<FileUploader
|
||||
accept={{
|
||||
"pdf/*": [],
|
||||
"application/pdf": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .pdf."
|
||||
|
|
@ -1455,8 +1520,7 @@ const EventModal = ({
|
|||
/>
|
||||
<FileUploader
|
||||
accept={{
|
||||
"mp3/*": [],
|
||||
"wav/*": [],
|
||||
"audio/*": [],
|
||||
}}
|
||||
maxSize={100}
|
||||
label="Upload file dengan format .mp3 atau .wav."
|
||||
|
|
@ -1482,7 +1546,8 @@ const EventModal = ({
|
|||
type="button"
|
||||
onClick={onPlayPause}
|
||||
disabled={isPlaying}
|
||||
className={`flex items-center gap-2 ${isPlaying
|
||||
className={`flex items-center gap-2 ${
|
||||
isPlaying
|
||||
? "bg-gray-300 cursor-not-allowed"
|
||||
: "bg-primary text-white"
|
||||
} p-2 rounded`}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { deleteMedia } from "@/service/content/content";
|
|||
import { error } from "@/lib/swal";
|
||||
import Swal from "sweetalert2";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const useTableColumns = () => {
|
||||
const t = useTranslations("Table");
|
||||
|
|
@ -181,14 +182,11 @@ const useTableColumns = () => {
|
|||
header: t("action", { defaultValue: "Action" }),
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const data = { id };
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
|
|
@ -230,16 +228,21 @@ const useTableColumns = () => {
|
|||
const [isMabesApprover, setIsMabesApprover] = React.useState(false);
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const roleId = Number(getCookiesDecrypt("urie")); // pastikan jadi number
|
||||
|
||||
React.useEffect(() => {
|
||||
if (userLevelId !== undefined && roleId !== undefined) {
|
||||
setIsMabesApprover(
|
||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
||||
Number(userLevelId) === 216 && Number(roleId) === 3
|
||||
);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
const canEdit =
|
||||
Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover ||
|
||||
roleId === 14;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -260,16 +263,8 @@ const useTableColumns = () => {
|
|||
View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
{/* <Link
|
||||
href={`/contributor/content/audio/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> */}
|
||||
{(Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover) && (
|
||||
|
||||
{canEdit && (
|
||||
<Link
|
||||
href={`/contributor/content/audio/update/${row.original.id}`}
|
||||
>
|
||||
|
|
@ -279,6 +274,7 @@ const useTableColumns = () => {
|
|||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
|
|
@ -286,15 +282,6 @@ const useTableColumns = () => {
|
|||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(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" />
|
||||
Hapus
|
||||
</DropdownMenuItem>
|
||||
)} */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -186,11 +186,7 @@ const useTableColumns = () => {
|
|||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const data = { id };
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
|
|
@ -232,16 +228,21 @@ const useTableColumns = () => {
|
|||
const [isMabesApprover, setIsMabesApprover] = React.useState(false);
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const roleId = Number(getCookiesDecrypt("urie")); // pastikan jadi number
|
||||
|
||||
React.useEffect(() => {
|
||||
if (userLevelId !== undefined && roleId !== undefined) {
|
||||
setIsMabesApprover(
|
||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
||||
Number(userLevelId) === 216 && Number(roleId) === 3
|
||||
);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
const canEdit =
|
||||
Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover ||
|
||||
roleId === 14;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -262,16 +263,8 @@ const useTableColumns = () => {
|
|||
View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
{/* <Link
|
||||
href={`/contributor/content/image/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> */}
|
||||
{(Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover) && (
|
||||
|
||||
{canEdit && (
|
||||
<Link
|
||||
href={`/contributor/content/image/update/${row.original.id}`}
|
||||
>
|
||||
|
|
@ -281,6 +274,7 @@ const useTableColumns = () => {
|
|||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
|
|
@ -288,20 +282,138 @@ const useTableColumns = () => {
|
|||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(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" />
|
||||
Hapus
|
||||
</DropdownMenuItem>
|
||||
)} */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
// {
|
||||
// id: "actions",
|
||||
// accessorKey: "action",
|
||||
// header: t("action", { defaultValue: "Action" }),
|
||||
// enableHiding: false,
|
||||
// cell: ({ row }) => {
|
||||
// const router = useRouter();
|
||||
// const MySwal = withReactContent(Swal);
|
||||
|
||||
// async function doDelete(id: any) {
|
||||
// // loading();
|
||||
// const data = {
|
||||
// id,
|
||||
// };
|
||||
|
||||
// const response = await deleteMedia(data);
|
||||
|
||||
// if (response?.error) {
|
||||
// error(response.message);
|
||||
// return false;
|
||||
// }
|
||||
// success();
|
||||
// }
|
||||
|
||||
// function success() {
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// const handleDeleteMedia = (id: any) => {
|
||||
// MySwal.fire({
|
||||
// title: "Hapus Data",
|
||||
// text: "",
|
||||
// icon: "warning",
|
||||
// showCancelButton: true,
|
||||
// cancelButtonColor: "#3085d6",
|
||||
// confirmButtonColor: "#d33",
|
||||
// confirmButtonText: "Hapus",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// doDelete(id);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// const [isMabesApprover, setIsMabesApprover] = React.useState(false);
|
||||
// const userId = getCookiesDecrypt("uie");
|
||||
// const userLevelId = getCookiesDecrypt("ulie");
|
||||
// const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (userLevelId !== undefined && roleId !== undefined) {
|
||||
// setIsMabesApprover(
|
||||
// Number(userLevelId) == 216 && Number(roleId) == 3
|
||||
// );
|
||||
// }
|
||||
// }, [userLevelId, roleId]);
|
||||
|
||||
// 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/content/image/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/content/image/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> */}
|
||||
// {(Number(row.original.uploadedById) === Number(userId) ||
|
||||
// isMabesApprover) && (
|
||||
// <Link
|
||||
// href={`/contributor/content/image/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={() => handleDeleteMedia(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>
|
||||
// {/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||
// <DropdownMenuItem
|
||||
// onClick={() => handleDeleteMedia(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" />
|
||||
// Hapus
|
||||
// </DropdownMenuItem>
|
||||
// )} */}
|
||||
// </DropdownMenuContent>
|
||||
// </DropdownMenu>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
||||
return columns;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -187,6 +187,13 @@ const TableSPIT = () => {
|
|||
setCategories(resCategory || []);
|
||||
}
|
||||
|
||||
// Panggil fetchData otomatis ketika dateFilter berubah
|
||||
React.useEffect(() => {
|
||||
if (dateFilter) {
|
||||
fetchData();
|
||||
}
|
||||
}, [dateFilter]);
|
||||
|
||||
async function fetchData() {
|
||||
let isPublish;
|
||||
if (statusFilter.length === 0) {
|
||||
|
|
@ -198,7 +205,7 @@ const TableSPIT = () => {
|
|||
}
|
||||
|
||||
const formattedStartDate = dateFilter
|
||||
? format(parseISO(dateFilter), "yyyy-MM-dd")
|
||||
? format(new Date(dateFilter + "T00:00:00"), "yyyy-MM-dd")
|
||||
: "";
|
||||
|
||||
try {
|
||||
|
|
@ -240,15 +247,15 @@ const TableSPIT = () => {
|
|||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between items-center px-5">
|
||||
<div className="w-[150px] md:w-[250px] lg:w-[250px]">
|
||||
<div className="w-[150px] md:w-[250px] lg:w-[300px]">
|
||||
<InputGroup merged>
|
||||
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
|
||||
<Search className=" h-4 w-4 dark:text-white" />
|
||||
<Search className=" h-6 w-6 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"
|
||||
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white text-[16px]"
|
||||
value={search}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
|
|
@ -305,7 +312,7 @@ const TableSPIT = () => {
|
|||
<div className="flex flex-row justify-between my-1 mx-1">
|
||||
<p>Filter</p>
|
||||
</div>
|
||||
<div className="mx-2 my-1">
|
||||
{/* <div className="mx-2 my-1">
|
||||
<Label>{t("date", { defaultValue: "Date" })}</Label>
|
||||
<Input
|
||||
type="date"
|
||||
|
|
@ -313,7 +320,21 @@ const TableSPIT = () => {
|
|||
onChange={(e) => setDateFilter(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div> */}
|
||||
<div className="mx-2 my-1">
|
||||
<Label>{t("date", { defaultValue: "Tanggal" })}</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={dateFilter}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setDateFilter(value);
|
||||
fetchData(); // langsung panggil data baru
|
||||
}}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Label className="ml-2 mt-2">Status</Label>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { deleteMedia } from "@/service/content/content";
|
|||
import withReactContent from "sweetalert2-react-content";
|
||||
import Swal from "sweetalert2";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const useTableColumns = () => {
|
||||
const t = useTranslations("Table");
|
||||
|
|
@ -176,20 +177,18 @@ const useTableColumns = () => {
|
|||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: t("action", { defaultValue: "Action" }),
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const data = { id };
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
|
|
@ -231,16 +230,21 @@ const useTableColumns = () => {
|
|||
const [isMabesApprover, setIsMabesApprover] = React.useState(false);
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const roleId = Number(getCookiesDecrypt("urie")); // pastikan jadi number
|
||||
|
||||
React.useEffect(() => {
|
||||
if (userLevelId !== undefined && roleId !== undefined) {
|
||||
setIsMabesApprover(
|
||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
||||
Number(userLevelId) === 216 && Number(roleId) === 3
|
||||
);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
const canEdit =
|
||||
Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover ||
|
||||
roleId === 14;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -261,8 +265,8 @@ const useTableColumns = () => {
|
|||
View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
{(Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover) && (
|
||||
|
||||
{canEdit && (
|
||||
<Link
|
||||
href={`/contributor/content/teks/update/${row.original.id}`}
|
||||
>
|
||||
|
|
@ -272,14 +276,7 @@ const useTableColumns = () => {
|
|||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
{/* <Link
|
||||
href={`/contributor/content/teks/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={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
|
|
@ -287,20 +284,136 @@ const useTableColumns = () => {
|
|||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(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" />
|
||||
Hapus
|
||||
</DropdownMenuItem>
|
||||
)} */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: "actions",
|
||||
// accessorKey: "action",
|
||||
// header: t("action", { defaultValue: "Action" }),
|
||||
// enableHiding: false,
|
||||
// cell: ({ row }) => {
|
||||
// const MySwal = withReactContent(Swal);
|
||||
|
||||
// async function doDelete(id: any) {
|
||||
// // loading();
|
||||
// const data = {
|
||||
// id,
|
||||
// };
|
||||
|
||||
// const response = await deleteMedia(data);
|
||||
|
||||
// if (response?.error) {
|
||||
// error(response.message);
|
||||
// return false;
|
||||
// }
|
||||
// success();
|
||||
// }
|
||||
|
||||
// function success() {
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// const handleDeleteMedia = (id: any) => {
|
||||
// MySwal.fire({
|
||||
// title: "Hapus Data",
|
||||
// text: "",
|
||||
// icon: "warning",
|
||||
// showCancelButton: true,
|
||||
// cancelButtonColor: "#3085d6",
|
||||
// confirmButtonColor: "#d33",
|
||||
// confirmButtonText: "Hapus",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// doDelete(id);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// const [isMabesApprover, setIsMabesApprover] = React.useState(false);
|
||||
// const userId = getCookiesDecrypt("uie");
|
||||
// const userLevelId = getCookiesDecrypt("ulie");
|
||||
// const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (userLevelId !== undefined && roleId !== undefined) {
|
||||
// setIsMabesApprover(
|
||||
// Number(userLevelId) == 216 && Number(roleId) == 3
|
||||
// );
|
||||
// }
|
||||
// }, [userLevelId, roleId]);
|
||||
|
||||
// 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/content/teks/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>
|
||||
// {(Number(row.original.uploadedById) === Number(userId) ||
|
||||
// isMabesApprover) && (
|
||||
// <Link
|
||||
// href={`/contributor/content/teks/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>
|
||||
// )}
|
||||
// {/* <Link
|
||||
// href={`/contributor/content/teks/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={() => handleDeleteMedia(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>
|
||||
// {/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||
// <DropdownMenuItem
|
||||
// onClick={() => handleDeleteMedia(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" />
|
||||
// Hapus
|
||||
// </DropdownMenuItem>
|
||||
// )} */}
|
||||
// </DropdownMenuContent>
|
||||
// </DropdownMenu>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
];
|
||||
return columns;
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,6 +18,7 @@ import Swal from "sweetalert2";
|
|||
import withReactContent from "sweetalert2-react-content";
|
||||
import { error } from "@/lib/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "@/i18n/routing";
|
||||
|
||||
const useTableColumns = () => {
|
||||
const t = useTranslations("Table");
|
||||
|
|
@ -176,20 +177,18 @@ const useTableColumns = () => {
|
|||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: t("action", { defaultValue: "Action" }),
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
async function doDelete(id: any) {
|
||||
// loading();
|
||||
const data = {
|
||||
id,
|
||||
};
|
||||
|
||||
const data = { id };
|
||||
const response = await deleteMedia(data);
|
||||
|
||||
if (response?.error) {
|
||||
|
|
@ -231,16 +230,21 @@ const useTableColumns = () => {
|
|||
const [isMabesApprover, setIsMabesApprover] = React.useState(false);
|
||||
const userId = getCookiesDecrypt("uie");
|
||||
const userLevelId = getCookiesDecrypt("ulie");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const roleId = Number(getCookiesDecrypt("urie")); // pastikan jadi number
|
||||
|
||||
React.useEffect(() => {
|
||||
if (userLevelId !== undefined && roleId !== undefined) {
|
||||
setIsMabesApprover(
|
||||
Number(userLevelId) == 216 && Number(roleId) == 3
|
||||
Number(userLevelId) === 216 && Number(roleId) === 3
|
||||
);
|
||||
}
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
const canEdit =
|
||||
Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover ||
|
||||
roleId === 14;
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -261,16 +265,8 @@ const useTableColumns = () => {
|
|||
View
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
{/* <Link
|
||||
href={`/contributor/content/video/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> */}
|
||||
{(Number(row.original.uploadedById) === Number(userId) ||
|
||||
isMabesApprover) && (
|
||||
|
||||
{canEdit && (
|
||||
<Link
|
||||
href={`/contributor/content/video/update/${row.original.id}`}
|
||||
>
|
||||
|
|
@ -280,6 +276,7 @@ const useTableColumns = () => {
|
|||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(row.original.id)}
|
||||
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
|
||||
|
|
@ -287,20 +284,136 @@ const useTableColumns = () => {
|
|||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
{/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleDeleteMedia(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" />
|
||||
Hapus
|
||||
</DropdownMenuItem>
|
||||
)} */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: "actions",
|
||||
// accessorKey: "action",
|
||||
// header: t("action", { defaultValue: "Action" }),
|
||||
// enableHiding: false,
|
||||
// cell: ({ row }) => {
|
||||
// const MySwal = withReactContent(Swal);
|
||||
|
||||
// async function doDelete(id: any) {
|
||||
// // loading();
|
||||
// const data = {
|
||||
// id,
|
||||
// };
|
||||
|
||||
// const response = await deleteMedia(data);
|
||||
|
||||
// if (response?.error) {
|
||||
// error(response.message);
|
||||
// return false;
|
||||
// }
|
||||
// success();
|
||||
// }
|
||||
|
||||
// function success() {
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// window.location.reload();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// const handleDeleteMedia = (id: any) => {
|
||||
// MySwal.fire({
|
||||
// title: "Hapus Data",
|
||||
// text: "",
|
||||
// icon: "warning",
|
||||
// showCancelButton: true,
|
||||
// cancelButtonColor: "#3085d6",
|
||||
// confirmButtonColor: "#d33",
|
||||
// confirmButtonText: "Hapus",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// doDelete(id);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// const [isMabesApprover, setIsMabesApprover] = React.useState(false);
|
||||
// const userId = getCookiesDecrypt("uie");
|
||||
// const userLevelId = getCookiesDecrypt("ulie");
|
||||
// const roleId = getCookiesDecrypt("urie");
|
||||
|
||||
// React.useEffect(() => {
|
||||
// if (userLevelId !== undefined && roleId !== undefined) {
|
||||
// setIsMabesApprover(
|
||||
// Number(userLevelId) == 216 && Number(roleId) == 3
|
||||
// );
|
||||
// }
|
||||
// }, [userLevelId, roleId]);
|
||||
|
||||
// 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/content/video/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/content/video/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> */}
|
||||
// {(Number(row.original.uploadedById) === Number(userId) ||
|
||||
// isMabesApprover) && (
|
||||
// <Link
|
||||
// href={`/contributor/content/video/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={() => handleDeleteMedia(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>
|
||||
// {/* {(row.original.uploadedById === userId || isMabesApprover) && (
|
||||
// <DropdownMenuItem
|
||||
// onClick={() => handleDeleteMedia(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" />
|
||||
// Hapus
|
||||
// </DropdownMenuItem>
|
||||
// )} */}
|
||||
// </DropdownMenuContent>
|
||||
// </DropdownMenu>
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
];
|
||||
|
||||
return columns;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,6 +26,7 @@ import { deleteBlog } from "@/service/blog/blog";
|
|||
import { error, loading, close } from "@/lib/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import axios from "axios";
|
||||
import { downloadReport } from "@/service/report/report";
|
||||
|
||||
const useTableColumns = ({
|
||||
handlePreview,
|
||||
|
|
@ -127,14 +128,10 @@ const useTableColumns = ({
|
|||
const handleDownload = async (id: string) => {
|
||||
try {
|
||||
loading();
|
||||
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 response = await downloadReport(id);
|
||||
console.log(response?.data);
|
||||
const url = window.URL.createObjectURL(new Blob([response?.data], { type : "application/pdf"}));
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute("download", `report-${id}.pdf`);
|
||||
|
|
@ -152,6 +149,34 @@ const useTableColumns = ({
|
|||
}
|
||||
};
|
||||
|
||||
// const handleDownload = async (id: string) => {
|
||||
// try {
|
||||
// loading();
|
||||
// const response = await axios.get(
|
||||
// `https://new.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();
|
||||
// close();
|
||||
// } catch (error) {
|
||||
// console.error("Download failed", error);
|
||||
// MySwal.fire({
|
||||
// title: "Gagal",
|
||||
// text: "Terjadi kesalahan saat mengunduh file.",
|
||||
// icon: "error",
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ import withReactContent from "sweetalert2-react-content";
|
|||
import Swal from "sweetalert2";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const useTableColumns = () => {
|
||||
const useTableColumns = (
|
||||
activeTab: "ta" | "daily" | "special" | "mabes-koor",
|
||||
) => {
|
||||
const t = useTranslations("Table");
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -113,6 +115,31 @@ const useTableColumns = () => {
|
|||
const MySwal = withReactContent(Swal);
|
||||
const roleId = Number(getCookiesDecrypt("urie")) || 0;
|
||||
|
||||
// ❗ jika tab = "special"
|
||||
if (activeTab === "special") {
|
||||
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">
|
||||
<Link href={`/contributor/task/detail/${row.original.id}`}>
|
||||
<DropdownMenuItem className="p-2 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>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteProcess(id: any) {
|
||||
loading();
|
||||
const resDelete = await deleteTaskTa(id);
|
||||
|
|
@ -165,7 +192,11 @@ const useTableColumns = () => {
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="p-0" align="end">
|
||||
{(roleId == 11 || roleId == 12 || roleId == 19) && (
|
||||
{/* {(roleId == 11 || roleId == 12 || roleId == 19) && ( */}
|
||||
{(roleId == 11 ||
|
||||
roleId == 12 ||
|
||||
roleId == 19 ||
|
||||
roleId == 3) && (
|
||||
<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" />
|
||||
|
|
@ -173,15 +204,16 @@ const useTableColumns = () => {
|
|||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
{roleId == 11 && (
|
||||
<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>
|
||||
)}
|
||||
{(roleId == 11 || roleId == 12 || roleId == 19) && (
|
||||
{roleId == 11 ||
|
||||
(roleId == 3 && (
|
||||
<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>
|
||||
))}
|
||||
{(roleId == 12 || roleId == 19) && (
|
||||
<Link
|
||||
href={`/contributor/task-ta/upload-task/${row.original.id}`}
|
||||
>
|
||||
|
|
@ -191,15 +223,16 @@ const useTableColumns = () => {
|
|||
</DropdownMenuItem>
|
||||
</Link>
|
||||
)}
|
||||
{roleId == 11 && (
|
||||
<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>
|
||||
)}
|
||||
{roleId == 11 ||
|
||||
(roleId == 3 && (
|
||||
<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>
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -35,10 +35,19 @@ const TaskTaPage = () => {
|
|||
<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", { defaultValue: "Tabel" })} {t("task-ta", { defaultValue: "Task Ta" })}
|
||||
{t("tabel", { defaultValue: "Tabel" })}{" "}
|
||||
{t("task-ta", { defaultValue: "Task Ta" })}
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
{roleId !== 12 && (
|
||||
{/* {roleId !== 12 && (
|
||||
<Link href={"/contributor/task-ta/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-task", { defaultValue: "Create Task" })}
|
||||
</Button>
|
||||
</Link>
|
||||
)} */}
|
||||
{roleId !== 12 && roleId !== 19 && (
|
||||
<Link href={"/contributor/task-ta/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ 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 {
|
||||
checkAuthorization,
|
||||
checkLoginSession,
|
||||
getCookiesDecrypt,
|
||||
} from "@/lib/utils";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
|
|
@ -13,12 +17,13 @@ const TaskPage = () => {
|
|||
const t = useTranslations("AnalyticsDashboard");
|
||||
useEffect(() => {
|
||||
function initState() {
|
||||
checkAuthorization("admin");
|
||||
checkAuthorization("admin");
|
||||
checkLoginSession();
|
||||
}
|
||||
|
||||
initState();
|
||||
}, []);
|
||||
const levelNumber = getCookiesDecrypt("ulne");
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -29,16 +34,28 @@ const TaskPage = () => {
|
|||
<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", { defaultValue: "Tabel" })} {t("task", { defaultValue: "Task" })}
|
||||
{t("tabel", { defaultValue: "Tabel" })}{" "}
|
||||
{t("task", { defaultValue: "Task" })}
|
||||
</div>
|
||||
<div className="flex-none">
|
||||
{Number(levelNumber) !== 3 && (
|
||||
<div className="flex-none">
|
||||
<Link href="/contributor/task/create">
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-task", { defaultValue: "Create Task" })}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* <div className="flex-none">
|
||||
<Link href={"/contributor/task/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-task", { defaultValue: "Create Task" })}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import ReportTable from "../contributor/report/components/report-table";
|
|||
const DashboardPage = () => {
|
||||
const t = useTranslations("AnalyticsDashboard");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const levelNumber = Number(getCookiesDecrypt("ulne"));
|
||||
|
||||
return Number(roleId) == 2 || Number(roleId) == 11 || Number(roleId) == 12 ? (
|
||||
<div>
|
||||
|
|
@ -54,18 +55,23 @@ const DashboardPage = () => {
|
|||
>
|
||||
{t("schedule", { defaultValue: "Schedule" })}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="indeks"
|
||||
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
|
||||
>
|
||||
{t("indeks", { defaultValue: "Indeks" })}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="report"
|
||||
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
|
||||
>
|
||||
{t("report", { defaultValue: "Report" })}
|
||||
</TabsTrigger>
|
||||
{levelNumber !== 3 && (
|
||||
<>
|
||||
<TabsTrigger
|
||||
value="indeks"
|
||||
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
|
||||
>
|
||||
{t("indeks", { defaultValue: "Indeks" })}
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger
|
||||
value="report"
|
||||
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
|
||||
>
|
||||
{t("report", { defaultValue: "Report" })}
|
||||
</TabsTrigger>
|
||||
</>
|
||||
)}
|
||||
</TabsList>
|
||||
</Card>
|
||||
<TabsContent value="routine-task">
|
||||
|
|
@ -75,18 +81,24 @@ const DashboardPage = () => {
|
|||
<CardContent className="p-4">
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<StatisticsBlock
|
||||
title={t("Hasil_unggah_disetujui_hari_ini", { defaultValue: "Hasil Unggah Disetujui Hari Ini" })}
|
||||
title={t("Hasil_unggah_disetujui_hari_ini", {
|
||||
defaultValue: "Hasil Unggah Disetujui Hari Ini",
|
||||
})}
|
||||
total="3,564"
|
||||
className="bg-info/10 border-none shadow-none"
|
||||
/>
|
||||
<StatisticsBlock
|
||||
title={t("Hasil_unggah_direvisi_hari_ini", { defaultValue: "Hasil Unggah Direvisi Hari Ini" })}
|
||||
title={t("Hasil_unggah_direvisi_hari_ini", {
|
||||
defaultValue: "Hasil Unggah Direvisi Hari Ini",
|
||||
})}
|
||||
total="564"
|
||||
className="bg-warning/10 border-none shadow-none"
|
||||
chartColor="#FB8F65"
|
||||
/>
|
||||
<StatisticsBlock
|
||||
title={t("Hasil_unggah_ditolak_hari_ini", { defaultValue: "Hasil Unggah Ditolak Hari Ini" })}
|
||||
title={t("Hasil_unggah_ditolak_hari_ini", {
|
||||
defaultValue: "Hasil Unggah Ditolak Hari Ini",
|
||||
})}
|
||||
total="+5.0%"
|
||||
className="bg-primary/10 border-none shadow-none"
|
||||
chartColor="#2563eb"
|
||||
|
|
@ -101,7 +113,9 @@ const DashboardPage = () => {
|
|||
<Card>
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<CardTitle className="flex-1 text-lg">
|
||||
{t("Total-Content-Production", { defaultValue: "Total Content Production" })}
|
||||
{t("Total-Content-Production", {
|
||||
defaultValue: "Total Content Production",
|
||||
})}
|
||||
</CardTitle>
|
||||
<DashboardDropdown />
|
||||
</CardHeader>
|
||||
|
|
@ -113,7 +127,9 @@ const DashboardPage = () => {
|
|||
<div className="lg:col-span-8 col-span-12">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<CardTitle className="flex-1">{t("tabel", { defaultValue: "Tabel" })}</CardTitle>
|
||||
<CardTitle className="flex-1">
|
||||
{t("tabel", { defaultValue: "Tabel" })}
|
||||
</CardTitle>
|
||||
{/* <DashboardDropdown /> */}
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
|
|
@ -132,14 +148,26 @@ const DashboardPage = () => {
|
|||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
Tabel Penugasan
|
||||
</div>
|
||||
<div>
|
||||
{Number(levelNumber) !== 3 && (
|
||||
<div className="flex-none">
|
||||
<Link href="/contributor/task/create">
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-task", {
|
||||
defaultValue: "Create Task",
|
||||
})}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{/* <div>
|
||||
<Link href={"/contributor/task/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon />
|
||||
Buat Penugasan
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</Card>
|
||||
<CardContent className="p-0 mt-3">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
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,
|
||||
|
|
@ -10,13 +8,42 @@ import {
|
|||
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 { deleteTicketInternal } from "@/service/communication/communication";
|
||||
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 MySwal = withReactContent(Swal);
|
||||
|
||||
const useTableColumns = (onDeleteSuccess?: () => void) => {
|
||||
const t = useTranslations("Table");
|
||||
|
||||
const handleDelete = async (id: any) => {
|
||||
MySwal.fire({
|
||||
title: "Delete Data?",
|
||||
text: "Apakah Anda yakin ingin menghapus data ini?",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Ya, hapus",
|
||||
}).then(async (result) => {
|
||||
if (result.isConfirmed) {
|
||||
try {
|
||||
await deleteTicketInternal(id);
|
||||
|
||||
MySwal.fire("Sukses", "Data berhasil dihapus!", "success");
|
||||
|
||||
if (onDeleteSuccess) onDeleteSuccess();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
MySwal.fire("Gagal", "Terjadi kesalahan saat menghapus", "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
|
|
@ -67,46 +94,46 @@ const useTableColumns = () => {
|
|||
},
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "action",
|
||||
header: t("action", { defaultValue: "Action" }),
|
||||
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={`/shared/communication/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={`/shared/communication/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
|
||||
header: t("action"),
|
||||
cell: ({ row }) => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="icon" className="bg-transparent hover:bg-transparent">
|
||||
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="p-0">
|
||||
{/* View */}
|
||||
<Link
|
||||
href={`/shared/communication/internal/detail/${row.original.id}`}
|
||||
>
|
||||
<DropdownMenuItem className="p-2 border-b cursor-pointer hover:bg-slate-100">
|
||||
<Eye className="w-4 h-4 me-1.5" />
|
||||
View
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
</Link>
|
||||
|
||||
{/* Edit */}
|
||||
<Link
|
||||
href={`/shared/communication/internal/update/${row.original.id}`}
|
||||
>
|
||||
<DropdownMenuItem className="p-2 border-b cursor-pointer hover:bg-slate-100">
|
||||
<SquarePen className="w-4 h-4 me-1.5" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
|
||||
{/* Delete */}
|
||||
<DropdownMenuItem
|
||||
className="p-2 text-destructive bg-destructive/30 cursor-pointer"
|
||||
onClick={() => handleDelete(row.original.id)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 me-1.5" />
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ import useTableColumns from "./columns";
|
|||
const InternalTable = () => {
|
||||
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>([]);
|
||||
|
|
@ -79,7 +78,6 @@ const InternalTable = () => {
|
|||
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),
|
||||
|
|
@ -87,11 +85,7 @@ const InternalTable = () => {
|
|||
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 roleId = getCookiesDecrypt("urie");
|
||||
const columns = useTableColumns();
|
||||
const columns = useTableColumns(() => fetchData());
|
||||
const table = useReactTable({
|
||||
data: dataTable,
|
||||
columns,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import { useTranslations } from "next-intl";
|
|||
const ContestPage = () => {
|
||||
const [userLevelId, setUserLevelId] = useState<any>(null);
|
||||
const t = useTranslations("Contest");
|
||||
const levelNumber = Number(getCookiesDecrypt("ulne"));
|
||||
|
||||
useEffect(() => {
|
||||
setUserLevelId(Number(getCookiesDecrypt("ulie")));
|
||||
}, []);
|
||||
|
|
@ -25,18 +27,36 @@ const ContestPage = () => {
|
|||
<CardTitle>
|
||||
<div className="flex items-center">
|
||||
<div className="flex-1 text-xl font-medium text-default-900">
|
||||
{t("table", { defaultValue: "Table" })} {t("contest", { defaultValue: "Contest" })}
|
||||
{t("table", { defaultValue: "Table" })}{" "}
|
||||
{t("contest", { defaultValue: "Contest" })}
|
||||
</div>
|
||||
{userLevelId !== 776 && userLevelId !== null && (
|
||||
{userLevelId !== 776 &&
|
||||
userLevelId !== null &&
|
||||
levelNumber !== 3 && (
|
||||
<div className="flex-none">
|
||||
<Link href={"/shared/contest/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-contest", {
|
||||
defaultValue: "Create Contest",
|
||||
})}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* {userLevelId !== 776 && userLevelId !== null && (
|
||||
<div className="flex-none">
|
||||
<Link href={"/shared/contest/create"}>
|
||||
<Button color="primary" className="text-white">
|
||||
<UploadIcon size={18} className="mr-2" />
|
||||
{t("create-contest", { defaultValue: "Create Contest" })}
|
||||
{t("create-contest", {
|
||||
defaultValue: "Create Contest",
|
||||
})}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const VideoSliderPage = () => {
|
|||
});
|
||||
const initFetch = async () => {
|
||||
const request = {
|
||||
sortBy: type == "popular" ? "clickCount" : "createdAt",
|
||||
sortBy: type == "popular" ? "favorite" : "createdAt",
|
||||
contentTypeId: "2",
|
||||
};
|
||||
const response = await getListContent(request);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const AudioSliderPage = () => {
|
|||
});
|
||||
const initFetch = async () => {
|
||||
const request = {
|
||||
sortBy: type == "popular" ? "clickCount" : "createdAt",
|
||||
sortBy: type == "popular" ? "favorite" : "createdAt",
|
||||
contentTypeId: "4",
|
||||
};
|
||||
const response = await getListContent(request);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const ImageSliderPage = () => {
|
|||
});
|
||||
const initFetch = async () => {
|
||||
const request = {
|
||||
sortBy: type == "popular" ? "clickCount" : "createdAt",
|
||||
sortBy: type == "popular" ? "favorite" : "createdAt",
|
||||
contentTypeId: "3",
|
||||
};
|
||||
const response = await getListContent(request);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const TeksSliderPage = () => {
|
|||
});
|
||||
const initFetch = async () => {
|
||||
const request = {
|
||||
sortBy: type == "popular" ? "clickCount" : "createdAt",
|
||||
sortBy: type == "popular" ? "favorite" : "createdAt",
|
||||
contentTypeId: "1",
|
||||
};
|
||||
const response = await getListContent(request);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export default function FilterPage() {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -97,7 +97,7 @@ export default function FilterPage() {
|
|||
|
||||
setSearchTitle(title);
|
||||
setCategoryFilter(category ? category.split("&") : []);
|
||||
setSortByOpt(sortBy === "popular" ? "clickCount" : "createdAt");
|
||||
setSortByOpt(sortBy === "popular" ? "favorite" : "createdAt");
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -381,7 +381,7 @@ export default function FilterPage() {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -435,7 +435,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,942 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [audioData, setAudioData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"4",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setAudioData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"4",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setAudioData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: audioData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>
|
||||
{" "}
|
||||
Audio {">"}{" "}
|
||||
<span className="font-bold">
|
||||
{t("allAudio", { defaultValue: "All Audio" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadableImage",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
{/* Left */}
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", { defaultValue: "Search Title" })}
|
||||
className="mt-1 w-full text-sm border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "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("searchDate", {
|
||||
defaultValue: "Search Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1 ? "bg-[#bb3523] text-white" : ""
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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("wav")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "wav")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
WAV
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="jpeg"
|
||||
value="jpeg"
|
||||
checked={formatFilter.includes("mp3")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mp3")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MP3
|
||||
</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>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : audioData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{audioData?.map((audio: any) => (
|
||||
<Card
|
||||
key={audio?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent
|
||||
key={audio?.id}
|
||||
className="md:basis-1/2 lg:basis-1/3"
|
||||
>
|
||||
<Link
|
||||
href={`${prefixPath}/audio/detail/${audio?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`${prefixPath}/document/detail/${text?.slug}`}
|
||||
// href={prefixPath + `/document/detail/${text?.slug}`}
|
||||
// onClick={() =>
|
||||
// router.push(prefixPath + `/document/detail/${text?.slug}`)
|
||||
// }
|
||||
className="cursor-pointer rounded-lg shadow-md overflow-hidden bg-white dark:bg-black dark:border dark:border-gray-500"
|
||||
>
|
||||
{/* Ikon di tengah dengan latar kuning */}
|
||||
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[180px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
height="150"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
||||
/>
|
||||
</svg>{" "}
|
||||
</div>
|
||||
|
||||
{/* Konten bawah */}
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
{/* Kategori merah */}
|
||||
<div className="text-[12px] font-bold text-red-600 uppercase">
|
||||
{audio?.categoryName?.toUpperCase() ?? "Text"}
|
||||
</div>
|
||||
|
||||
{/* Judul */}
|
||||
<div className="font-semibold text-gray-900 dark:text-white text-xl leading-snug line-clamp-4">
|
||||
{audio?.title}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllAudioPage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="4"
|
||||
// title="Semua Konten Audio per Polda"
|
||||
// basePath="audio"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -83,7 +83,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -451,7 +451,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,988 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [documentData, setDocumentData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"3",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setDocumentData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"3",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setDocumentData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: documentData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>
|
||||
{" "}
|
||||
{t("text", { defaultValue: "Text" })} {">"}{" "}
|
||||
<span className="font-bold">
|
||||
{t("allText", { defaultValue: "All Text" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadableImage",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
{/* Left */}
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", { defaultValue: "Search Title" })}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "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", {
|
||||
defaultValue: "Select Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1 ? "bg-[#bb3523] text-white" : ""
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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="doc"
|
||||
value="doc"
|
||||
checked={formatFilter.includes("doc")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "doc")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
DOC
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="docx"
|
||||
value="docx"
|
||||
checked={formatFilter.includes("docx")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "docx")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
DOCX
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="pdf"
|
||||
value="pdf"
|
||||
checked={formatFilter.includes("pdf")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "pdf")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
PDF
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="ppt"
|
||||
value="ppt"
|
||||
checked={formatFilter.includes("ppt")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "ppt")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
PPT
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="pptx"
|
||||
value="pptx"
|
||||
checked={formatFilter.includes("pptx")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "pptx")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
PPTX
|
||||
</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>
|
||||
|
||||
{/* RIGHT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : documentData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{documentData?.map((document: any) => (
|
||||
<Card
|
||||
key={document?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent
|
||||
key={document?.id}
|
||||
className="md:basis-1/2 lg:basis-1/3"
|
||||
>
|
||||
<Link
|
||||
href={`${prefixPath}/document/detail/${document?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`${prefixPath}/document/detail/${text?.slug}`}
|
||||
// href={prefixPath + `/document/detail/${text?.slug}`}
|
||||
// onClick={() =>
|
||||
// router.push(prefixPath + `/document/detail/${text?.slug}`)
|
||||
// }
|
||||
className="cursor-pointer rounded-lg shadow-md overflow-hidden bg-white dark:bg-black dark:border dark:border-gray-500"
|
||||
>
|
||||
{/* Ikon di tengah dengan latar kuning */}
|
||||
<div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
height="150"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207zM7 7.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M7.5 9a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zM7 11.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M5.5 8a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1M6 9.5a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0M5.5 12a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Konten bawah */}
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
{/* Kategori merah */}
|
||||
<div className="text-[12px] font-bold text-red-600 uppercase">
|
||||
{document?.categoryName?.toUpperCase() ?? "Text"}
|
||||
</div>
|
||||
|
||||
{/* Judul */}
|
||||
<div className="font-semibold text-gray-900 dark:text-white text-xl leading-snug line-clamp-4">
|
||||
{document?.title}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllDocumentPage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="3"
|
||||
// title="Semua Konten Teks per Polda"
|
||||
// basePath="document"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -84,7 +84,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -432,7 +432,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,895 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [imageData, setImageData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
|
||||
// async function fetchCategories() {
|
||||
// const response = await getPublicCategoryData("", "", locale === "en");
|
||||
// setCategories(response?.data?.data?.content || []);
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"1",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setImageData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"1",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setImageData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: imageData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>{t("image", { defaultValue: "Image" })}</p> {">"}
|
||||
<p>
|
||||
<span className="font-bold">
|
||||
{t("allImage", { defaultValue: "All Image" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadableImage",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
{/* Left */}
|
||||
<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>
|
||||
|
||||
{isFilterOpen && (
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", {
|
||||
defaultValue: "Search Title",
|
||||
})}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "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", {
|
||||
defaultValue: "Select Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : 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"
|
||||
>
|
||||
<CardContent className="flex flex-col p-0">
|
||||
<Link
|
||||
href={`${prefixPath}/image/detail/${image?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`/image/detail/${image?.slug}`}
|
||||
>
|
||||
<div className="h-60 bg-[#e9e9e9]">
|
||||
<ImageBlurry
|
||||
src={
|
||||
image?.smallThumbnailLink || image?.thumbnailLink
|
||||
}
|
||||
alt={image?.title}
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-row justify-between mb-1">
|
||||
<p className="text-[9px] font-bold text-[#bb3523]">
|
||||
{image?.categoryName?.toUpperCase() ??
|
||||
"Giat Pimpinan"}
|
||||
</p>
|
||||
<p className="flex flex-row items-center text-[9px] gap-1 text-gray-600">
|
||||
{formatDateToIndonesian(
|
||||
new Date(image?.createdAt)
|
||||
)}{" "}
|
||||
{image?.timezone ?? "WIB"} |
|
||||
<Icon
|
||||
icon="formkit:eye"
|
||||
width="15"
|
||||
height="15"
|
||||
/>{" "}
|
||||
{image.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm lg:text-base font-semibold text-black dark:text-white line-clamp-3">
|
||||
{image?.title}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllImagePage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="1"
|
||||
// title="Semua Foto per Polda"
|
||||
// basePath="image"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -82,7 +82,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -445,7 +445,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,987 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [videoData, setVideoData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"2",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setVideoData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"2",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setVideoData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: videoData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>{t("video", { defaultValue: "Video" })}</p> {">"}
|
||||
<p>
|
||||
<span className="font-bold">
|
||||
{t("allVideo", { defaultValue: "All Video" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadablevideo",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", { defaultValue: "Search Title" })}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "Date" })}
|
||||
</label>
|
||||
<div className="flex flex-row justify justify-between gap-2">
|
||||
<ReactDatePicker
|
||||
selectsRange
|
||||
className="mt-1 w-full text-sm border 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", {
|
||||
defaultValue: "Select Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1 ? "bg-[#bb3523] text-white" : ""
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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="mk4"
|
||||
value="mk4"
|
||||
checked={formatFilter.includes("mk4")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mk4")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MK4
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="mov"
|
||||
value="mov"
|
||||
checked={formatFilter.includes("mov")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mov")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MOV
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="mp4"
|
||||
value="mp4"
|
||||
checked={formatFilter.includes("mp4")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mp4")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MP4
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="avi"
|
||||
value="avi"
|
||||
checked={formatFilter.includes("avi")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "avi")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
AVI
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="wmv"
|
||||
value="wmv"
|
||||
checked={formatFilter.includes("wmv")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "wmv")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
WMV
|
||||
</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>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : videoData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{videoData.map((video: any) => (
|
||||
<Card
|
||||
key={video?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent className="flex flex-col p-0">
|
||||
<Link
|
||||
href={`${prefixPath}/video/detail/${video?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`/video/detail/${video?.slug}`}
|
||||
>
|
||||
<div className="h-60 bg-[#e9e9e9]">
|
||||
<ImageBlurry
|
||||
src={
|
||||
video?.smallThumbnailLink || video?.thumbnailLink
|
||||
}
|
||||
alt={video?.title}
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-row justify-between mb-1">
|
||||
<p className="text-[9px] font-bold text-[#bb3523]">
|
||||
{video?.categoryName?.toUpperCase() ??
|
||||
"Giat Pimpinan"}
|
||||
</p>
|
||||
<p className="flex flex-row items-center text-[9px] gap-1 text-gray-600">
|
||||
{formatDateToIndonesian(
|
||||
new Date(video?.createdAt)
|
||||
)}{" "}
|
||||
{video?.timezone ?? "WIB"} |
|
||||
<Icon
|
||||
icon="formkit:eye"
|
||||
width="15"
|
||||
height="15"
|
||||
/>{" "}
|
||||
{video.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm lg:text-base font-semibold text-black dark:text-white line-clamp-3">
|
||||
{video?.title}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllVideoPage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="2"
|
||||
// title="Semua Konten Audio Visual per Polda"
|
||||
// basePath="video"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -85,7 +85,7 @@ const LatestNews = (props: { type: string }) => {
|
|||
|
||||
// useEffect(() => {
|
||||
// async function fetchCategories() {
|
||||
// const url = "https://netidhub.com/api/csrf";
|
||||
// const url = "https://new.netidhub.com/api/csrf";
|
||||
|
||||
// try {
|
||||
// const response = await fetch(url);
|
||||
|
|
@ -119,7 +119,7 @@ const LatestNews = (props: { type: string }) => {
|
|||
// }
|
||||
|
||||
async function fetchData() {
|
||||
const sortBy = poldaName === "popular" ? "clickCount" : "createdAt";
|
||||
const sortBy = poldaName === "popular" ? "favorite" : "createdAt";
|
||||
const res = await listData("1", "", "", 5, 0, sortBy, "", "", poldaName);
|
||||
let data = res?.data?.data?.content;
|
||||
setContent(data);
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const NationalNews = () => {
|
|||
|
||||
// useEffect(() => {
|
||||
// async function fetchCategories() {
|
||||
// const url = "https://netidhub.com/api/csrf";
|
||||
// const url = "https://new.netidhub.com/api/csrf";
|
||||
|
||||
// try {
|
||||
// const response = await fetch(url);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const PopularNews = () => {
|
|||
|
||||
useEffect(() => {
|
||||
async function fetchCategories() {
|
||||
const url = "https://netidhub.com/api/csrf";
|
||||
const url = "https://new.netidhub.com/api/csrf";
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ const RegionalNews = () => {
|
|||
|
||||
// useEffect(() => {
|
||||
// async function fetchCategories() {
|
||||
// const url = "https://netidhub.com/api/csrf";
|
||||
// const url = "https://new.netidhub.com/api/csrf";
|
||||
|
||||
// try {
|
||||
// const response = await fetch(url);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export default function FilterPage() {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -97,7 +97,7 @@ export default function FilterPage() {
|
|||
|
||||
setSearchTitle(title);
|
||||
setCategoryFilter(category ? category.split("&") : []);
|
||||
setSortByOpt(sortBy === "popular" ? "clickCount" : "createdAt");
|
||||
setSortByOpt(sortBy === "popular" ? "favorite" : "createdAt");
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -381,7 +381,7 @@ export default function FilterPage() {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -435,7 +435,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -442,7 +442,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -419,7 +419,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -434,7 +434,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export default function FilterPage() {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -98,7 +98,7 @@ export default function FilterPage() {
|
|||
|
||||
setSearchTitle(title);
|
||||
setCategoryFilter(category ? category.split("&") : []);
|
||||
setSortByOpt(sortBy === "popular" ? "clickCount" : "createdAt");
|
||||
setSortByOpt(sortBy === "popular" ? "favorite" : "createdAt");
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -382,7 +382,7 @@ export default function FilterPage() {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
@ -569,18 +569,19 @@ export default function FilterPage() {
|
|||
</label>
|
||||
</li>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
|
|
@ -590,20 +591,41 @@ export default function FilterPage() {
|
|||
</svg>
|
||||
</button>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1
|
||||
? "bg-[#bb3523] text-white"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
|
|
@ -611,12 +633,12 @@ export default function FilterPage() {
|
|||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -437,7 +437,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
@ -511,8 +511,8 @@ const FilterPage = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Left */}
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
{/* Left */}
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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} />
|
||||
|
|
@ -593,6 +593,116 @@ const FilterPage = () => {
|
|||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "Categories" })}
|
||||
</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{categories?.map((category: any) => (
|
||||
<li key={category?.id}>
|
||||
|
|
@ -670,7 +780,7 @@ const FilterPage = () => {
|
|||
</button>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* Garis */}
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
{/* Garis */}
|
||||
|
|
|
|||
|
|
@ -290,25 +290,29 @@ const Galery = (props: any) => {
|
|||
// toast.success("Link Berhasil Di Copy");
|
||||
};
|
||||
|
||||
async function shareToEmail() {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
async function shareToEmail(uploadId: any) {
|
||||
if (!userRoleId) {
|
||||
router.push("/auth/login");
|
||||
} else {
|
||||
const data = {
|
||||
mediaUploadId: id?.split("-")?.[0],
|
||||
email: emailShareList || [emailShareInput],
|
||||
message: emailMessageInput,
|
||||
url: window.location.href,
|
||||
};
|
||||
loading();
|
||||
const res = await sendMediaUploadToEmail(data);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
close();
|
||||
successCallback("Konten Telah Dikirim");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
mediaUploadId: uploadId, // ← FIX: ID valid dari response
|
||||
email: emailShareList || [emailShareInput],
|
||||
message: emailMessageInput,
|
||||
url: window.location.href,
|
||||
};
|
||||
|
||||
loading();
|
||||
const res = await sendMediaUploadToEmail(data);
|
||||
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return;
|
||||
}
|
||||
|
||||
close();
|
||||
successCallback("Konten Telah Dikirim");
|
||||
}
|
||||
|
||||
const handleEmailList = (e: any) => {
|
||||
|
|
@ -349,12 +353,18 @@ const Galery = (props: any) => {
|
|||
Saya */}
|
||||
{pathname?.split("/")[1] == "in" ? (
|
||||
<>
|
||||
<span className="text-black ">{t("gallery", { defaultValue: "Gallery" })}</span>
|
||||
<span className="text-black ">
|
||||
{t("gallery", { defaultValue: "Gallery" })}
|
||||
</span>
|
||||
|
||||
{t("my", { defaultValue: "My" })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-black">{t("my", { defaultValue: "My" })}</span>
|
||||
<span className="text-black">
|
||||
{t("my", { defaultValue: "My" })}
|
||||
</span>
|
||||
|
||||
{t("gallery", { defaultValue: "Gallery" })}
|
||||
</>
|
||||
)}
|
||||
|
|
@ -457,7 +467,9 @@ const Galery = (props: any) => {
|
|||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("save", { defaultValue: "Save" })}{" "}
|
||||
{t("save", {
|
||||
defaultValue: "Save",
|
||||
})}{" "}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
|
|
@ -481,18 +493,25 @@ const Galery = (props: any) => {
|
|||
fontSize={20}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("share", { defaultValue: "Share" })}{" "}
|
||||
{t("share", {
|
||||
defaultValue: "Share",
|
||||
})}{" "}
|
||||
</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", { defaultValue: "Share To" })}{" "}
|
||||
{t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}{" "}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", { defaultValue: "Destination Email" })}
|
||||
{t("destinationEmail", {
|
||||
defaultValue:
|
||||
"Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
|
|
@ -503,14 +522,22 @@ const Galery = (props: any) => {
|
|||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("shareTo", { defaultValue: "Share To" })}
|
||||
placeholder={t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
onClick={() =>
|
||||
shareToEmail(
|
||||
video.mediaUploadId
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
{t("send", {
|
||||
defaultValue: "Send",
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
@ -625,10 +652,16 @@ const Galery = (props: any) => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", { defaultValue: "Destination Email" })}
|
||||
{t("destinationEmail", {
|
||||
defaultValue: "Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
|
|
@ -637,12 +670,16 @@ const Galery = (props: any) => {
|
|||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("pressEnter", { defaultValue: "Press Enter" })}
|
||||
placeholder={t("pressEnter", {
|
||||
defaultValue: "Press Enter",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
onClick={() =>
|
||||
shareToEmail(audio.mediaUploadId)
|
||||
}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
</Button>
|
||||
|
|
@ -732,7 +769,9 @@ const Galery = (props: any) => {
|
|||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("save", { defaultValue: "Save" })}{" "}
|
||||
{t("save", {
|
||||
defaultValue: "Save",
|
||||
})}{" "}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
|
|
@ -756,18 +795,25 @@ const Galery = (props: any) => {
|
|||
fontSize={20}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("share", { defaultValue: "Share" })}{" "}
|
||||
{t("share", {
|
||||
defaultValue: "Share",
|
||||
})}{" "}
|
||||
</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", { defaultValue: "Share To" })}{" "}
|
||||
{t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}{" "}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", { defaultValue: "Destination Email" })}
|
||||
{t("destinationEmail", {
|
||||
defaultValue:
|
||||
"Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
|
|
@ -778,14 +824,22 @@ const Galery = (props: any) => {
|
|||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("shareTo", { defaultValue: "Share To" })}
|
||||
placeholder={t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
onClick={() =>
|
||||
shareToEmail(
|
||||
image.mediaUploadId
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
{t("send", {
|
||||
defaultValue: "Send",
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
@ -884,10 +938,14 @@ const Galery = (props: any) => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", { defaultValue: "Share To" })}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", { defaultValue: "Destination Email" })}
|
||||
{t("destinationEmail", {
|
||||
defaultValue: "Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
|
|
@ -896,12 +954,16 @@ const Galery = (props: any) => {
|
|||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("pressEnter", { defaultValue: "Press Enter" })}
|
||||
placeholder={t("pressEnter", {
|
||||
defaultValue: "Press Enter",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
onClick={() =>
|
||||
shareToEmail(document.mediaUploadId)
|
||||
}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { close, error, loading, successCallback } from "@/config/swal";
|
||||
import { checkWishlistStatus, deleteWishlist, getInfoProfile, mediaWishlist, saveWishlist } from "@/service/landing/landing";
|
||||
import {
|
||||
checkWishlistStatus,
|
||||
deleteWishlist,
|
||||
getInfoProfile,
|
||||
mediaWishlist,
|
||||
saveWishlist,
|
||||
} from "@/service/landing/landing";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
|
|
@ -13,8 +19,17 @@ import withReactContent from "sweetalert2-react-content";
|
|||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import Swal from "sweetalert2";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -61,11 +76,23 @@ const Galery = (props: any) => {
|
|||
}, [page, category, title]);
|
||||
|
||||
async function getDataVideo() {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: category || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
const response = await mediaWishlist("2", isInstitute ? instituteId : "", name, filter, "9", pages, sortBy, format);
|
||||
const response = await mediaWishlist(
|
||||
"2",
|
||||
isInstitute ? instituteId : "",
|
||||
name,
|
||||
filter,
|
||||
"9",
|
||||
pages,
|
||||
sortBy,
|
||||
format
|
||||
);
|
||||
|
||||
setGetTotalPage(response?.data?.data?.totalPages);
|
||||
setContentVideo(response?.data?.data?.content);
|
||||
|
|
@ -94,12 +121,24 @@ const Galery = (props: any) => {
|
|||
}, [page, category, title]);
|
||||
|
||||
async function getDataDocument() {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: category || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
|
||||
const response = await mediaWishlist("3", isInstitute ? instituteId : "", name, filter, "12", pages, sortBy, format);
|
||||
const response = await mediaWishlist(
|
||||
"3",
|
||||
isInstitute ? instituteId : "",
|
||||
name,
|
||||
filter,
|
||||
"12",
|
||||
pages,
|
||||
sortBy,
|
||||
format
|
||||
);
|
||||
|
||||
setGetTotalPage(response?.data?.data?.totalPages);
|
||||
setContentDocument(response?.data?.data?.content);
|
||||
|
|
@ -119,12 +158,24 @@ const Galery = (props: any) => {
|
|||
}, [change, refresh]);
|
||||
|
||||
async function getDataAudio() {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: category || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
|
||||
const response = await mediaWishlist("4", isInstitute ? instituteId : "", name, filter, "6", pages, sortBy, format);
|
||||
const response = await mediaWishlist(
|
||||
"4",
|
||||
isInstitute ? instituteId : "",
|
||||
name,
|
||||
filter,
|
||||
"6",
|
||||
pages,
|
||||
sortBy,
|
||||
format
|
||||
);
|
||||
|
||||
setGetTotalPage(response?.data?.data?.totalPages);
|
||||
setContentAudio(response?.data?.data?.content);
|
||||
|
|
@ -140,12 +191,24 @@ const Galery = (props: any) => {
|
|||
}, [page, category, title, refresh]);
|
||||
|
||||
async function getDataImage() {
|
||||
const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: category || "";
|
||||
|
||||
const name = title == undefined ? "" : title;
|
||||
const format = formatFilter == undefined ? "" : formatFilter?.join(",");
|
||||
|
||||
const response = await mediaWishlist("1", isInstitute ? instituteId : "", name, filter, "12", pages, sortBy, format);
|
||||
const response = await mediaWishlist(
|
||||
"1",
|
||||
isInstitute ? instituteId : "",
|
||||
name,
|
||||
filter,
|
||||
"12",
|
||||
pages,
|
||||
sortBy,
|
||||
format
|
||||
);
|
||||
|
||||
setGetTotalPage(response?.data?.data?.totalPages);
|
||||
setContentImage(response?.data?.data?.content);
|
||||
|
|
@ -227,7 +290,9 @@ const Galery = (props: any) => {
|
|||
};
|
||||
|
||||
const copyToClip = async (url: any) => {
|
||||
await navigator.clipboard.writeText(`https://mediahub.polri.go.id/video/detail/${url}`);
|
||||
await navigator.clipboard.writeText(
|
||||
`https://mediahub.polri.go.id/video/detail/${url}`
|
||||
);
|
||||
setCopySuccess("Copied");
|
||||
// toast.success("Link Berhasil Di Copy");
|
||||
toast({
|
||||
|
|
@ -235,25 +300,29 @@ const Galery = (props: any) => {
|
|||
});
|
||||
};
|
||||
|
||||
async function shareToEmail() {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
async function shareToEmail(uploadId: any) {
|
||||
if (!userRoleId) {
|
||||
router.push("/auth/login");
|
||||
} else {
|
||||
const data = {
|
||||
mediaUploadId: id?.split("-")?.[0],
|
||||
email: emailShareList || [emailShareInput],
|
||||
message: emailMessageInput,
|
||||
url: window.location.href,
|
||||
};
|
||||
loading();
|
||||
const res = await sendMediaUploadToEmail(data);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
close();
|
||||
successCallback("Konten Telah Dikirim");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
mediaUploadId: uploadId, // ← FIX: ID valid dari response
|
||||
email: emailShareList || [emailShareInput],
|
||||
message: emailMessageInput,
|
||||
url: window.location.href,
|
||||
};
|
||||
|
||||
loading();
|
||||
const res = await sendMediaUploadToEmail(data);
|
||||
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return;
|
||||
}
|
||||
|
||||
close();
|
||||
successCallback("Konten Telah Dikirim");
|
||||
}
|
||||
|
||||
const handleEmailList = (e: any) => {
|
||||
|
|
@ -290,7 +359,9 @@ const Galery = (props: any) => {
|
|||
<div className="flex flex-col mt-4">
|
||||
<div className="mx-auto w-full max-w-7xl justify-start flex flex-col gap-5 mb-4">
|
||||
<h1 className="text-2xl w-fit font-bold bg-[#bb3523] px-4 py-1 rounded-lg text-center text-white">
|
||||
<span className="text-black">{t("gallery", { defaultValue: "Gallery" })} </span>
|
||||
<span className="text-black">
|
||||
{t("gallery", { defaultValue: "Gallery" })}{" "}
|
||||
</span>
|
||||
{profile?.institute?.name}
|
||||
</h1>
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
|
|
@ -301,21 +372,27 @@ const Galery = (props: any) => {
|
|||
>
|
||||
{t("image", { defaultValue: "Image" })}
|
||||
</TabsTrigger>
|
||||
<div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
|
||||
<div className="text-[#bb3523] text-lg hidden md:inline-block">
|
||||
|
|
||||
</div>
|
||||
<TabsTrigger
|
||||
value="video"
|
||||
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
|
||||
>
|
||||
{t("video", { defaultValue: "Video" })}
|
||||
</TabsTrigger>
|
||||
<div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
|
||||
<div className="text-[#bb3523] text-lg hidden md:inline-block">
|
||||
|
|
||||
</div>
|
||||
<TabsTrigger
|
||||
value="text"
|
||||
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
|
||||
>
|
||||
{t("text", { defaultValue: "Text" })}
|
||||
</TabsTrigger>
|
||||
<div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
|
||||
<div className="text-[#bb3523] text-lg hidden md:inline-block">
|
||||
|
|
||||
</div>
|
||||
<TabsTrigger
|
||||
value="audio"
|
||||
className="relative text-xs md:text-xl font-bold text-black dark:text-white dark:bg-transparent before:absolute before:top-full before:left-0 before:h-px before:w-full data-[state=active]:before:bg-primary"
|
||||
|
|
@ -331,50 +408,140 @@ const Galery = (props: any) => {
|
|||
contentVideo?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{contentVideo?.map((video: any) => (
|
||||
<Card key={video?.id} className="hover:scale-105 transition-transform duration-300">
|
||||
<Card
|
||||
key={video?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||
<div>
|
||||
<div className="relative group overflow-hidden shadow-md hover:shadow-lg">
|
||||
<div className="relative h-60 rounded-lg overflow-hidden">
|
||||
<ImageBlurry src={video?.mediaUpload?.thumbnailLink} alt={video?.mediaUpload?.title} style={{ objectFit: "cover", width: "100%", height: "100%" }} />
|
||||
<ImageBlurry
|
||||
src={video?.mediaUpload?.thumbnailLink}
|
||||
alt={video?.mediaUpload?.title}
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-black text-white">
|
||||
<Link href={`/video/detail/${video?.mediaUpload?.slug}`}>
|
||||
<p className="text-sm p-2 lg:text-base font-semibold truncate">{video?.mediaUpload?.title}</p>
|
||||
<Link
|
||||
href={`/video/detail/${video?.mediaUpload?.slug}`}
|
||||
>
|
||||
<p className="text-sm p-2 lg:text-base font-semibold truncate">
|
||||
{video?.mediaUpload?.title}
|
||||
</p>
|
||||
</Link>
|
||||
<p className="flex text-[10px] mr-1 mb-2 items-center justify-end self-end">
|
||||
<Popover>
|
||||
<PopoverTrigger className="flex cursor-pointer" asChild>
|
||||
<PopoverTrigger
|
||||
className="flex cursor-pointer"
|
||||
asChild
|
||||
>
|
||||
<a className="flex justify-end items-center">
|
||||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
<Icon
|
||||
className="text-white ml-1"
|
||||
fontSize={25}
|
||||
icon="tabler:dots"
|
||||
/>
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(video?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })} </p>
|
||||
<div
|
||||
onClick={() =>
|
||||
handleSaveWishlist(
|
||||
video?.mediaUpload?.id
|
||||
)
|
||||
}
|
||||
className="cursor-pointer flex flex-row gap-2 hover:text-red-800"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:bookmark-outline"
|
||||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("save", {
|
||||
defaultValue: "Save",
|
||||
})}{" "}
|
||||
</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${video?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
<Link
|
||||
href={`/content-management/rewrite/create/${video?.mediaUpload?.id}`}
|
||||
className="flex flex-row hover:text-red-800 gap-2"
|
||||
>
|
||||
<Icon
|
||||
icon="jam:write"
|
||||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
Content Rewrite
|
||||
</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex flex-row items-center gap-3">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-1">{t("share", { defaultValue: "Share" })} </p>
|
||||
<Icon
|
||||
icon="oi:share"
|
||||
fontSize={20}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("share", {
|
||||
defaultValue: "Share",
|
||||
})}{" "}
|
||||
</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })} </h1>
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}{" "}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("shareTo", { defaultValue: "Share To" })} />
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", {
|
||||
defaultValue:
|
||||
"Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
onChange={(event) =>
|
||||
setEmailShareInput(
|
||||
event.target.value
|
||||
)
|
||||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() =>
|
||||
shareToEmail(
|
||||
video.mediaUploadId
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("send", {
|
||||
defaultValue: "Send",
|
||||
})}
|
||||
</Button>
|
||||
|
||||
{/* <Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
>
|
||||
{t("send", {
|
||||
defaultValue: "Send",
|
||||
})}
|
||||
</Button> */}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
|
@ -392,33 +559,64 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image
|
||||
width={1920}
|
||||
height={1080}
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
className="h-52 w-52 my-4"
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "audio" ? (
|
||||
contentAudio?.length > 0 ? (
|
||||
<div className=" grid grid-cols-1 gap-6 ">
|
||||
{contentAudio?.map((audio: any) => (
|
||||
<div key={audio?.id} className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full">
|
||||
<div
|
||||
key={audio?.id}
|
||||
className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16">
|
||||
<svg width="32" height="34" viewBox="0 0 32 34" fill="null" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
width="32"
|
||||
height="34"
|
||||
viewBox="0 0 32 34"
|
||||
fill="null"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M23.404 0.452014C23.7033 0.35857 24.0204 0.336816 24.3297 0.388509C24.639 0.440203 24.9318 0.563895 25.1845 0.749599C25.4371 0.935304 25.6426 1.17782 25.7843 1.45756C25.9259 1.73731 25.9998 2.04644 26 2.36001V14.414C25.3462 14.2296 24.6766 14.1064 24 14.046V8.36001L10 12.736V27C10 28.1264 9.6197 29.2197 8.92071 30.1029C8.22172 30.9861 7.24499 31.6075 6.14877 31.8663C5.05255 32.125 3.90107 32.0061 2.88089 31.5287C1.86071 31.0514 1.03159 30.2435 0.52787 29.2361C0.024152 28.2286 -0.124656 27.0806 0.105556 25.9781C0.335768 24.8755 0.931513 23.883 1.79627 23.1613C2.66102 22.4396 3.74413 22.031 4.87009 22.0017C5.99606 21.9724 7.09893 22.3242 8.00001 23V6.73601C7.99982 6.30956 8.13596 5.8942 8.38854 5.55059C8.64112 5.20698 8.99692 4.9531 9.40401 4.82601L23.404 0.452014ZM10 10.64L24 6.26601V2.36001L10 6.73601V10.64ZM5.00001 24C4.20436 24 3.44129 24.3161 2.87869 24.8787C2.31608 25.4413 2.00001 26.2044 2.00001 27C2.00001 27.7957 2.31608 28.5587 2.87869 29.1213C3.44129 29.6839 4.20436 30 5.00001 30C5.79566 30 6.55872 29.6839 7.12133 29.1213C7.68394 28.5587 8.00001 27.7957 8.00001 27C8.00001 26.2044 7.68394 25.4413 7.12133 24.8787C6.55872 24.3161 5.79566 24 5.00001 24ZM32 25C32 27.387 31.0518 29.6761 29.364 31.364C27.6761 33.0518 25.387 34 23 34C20.6131 34 18.3239 33.0518 16.636 31.364C14.9482 29.6761 14 27.387 14 25C14 22.6131 14.9482 20.3239 16.636 18.6361C18.3239 16.9482 20.6131 16 23 16C25.387 16 27.6761 16.9482 29.364 18.6361C31.0518 20.3239 32 22.6131 32 25ZM27.47 24.128L21.482 20.828C21.3298 20.7443 21.1583 20.7016 20.9846 20.7043C20.8108 20.707 20.6408 20.7549 20.4912 20.8433C20.3416 20.9317 20.2176 21.0576 20.1315 21.2086C20.0453 21.3595 20 21.5302 20 21.704V28.304C20 28.4778 20.0453 28.6486 20.1315 28.7995C20.2176 28.9504 20.3416 29.0763 20.4912 29.1647C20.6408 29.2531 20.8108 29.301 20.9846 29.3037C21.1583 29.3064 21.3298 29.2638 21.482 29.18L27.47 25.88C27.6268 25.7937 27.7575 25.6669 27.8486 25.5128C27.9397 25.3587 27.9877 25.183 27.9877 25.004C27.9877 24.825 27.9397 24.6493 27.8486 24.4952C27.7575 24.3412 27.6268 24.2143 27.47 24.128Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<Link href={`/audio/detail/${audio?.mediaUpload?.slug}`} className="flex flex-col flex-1">
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">{audio?.mediaUpload?.title}</div>
|
||||
<Link
|
||||
href={`/audio/detail/${audio?.mediaUpload?.slug}`}
|
||||
className="flex flex-col flex-1"
|
||||
>
|
||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
{audio?.mediaUpload?.title}
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<div className="mt-2">
|
||||
<img src="/assets/wave.svg" className="w-80" />
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center text-gray-500 dark:text-gray-400">
|
||||
<img src="/assets/audio-icon.png" alt="#" className="flex items-center justify-center" />
|
||||
<div className="flex mx-2 items-center justify-center">{audio?.mediaUpload?.duration}</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<img
|
||||
src="/assets/audio-icon.png"
|
||||
alt="#"
|
||||
className="flex items-center justify-center"
|
||||
/>
|
||||
<div className="flex mx-2 items-center justify-center">
|
||||
{audio?.mediaUpload?.duration}
|
||||
</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
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"
|
||||
|
|
@ -427,38 +625,92 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger className="flex justify-end gap-1 cursor-pointer" asChild>
|
||||
<PopoverTrigger
|
||||
className="flex justify-end gap-1 cursor-pointer"
|
||||
asChild
|
||||
>
|
||||
<a className="flex justify-end items-end place-items-end">
|
||||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
<Icon
|
||||
className="text-white ml-1"
|
||||
fontSize={25}
|
||||
icon="tabler:dots"
|
||||
/>
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(audio?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })}</p>
|
||||
<div
|
||||
onClick={() =>
|
||||
handleSaveWishlist(audio?.mediaUpload?.id)
|
||||
}
|
||||
className="cursor-pointer flex flex-row gap-2 hover:text-red-800"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:bookmark-outline"
|
||||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("save", { defaultValue: "Save" })}
|
||||
</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${audio?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Link
|
||||
href={`/content-management/rewrite/create/${audio?.mediaUpload?.id}`}
|
||||
className="flex flex-row hover:text-red-800 gap-2"
|
||||
>
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
Content Rewrite
|
||||
</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-2">{t("share", { defaultValue: "Share" })}</p>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("share", { defaultValue: "Share" })}
|
||||
</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter", { defaultValue: "Press Enter" })} />
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", {
|
||||
defaultValue: "Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
onChange={(event) =>
|
||||
setEmailShareInput(event.target.value)
|
||||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("pressEnter", {
|
||||
defaultValue: "Press Enter",
|
||||
})}
|
||||
/>
|
||||
</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(audio.mediaUploadId)
|
||||
}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
</Button>
|
||||
{/* <Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
</Button> */}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
|
@ -470,56 +722,148 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image
|
||||
width={1920}
|
||||
height={1080}
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
className="h-52 w-52 my-4"
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
) : selectedTab == "image" ? (
|
||||
contentImage?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{contentImage?.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">
|
||||
<div>
|
||||
<div className="relative group overflow-hidden shadow-md hover:shadow-lg">
|
||||
<div className="relative h-60 rounded-lg overflow-hidden">
|
||||
<ImageBlurry src={image?.mediaUpload?.thumbnailLink} alt={image?.mediaUpload?.title} style={{ objectFit: "cover", width: "100%", height: "100%" }} />
|
||||
<ImageBlurry
|
||||
src={image?.mediaUpload?.thumbnailLink}
|
||||
alt={image?.mediaUpload?.title}
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-black text-white">
|
||||
<Link href={`/video/detail/${image?.mediaUpload?.slug}`}>
|
||||
<p className="text-sm p-2 lg:text-base font-semibold truncate">{image?.mediaUpload?.title}</p>
|
||||
<Link
|
||||
href={`/video/detail/${image?.mediaUpload?.slug}`}
|
||||
>
|
||||
<p className="text-sm p-2 lg:text-base font-semibold truncate">
|
||||
{image?.mediaUpload?.title}
|
||||
</p>
|
||||
</Link>
|
||||
<p className="flex text-[10px] mr-1 mb-2 items-center justify-end self-end">
|
||||
<Popover>
|
||||
<PopoverTrigger className="flex cursor-pointer" asChild>
|
||||
<PopoverTrigger
|
||||
className="flex cursor-pointer"
|
||||
asChild
|
||||
>
|
||||
<a className="flex justify-end items-end place-items-end">
|
||||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
<Icon
|
||||
className="text-white ml-1"
|
||||
fontSize={25}
|
||||
icon="tabler:dots"
|
||||
/>
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(image?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })}</p>
|
||||
<div
|
||||
onClick={() =>
|
||||
handleSaveWishlist(
|
||||
image?.mediaUpload?.id
|
||||
)
|
||||
}
|
||||
className="cursor-pointer flex flex-row gap-2 hover:text-red-800"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:bookmark-outline"
|
||||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("save", {
|
||||
defaultValue: "Save",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${image?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
<Link
|
||||
href={`/content-management/rewrite/create/${image?.mediaUpload?.id}`}
|
||||
className="flex flex-row hover:text-red-800 gap-2"
|
||||
>
|
||||
<Icon
|
||||
icon="jam:write"
|
||||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
Content Rewrite
|
||||
</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex flex-row items-center gap-3">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-1"> {t("share", { defaultValue: "Share" })}</p>
|
||||
<Icon
|
||||
icon="oi:share"
|
||||
fontSize={20}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{" "}
|
||||
{t("share", {
|
||||
defaultValue: "Share",
|
||||
})}
|
||||
</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", {
|
||||
defaultValue: "Share To",
|
||||
})}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter", { defaultValue: "Press Enter" })} />
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", {
|
||||
defaultValue:
|
||||
"Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
onChange={(event) =>
|
||||
setEmailShareInput(
|
||||
event.target.value
|
||||
)
|
||||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t(
|
||||
"pressEnter",
|
||||
{
|
||||
defaultValue:
|
||||
"Press Enter",
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() =>
|
||||
shareToEmail(
|
||||
image.mediaUploadId
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("send", {
|
||||
defaultValue: "Send",
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
|
@ -538,15 +882,30 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image
|
||||
width={1920}
|
||||
height={1080}
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
className="h-52 w-52 my-4"
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
) : contentDocument.length > 0 ? (
|
||||
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{contentDocument?.map((document: any) => (
|
||||
<div key={document?.id} className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full">
|
||||
<div
|
||||
key={document?.id}
|
||||
className="flex flex-col bg-yellow-500 sm:flex-row items-center dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
||||
>
|
||||
<div className="flex items-center justify-center rounded-lg w-16 h-16">
|
||||
<svg width="28" height="34" viewBox="0 0 28 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
width="28"
|
||||
height="34"
|
||||
viewBox="0 0 28 34"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.6665 17.4167C5.6665 17.0851 5.7982 16.7672 6.03262 16.5328C6.26704 16.2984 6.58498 16.1667 6.9165 16.1667C7.24802 16.1667 7.56597 16.2984 7.80039 16.5328C8.03481 16.7672 8.1665 17.0851 8.1665 17.4167C8.1665 17.7482 8.03481 18.0661 7.80039 18.3005C7.56597 18.535 7.24802 18.6667 6.9165 18.6667C6.58498 18.6667 6.26704 18.535 6.03262 18.3005C5.7982 18.0661 5.6665 17.7482 5.6665 17.4167ZM6.9165 21.1667C6.58498 21.1667 6.26704 21.2984 6.03262 21.5328C5.7982 21.7672 5.6665 22.0851 5.6665 22.4167C5.6665 22.7482 5.7982 23.0661 6.03262 23.3005C6.26704 23.535 6.58498 23.6667 6.9165 23.6667C7.24802 23.6667 7.56597 23.535 7.80039 23.3005C8.03481 23.0661 8.1665 22.7482 8.1665 22.4167C8.1665 22.0851 8.03481 21.7672 7.80039 21.5328C7.56597 21.2984 7.24802 21.1667 6.9165 21.1667ZM5.6665 27.4167C5.6665 27.0851 5.7982 26.7672 6.03262 26.5328C6.26704 26.2984 6.58498 26.1667 6.9165 26.1667C7.24802 26.1667 7.56597 26.2984 7.80039 26.5328C8.03481 26.7672 8.1665 27.0851 8.1665 27.4167C8.1665 27.7482 8.03481 28.0661 7.80039 28.3005C7.56597 28.535 7.24802 28.6667 6.9165 28.6667C6.58498 28.6667 6.26704 28.535 6.03262 28.3005C5.7982 28.0661 5.6665 27.7482 5.6665 27.4167ZM11.9165 16.1667C11.585 16.1667 11.267 16.2984 11.0326 16.5328C10.7982 16.7672 10.6665 17.0851 10.6665 17.4167C10.6665 17.7482 10.7982 18.0661 11.0326 18.3005C11.267 18.535 11.585 18.6667 11.9165 18.6667H21.0832C21.4147 18.6667 21.7326 18.535 21.9671 18.3005C22.2015 18.0661 22.3332 17.7482 22.3332 17.4167C22.3332 17.0851 22.2015 16.7672 21.9671 16.5328C21.7326 16.2984 21.4147 16.1667 21.0832 16.1667H11.9165ZM10.6665 22.4167C10.6665 22.0851 10.7982 21.7672 11.0326 21.5328C11.267 21.2984 11.585 21.1667 11.9165 21.1667H21.0832C21.4147 21.1667 21.7326 21.2984 21.9671 21.5328C22.2015 21.7672 22.3332 22.0851 22.3332 22.4167C22.3332 22.7482 22.2015 23.0661 21.9671 23.3005C21.7326 23.535 21.4147 23.6667 21.0832 23.6667H11.9165C11.585 23.6667 11.267 23.535 11.0326 23.3005C10.7982 23.0661 10.6665 22.7482 10.6665 22.4167ZM11.9165 26.1667C11.585 26.1667 11.267 26.2984 11.0326 26.5328C10.7982 26.7672 10.6665 27.0851 10.6665 27.4167C10.6665 27.7482 10.7982 28.0661 11.0326 28.3005C11.267 28.535 11.585 28.6667 11.9165 28.6667H21.0832C21.4147 28.6667 21.7326 28.535 21.9671 28.3005C22.2015 28.0661 22.3332 27.7482 22.3332 27.4167C22.3332 27.0851 22.2015 26.7672 21.9671 26.5328C21.7326 26.2984 21.4147 26.1667 21.0832 26.1667H11.9165ZM26.3565 11.0233L16.6415 1.31C16.6157 1.28605 16.5885 1.26378 16.5598 1.24333C16.5392 1.22742 16.5192 1.21074 16.4998 1.19333C16.3852 1.08512 16.2632 0.984882 16.1348 0.893332C16.0922 0.865802 16.0476 0.841298 16.0015 0.819999L15.9215 0.779999L15.8382 0.731666C15.7482 0.679999 15.6565 0.626665 15.5615 0.586665C15.2296 0.454104 14.8783 0.376423 14.5215 0.356665C14.4885 0.354519 14.4557 0.350625 14.4232 0.344999C14.3779 0.338012 14.3323 0.334114 14.2865 0.333332H3.99984C3.11578 0.333332 2.26794 0.684521 1.64281 1.30964C1.01769 1.93476 0.666504 2.78261 0.666504 3.66667V30.3333C0.666504 31.2174 1.01769 32.0652 1.64281 32.6904C2.26794 33.3155 3.11578 33.6667 3.99984 33.6667H23.9998C24.8839 33.6667 25.7317 33.3155 26.3569 32.6904C26.982 32.0652 27.3332 31.2174 27.3332 30.3333V13.38C27.333 12.496 26.9817 11.6483 26.3565 11.0233ZM24.8332 30.3333C24.8332 30.5543 24.7454 30.7663 24.5891 30.9226C24.4328 31.0789 24.2208 31.1667 23.9998 31.1667H3.99984C3.77882 31.1667 3.56686 31.0789 3.41058 30.9226C3.2543 30.7663 3.1665 30.5543 3.1665 30.3333V3.66667C3.1665 3.44565 3.2543 3.23369 3.41058 3.07741C3.56686 2.92113 3.77882 2.83333 3.99984 2.83333H13.9998V10.3333C13.9998 11.2174 14.351 12.0652 14.9761 12.6904C15.6013 13.3155 16.4491 13.6667 17.3332 13.6667H24.8332V30.3333ZM16.4998 4.70166L22.9632 11.1667H17.3332C17.1122 11.1667 16.9002 11.0789 16.7439 10.9226C16.5876 10.7663 16.4998 10.5543 16.4998 10.3333V4.70166Z"
|
||||
fill="black"
|
||||
|
|
@ -555,47 +914,103 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1 gap-2">
|
||||
<Link href={`/document/detail/${document?.mediaUpload?.slug}`} className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
|
||||
<Link
|
||||
href={`/document/detail/${document?.mediaUpload?.slug}`}
|
||||
className="font-semibold text-gray-900 dark:text-white mt-1 text-sm"
|
||||
>
|
||||
{document?.mediaUpload?.title}
|
||||
</Link>
|
||||
<div className="flex gap-2 items-center text-xs text-red-500 dark:text-red-500">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512">
|
||||
<path fill="#f00" d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="#f00"
|
||||
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
|
||||
/>
|
||||
</svg>
|
||||
Download {t("document", { defaultValue: "Document" })}
|
||||
</div>
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger className="flex justify-end gap-1 cursor-pointer" asChild>
|
||||
<PopoverTrigger
|
||||
className="flex justify-end gap-1 cursor-pointer"
|
||||
asChild
|
||||
>
|
||||
<a className="flex justify-end items-end place-items-end">
|
||||
<Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
|
||||
<Icon
|
||||
className="text-white ml-1"
|
||||
fontSize={25}
|
||||
icon="tabler:dots"
|
||||
/>
|
||||
</a>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-52">
|
||||
<div onClick={() => handleSaveWishlist(document?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
|
||||
<Icon icon="material-symbols:bookmark-outline" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })}</p>
|
||||
<div
|
||||
onClick={() =>
|
||||
handleSaveWishlist(document?.mediaUpload?.id)
|
||||
}
|
||||
className="cursor-pointer flex flex-row gap-2 hover:text-red-800"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:bookmark-outline"
|
||||
fontSize={25}
|
||||
/>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("save", { defaultValue: "Save" })}
|
||||
</p>
|
||||
</div>
|
||||
<Link href={`/content-management/rewrite/create/${document?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
|
||||
<Link
|
||||
href={`/content-management/rewrite/create/${document?.mediaUpload?.id}`}
|
||||
className="flex flex-row hover:text-red-800 gap-2"
|
||||
>
|
||||
<Icon icon="jam:write" fontSize={25} />
|
||||
<p className="text-base font-semibold mb-2">Content Rewrite</p>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
Content Rewrite
|
||||
</p>
|
||||
</Link>
|
||||
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full flex items-center gap-2">
|
||||
<Icon icon="oi:share" fontSize={20} />
|
||||
<p className="text-base font-semibold mb-2">{t("share", { defaultValue: "Share" })}</p>
|
||||
<p className="text-base font-semibold mb-2">
|
||||
{t("share", { defaultValue: "Share" })}
|
||||
</p>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
|
||||
<h1 className="mb-2">
|
||||
{t("shareTo", { defaultValue: "Share To" })}
|
||||
</h1>
|
||||
<div className="flex flex-col mb-2">
|
||||
<p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
|
||||
<Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter", { defaultValue: "Press Enter" })} />
|
||||
<p className="text-base font-semibold mb-1">
|
||||
{t("destinationEmail", {
|
||||
defaultValue: "Destination Email",
|
||||
})}
|
||||
</p>
|
||||
<Input
|
||||
value={emailShareInput}
|
||||
onChange={(event) =>
|
||||
setEmailShareInput(event.target.value)
|
||||
}
|
||||
onKeyPress={handleEmailList}
|
||||
type="email"
|
||||
placeholder={t("pressEnter", {
|
||||
defaultValue: "Press Enter",
|
||||
})}
|
||||
/>
|
||||
</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(document?.mediaUploadId)
|
||||
}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -609,7 +1024,13 @@ const Galery = (props: any) => {
|
|||
</div>
|
||||
) : (
|
||||
<p className="flex items-center justify-center">
|
||||
<Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
|
||||
<Image
|
||||
width={1920}
|
||||
height={1080}
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
className="h-52 w-52 my-4"
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking"
|
|||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { sendMediaRewriteToEmail } from "@/service/content/content";
|
||||
|
||||
const page = () => {
|
||||
const [, setProfile] = useState();
|
||||
|
|
@ -175,18 +176,16 @@ const page = () => {
|
|||
}
|
||||
};
|
||||
|
||||
async function shareToEmail() {
|
||||
async function shareToEmail(uploadId: any) {
|
||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||
router.push("/auth/login");
|
||||
} else {
|
||||
const data = {
|
||||
mediaUploadId: id?.split("-")?.[0],
|
||||
id: uploadId,
|
||||
email: emailShareList || [emailShareInput],
|
||||
message: emailMessageInput,
|
||||
url: window.location.href,
|
||||
};
|
||||
loading();
|
||||
const res = await sendMediaUploadToEmail(data);
|
||||
const res = await sendMediaRewriteToEmail(data.id, data.email);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
|
|
@ -196,6 +195,7 @@ const page = () => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const handleEmailList = (e: any) => {
|
||||
const arrayEmail: any = [];
|
||||
for (let i = 0; i < emailShareList?.length; i += 1) {
|
||||
|
|
@ -328,7 +328,7 @@ const page = () => {
|
|||
</div>
|
||||
<Button
|
||||
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
|
||||
onClick={() => shareToEmail()}
|
||||
onClick={() => shareToEmail(image.id)}
|
||||
>
|
||||
{t("send", { defaultValue: "Send" })}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -453,7 +453,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
@ -500,7 +500,6 @@ const FilterPage = () => {
|
|||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
|
||||
<div className="flex flex-col md:flex-row items-start gap-5 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>
|
||||
{" "}
|
||||
|
|
@ -527,8 +526,8 @@ const FilterPage = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Left */}
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4 ">
|
||||
{/* Left */}
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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} />
|
||||
|
|
@ -609,6 +608,116 @@ const FilterPage = () => {
|
|||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "Categories" })}
|
||||
</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{categories.map((category: any) => (
|
||||
<li key={category?.id}>
|
||||
|
|
@ -686,7 +795,7 @@ const FilterPage = () => {
|
|||
</button>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* Garis */}
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
{/* Garis */}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -441,7 +441,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
@ -627,6 +627,7 @@ const FilterPage = () => {
|
|||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "Categories" })}
|
||||
</h3>
|
||||
|
||||
<ul className="mt-2 space-y-2">
|
||||
{categories.map((category: any) => (
|
||||
<li key={category?.id}>
|
||||
|
|
@ -648,65 +649,90 @@ const FilterPage = () => {
|
|||
</label>
|
||||
</li>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</ul>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: ""
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
{pageNum}
|
||||
</button>
|
||||
))}
|
||||
));
|
||||
})()}
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</ul>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Garis */}
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
{/* Garis */}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,15 @@ import { Link } from "@/components/navigation";
|
|||
import { useTranslations } from "next-intl";
|
||||
|
||||
const profileSchema = z.object({
|
||||
username: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
fullname: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
memberIdentity: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
email: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
address: z.string().min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
|
||||
phoneNumber: z.string().min(1, { message: "Kategori diperlukan" }),
|
||||
username: z.string().optional(),
|
||||
fullname: z.string().optional(),
|
||||
memberIdentity: z.string().optional(),
|
||||
email: z.string().email("Format email tidak valid").optional(),
|
||||
address: z.string().optional(),
|
||||
phoneNumber: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
type Detail = {
|
||||
id: number;
|
||||
userId: any;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,942 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [audioData, setAudioData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"4",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setAudioData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"4",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setAudioData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: audioData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>
|
||||
{" "}
|
||||
Audio {">"}{" "}
|
||||
<span className="font-bold">
|
||||
{t("allAudio", { defaultValue: "All Audio" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadableImage",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
{/* Left */}
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", { defaultValue: "Search Title" })}
|
||||
className="mt-1 w-full text-sm border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "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("searchDate", {
|
||||
defaultValue: "Search Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1 ? "bg-[#bb3523] text-white" : ""
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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("wav")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "wav")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
WAV
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="jpeg"
|
||||
value="jpeg"
|
||||
checked={formatFilter.includes("mp3")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mp3")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MP3
|
||||
</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>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : audioData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{audioData?.map((audio: any) => (
|
||||
<Card
|
||||
key={audio?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent
|
||||
key={audio?.id}
|
||||
className="md:basis-1/2 lg:basis-1/3"
|
||||
>
|
||||
<Link
|
||||
href={`${prefixPath}/audio/detail/${audio?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`${prefixPath}/document/detail/${text?.slug}`}
|
||||
// href={prefixPath + `/document/detail/${text?.slug}`}
|
||||
// onClick={() =>
|
||||
// router.push(prefixPath + `/document/detail/${text?.slug}`)
|
||||
// }
|
||||
className="cursor-pointer rounded-lg shadow-md overflow-hidden bg-white dark:bg-black dark:border dark:border-gray-500"
|
||||
>
|
||||
{/* Ikon di tengah dengan latar kuning */}
|
||||
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[180px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
height="150"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
||||
/>
|
||||
</svg>{" "}
|
||||
</div>
|
||||
|
||||
{/* Konten bawah */}
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
{/* Kategori merah */}
|
||||
<div className="text-[12px] font-bold text-red-600 uppercase">
|
||||
{audio?.categoryName?.toUpperCase() ?? "Text"}
|
||||
</div>
|
||||
|
||||
{/* Judul */}
|
||||
<div className="font-semibold text-gray-900 dark:text-white text-xl leading-snug line-clamp-4">
|
||||
{audio?.title}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllAudioPage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="4"
|
||||
// title="Semua Konten Audio per Polda"
|
||||
// basePath="audio"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -0,0 +1,988 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [documentData, setDocumentData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"3",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setDocumentData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"3",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setDocumentData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: documentData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>
|
||||
{" "}
|
||||
{t("text", { defaultValue: "Text" })} {">"}{" "}
|
||||
<span className="font-bold">
|
||||
{t("allText", { defaultValue: "All Text" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadableImage",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
{/* Left */}
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", { defaultValue: "Search Title" })}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "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", {
|
||||
defaultValue: "Select Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1 ? "bg-[#bb3523] text-white" : ""
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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="doc"
|
||||
value="doc"
|
||||
checked={formatFilter.includes("doc")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "doc")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
DOC
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="docx"
|
||||
value="docx"
|
||||
checked={formatFilter.includes("docx")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "docx")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
DOCX
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="pdf"
|
||||
value="pdf"
|
||||
checked={formatFilter.includes("pdf")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "pdf")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
PDF
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="ppt"
|
||||
value="ppt"
|
||||
checked={formatFilter.includes("ppt")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "ppt")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
PPT
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="pptx"
|
||||
value="pptx"
|
||||
checked={formatFilter.includes("pptx")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "pptx")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
PPTX
|
||||
</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>
|
||||
|
||||
{/* RIGHT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : documentData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{documentData?.map((document: any) => (
|
||||
<Card
|
||||
key={document?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent
|
||||
key={document?.id}
|
||||
className="md:basis-1/2 lg:basis-1/3"
|
||||
>
|
||||
<Link
|
||||
href={`${prefixPath}/document/detail/${document?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`${prefixPath}/document/detail/${text?.slug}`}
|
||||
// href={prefixPath + `/document/detail/${text?.slug}`}
|
||||
// onClick={() =>
|
||||
// router.push(prefixPath + `/document/detail/${text?.slug}`)
|
||||
// }
|
||||
className="cursor-pointer rounded-lg shadow-md overflow-hidden bg-white dark:bg-black dark:border dark:border-gray-500"
|
||||
>
|
||||
{/* Ikon di tengah dengan latar kuning */}
|
||||
<div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
height="150"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207zM7 7.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M7.5 9a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zM7 11.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M5.5 8a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1M6 9.5a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0M5.5 12a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Konten bawah */}
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
{/* Kategori merah */}
|
||||
<div className="text-[12px] font-bold text-red-600 uppercase">
|
||||
{document?.categoryName?.toUpperCase() ?? "Text"}
|
||||
</div>
|
||||
|
||||
{/* Judul */}
|
||||
<div className="font-semibold text-gray-900 dark:text-white text-xl leading-snug line-clamp-4">
|
||||
{document?.title}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllDocumentPage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="3"
|
||||
// title="Semua Konten Teks per Polda"
|
||||
// basePath="document"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -0,0 +1,895 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [imageData, setImageData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
|
||||
// async function fetchCategories() {
|
||||
// const response = await getPublicCategoryData("", "", locale === "en");
|
||||
// setCategories(response?.data?.data?.content || []);
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"1",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setImageData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"1",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setImageData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: imageData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>{t("image", { defaultValue: "Image" })}</p> {">"}
|
||||
<p>
|
||||
<span className="font-bold">
|
||||
{t("allImage", { defaultValue: "All Image" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadableImage",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
{/* Left */}
|
||||
<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>
|
||||
|
||||
{isFilterOpen && (
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", {
|
||||
defaultValue: "Search Title",
|
||||
})}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "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", {
|
||||
defaultValue: "Select Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : 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"
|
||||
>
|
||||
<CardContent className="flex flex-col p-0">
|
||||
<Link
|
||||
href={`${prefixPath}/image/detail/${image?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`/image/detail/${image?.slug}`}
|
||||
>
|
||||
<div className="h-60 bg-[#e9e9e9]">
|
||||
<ImageBlurry
|
||||
src={
|
||||
image?.smallThumbnailLink || image?.thumbnailLink
|
||||
}
|
||||
alt={image?.title}
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-row justify-between mb-1">
|
||||
<p className="text-[9px] font-bold text-[#bb3523]">
|
||||
{image?.categoryName?.toUpperCase() ??
|
||||
"Giat Pimpinan"}
|
||||
</p>
|
||||
<p className="flex flex-row items-center text-[9px] gap-1 text-gray-600">
|
||||
{formatDateToIndonesian(
|
||||
new Date(image?.createdAt)
|
||||
)}{" "}
|
||||
{image?.timezone ?? "WIB"} |
|
||||
<Icon
|
||||
icon="formkit:eye"
|
||||
width="15"
|
||||
height="15"
|
||||
/>{" "}
|
||||
{image.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm lg:text-base font-semibold text-black dark:text-white line-clamp-3">
|
||||
{image?.title}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllImagePage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="1"
|
||||
// title="Semua Foto per Polda"
|
||||
// basePath="image"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -0,0 +1,987 @@
|
|||
"use client";
|
||||
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 { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
getPublicCategoryData,
|
||||
getUserLevelListByParent,
|
||||
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";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ImageBlurry from "@/components/ui/image-blurry";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import Image from "next/image";
|
||||
|
||||
const columns: ColumnDef<any>[] = [
|
||||
{
|
||||
accessorKey: "no",
|
||||
header: "No",
|
||||
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
export default function FilterPage() {
|
||||
const router = useRouter();
|
||||
const asPath = usePathname();
|
||||
const params = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const locale = params?.locale;
|
||||
const t = useTranslations("FilterPage");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [videoData, setVideoData] = useState<any[]>([]);
|
||||
const [totalData, setTotalData] = useState<number>(1);
|
||||
const [totalPage, setTotalPage] = useState<number>(1);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [rowSelection, setRowSelection] = useState({});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalContent, setTotalContent] = useState(0);
|
||||
const [change, setChange] = useState(false);
|
||||
const sortBy = searchParams?.get("sortBy");
|
||||
const title = searchParams?.get("title");
|
||||
const categorie = searchParams?.get("category");
|
||||
const group = searchParams?.get("group");
|
||||
const [categoryFilter, setCategoryFilter] = useState<any[]>([]);
|
||||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const [formatFilter, setFormatFilter] = useState<any[]>([]);
|
||||
const pages = page ? page - 1 : 0;
|
||||
const [startDateString, setStartDateString] = useState<any>();
|
||||
const [endDateString, setEndDateString] = useState<any>();
|
||||
const [dateRange, setDateRange] = useState<any>([null, null]);
|
||||
const [handleClose, setHandleClose] = useState(false);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
||||
let prefixPath = poldaName
|
||||
? `/polda/${poldaName}`
|
||||
: satkerName
|
||||
? `/satker/${satkerName}`
|
||||
: "";
|
||||
|
||||
// === INIT ===
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setIsLoading(false), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pageFromUrl = searchParams?.get("page");
|
||||
if (pageFromUrl) setPage(Number(pageFromUrl));
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (categorie) {
|
||||
setCategoryFilter(
|
||||
categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie]
|
||||
);
|
||||
}
|
||||
}, [categorie]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories(categoryPage);
|
||||
}, []);
|
||||
|
||||
const fetchCategories = async (pageNumber: number) => {
|
||||
const groupParam =
|
||||
poldaName && poldaName.length > 1
|
||||
? poldaName
|
||||
: satkerName && satkerName.length > 1
|
||||
? "satker-" + satkerName
|
||||
: "";
|
||||
|
||||
const isInt = locale === "en";
|
||||
|
||||
const response = await getPublicCategoryData(
|
||||
groupParam,
|
||||
"",
|
||||
isInt,
|
||||
pageNumber
|
||||
); // halaman 1-based
|
||||
|
||||
const content = response?.data?.data?.content || [];
|
||||
const total = response?.data?.data?.totalPages || 1;
|
||||
|
||||
setCategories(content);
|
||||
setCategoryTotalPages(total);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (asPath?.includes("/regional")) getDataRegional();
|
||||
else getDataAll();
|
||||
}, [
|
||||
asPath,
|
||||
monthYearFilter,
|
||||
page,
|
||||
sortBy,
|
||||
sortByOpt,
|
||||
title,
|
||||
startDateString,
|
||||
endDateString,
|
||||
categorie,
|
||||
formatFilter,
|
||||
]);
|
||||
|
||||
async function getDataAll() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listData(
|
||||
"2",
|
||||
name,
|
||||
filter,
|
||||
12,
|
||||
pages,
|
||||
sortByOpt,
|
||||
format,
|
||||
"",
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
locale == "en" ? true : false
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setVideoData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
async function getDataRegional() {
|
||||
const filter =
|
||||
categoryFilter?.length > 0
|
||||
? categoryFilter?.sort().join(",")
|
||||
: categorie || "";
|
||||
|
||||
const name = title || "";
|
||||
const format = formatFilter?.length > 0 ? formatFilter.join(",") : "";
|
||||
|
||||
loading();
|
||||
const response = await listDataRegional(
|
||||
"2",
|
||||
name,
|
||||
filter,
|
||||
format,
|
||||
"",
|
||||
startDateString,
|
||||
endDateString,
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "")
|
||||
: "",
|
||||
monthYearFilter
|
||||
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||
: "",
|
||||
12,
|
||||
pages,
|
||||
sortByOpt
|
||||
);
|
||||
close();
|
||||
|
||||
const data = response?.data?.data;
|
||||
setVideoData(data?.content || []);
|
||||
setTotalData(data?.totalElements || 0);
|
||||
setTotalPage(data?.totalPages || 1);
|
||||
setTotalContent(data?.totalElements || 0);
|
||||
}
|
||||
|
||||
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||
let filter = [...categoryFilter];
|
||||
if (e) filter.push(String(id));
|
||||
else filter = filter.filter((item) => item !== String(id));
|
||||
router.push(`?category=${filter.join("&")}`);
|
||||
};
|
||||
|
||||
const handleFormatFilter = (e: boolean, id: string) => {
|
||||
let filter = [...formatFilter];
|
||||
if (e) filter.push(id);
|
||||
else filter = filter.filter((item) => item !== id);
|
||||
setFormatFilter(filter);
|
||||
};
|
||||
|
||||
const handleSorting = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setSortByOpt(value === "latest" ? "createdAt" : "favorite");
|
||||
};
|
||||
|
||||
const handleDeleteDate = () => {
|
||||
setDateRange([null, null]);
|
||||
setStartDateString("");
|
||||
setEndDateString("");
|
||||
setHandleClose(false);
|
||||
};
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!searchTitle) router.push("?title=");
|
||||
else router.push(`?title=${searchTitle.toLowerCase()}`);
|
||||
};
|
||||
|
||||
// === REACT TABLE ===
|
||||
const table = useReactTable({
|
||||
data: videoData,
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const [calenderState, setCalenderState] = useState(false);
|
||||
const [categoryPage, setCategoryPage] = useState(1);
|
||||
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||
let typingTimer: any;
|
||||
|
||||
const cleanCheckbox = () => {
|
||||
setCategoryFilter([]);
|
||||
setFormatFilter([]);
|
||||
router.push(`?category=&title=`);
|
||||
setDateRange([null, null]);
|
||||
setMonthYearFilter(null);
|
||||
setChange(!change);
|
||||
};
|
||||
|
||||
const handleKeyDown = () => {
|
||||
clearTimeout(typingTimer);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||
<p>{t("video", { defaultValue: "Video" })}</p> {">"}
|
||||
<p>
|
||||
<span className="font-bold">
|
||||
{t("allVideo", { defaultValue: "All Video" })}
|
||||
</span>
|
||||
</p>
|
||||
<p className="font-bold">|</p>
|
||||
{!title ? (
|
||||
<p>
|
||||
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||
"downloadablevideo",
|
||||
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||
)}`}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||
<span className="font-bold">"{title}"</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] 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", { defaultValue: "Search" })}
|
||||
</label>
|
||||
<Input
|
||||
value={searchTitle}
|
||||
onChange={(e) => setSearchTitle(e.target.value)}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={t("searchTitle", { defaultValue: "Search Title" })}
|
||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("monthYear", { defaultValue: "Month Year" })}
|
||||
</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", {
|
||||
defaultValue: "Select Year",
|
||||
})}
|
||||
showMonthYearPicker
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("date", { defaultValue: "Date" })}
|
||||
</label>
|
||||
<div className="flex flex-row justify justify-between gap-2">
|
||||
<ReactDatePicker
|
||||
selectsRange
|
||||
className="mt-1 w-full text-sm border 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", {
|
||||
defaultValue: "Select Date",
|
||||
})}
|
||||
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", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCategoryPage(i + 1)}
|
||||
className={`px-3 py-1 border rounded ${
|
||||
categoryPage === i + 1 ? "bg-[#bb3523] text-white" : ""
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</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="mk4"
|
||||
value="mk4"
|
||||
checked={formatFilter.includes("mk4")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mk4")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MK4
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="mov"
|
||||
value="mov"
|
||||
checked={formatFilter.includes("mov")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mov")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MOV
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="mp4"
|
||||
value="mp4"
|
||||
checked={formatFilter.includes("mp4")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "mp4")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
MP4
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="avi"
|
||||
value="avi"
|
||||
checked={formatFilter.includes("avi")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "avi")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
AVI
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="inline-flex items-center">
|
||||
<Checkbox
|
||||
id="wmv"
|
||||
value="wmv"
|
||||
checked={formatFilter.includes("wmv")}
|
||||
onCheckedChange={(e) =>
|
||||
handleFormatFilter(Boolean(e), "wmv")
|
||||
}
|
||||
/>
|
||||
<span className="ml-2 text-gray-700 dark:text-white">
|
||||
WMV
|
||||
</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>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||
<Reveal>
|
||||
<div className="flex flex-col items-end mb-4">
|
||||
<h2 className="text-lg font-semibold">Sort By</h2>
|
||||
<select
|
||||
defaultValue={sortBy == "popular" ? "popular" : "latest"}
|
||||
onChange={handleSorting}
|
||||
className="border rounded-md py-2 px-3"
|
||||
>
|
||||
<option value="latest">Latest</option>
|
||||
<option value="popular">Most Popular</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
<Skeleton className="h-[200px] w-full rounded-xl" />
|
||||
</div>
|
||||
) : videoData?.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{videoData.map((video: any) => (
|
||||
<Card
|
||||
key={video?.id}
|
||||
className="hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<CardContent className="flex flex-col p-0">
|
||||
<Link
|
||||
href={`${prefixPath}/video/detail/${video?.slug
|
||||
?.split("/")
|
||||
.pop()}`}
|
||||
// href={`/video/detail/${video?.slug}`}
|
||||
>
|
||||
<div className="h-60 bg-[#e9e9e9]">
|
||||
<ImageBlurry
|
||||
src={
|
||||
video?.smallThumbnailLink || video?.thumbnailLink
|
||||
}
|
||||
alt={video?.title}
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-row justify-between mb-1">
|
||||
<p className="text-[9px] font-bold text-[#bb3523]">
|
||||
{video?.categoryName?.toUpperCase() ??
|
||||
"Giat Pimpinan"}
|
||||
</p>
|
||||
<p className="flex flex-row items-center text-[9px] gap-1 text-gray-600">
|
||||
{formatDateToIndonesian(
|
||||
new Date(video?.createdAt)
|
||||
)}{" "}
|
||||
{video?.timezone ?? "WIB"} |
|
||||
<Icon
|
||||
icon="formkit:eye"
|
||||
width="15"
|
||||
height="15"
|
||||
/>{" "}
|
||||
{video.clickCount}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm lg:text-base font-semibold text-black dark:text-white line-clamp-3">
|
||||
{video?.title}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center py-10">
|
||||
<Image
|
||||
src="/assets/empty-data.png"
|
||||
alt="empty"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPage > 1 && (
|
||||
<LandingPagination
|
||||
table={table}
|
||||
totalData={totalData}
|
||||
totalPage={totalPage}
|
||||
/>
|
||||
)}
|
||||
</Reveal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import AllContentPage from "@/components/landing-page/all-content-page";
|
||||
|
||||
// const regions = [
|
||||
// { name: "Polda Aceh", slug: "aceh", logo: "/logo/polda/polda-aceh.png" },
|
||||
// { name: "Polda Bali", slug: "bali", logo: "/logo/polda/polda-bali.png" },
|
||||
// {
|
||||
// name: "Polda Bangka Belitung",
|
||||
// slug: "bangka-belitung",
|
||||
// logo: "/logo/polda/polda-bangka-belitung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Banten",
|
||||
// slug: "banten",
|
||||
// logo: "/logo/polda/polda-banten.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Bengkulu",
|
||||
// slug: "bengkulu",
|
||||
// logo: "/logo/polda/polda-bengkulu.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda DIY",
|
||||
// slug: "jogja",
|
||||
// logo: "/logo/polda/polda-jogja.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Gorontalo",
|
||||
// slug: "gorontalo",
|
||||
// logo: "/logo/polda/polda-gorontalo.png",
|
||||
// },
|
||||
// { name: "Polda Jambi", slug: "jambi", logo: "/logo/polda/polda-jambi.png" },
|
||||
// {
|
||||
// name: "Polda Jawa Barat",
|
||||
// slug: "jawa-barat",
|
||||
// logo: "/logo/polda/polda-jawa-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Tengah",
|
||||
// slug: "jawa-tengah",
|
||||
// logo: "/logo/polda/polda-jawa-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Jawa Timur",
|
||||
// slug: "jawa-timur",
|
||||
// logo: "/logo/polda/polda-jawa-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Barat",
|
||||
// slug: "kalimantan-barat",
|
||||
// logo: "/logo/polda/polda-kalimantan-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Selatan",
|
||||
// slug: "kalimantan-selatan",
|
||||
// logo: "/logo/polda/polda-kalimantan-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Tengah",
|
||||
// slug: "kalimantan-tengah",
|
||||
// logo: "/logo/polda/polda-kalimantan-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Timur",
|
||||
// slug: "kalimantan-timur",
|
||||
// logo: "/logo/polda/polda-kalimantan-timur.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kalimantan Utara",
|
||||
// slug: "kalimantan-utara",
|
||||
// logo: "/logo/polda/polda-kalimantan-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Kepulauan Riau",
|
||||
// slug: "kepulauan-riau",
|
||||
// logo: "/logo/polda/polda-kepulauan-riau.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Lampung",
|
||||
// slug: "lampung",
|
||||
// logo: "/logo/polda/polda-lampung.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku",
|
||||
// slug: "maluku",
|
||||
// logo: "/logo/polda/polda-maluku.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Maluku Utara",
|
||||
// slug: "maluku-utara",
|
||||
// logo: "/logo/polda/polda-maluku-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Metro Jaya",
|
||||
// slug: "metro-jaya",
|
||||
// logo: "/logo/polda/polda-metro-jaya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTB",
|
||||
// slug: "ntb",
|
||||
// logo: "/logo/polda/polda-ntb.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda NTT",
|
||||
// slug: "ntt",
|
||||
// logo: "/logo/polda/polda-ntt.png",
|
||||
// },
|
||||
// { name: "Polda Papua", slug: "papua", logo: "/logo/polda/polda-papua.png" },
|
||||
// {
|
||||
// name: "Polda Papua Barat",
|
||||
// slug: "papua-barat",
|
||||
// logo: "/logo/polda/polda-papua-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Barat Daya",
|
||||
// slug: "papua-barat-daya",
|
||||
// logo: "/logo/polda/polda-papua-barat-daya.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Papua Tengah",
|
||||
// slug: "papua-tengah",
|
||||
// logo: "/logo/polda/polda-papua-tengah.png",
|
||||
// },
|
||||
// { name: "Polda Riau", slug: "riau", logo: "/logo/polda/polda-riau.png" },
|
||||
// {
|
||||
// name: "Polda Sulawesi Barat",
|
||||
// slug: "sulawesi-barat",
|
||||
// logo: "/logo/polda/polda-sulawesi-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Selatan",
|
||||
// slug: "sulawesi-selatan",
|
||||
// logo: "/logo/polda/polda-sulawesi-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tengah",
|
||||
// slug: "sulawesi-tengah",
|
||||
// logo: "/logo/polda/polda-sulawesi-tengah.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Tenggara",
|
||||
// slug: "sulawesi-tenggara",
|
||||
// logo: "/logo/polda/polda-sulawesi-tenggara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sulawesi Utara",
|
||||
// slug: "sulawesi-utara",
|
||||
// logo: "/logo/polda/polda-sulawesi-utara.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Barat",
|
||||
// slug: "sumatera-barat",
|
||||
// logo: "/logo/polda/polda-sumatera-barat.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Selatan",
|
||||
// slug: "sumatera-selatan",
|
||||
// logo: "/logo/polda/polda-sumatera-selatan.png",
|
||||
// },
|
||||
// {
|
||||
// name: "Polda Sumatera Utara",
|
||||
// slug: "sumatera-utara",
|
||||
// logo: "/logo/polda/polda-sumatera-utara.png",
|
||||
// },
|
||||
// // {
|
||||
// // name: "Satuan Kerja POLRI",
|
||||
// // slug: "satker-polri",
|
||||
// // logo: "/logo/satker/SATUAN-KERJA-POLRI.png",
|
||||
// // },
|
||||
// // {
|
||||
// // name: "Internasional",
|
||||
// // slug: "internasional",
|
||||
// // logo: "/assets/polda/internasional.png",
|
||||
// // },
|
||||
// ];
|
||||
|
||||
// export default function PoldaAllVideoPage() {
|
||||
// return (
|
||||
// <AllContentPage
|
||||
// typeId="2"
|
||||
// title="Semua Konten Audio Visual per Polda"
|
||||
// basePath="video"
|
||||
// mode="polda"
|
||||
// dataList={regions}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
|
@ -69,7 +69,7 @@ export default function FilterPage() {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const isRegional = asPath?.includes("regional");
|
||||
const isSatker = asPath?.includes("satker");
|
||||
|
|
@ -371,7 +371,7 @@ export default function FilterPage() {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
@ -559,7 +559,88 @@ export default function FilterPage() {
|
|||
</label>
|
||||
</li>
|
||||
))}
|
||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* <div className="mt-4 flex gap-2 justify-center items-center">
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
|
|
@ -615,13 +696,13 @@ export default function FilterPage() {
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div> */}
|
||||
</ul>
|
||||
</div>
|
||||
{/* Garis */}
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
{/* Garis */}
|
||||
<div>
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
Format
|
||||
</h3>
|
||||
|
|
@ -672,8 +753,8 @@ export default function FilterPage() {
|
|||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="border-t border-black dark:border-white my-4"></div>
|
||||
</div> */}
|
||||
{/* <div className="border-t border-black dark:border-white my-4"></div> */}
|
||||
<div className="text-center">
|
||||
<a
|
||||
onClick={cleanCheckbox}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,32 @@ export async function generateMetadata({ params }: any): Promise<Metadata> {
|
|||
return {
|
||||
title: video.title,
|
||||
description: video.description,
|
||||
// openGraph: {
|
||||
// title: video?.title,
|
||||
// description: video?.description,
|
||||
// videos: [`${video?.smallThumbnailLink}`],
|
||||
// },
|
||||
openGraph: {
|
||||
title: video?.title,
|
||||
description: video?.description,
|
||||
videos: [`${video?.smallThumbnailLink}`],
|
||||
url: `https://mediahub.polri.go.id/in/video/detail/${slug}`,
|
||||
type: "video.other",
|
||||
images: [
|
||||
{
|
||||
url: video?.thumbnailLink,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
alt: video?.title || "Thumbnail Mediahub Polri",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: video?.title,
|
||||
description: video?.description,
|
||||
images: [
|
||||
video?.thumbnailLink,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const FilterPage = () => {
|
|||
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||
sortBy === "popular" ? "favorite" : "createdAt"
|
||||
);
|
||||
const poldaName = params?.polda_name;
|
||||
const satkerName = params?.satker_name;
|
||||
|
|
@ -451,7 +451,7 @@ const FilterPage = () => {
|
|||
if (e.target.value == "terbaru") {
|
||||
setSortByOpt("createdAt");
|
||||
} else {
|
||||
setSortByOpt("clickCount");
|
||||
setSortByOpt("favorite");
|
||||
}
|
||||
|
||||
setChange(!change);
|
||||
|
|
@ -610,6 +610,116 @@ const FilterPage = () => {
|
|||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "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>
|
||||
|
||||
{/* ⬇️ Pagination kategori (rata sejajar) */}
|
||||
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Tombol Prev */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||
}
|
||||
disabled={categoryPage === 1}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{(() => {
|
||||
const maxVisible = 4;
|
||||
let startPage = Math.max(
|
||||
1,
|
||||
Math.min(
|
||||
categoryPage - Math.floor(maxVisible / 2),
|
||||
categoryTotalPages - maxVisible + 1
|
||||
)
|
||||
);
|
||||
const endPage = Math.min(
|
||||
categoryTotalPages,
|
||||
startPage + maxVisible - 1
|
||||
);
|
||||
|
||||
const visiblePages = [];
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
visiblePages.push(i);
|
||||
}
|
||||
|
||||
return visiblePages.map((pageNum) => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => setCategoryPage(pageNum)}
|
||||
className={`px-3 py-1 border rounded text-sm transition-colors ${
|
||||
categoryPage === pageNum
|
||||
? "bg-[#bb3523] text-white"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
}`}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
));
|
||||
})()}
|
||||
|
||||
{/* Tombol Next */}
|
||||
<button
|
||||
onClick={() =>
|
||||
setCategoryPage((prev) =>
|
||||
Math.min(prev + 1, categoryTotalPages)
|
||||
)
|
||||
}
|
||||
disabled={categoryPage === categoryTotalPages}
|
||||
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||
{t("categories", { defaultValue: "Categories" })}
|
||||
</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
{categories.map((category: any) => (
|
||||
<li key={category?.id}>
|
||||
|
|
@ -687,7 +797,7 @@ const FilterPage = () => {
|
|||
</button>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* Garis */}
|
||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||
{/* Garis */}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import Swal from "sweetalert2";
|
|||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
import {
|
||||
getLocaleTime,
|
||||
|
|
@ -24,7 +25,6 @@ import {
|
|||
detailMediaSummary,
|
||||
getMediaBlastCampaignList,
|
||||
saveMediaBlastBroadcast,
|
||||
saveMediaBlastCampaign,
|
||||
} from "@/service/broadcast/broadcast";
|
||||
import { error } from "@/config/swal";
|
||||
import { Link, useRouter } from "@/i18n/routing";
|
||||
|
|
@ -44,27 +44,13 @@ import {
|
|||
import dynamic from "next/dynamic";
|
||||
|
||||
const CustomEditor = dynamic(
|
||||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
() => import("@/components/editor/custom-editor"),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
const animatedComponent = makeAnimated();
|
||||
|
||||
const FormSchema = z.object({
|
||||
title: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
url: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
thumbnail: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
detail: z.string({
|
||||
required_error: "Required",
|
||||
}),
|
||||
selected: z
|
||||
.array(
|
||||
z.object({
|
||||
|
|
@ -73,9 +59,16 @@ const FormSchema = z.object({
|
|||
value: z.string(),
|
||||
})
|
||||
)
|
||||
.refine((value) => value.length > 0, {
|
||||
message: "Required",
|
||||
}),
|
||||
.min(1, "Pilih minimal satu campaign"),
|
||||
|
||||
title: z.string().min(1, "Required"),
|
||||
url: z.string().min(1, "Required"),
|
||||
thumbnail: z.string().min(1, "Required"),
|
||||
detail: z.string().min(1, "Required"),
|
||||
|
||||
messageType: z
|
||||
.array(z.string())
|
||||
.min(1, "Pilih minimal satu tipe pesan (WA / Email)"),
|
||||
});
|
||||
|
||||
interface Campaign {
|
||||
|
|
@ -83,71 +76,132 @@ interface Campaign {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export default function ContentBlast(props: { type: string }) {
|
||||
export default function ContentBlast() {
|
||||
const editor = useRef(null);
|
||||
const id = useParams()?.id;
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
const { type } = props;
|
||||
|
||||
const [dataSelectCampaign, setDataSelectCampaign] = useState<Campaign[]>([]);
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: { selected: [], detail: "" },
|
||||
defaultValues: {
|
||||
selected: [],
|
||||
detail: "",
|
||||
messageType: [],
|
||||
},
|
||||
});
|
||||
|
||||
const selectedTypes = form.watch("messageType");
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||
if (form.getValues("detail") == "") {
|
||||
form.setError("detail", {
|
||||
type: "manual",
|
||||
message: "Required",
|
||||
});
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
MySwal.fire({
|
||||
title: "Kirim Broadcast?",
|
||||
text: "Pesan akan dikirim ke semua campaign yang dipilih",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonColor: "#3085d6",
|
||||
confirmButtonText: "Kirim",
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) save(data);
|
||||
});
|
||||
};
|
||||
|
||||
function htmlToWaText(html: string) {
|
||||
// Hilangkan link menjadi URL saja
|
||||
html = html.replace(/<a [^>]*href="([^"]+)"[^>]*>(.*?)<\/a>/gi, "$1");
|
||||
|
||||
// Ambil gambar sebagai URL
|
||||
html = html.replace(/<img [^>]*src="([^"]+)".*?>/gi, "$1");
|
||||
|
||||
// Hapus semua HTML tag
|
||||
html = html.replace(/<\/?[^>]+>/gi, "");
|
||||
|
||||
// Decode HTML entities
|
||||
html = html.replace(/&/g, "&");
|
||||
|
||||
// Rapikan spasi
|
||||
return html.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
const save = async (data: z.infer<typeof FormSchema>) => {
|
||||
const selectedCampaign = form.getValues("selected");
|
||||
const selectedCampaign = data.selected;
|
||||
const mediaRes = await detailMediaSummary(String(id));
|
||||
const details = mediaRes?.data?.data;
|
||||
|
||||
for (let i = 0; i < selectedCampaign.length; i++) {
|
||||
const reqData = {
|
||||
mediaUploadId: id,
|
||||
mediaBlastCampaignId: selectedCampaign[i].id,
|
||||
subject: type == "wa" ? `*${data.title}*` : data.title,
|
||||
body: data.detail?.replace(/\n/g, ""),
|
||||
type: type,
|
||||
isScheduled: false,
|
||||
thumbnail: data?.thumbnail,
|
||||
sendDate: getLocaleTimestamp(new Date()),
|
||||
sendTime: getLocaleTime(new Date()),
|
||||
contentUrl: data.url,
|
||||
};
|
||||
const campaignId = selectedCampaign[i].id;
|
||||
|
||||
console.log("req =>", reqData);
|
||||
const response = await saveMediaBlastBroadcast(reqData);
|
||||
for (let mt of data.messageType) {
|
||||
let finalBody = data.detail;
|
||||
if (mt === "wa") {
|
||||
finalBody = textEllipsis(details?.description || "", 150);
|
||||
}
|
||||
|
||||
const payload = {
|
||||
mediaUploadId: id,
|
||||
mediaBlastCampaignId: campaignId,
|
||||
subject: mt === "wa" ? `*${data.title}*` : data.title,
|
||||
body: mt === "wa" ? htmlToWaText(finalBody) : finalBody,
|
||||
type: mt,
|
||||
isScheduled: false,
|
||||
thumbnail: data.thumbnail,
|
||||
sendDate: getLocaleTimestamp(new Date()),
|
||||
sendTime: getLocaleTime(new Date()),
|
||||
contentUrl: data.url,
|
||||
};
|
||||
|
||||
console.log("REQ =>", payload);
|
||||
await saveMediaBlastBroadcast(payload);
|
||||
}
|
||||
}
|
||||
|
||||
setOpenModal(true);
|
||||
};
|
||||
|
||||
// const save = async (data: z.infer<typeof FormSchema>) => {
|
||||
// const selectedCampaign = data.selected;
|
||||
|
||||
// for (let i = 0; i < selectedCampaign.length; i++) {
|
||||
// const campaignId = selectedCampaign[i].id;
|
||||
|
||||
// // Loop WA / Email
|
||||
// for (let mt of data.messageType) {
|
||||
// const payload = {
|
||||
// mediaUploadId: id,
|
||||
// mediaBlastCampaignId: campaignId,
|
||||
// subject: mt === "wa" ? `*${data.title}*` : data.title,
|
||||
// // body: data.detail.replace(/\n/g, ""),
|
||||
// body: mt === "wa" ? htmlToWaText(data.detail) : data.detail,
|
||||
// type: mt, // <-- WA / email
|
||||
// isScheduled: false,
|
||||
// thumbnail: data.thumbnail,
|
||||
// sendDate: getLocaleTimestamp(new Date()),
|
||||
// sendTime: getLocaleTime(new Date()),
|
||||
// contentUrl: data.url,
|
||||
// };
|
||||
|
||||
// console.log("REQ =>", payload);
|
||||
// await saveMediaBlastBroadcast(payload);
|
||||
// }
|
||||
// }
|
||||
|
||||
// setOpenModal(true);
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
async function init() {
|
||||
const response = await detailMediaSummary(String(id));
|
||||
const details = response?.data?.data;
|
||||
|
||||
if (!details) return;
|
||||
|
||||
form.setValue("thumbnail", details.smallThumbnailLink);
|
||||
form.setValue("title", details.title);
|
||||
form.setValue("url", details.pageUrl);
|
||||
let pageUrl = details?.pageUrl || "";
|
||||
if (pageUrl.includes("mediahub.polri.go.id")) {
|
||||
pageUrl = pageUrl.replace(
|
||||
|
|
@ -157,59 +211,43 @@ export default function ContentBlast(props: { type: string }) {
|
|||
}
|
||||
);
|
||||
}
|
||||
if (details != undefined) {
|
||||
form.setValue("thumbnail", details.smallThumbnailLink);
|
||||
let body = `<div><p>Berita hari ini !!!</p>
|
||||
<div style='margin-top:20px;border:1px solid;border-radius:10px;width:500px;background-color:#f7f7f7'>
|
||||
<div>
|
||||
<img style='width:500px;height:auto;border-radius:10px;object-fit:cover' src='${
|
||||
details?.smallThumbnailLink
|
||||
}'>
|
||||
</div>
|
||||
<a style='padding:5px 20px;margin:0;text-decoration:none' href='${pageUrl}'>${pageUrl}</a>
|
||||
<h3 style='padding:5px 20px;margin:0'>${details?.title}</h3>
|
||||
<p style='padding:0 20px;margin:0;margin-bottom:10px'>
|
||||
${textEllipsis(details?.description, 150)}
|
||||
</p>
|
||||
</div>
|
||||
</div>`;
|
||||
form.setValue("title", `${details?.title}`);
|
||||
form.setValue(
|
||||
"url",
|
||||
details?.pageUrl || "https://mediahub.polri.go.id"
|
||||
);
|
||||
if (type == "wa") {
|
||||
body = `${textEllipsis(details?.description, 150)}`;
|
||||
form.setValue("detail", body);
|
||||
} else {
|
||||
form.setValue("detail", body);
|
||||
}
|
||||
}
|
||||
|
||||
const body = `<div><p>Berita hari ini !!!</p>
|
||||
<div style='margin-top:20px;border:1px solid;border-radius:10px;width:500px;background-color:#f7f7f7'>
|
||||
<div>
|
||||
<img style='width:500px;height:auto;border-radius:10px;object-fit:cover' src='${
|
||||
details.smallThumbnailLink
|
||||
}'>
|
||||
</div>
|
||||
<a style='padding:5px 20px;margin:0;text-decoration:none' href='${pageUrl}'>${pageUrl}</a>
|
||||
|
||||
<h3 style='padding:5px 20px;margin:0'>${details.title}</h3>
|
||||
<p style='padding:0 20px;margin:0;margin-bottom:10px'>
|
||||
${textEllipsis(details.description, 150)}
|
||||
</p>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
form.setValue("detail", body);
|
||||
}
|
||||
|
||||
async function getCampaign() {
|
||||
async function loadCampaign() {
|
||||
const response = await getMediaBlastCampaignList();
|
||||
const campaign = response?.data?.data?.content;
|
||||
handleLabelCampaign(campaign);
|
||||
console.log(campaign);
|
||||
}
|
||||
|
||||
initState();
|
||||
getCampaign();
|
||||
init();
|
||||
loadCampaign();
|
||||
}, [id]);
|
||||
|
||||
function handleLabelCampaign(data: any) {
|
||||
const optionArr: any = [];
|
||||
|
||||
data.map((option: any) => {
|
||||
optionArr.push({
|
||||
id: option.id,
|
||||
label: option.title,
|
||||
value: option.title,
|
||||
});
|
||||
});
|
||||
console.log("option arr", optionArr);
|
||||
setDataSelectCampaign(optionArr);
|
||||
const arr = data.map((item: any) => ({
|
||||
id: item.id,
|
||||
label: item.title,
|
||||
value: item.title,
|
||||
}));
|
||||
setDataSelectCampaign(arr);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -218,104 +256,127 @@ export default function ContentBlast(props: { type: string }) {
|
|||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-3 bg-white rounded-sm p-4"
|
||||
>
|
||||
<p className="fonnt-semibold">Broadcast</p>
|
||||
<p className="font-semibold">Broadcast</p>
|
||||
|
||||
{/* SELECT CAMPAIGN */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="selected"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Subject</FormLabel>
|
||||
<FormLabel>Pilih Campaign</FormLabel>
|
||||
<Select
|
||||
className="z-50"
|
||||
options={dataSelectCampaign}
|
||||
closeMenuOnSelect={false}
|
||||
components={animatedComponent}
|
||||
onChange={field.onChange}
|
||||
isMulti
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* CHECKBOX MESSAGE TYPE */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="messageType"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Kirim sebagai:</FormLabel>
|
||||
|
||||
<div className="flex gap-6">
|
||||
<label className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={field.value.includes("wa")}
|
||||
onCheckedChange={(checked) => {
|
||||
checked
|
||||
? field.onChange([...field.value, "wa"])
|
||||
: field.onChange(field.value.filter((v) => v !== "wa"));
|
||||
}}
|
||||
/>
|
||||
WhatsApp
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={field.value.includes("email")}
|
||||
onCheckedChange={(checked) => {
|
||||
checked
|
||||
? field.onChange([...field.value, "email"])
|
||||
: field.onChange(
|
||||
field.value.filter((v) => v !== "email")
|
||||
);
|
||||
}}
|
||||
/>
|
||||
Email
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* TITLE */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Subject</FormLabel>
|
||||
<Input
|
||||
value={field.value}
|
||||
placeholder="Masukkan Judul"
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
|
||||
<FormLabel>Judul</FormLabel>
|
||||
<Input {...field} placeholder="Masukkan judul" />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* DETAIL */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="detail"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Detail Perencanaan</FormLabel>
|
||||
{type === "wa" ? (
|
||||
<Textarea value={field.value} onChange={field.onChange} />
|
||||
) : (
|
||||
<CustomEditor
|
||||
onChange={field.onChange}
|
||||
initialData={field.value}
|
||||
/>
|
||||
)}
|
||||
<FormLabel>Isi Pesan</FormLabel>
|
||||
|
||||
<CustomEditor
|
||||
onChange={field.onChange}
|
||||
initialData={field.value}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-row gap-2 mt-4 pt-4">
|
||||
<Button
|
||||
size="md"
|
||||
type="button"
|
||||
variant="outline"
|
||||
color="destructive"
|
||||
className="text-xs"
|
||||
>
|
||||
|
||||
{/* BUTTONS */}
|
||||
<div className="flex gap-2 mt-4">
|
||||
<Button variant="outline" type="button">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="md" type="submit" color="primary" className="text-xs">
|
||||
<Button type="submit" color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* MODAL SENT */}
|
||||
<Dialog open={openModal}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Terkirim !!</DialogTitle>
|
||||
<DialogTitle>Terkirim!</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col justify-center items-center gap-3 mb-3 text-sm">
|
||||
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<img
|
||||
src="/assets/img/illust-for-broadcast-sent.png"
|
||||
className="w-[70%]"
|
||||
/>
|
||||
Untuk melihat Email Terkirim silahkan cek menu “Sent”!
|
||||
Pesan telah dikirim sesuai pilihan Anda.
|
||||
</div>
|
||||
<DialogFooter className="flex justify-center">
|
||||
{/* <Link
|
||||
href={`/admin/broadcast/campaign-list/detail/${
|
||||
form.getValues("selected")[0]?.id
|
||||
}`}
|
||||
>
|
||||
<Button type="button" color="success">
|
||||
Menu "Sent"
|
||||
</Button>
|
||||
</Link> */}
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => router.push("/admin/broadcast")}
|
||||
color="primary"
|
||||
>
|
||||
Okay
|
||||
<DialogFooter className="flex justify-center">
|
||||
<Button onClick={() => router.push("/admin/broadcast")}>
|
||||
OK
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
|
@ -324,3 +385,330 @@ export default function ContentBlast(props: { type: string }) {
|
|||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
// "use client";
|
||||
// import { z } from "zod";
|
||||
// import { useForm } from "react-hook-form";
|
||||
// import { zodResolver } from "@hookform/resolvers/zod";
|
||||
// import {
|
||||
// Form,
|
||||
// FormField,
|
||||
// FormItem,
|
||||
// FormLabel,
|
||||
// FormMessage,
|
||||
// } from "@/components/ui/form";
|
||||
// import withReactContent from "sweetalert2-react-content";
|
||||
// import Swal from "sweetalert2";
|
||||
|
||||
// import { Input } from "@/components/ui/input";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
|
||||
// import {
|
||||
// getLocaleTime,
|
||||
// getLocaleTimestamp,
|
||||
// textEllipsis,
|
||||
// } from "@/utils/globals";
|
||||
// import {
|
||||
// detailMediaSummary,
|
||||
// getMediaBlastCampaignList,
|
||||
// saveMediaBlastBroadcast,
|
||||
// saveMediaBlastCampaign,
|
||||
// } from "@/service/broadcast/broadcast";
|
||||
// import { error } from "@/config/swal";
|
||||
// import { Link, useRouter } from "@/i18n/routing";
|
||||
|
||||
// import { useEffect, useRef, useState } from "react";
|
||||
// import { useParams } from "next/navigation";
|
||||
// import Select from "react-select";
|
||||
// import makeAnimated from "react-select/animated";
|
||||
// import { Textarea } from "@/components/ui/textarea";
|
||||
// import {
|
||||
// Dialog,
|
||||
// DialogContent,
|
||||
// DialogFooter,
|
||||
// DialogHeader,
|
||||
// DialogTitle,
|
||||
// } from "@/components/ui/dialog";
|
||||
// import dynamic from "next/dynamic";
|
||||
|
||||
// const CustomEditor = dynamic(
|
||||
// () => {
|
||||
// return import("@/components/editor/custom-editor");
|
||||
// },
|
||||
// { ssr: false }
|
||||
// );
|
||||
|
||||
// const animatedComponent = makeAnimated();
|
||||
|
||||
// const FormSchema = z.object({
|
||||
// title: z.string({
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
// url: z.string({
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
// thumbnail: z.string({
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
// detail: z.string({
|
||||
// required_error: "Required",
|
||||
// }),
|
||||
// selected: z
|
||||
// .array(
|
||||
// z.object({
|
||||
// id: z.number(),
|
||||
// label: z.string(),
|
||||
// value: z.string(),
|
||||
// })
|
||||
// )
|
||||
// .refine((value) => value.length > 0, {
|
||||
// message: "Required",
|
||||
// }),
|
||||
// });
|
||||
|
||||
// interface Campaign {
|
||||
// id: string;
|
||||
// name: string;
|
||||
// }
|
||||
|
||||
// export default function ContentBlast(props: { type: string }) {
|
||||
// const editor = useRef(null);
|
||||
// const id = useParams()?.id;
|
||||
// const MySwal = withReactContent(Swal);
|
||||
// const router = useRouter();
|
||||
// const { type } = props;
|
||||
|
||||
// const [dataSelectCampaign, setDataSelectCampaign] = useState<Campaign[]>([]);
|
||||
// const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
// const form = useForm<z.infer<typeof FormSchema>>({
|
||||
// resolver: zodResolver(FormSchema),
|
||||
// defaultValues: { selected: [], detail: "" },
|
||||
// });
|
||||
|
||||
// const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||
// if (form.getValues("detail") == "") {
|
||||
// form.setError("detail", {
|
||||
// type: "manual",
|
||||
// message: "Required",
|
||||
// });
|
||||
// } else {
|
||||
// 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>) => {
|
||||
// const selectedCampaign = form.getValues("selected");
|
||||
|
||||
// for (let i = 0; i < selectedCampaign.length; i++) {
|
||||
// const reqData = {
|
||||
// mediaUploadId: id,
|
||||
// mediaBlastCampaignId: selectedCampaign[i].id,
|
||||
// subject: type == "wa" ? `*${data.title}*` : data.title,
|
||||
// body: data.detail?.replace(/\n/g, ""),
|
||||
// type: type,
|
||||
// isScheduled: false,
|
||||
// thumbnail: data?.thumbnail,
|
||||
// sendDate: getLocaleTimestamp(new Date()),
|
||||
// sendTime: getLocaleTime(new Date()),
|
||||
// contentUrl: data.url,
|
||||
// };
|
||||
|
||||
// console.log("req =>", reqData);
|
||||
// const response = await saveMediaBlastBroadcast(reqData);
|
||||
// }
|
||||
// setOpenModal(true);
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// async function initState() {
|
||||
// const response = await detailMediaSummary(String(id));
|
||||
// const details = response?.data?.data;
|
||||
// let pageUrl = details?.pageUrl || "";
|
||||
// if (pageUrl.includes("mediahub.polri.go.id")) {
|
||||
// pageUrl = pageUrl.replace(
|
||||
// /(\.id)(\/|$)/,
|
||||
// (match: any, p1: any, p2: any) => {
|
||||
// return p2.startsWith("/in") ? match : `${p1}/in${p2}`;
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// if (details != undefined) {
|
||||
// form.setValue("thumbnail", details.smallThumbnailLink);
|
||||
// let body = `<div><p>Berita hari ini !!!</p>
|
||||
// <div style='margin-top:20px;border:1px solid;border-radius:10px;width:500px;background-color:#f7f7f7'>
|
||||
// <div>
|
||||
// <img style='width:500px;height:auto;border-radius:10px;object-fit:cover' src='${
|
||||
// details?.smallThumbnailLink
|
||||
// }'>
|
||||
// </div>
|
||||
// <a style='padding:5px 20px;margin:0;text-decoration:none' href='${pageUrl}'>${pageUrl}</a>
|
||||
// <h3 style='padding:5px 20px;margin:0'>${details?.title}</h3>
|
||||
// <p style='padding:0 20px;margin:0;margin-bottom:10px'>
|
||||
// ${textEllipsis(details?.description, 150)}
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>`;
|
||||
// form.setValue("title", `${details?.title}`);
|
||||
// form.setValue(
|
||||
// "url",
|
||||
// details?.pageUrl || "https://mediahub.polri.go.id"
|
||||
// );
|
||||
// if (type == "wa") {
|
||||
// body = `${textEllipsis(details?.description, 150)}`;
|
||||
// form.setValue("detail", body);
|
||||
// } else {
|
||||
// form.setValue("detail", body);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// async function getCampaign() {
|
||||
// const response = await getMediaBlastCampaignList();
|
||||
// const campaign = response?.data?.data?.content;
|
||||
// handleLabelCampaign(campaign);
|
||||
// console.log(campaign);
|
||||
// }
|
||||
|
||||
// initState();
|
||||
// getCampaign();
|
||||
// }, [id]);
|
||||
|
||||
// function handleLabelCampaign(data: any) {
|
||||
// const optionArr: any = [];
|
||||
|
||||
// data.map((option: any) => {
|
||||
// optionArr.push({
|
||||
// id: option.id,
|
||||
// label: option.title,
|
||||
// value: option.title,
|
||||
// });
|
||||
// });
|
||||
// console.log("option arr", optionArr);
|
||||
// setDataSelectCampaign(optionArr);
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <Form {...form}>
|
||||
// <form
|
||||
// onSubmit={form.handleSubmit(onSubmit)}
|
||||
// className="space-y-3 bg-white rounded-sm p-4"
|
||||
// >
|
||||
// <p className="fonnt-semibold">Broadcast</p>
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="selected"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Subject</FormLabel>
|
||||
// <Select
|
||||
// className="z-50"
|
||||
// options={dataSelectCampaign}
|
||||
// closeMenuOnSelect={false}
|
||||
// components={animatedComponent}
|
||||
// onChange={field.onChange}
|
||||
// isMulti
|
||||
// />
|
||||
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="title"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Subject</FormLabel>
|
||||
// <Input
|
||||
// value={field.value}
|
||||
// placeholder="Masukkan Judul"
|
||||
// onChange={field.onChange}
|
||||
// />
|
||||
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="detail"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Detail Perencanaan</FormLabel>
|
||||
// {type === "wa" ? (
|
||||
// <Textarea value={field.value} onChange={field.onChange} />
|
||||
// ) : (
|
||||
// <CustomEditor
|
||||
// onChange={field.onChange}
|
||||
// initialData={field.value}
|
||||
// />
|
||||
// )}
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <div className="flex flex-row gap-2 mt-4 pt-4">
|
||||
// <Button
|
||||
// size="md"
|
||||
// type="button"
|
||||
// variant="outline"
|
||||
// color="destructive"
|
||||
// className="text-xs"
|
||||
// >
|
||||
// Cancel
|
||||
// </Button>
|
||||
// <Button size="md" type="submit" color="primary" className="text-xs">
|
||||
// Submit
|
||||
// </Button>
|
||||
// </div>
|
||||
// <Dialog open={openModal}>
|
||||
// <DialogContent>
|
||||
// <DialogHeader>
|
||||
// <DialogTitle>Terkirim !!</DialogTitle>
|
||||
// </DialogHeader>
|
||||
// <div className="flex flex-col justify-center items-center gap-3 mb-3 text-sm">
|
||||
// <img
|
||||
// src="/assets/img/illust-for-broadcast-sent.png"
|
||||
// className="w-[70%]"
|
||||
// />
|
||||
// Untuk melihat Email Terkirim silahkan cek menu “Sent”!
|
||||
// </div>
|
||||
// <DialogFooter className="flex justify-center">
|
||||
// {/* <Link
|
||||
// href={`/admin/broadcast/campaign-list/detail/${
|
||||
// form.getValues("selected")[0]?.id
|
||||
// }`}
|
||||
// >
|
||||
// <Button type="button" color="success">
|
||||
// Menu "Sent"
|
||||
// </Button>
|
||||
// </Link> */}
|
||||
|
||||
// <Button
|
||||
// type="button"
|
||||
// onClick={() => router.push("/admin/broadcast")}
|
||||
// color="primary"
|
||||
// >
|
||||
// Okay
|
||||
// </Button>
|
||||
// </DialogFooter>
|
||||
// </DialogContent>
|
||||
// </Dialog>
|
||||
// </form>
|
||||
// </Form>
|
||||
// );
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"use client";
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
|
|||
|
|
@ -139,17 +139,27 @@ export default function FormQuestionsForward() {
|
|||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(taskSchema),
|
||||
defaultValues: {
|
||||
title: "",
|
||||
description: "",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function initState() {
|
||||
const response = await getQuestionTicket(id);
|
||||
setDetail(response?.data?.data);
|
||||
const data = response?.data?.data;
|
||||
setDetail(data);
|
||||
|
||||
if (response?.data !== null) {
|
||||
setDetailTickets(response?.data?.data);
|
||||
if (data) {
|
||||
reset({
|
||||
title: data.message ?? "",
|
||||
description: data.emergencyIssue?.description ?? "",
|
||||
});
|
||||
}
|
||||
|
||||
setDetailTickets(data);
|
||||
|
||||
if (detailTickets?.emergencyIssue) {
|
||||
reset({
|
||||
title: detailTickets.emergencyIssue.title || "",
|
||||
|
|
@ -301,14 +311,15 @@ export default function FormQuestionsForward() {
|
|||
{` `}
|
||||
mengirimkan pesan untuk{` `}
|
||||
<Link
|
||||
href={
|
||||
detail?.feed
|
||||
? detail?.feed?.permalink_url == undefined
|
||||
? detail?.feedUrl
|
||||
: detail?.feed?.permalink_url
|
||||
: ""
|
||||
}
|
||||
target="_blank"
|
||||
// href={
|
||||
// detail?.feed
|
||||
// ? detail?.feed?.permalink_url == undefined
|
||||
// ? detail?.feedUrl
|
||||
// : detail?.feed?.permalink_url
|
||||
// : ""
|
||||
// }
|
||||
// target="_blank"
|
||||
href={detail?.feedUrl}
|
||||
className="font-bold"
|
||||
>
|
||||
{detail?.message}
|
||||
|
|
@ -334,7 +345,7 @@ export default function FormQuestionsForward() {
|
|||
<p className="mx-3 mt-3">Properties</p>
|
||||
<div className="space-y-2 px-3">
|
||||
<Label>Judul</Label>
|
||||
<Controller
|
||||
{/* <Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
|
|
@ -346,6 +357,18 @@ export default function FormQuestionsForward() {
|
|||
placeholder="Masukkan judul"
|
||||
/>
|
||||
)}
|
||||
/> */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
{...field}
|
||||
placeholder="Masukkan judul"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
createMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
rejectFiles,
|
||||
submitApproval,
|
||||
} from "@/service/content/content";
|
||||
|
|
@ -114,7 +115,7 @@ const ViewEditor = dynamic(
|
|||
() => {
|
||||
return import("@/components/editor/view-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
interface Destination {
|
||||
|
|
@ -197,6 +198,12 @@ export default function FormAudioDetail() {
|
|||
satker: boolean;
|
||||
}>
|
||||
>([]);
|
||||
const [creatorLevelNumber, setCreatorLevelNumber] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const isContentFromSatker = creatorLevelNumber === 3;
|
||||
const isContentFromMabesOrPolda =
|
||||
creatorLevelNumber === 1 || creatorLevelNumber === 2;
|
||||
|
||||
useEffect(() => {
|
||||
if (Number(userLevelId) === 216 && Number(roleId) === 3) {
|
||||
|
|
@ -208,7 +215,7 @@ export default function FormAudioDetail() {
|
|||
const handleFileUnitChange = (
|
||||
fileIndex: number,
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
value: boolean,
|
||||
) => {
|
||||
setFileUnitSelections((prev) => {
|
||||
const newSelections = [...prev];
|
||||
|
|
@ -228,7 +235,7 @@ export default function FormAudioDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[fileIndex] || new Set()
|
||||
newArray[fileIndex] || new Set(),
|
||||
);
|
||||
|
||||
if (value) {
|
||||
|
|
@ -259,12 +266,12 @@ export default function FormAudioDetail() {
|
|||
(item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
currentFileCheckedLevels.has(Number(item.id))
|
||||
currentFileCheckedLevels.has(Number(item.id)),
|
||||
);
|
||||
|
||||
if (!hasSelectedPolda) {
|
||||
alert(
|
||||
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."
|
||||
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES.",
|
||||
);
|
||||
return prev; // Batalkan perubahan
|
||||
}
|
||||
|
|
@ -301,7 +308,7 @@ export default function FormAudioDetail() {
|
|||
useEffect(() => {
|
||||
if (detail?.assignedToTopLevel) {
|
||||
const outputSet = new Set(
|
||||
detail.assignedToTopLevel.split(",").map(Number)
|
||||
detail.assignedToTopLevel.split(",").map(Number),
|
||||
);
|
||||
setUnitSelection({
|
||||
semua: outputSet.has(0),
|
||||
|
|
@ -326,7 +333,7 @@ export default function FormAudioDetail() {
|
|||
acc[polda.id] = false;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
{},
|
||||
);
|
||||
setExpandedPolda(initialExpandedState);
|
||||
console.log("polres", initialExpandedState);
|
||||
|
|
@ -361,7 +368,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
value: boolean,
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
|
|
@ -471,7 +478,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -485,7 +492,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data.data.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -493,7 +500,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
o.name.toLowerCase().includes("pers rilis")
|
||||
o.name.toLowerCase().includes("pers rilis"),
|
||||
);
|
||||
|
||||
if (findCategory) {
|
||||
|
|
@ -541,6 +548,9 @@ export default function FormAudioDetail() {
|
|||
const details = response?.data?.data;
|
||||
setFiles(details?.files);
|
||||
setDetail(details);
|
||||
if (details?.uploadedBy?.userLevel?.levelNumber) {
|
||||
setCreatorLevelNumber(details.uploadedBy.userLevel.levelNumber);
|
||||
}
|
||||
setMain({
|
||||
type: details?.fileType.name,
|
||||
url: details?.files[0]?.url,
|
||||
|
|
@ -551,14 +561,14 @@ export default function FormAudioDetail() {
|
|||
|
||||
if (details?.assignedToLevel) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
details.assignedToLevel.split(",").map(Number),
|
||||
);
|
||||
setCheckedLevels(levels);
|
||||
}
|
||||
|
||||
if (details?.publishedForObject) {
|
||||
const publisherIds = details?.publishedForObject.map(
|
||||
(obj: any) => obj.id
|
||||
(obj: any) => obj.id,
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
}
|
||||
|
|
@ -708,7 +718,7 @@ export default function FormAudioDetail() {
|
|||
const setupPlacement = (
|
||||
index: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
checked: boolean,
|
||||
) => {
|
||||
let temp = [...filePlacements];
|
||||
if (checked) {
|
||||
|
|
@ -719,7 +729,7 @@ export default function FormAudioDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[index] || new Set()
|
||||
newArray[index] || new Set(),
|
||||
);
|
||||
|
||||
// Checklist semua item di modal
|
||||
|
|
@ -769,7 +779,7 @@ export default function FormAudioDetail() {
|
|||
// Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist
|
||||
// JANGAN include satker dalam perhitungan auto "all"
|
||||
const nonSatkerItems = now.filter(
|
||||
(item) => item !== "satker" && item !== "all"
|
||||
(item) => item !== "satker" && item !== "all",
|
||||
);
|
||||
if (nonSatkerItems.length === 3 && !now.includes("all")) {
|
||||
now.push("all");
|
||||
|
|
@ -784,7 +794,7 @@ export default function FormAudioDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[index] || new Set()
|
||||
newArray[index] || new Set(),
|
||||
);
|
||||
|
||||
// Unchecklist semua item di modal
|
||||
|
|
@ -818,7 +828,7 @@ export default function FormAudioDetail() {
|
|||
// Hapus "all" jika tidak semua item ter-checklist
|
||||
if (now.includes("all")) {
|
||||
const nonSatkerItems = now.filter(
|
||||
(item) => item !== "satker" && item !== "all"
|
||||
(item) => item !== "satker" && item !== "all",
|
||||
);
|
||||
if (nonSatkerItems.length < 3) {
|
||||
const newData = now.filter((b) => b !== "all");
|
||||
|
|
@ -837,7 +847,7 @@ export default function FormAudioDetail() {
|
|||
type: string,
|
||||
url: string,
|
||||
names: string,
|
||||
format: string
|
||||
format: string,
|
||||
) => {
|
||||
console.log("Test 3 :", type, url, names, format);
|
||||
setMain({
|
||||
|
|
@ -872,7 +882,7 @@ export default function FormAudioDetail() {
|
|||
const updateModalChecklistLevels = (
|
||||
fileIndex: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
checked: boolean,
|
||||
) => {
|
||||
if (!listDest || listDest.length === 0) return;
|
||||
|
||||
|
|
@ -905,7 +915,7 @@ export default function FormAudioDetail() {
|
|||
} else if (placement === "satker") {
|
||||
// Checklist SATKER POLRI dan semua sub-item di bawahnya
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.add(Number(satkerItem.id));
|
||||
|
|
@ -941,7 +951,7 @@ export default function FormAudioDetail() {
|
|||
} else if (placement === "satker") {
|
||||
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.delete(Number(satkerItem.id));
|
||||
|
|
@ -974,7 +984,7 @@ export default function FormAudioDetail() {
|
|||
// Fungsi untuk mengupdate checklist levels untuk file tertentu
|
||||
const handleFileCheckboxChangePlacement = (
|
||||
fileIndex: number,
|
||||
levelId: number
|
||||
levelId: number,
|
||||
) => {
|
||||
setFileCheckedLevels((prev) => {
|
||||
const newArray = [...prev];
|
||||
|
|
@ -986,7 +996,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
|
||||
const poldaItem = listDest.find(
|
||||
(item: any) => Number(item.id) === levelId
|
||||
(item: any) => Number(item.id) === levelId,
|
||||
) as any;
|
||||
if (
|
||||
poldaItem &&
|
||||
|
|
@ -1009,7 +1019,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => Number(item.id) === levelId
|
||||
(item: any) => Number(item.id) === levelId,
|
||||
) as any;
|
||||
if (satkerItem && satkerItem.name === "SATKER POLRI") {
|
||||
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
|
||||
|
|
@ -1040,7 +1050,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
// Hitung total POLDA yang ada (bukan SATKER POLRI)
|
||||
const totalPoldaCount = listDest.filter(
|
||||
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
|
||||
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI",
|
||||
).length;
|
||||
|
||||
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
|
||||
|
|
@ -1067,7 +1077,7 @@ export default function FormAudioDetail() {
|
|||
}
|
||||
return total;
|
||||
},
|
||||
0
|
||||
0,
|
||||
);
|
||||
|
||||
// Hitung berapa banyak POLRES yang ter-checklist
|
||||
|
|
@ -1077,7 +1087,7 @@ export default function FormAudioDetail() {
|
|||
return (
|
||||
total +
|
||||
item.subDestination.filter((sub: any) =>
|
||||
currentFileLevels.has(Number(sub.id))
|
||||
currentFileLevels.has(Number(sub.id)),
|
||||
).length
|
||||
);
|
||||
}
|
||||
|
|
@ -1086,7 +1096,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
// Cek apakah SATKER POLRI ter-checklist
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
const isSatkerChecked =
|
||||
satkerItem && currentFileLevels.has(Number(satkerItem.id));
|
||||
|
|
@ -1120,7 +1130,7 @@ export default function FormAudioDetail() {
|
|||
|
||||
// Cek apakah semua sub-items sudah ter-checklist
|
||||
const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
|
||||
currentFileLevels.has(Number(sub.id))
|
||||
currentFileLevels.has(Number(sub.id)),
|
||||
);
|
||||
|
||||
if (allSubItemsChecked) {
|
||||
|
|
@ -1203,7 +1213,7 @@ export default function FormAudioDetail() {
|
|||
{detail &&
|
||||
!categories.find(
|
||||
(cat) =>
|
||||
String(cat.id) === String(detail.category.id)
|
||||
String(cat.id) === String(detail.category.id),
|
||||
) && (
|
||||
<SelectItem
|
||||
key={String(detail.category.id)}
|
||||
|
|
@ -1463,7 +1473,7 @@ export default function FormAudioDetail() {
|
|||
Tingkat Distribusi:
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{/* {[
|
||||
{ key: "semua", label: "Semua" },
|
||||
{ key: "nasional", label: "Nasional" },
|
||||
{ key: "wilayah", label: "Wilayah" },
|
||||
|
|
@ -1471,6 +1481,39 @@ export default function FormAudioDetail() {
|
|||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
] */}
|
||||
{[
|
||||
{ key: "semua", label: "Semua" },
|
||||
|
||||
...(isContentFromMabesOrPolda
|
||||
? [
|
||||
{
|
||||
key: "nasional",
|
||||
label: "Nasional",
|
||||
},
|
||||
{
|
||||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
{
|
||||
key: "wilayah",
|
||||
label: "Wilayah",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(isContentFromSatker
|
||||
? [
|
||||
{
|
||||
key: "nasional",
|
||||
label: "Nasional",
|
||||
},
|
||||
{
|
||||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
|
|
@ -1487,12 +1530,12 @@ export default function FormAudioDetail() {
|
|||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
value as boolean,
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
Boolean(value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -1508,251 +1551,262 @@ export default function FormAudioDetail() {
|
|||
</div>
|
||||
|
||||
{/* Detail Wilayah */}
|
||||
{fileUnitSelections[index]?.wilayah && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
{/* {fileUnitSelections[index]?.wilayah && ( */}
|
||||
{!isContentFromSatker &&
|
||||
fileUnitSelections[index]?.wilayah && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{ key: "polres", label: "POLRES" },
|
||||
{ key: "satker", label: "SATKER" },
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{ key: "polres", label: "POLRES" },
|
||||
{ key: "satker", label: "SATKER" },
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean,
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan POLRES
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map((polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(polda.id)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(polda.id)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{polda.subDestination && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id
|
||||
);
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Sub-items */}
|
||||
{polda.subDestination &&
|
||||
expandedPolda[polda.id] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(sub: any) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
)
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
polda
|
||||
)
|
||||
}
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={12}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={12}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan
|
||||
POLRES
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map((polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(polda.id),
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(polda.id),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{polda.subDestination && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id,
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(sub: any) => (
|
||||
<Label
|
||||
key={sub.id}
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sub-items */}
|
||||
{polda.subDestination &&
|
||||
expandedPolda[
|
||||
polda.id
|
||||
] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(sub: any) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
sub.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
polda,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{sub.name}
|
||||
</span>
|
||||
</Label>
|
||||
)
|
||||
)}
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal
|
||||
Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih
|
||||
Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(sub: any) => (
|
||||
<Label
|
||||
key={sub.id}
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id,
|
||||
),
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(
|
||||
sub.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{sub.name}
|
||||
</span>
|
||||
</Label>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>
|
||||
{/* {t("save", {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>
|
||||
{/* {t("save", {
|
||||
defaultValue: "Simpan",
|
||||
})} */}
|
||||
Simpan
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
Simpan
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -1831,7 +1885,7 @@ export default function FormAudioDetail() {
|
|||
>
|
||||
{template}
|
||||
</Button>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1862,7 +1916,72 @@ export default function FormAudioDetail() {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</Card>
|
||||
{Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
{Number(roleId) === 14 ? (
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<Button
|
||||
onClick={() => actionApproval("2")}
|
||||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" />
|
||||
{t("accept", { defaultValue: "Accept" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision", { defaultValue: "Revision" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
color="destructive"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
{t("reject", { defaultValue: "Reject" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : (Number(detail?.needApprovalFromLevel) ===
|
||||
Number(userLevelId) ||
|
||||
(detail?.isInternationalMedia === true &&
|
||||
detail?.isForwardFromNational === true &&
|
||||
Number(detail?.statusId) === 1)) &&
|
||||
(Number(detail?.uploadedById) === Number(userId) ||
|
||||
Number(detail?.needApprovalFromLevel) ===
|
||||
Number(userLevelId)) ? (
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<Button
|
||||
onClick={() => actionApproval("2")}
|
||||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" />
|
||||
{t("accept", { defaultValue: "Accept" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision", { defaultValue: "Revision" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
color="destructive"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
{t("reject", { defaultValue: "Reject" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{/* {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
Number(detail?.uploadedById) == Number(userId) ? (
|
||||
""
|
||||
) : (
|
||||
|
|
@ -1895,7 +2014,7 @@ export default function FormAudioDetail() {
|
|||
)
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
createMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
uploadThumbnail,
|
||||
} from "@/service/content/content";
|
||||
import { uploadThumbnailBlog } from "@/service/blog/blog";
|
||||
|
|
@ -145,10 +146,10 @@ export default function FormAudio() {
|
|||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const [fileError, setFileError] = useState<string | null>(null);
|
||||
type FileWithPreview = File & { preview: string };
|
||||
|
||||
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
|
||||
const [translatedContent, setTranslatedContent] = React.useState("");
|
||||
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
||||
const [translatedTitle, setTranslatedTitle] = useState("");
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
|
|
@ -465,7 +466,7 @@ export default function FormAudio() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -523,20 +524,145 @@ export default function FormAudio() {
|
|||
}
|
||||
}, [articleBody, setValue]);
|
||||
|
||||
// const save = async (data: AudioSchema) => {
|
||||
// loading();
|
||||
// const finalTags = tags.join(", ");
|
||||
// const finalTitle = isSwitchOn ? title : data.title;
|
||||
// const finalDescription = isSwitchOn
|
||||
// ? data.description
|
||||
// : selectedFileType === "rewrite"
|
||||
// ? data.rewriteDescription
|
||||
// : data.descriptionOri;
|
||||
// if (!finalDescription?.trim()) {
|
||||
// MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// let requestData: {
|
||||
// title: string;
|
||||
// description: string;
|
||||
// htmlDescription: string;
|
||||
// fileTypeId: string;
|
||||
// categoryId: any;
|
||||
// subCategoryId: any;
|
||||
// uploadedBy: string;
|
||||
// statusId: string;
|
||||
// publishedFor: string;
|
||||
// creatorName: string;
|
||||
// tags: string;
|
||||
// isYoutube: boolean;
|
||||
// isInternationalMedia: boolean;
|
||||
// attachFromScheduleId?: number;
|
||||
// } = {
|
||||
// ...data,
|
||||
// title: finalTitle,
|
||||
// description: htmlToString(finalDescription),
|
||||
// htmlDescription: finalDescription,
|
||||
// fileTypeId,
|
||||
// categoryId: selectedCategory,
|
||||
// subCategoryId: selectedCategory,
|
||||
// uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
// statusId: "1",
|
||||
// publishedFor: publishedFor.join(","),
|
||||
// creatorName: data.creatorName,
|
||||
// tags: finalTags,
|
||||
// isYoutube: false,
|
||||
// isInternationalMedia: false,
|
||||
// };
|
||||
|
||||
// let id = Cookies.get("idCreate");
|
||||
|
||||
// if (scheduleId !== undefined) {
|
||||
// requestData.attachFromScheduleId = Number(scheduleId);
|
||||
// }
|
||||
|
||||
// if (id == undefined) {
|
||||
// const response = await createMedia(requestData);
|
||||
// console.log("Form Data Submitted:", requestData);
|
||||
|
||||
// if (response?.error) {
|
||||
// MySwal.fire("Error", response?.message, "error");
|
||||
// return;
|
||||
// }
|
||||
// Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
||||
// id = response?.data?.data;
|
||||
|
||||
// const formMedia = new FormData();
|
||||
// console.log("Thumbnail : ", files[0]);
|
||||
// formMedia.append("file", files[0]);
|
||||
// const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||
// if (responseThumbnail?.error == true) {
|
||||
// error(responseThumbnail?.message);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// const progressInfoArr = [];
|
||||
// for (const item of files) {
|
||||
// progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||
// }
|
||||
// progressInfo = progressInfoArr;
|
||||
// setIsStartUpload(true);
|
||||
// setProgressList(progressInfoArr);
|
||||
|
||||
// close();
|
||||
// // showProgress();
|
||||
// files.map(async (item: any, index: number) => {
|
||||
// await uploadResumableFile(index, String(id), item, "0");
|
||||
// });
|
||||
|
||||
// Cookies.remove("idCreate");
|
||||
|
||||
// // MySwal.fire("Sukses", "Data berhasil disimpan.", "success");
|
||||
// };
|
||||
|
||||
const save = async (data: AudioSchema) => {
|
||||
loading();
|
||||
const finalTags = tags.join(", ");
|
||||
const finalTitle = isSwitchOn ? title : data.title;
|
||||
const finalDescription = isSwitchOn
|
||||
? data.description
|
||||
: selectedFileType === "rewrite"
|
||||
? data.rewriteDescription
|
||||
: data.descriptionOri;
|
||||
// const finalTitle = isSwitchOn ? title : data.title;
|
||||
|
||||
// // pilih description dasar
|
||||
// let finalDescription = isSwitchOn
|
||||
// ? data.description
|
||||
// : selectedFileType === "rewrite"
|
||||
// ? data.rewriteDescription
|
||||
// : data.descriptionOri;
|
||||
|
||||
const finalTitle =
|
||||
translatedTitle && translatedTitle.trim() !== ""
|
||||
? translatedTitle
|
||||
: isSwitchOn
|
||||
? title
|
||||
: data.title;
|
||||
|
||||
const finalDescription =
|
||||
translatedContent && translatedContent.trim() !== ""
|
||||
? translatedContent
|
||||
: isSwitchOn
|
||||
? data.description
|
||||
: selectedFileType === "rewrite"
|
||||
? data.rewriteDescription
|
||||
: data.descriptionOri;
|
||||
|
||||
if (!finalDescription?.trim()) {
|
||||
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// 👉 tempelkan hasil translate ke field form & pakai sebagai description
|
||||
if (translatedContent) {
|
||||
data.descriptionOri = translatedContent;
|
||||
console.log(
|
||||
"🌍 Translate dimasukkan ke descriptionOri:",
|
||||
translatedContent
|
||||
);
|
||||
}
|
||||
|
||||
// if (!finalDescription?.trim()) {
|
||||
// MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
// return;
|
||||
// }
|
||||
|
||||
let requestData: {
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
@ -555,8 +681,8 @@ export default function FormAudio() {
|
|||
} = {
|
||||
...data,
|
||||
title: finalTitle,
|
||||
description: htmlToString(finalDescription),
|
||||
htmlDescription: finalDescription,
|
||||
description: htmlToString(finalDescription), // ✅ plain text versi translate
|
||||
htmlDescription: finalDescription, // ✅ html versi translate
|
||||
fileTypeId,
|
||||
categoryId: selectedCategory,
|
||||
subCategoryId: selectedCategory,
|
||||
|
|
@ -583,6 +709,7 @@ export default function FormAudio() {
|
|||
MySwal.fire("Error", response?.message, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
||||
id = response?.data?.data;
|
||||
|
||||
|
|
@ -596,15 +723,15 @@ export default function FormAudio() {
|
|||
}
|
||||
}
|
||||
|
||||
const progressInfoArr = [];
|
||||
for (const item of files) {
|
||||
progressInfoArr.push({ percentage: 0, fileName: item.name });
|
||||
}
|
||||
const progressInfoArr = files.map((item) => ({
|
||||
percentage: 0,
|
||||
fileName: item.name,
|
||||
}));
|
||||
progressInfo = progressInfoArr;
|
||||
setIsStartUpload(true);
|
||||
setProgressList(progressInfoArr);
|
||||
|
||||
close();
|
||||
// close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
|
|
@ -714,6 +841,8 @@ export default function FormAudio() {
|
|||
}
|
||||
if (counter == progressInfo.length) {
|
||||
setIsStartUpload(false);
|
||||
close();
|
||||
|
||||
// hideProgress();
|
||||
Cookies.remove("idCreate");
|
||||
successSubmit("/in/contributor/content/audio");
|
||||
|
|
@ -857,7 +986,44 @@ export default function FormAudio() {
|
|||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslate(true);
|
||||
const res = await translateText({
|
||||
text: getValues("title"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text || "";
|
||||
setTranslatedTitle(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
close();
|
||||
console.error("Translate title gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslate(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslate
|
||||
? "Translating..."
|
||||
: "Translate Title"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Title Bahasa Indonesia */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -867,10 +1033,27 @@ export default function FormAudio() {
|
|||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
placeholder="Masukkan Judul Bahasa Indonesia"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Versi English (muncul setelah translate) */}
|
||||
{translatedTitle && (
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Title
|
||||
</Label>
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={translatedTitle}
|
||||
onChange={(e) => setTranslatedTitle(e.target.value)}
|
||||
placeholder="Translated Title"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
|
|
@ -1623,7 +1806,7 @@ export default function FormAudio() {
|
|||
</Card>
|
||||
<div className="flex flex-row justify-end gap-3">
|
||||
<div className="mt-4">
|
||||
{levelNumber !== "2" && levelNumber !== "3" && (
|
||||
{levelNumber !== "2" && (
|
||||
<Button type="submit" color="primary">
|
||||
{t("submit", { defaultValue: "Submit" })}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -24,16 +24,13 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
|
||||
import { register } from "module";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
deleteFile,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
updateFilePlacements,
|
||||
uploadThumbnail,
|
||||
} from "@/service/content/content";
|
||||
|
|
@ -62,6 +59,9 @@ import {
|
|||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { translateText } from "@/service/content/ai";
|
||||
|
||||
const audioSchema = z.object({
|
||||
title: z.string().min(1, { message: "Judul diperlukan" }),
|
||||
|
|
@ -135,24 +135,20 @@ const CustomEditor = dynamic(
|
|||
export default function FormAudioUpdate() {
|
||||
const MySwal = withReactContent(Swal);
|
||||
const router = useRouter();
|
||||
|
||||
const { id } = useParams() as { id: string };
|
||||
console.log(id);
|
||||
const editor = useRef(null);
|
||||
type AudioSchema = z.infer<typeof audioSchema>;
|
||||
|
||||
let progressInfo: any = [];
|
||||
let counterUpdateProgress = 0;
|
||||
const [progressList, setProgressList] = useState<any>([]);
|
||||
let uploadPersen = 0;
|
||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const taskId = Cookies.get("taskId");
|
||||
const scheduleId = Cookies.get("scheduleId");
|
||||
const scheduleType = Cookies.get("scheduleType");
|
||||
|
||||
const t = useTranslations("Form");
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<any>();
|
||||
|
|
@ -197,6 +193,11 @@ export default function FormAudioUpdate() {
|
|||
useState(false);
|
||||
const [mainCheckboxChangeType, setMainCheckboxChangeType] =
|
||||
useState<string>("");
|
||||
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
|
||||
const [translatedContent, setTranslatedContent] = React.useState("");
|
||||
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const [translatedTitle, setTranslatedTitle] = useState("");
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", name: "SEMUA" },
|
||||
|
|
@ -212,7 +213,8 @@ export default function FormAudioUpdate() {
|
|||
const [tempFile, setTempFile] = useState<TempFileItem[]>([]);
|
||||
|
||||
let fileTypeId = "4";
|
||||
const isDetailOfRegionShowed = false;
|
||||
// const isDetailOfRegionShowed = false;
|
||||
const [isDetailOfRegionShowed, setIsDetailOfRegionShowed] = useState(false);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop: (acceptedFiles) => {
|
||||
|
|
@ -234,6 +236,7 @@ export default function FormAudioUpdate() {
|
|||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
} = useForm<AudioSchema>({
|
||||
resolver: zodResolver(audioSchema),
|
||||
|
|
@ -303,7 +306,7 @@ export default function FormAudioUpdate() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data.data.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -514,7 +517,8 @@ export default function FormAudioUpdate() {
|
|||
// Jika wilayah dicentang, auto centang POLDA, SATKER
|
||||
if (key === "wilayah") {
|
||||
currentSelection.wilayah = value;
|
||||
|
||||
setIsDetailOfRegionShowed(value);
|
||||
|
||||
if (value) {
|
||||
// Ketika wilayah dicentang, auto centang POLDA, SATKER
|
||||
currentSelection.polda = true;
|
||||
|
|
@ -535,7 +539,9 @@ export default function FormAudioUpdate() {
|
|||
});
|
||||
|
||||
// Checklist SATKER POLRI dan semua sub-itemsnya
|
||||
const satkerItem = listDest.find((item: any) => item.name === "SATKER POLRI");
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.add(Number(satkerItem.id));
|
||||
// Checklist semua sub-items di bawah SATKER POLRI
|
||||
|
|
@ -569,7 +575,9 @@ export default function FormAudioUpdate() {
|
|||
});
|
||||
|
||||
// Hapus SATKER POLRI dan semua sub-itemsnya
|
||||
const satkerItem = listDest.find((item: any) => item.name === "SATKER POLRI");
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.delete(Number(satkerItem.id));
|
||||
// Hapus semua sub-items di bawah SATKER POLRI
|
||||
|
|
@ -763,7 +771,11 @@ export default function FormAudioUpdate() {
|
|||
const temp = [];
|
||||
for (let i = 0; i < filePlacements?.length; i++) {
|
||||
const file = files[i] as any;
|
||||
if (file.id && filePlacements[file.id] && filePlacements[file.id].length > 0) {
|
||||
if (
|
||||
file.id &&
|
||||
filePlacements[file.id] &&
|
||||
filePlacements[file.id].length > 0
|
||||
) {
|
||||
const now = filePlacements[file.id];
|
||||
const normalizedNow = now.map((item): PlacementType => {
|
||||
const value = String(item);
|
||||
|
|
@ -873,14 +885,16 @@ export default function FormAudioUpdate() {
|
|||
} else if (placement === "semua") {
|
||||
placementToAdd = "all";
|
||||
}
|
||||
|
||||
|
||||
const now = temp[index] || [];
|
||||
if (!now.includes(placementToAdd)) {
|
||||
now.push(placementToAdd);
|
||||
}
|
||||
// Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist
|
||||
const requiredItems = ["mabes", "wilayah", "international"];
|
||||
const hasAllRequired = requiredItems.every(item => now.includes(item));
|
||||
const hasAllRequired = requiredItems.every((item) =>
|
||||
now.includes(item)
|
||||
);
|
||||
if (hasAllRequired && !now.includes("all")) {
|
||||
now.push("all");
|
||||
}
|
||||
|
|
@ -923,8 +937,8 @@ export default function FormAudioUpdate() {
|
|||
} else {
|
||||
if (placement === "wilayah") {
|
||||
// Ketika wilayah di-uncheck, hapus wilayah, polda, dan satker
|
||||
const now = temp[index]?.filter((a) =>
|
||||
a !== "wilayah" && a !== "polda" && a !== "satker"
|
||||
const now = temp[index]?.filter(
|
||||
(a) => a !== "wilayah" && a !== "polda" && a !== "satker"
|
||||
);
|
||||
temp[index] = now;
|
||||
} else if (placement === "polda") {
|
||||
|
|
@ -943,16 +957,18 @@ export default function FormAudioUpdate() {
|
|||
} else if (placement === "semua") {
|
||||
placementToRemove = "all";
|
||||
}
|
||||
|
||||
|
||||
const now = temp[index]?.filter((a) => a !== placementToRemove);
|
||||
temp[index] = now;
|
||||
}
|
||||
|
||||
|
||||
// Hapus "all" jika tidak semua item ter-checklist
|
||||
const currentNow = temp[index] || [];
|
||||
if (currentNow.includes("all")) {
|
||||
const requiredItems = ["mabes", "wilayah", "international"];
|
||||
const hasAllRequired = requiredItems.every(item => currentNow.includes(item));
|
||||
const hasAllRequired = requiredItems.every((item) =>
|
||||
currentNow.includes(item)
|
||||
);
|
||||
if (!hasAllRequired) {
|
||||
const newData = currentNow.filter((b) => b !== "all");
|
||||
temp[index] = newData;
|
||||
|
|
@ -1073,12 +1089,18 @@ export default function FormAudioUpdate() {
|
|||
.map((p: string) => {
|
||||
const trimmed = p.trim();
|
||||
switch (trimmed) {
|
||||
case "all": return "all";
|
||||
case "mabes": return "nasional";
|
||||
case "polda": return "wilayah";
|
||||
case "satker": return "satker";
|
||||
case "international": return "international";
|
||||
default: return trimmed;
|
||||
case "all":
|
||||
return "all";
|
||||
case "mabes":
|
||||
return "nasional";
|
||||
case "polda":
|
||||
return "wilayah";
|
||||
case "satker":
|
||||
return "satker";
|
||||
case "international":
|
||||
return "international";
|
||||
default:
|
||||
return trimmed;
|
||||
}
|
||||
});
|
||||
return mappedPlacements;
|
||||
|
|
@ -1112,8 +1134,10 @@ export default function FormAudioUpdate() {
|
|||
};
|
||||
|
||||
if (file.placements) {
|
||||
const placements = file.placements.split(",").map((p: string) => p.trim());
|
||||
|
||||
const placements = file.placements
|
||||
.split(",")
|
||||
.map((p: string) => p.trim());
|
||||
|
||||
// Map dari format backend ke checkbox
|
||||
if (placements.includes("all")) {
|
||||
selection.semua = true;
|
||||
|
|
@ -1177,7 +1201,6 @@ export default function FormAudioUpdate() {
|
|||
initState();
|
||||
}, [refresh, setValue]);
|
||||
|
||||
|
||||
const handleCheckboxChange = (id: string) => {
|
||||
if (id === "all") {
|
||||
// Select all options except "all"
|
||||
|
|
@ -1197,12 +1220,22 @@ export default function FormAudioUpdate() {
|
|||
|
||||
const save = async (data: AudioSchema) => {
|
||||
const finalTags = tags.join(", ");
|
||||
|
||||
// ✅ tentukan isi description sesuai pilihan bahasa
|
||||
// const descFinal =
|
||||
// selectedLang === "en" && translatedContent
|
||||
// ? translatedContent
|
||||
// : data.description;
|
||||
|
||||
const descFinal = translatedContent || data.description;
|
||||
const finalTitle = translatedTitle || data.title;
|
||||
|
||||
const requestData = {
|
||||
...data,
|
||||
id: detail?.id,
|
||||
title: data.title,
|
||||
description: htmlToString(data.description),
|
||||
htmlDescription: data.description,
|
||||
title: finalTitle,
|
||||
description: htmlToString(descFinal),
|
||||
htmlDescription: descFinal,
|
||||
fileTypeId,
|
||||
categoryId: selectedTarget,
|
||||
subCategoryId: selectedTarget,
|
||||
|
|
@ -1441,7 +1474,6 @@ export default function FormAudioUpdate() {
|
|||
</div>
|
||||
));
|
||||
|
||||
|
||||
function success() {
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -1503,7 +1535,43 @@ export default function FormAudioUpdate() {
|
|||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslate(true);
|
||||
const res = await translateText({
|
||||
text: getValues("title"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text || "";
|
||||
setTranslatedTitle(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
close();
|
||||
console.error("Translate title gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslate(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslate
|
||||
? "Translating..."
|
||||
: "Translate Title"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
|
|
@ -1511,18 +1579,36 @@ export default function FormAudioUpdate() {
|
|||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={field?.value}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Enter Title"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* English translated title appears below when available */}
|
||||
{translatedTitle && (
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Version
|
||||
</Label>
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={translatedTitle}
|
||||
onChange={(e) => setTranslatedTitle(e.target.value)}
|
||||
placeholder="Translated Title"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.title.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full space-y-2">
|
||||
<Label>{t("category", { defaultValue: "Category" })}</Label>
|
||||
|
|
@ -1565,6 +1651,84 @@ export default function FormAudioUpdate() {
|
|||
</div>
|
||||
|
||||
<div className="py-3 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslate(true);
|
||||
const res = await translateText({
|
||||
text: getValues("description"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text || "";
|
||||
// Overwrite data.description but still show both
|
||||
setTranslatedContent(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
close();
|
||||
console.error("Translate gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslate(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslate
|
||||
? "Translating..."
|
||||
: "Translate Description"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Editor Bahasa Indonesia */}
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
Indonesian Version
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<CustomEditor
|
||||
onChange={field.onChange}
|
||||
initialData={field.value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* English translated version muncul setelah translate */}
|
||||
{translatedContent && (
|
||||
<div className="mt-5">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Version
|
||||
</Label>
|
||||
<CustomEditor
|
||||
onChange={(val: any) => setTranslatedContent(val)}
|
||||
initialData={translatedContent}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.description?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* <div className="py-3 space-y-2">
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
|
|
@ -1580,7 +1744,7 @@ export default function FormAudioUpdate() {
|
|||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>
|
||||
{t("select-file", { defaultValue: "Select File" })}
|
||||
|
|
@ -1641,17 +1805,19 @@ export default function FormAudioUpdate() {
|
|||
<div className="flex-grow">
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
||||
/>
|
||||
</svg>{" "}
|
||||
<p className="font-medium">{file.fileName}</p>
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
||||
/>
|
||||
</svg>{" "}
|
||||
<p className="font-medium">
|
||||
{file.fileName}
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={file.url}
|
||||
|
|
@ -1663,7 +1829,7 @@ export default function FormAudioUpdate() {
|
|||
defaultValue: "View File",
|
||||
})}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-md p-4 border">
|
||||
{/* Checkbox Tingkat Utama */}
|
||||
<div className="space-y-4">
|
||||
|
|
@ -1709,276 +1875,290 @@ export default function FormAudioUpdate() {
|
|||
>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Detail Wilayah */}
|
||||
{fileUnitSelections[index]?.wilayah && isDetailOfRegionShowed && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
{fileUnitSelections[index]?.wilayah &&
|
||||
isDetailOfRegionShowed && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{ key: "satker", label: "SATKER" },
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{
|
||||
key: "satker",
|
||||
label: "SATKER",
|
||||
},
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan SATKER
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map(
|
||||
(polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
polda.id
|
||||
)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(polda.id)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{/* Tombol expand hanya untuk SATKER POLRI */}
|
||||
{polda.name === "SATKER POLRI" && polda.subDestination && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id
|
||||
);
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Sub-items hanya untuk SATKER POLRI */}
|
||||
{polda.name === "SATKER POLRI" && polda.subDestination &&
|
||||
expandedPolda[
|
||||
polda.id
|
||||
] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(
|
||||
sub: any
|
||||
) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
)
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan
|
||||
SATKER
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map(
|
||||
(polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
polda.id
|
||||
)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(
|
||||
polda.id
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{/* Tombol expand hanya untuk SATKER POLRI */}
|
||||
{polda.name ===
|
||||
"SATKER POLRI" &&
|
||||
polda.subDestination && (
|
||||
<button
|
||||
onClick={(
|
||||
e
|
||||
) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
polda
|
||||
)
|
||||
}
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal
|
||||
Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih
|
||||
Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(
|
||||
sub: any
|
||||
) => (
|
||||
<Label
|
||||
key={
|
||||
sub.id
|
||||
}
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sub-items hanya untuk SATKER POLRI */}
|
||||
{polda.name ===
|
||||
"SATKER POLRI" &&
|
||||
polda.subDestination &&
|
||||
expandedPolda[
|
||||
polda.id
|
||||
] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(
|
||||
sub: any
|
||||
) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
) ||
|
||||
false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
)
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
Number(sub.id)
|
||||
polda
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{
|
||||
sub.name
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal
|
||||
Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih
|
||||
Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(
|
||||
sub: any
|
||||
) => (
|
||||
<Label
|
||||
key={
|
||||
sub.id
|
||||
}
|
||||
</span>
|
||||
</Label>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>Simpan</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
) ||
|
||||
false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{
|
||||
sub.name
|
||||
}
|
||||
</span>
|
||||
</Label>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>Simpan</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -34,6 +34,7 @@ import {
|
|||
createMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
uploadThumbnail,
|
||||
} from "@/service/content/content";
|
||||
import { uploadThumbnailBlog } from "@/service/blog/blog";
|
||||
|
|
@ -81,7 +82,7 @@ const CustomEditor = dynamic(
|
|||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
export default function FormImage() {
|
||||
|
|
@ -112,12 +113,11 @@ export default function FormImage() {
|
|||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
|
||||
const [localContent, setLocalContent] = useState("");
|
||||
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
|
||||
const [articleIds, setArticleIds] = useState<string[]>([]);
|
||||
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
|
||||
const [articleBody, setArticleBody] = useState<string>("");
|
||||
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
|
||||
const [selectedWritingStyle, setSelectedWritingStyle] =
|
||||
|
|
@ -149,9 +149,14 @@ export default function FormImage() {
|
|||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
const [filesTemp, setFilesTemp] = useState<File[]>([]);
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
|
||||
const [translatedContent, setTranslatedContent] = React.useState("");
|
||||
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
||||
|
||||
// 🔹 state untuk translate judul
|
||||
const [translatedTitle, setTranslatedTitle] = useState("");
|
||||
const [isLoadingTranslateTitle, setIsLoadingTranslateTitle] = useState(false);
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
{ id: "5", label: "UMUM" },
|
||||
|
|
@ -178,17 +183,17 @@ export default function FormImage() {
|
|||
.filter(
|
||||
(file) =>
|
||||
["image/jpeg", "image/png", "image/jpg"].includes(file.type) &&
|
||||
file.size <= MAX_FILE_SIZE
|
||||
file.size <= MAX_FILE_SIZE,
|
||||
)
|
||||
.map((file) =>
|
||||
Object.assign(file, {
|
||||
preview: URL.createObjectURL(file),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
if (validFiles.length === 0) {
|
||||
toast.error(
|
||||
"File tidak valid. Hanya .jpg, .jpeg, .png maksimal 100MB yang diperbolehkan."
|
||||
"File tidak valid. Hanya .jpg, .jpeg, .png maksimal 100MB yang diperbolehkan.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -211,6 +216,7 @@ export default function FormImage() {
|
|||
title: z.string().min(1, { message: t("titleRequired") }),
|
||||
description: z.string().optional(),
|
||||
descriptionOri: z.string().optional(),
|
||||
htmlDescription: z.string().optional(),
|
||||
rewriteDescription: z.string().optional(),
|
||||
creatorName: z.string().min(1, { message: t("creatorRequired") }),
|
||||
files: z
|
||||
|
|
@ -221,12 +227,12 @@ export default function FormImage() {
|
|||
files.every(
|
||||
(file: File) =>
|
||||
["image/jpeg", "image/png", "image/jpg"].includes(file.type) &&
|
||||
file.size <= 100 * 1024 * 1024
|
||||
file.size <= 100 * 1024 * 1024,
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Hanya file .jpg, .jpeg, .png, maksimal 100MB yang diperbolehkan.",
|
||||
}
|
||||
},
|
||||
),
|
||||
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
|
||||
tags: z
|
||||
|
|
@ -448,9 +454,10 @@ export default function FormImage() {
|
|||
const articleData = await waitForStatusUpdate();
|
||||
const cleanArticleBody = articleData?.articleBody?.replace(
|
||||
/<img[^>]*>/g,
|
||||
""
|
||||
"",
|
||||
);
|
||||
const articleImagesData = articleData?.imagesUrl?.split(",");
|
||||
setArticleBody(cleanArticleBody || "");
|
||||
setValue("description", cleanArticleBody || "");
|
||||
setDetailData(articleData);
|
||||
setSelectedArticleId(id);
|
||||
|
|
@ -492,7 +499,7 @@ export default function FormImage() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data.data.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -500,7 +507,7 @@ export default function FormImage() {
|
|||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
o.name.toLowerCase().includes("pers rilis")
|
||||
o.name.toLowerCase().includes("pers rilis"),
|
||||
);
|
||||
|
||||
if (findCategory) {
|
||||
|
|
@ -523,7 +530,7 @@ export default function FormImage() {
|
|||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
.map((opt: any) => opt.id)
|
||||
.map((opt: any) => opt.id),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -554,39 +561,59 @@ export default function FormImage() {
|
|||
}
|
||||
|
||||
const finalTags = tags.join(", ");
|
||||
const finalTitle = isSwitchOn ? title : data.title;
|
||||
// const finalDescription = articleBody || data.description;
|
||||
const finalDescription = isSwitchOn
|
||||
? data.description
|
||||
: selectedFileType === "rewrite"
|
||||
? data.rewriteDescription
|
||||
: data.descriptionOri;
|
||||
// const finalTitle = isSwitchOn ? title : data.title;
|
||||
let finalTitle = isSwitchOn ? title : data.title;
|
||||
|
||||
// ✅ Jika sudah translate judul → kirim versi English
|
||||
if (translatedTitle && translatedTitle.trim() !== "") {
|
||||
finalTitle = translatedTitle;
|
||||
console.log("📤 Upload Title versi Inggris (translate)");
|
||||
}
|
||||
|
||||
// pilih description dasar
|
||||
// let finalDescription = isSwitchOn
|
||||
// ? data.description
|
||||
// : selectedFileType === "rewrite"
|
||||
// ? data.rewriteDescription
|
||||
// : data.descriptionOri;
|
||||
|
||||
// 👉 tempelkan hasil translate ke field form agar ikut terkirim
|
||||
// if (translatedContent) {
|
||||
// data.descriptionOri = translatedContent; // versi Inggris yang dikirim
|
||||
// } else {
|
||||
// data.descriptionOri = getValues("descriptionOri"); // fallback IDN
|
||||
// }
|
||||
|
||||
// ✅ Tentukan deskripsi final yang akan dikirim
|
||||
let finalDescription = "";
|
||||
|
||||
if (translatedContent && translatedContent.trim() !== "") {
|
||||
// jika user sudah translate → kirim versi Inggris
|
||||
finalDescription = translatedContent;
|
||||
console.log("📤 Upload versi Inggris (translate)");
|
||||
} else if (data.rewriteDescription && selectedFileType === "rewrite") {
|
||||
finalDescription = data.rewriteDescription;
|
||||
console.log("📤 Upload versi rewrite");
|
||||
} else if (data.description?.trim()) {
|
||||
finalDescription = data.description;
|
||||
} else if (data.descriptionOri?.trim()) {
|
||||
finalDescription = data.descriptionOri;
|
||||
}
|
||||
// else {
|
||||
// // fallback: gunakan versi Indonesia original
|
||||
// finalDescription = data.descriptionOri ?? "";
|
||||
// console.log("📤 Upload versi Indonesia (original)");
|
||||
// }
|
||||
|
||||
if (!finalDescription?.trim()) {
|
||||
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
let requestData: {
|
||||
title: string;
|
||||
description: string;
|
||||
htmlDescription: string;
|
||||
fileTypeId: string;
|
||||
categoryId: any;
|
||||
subCategoryId: any;
|
||||
uploadedBy: string;
|
||||
statusId: string;
|
||||
publishedFor: string;
|
||||
creatorName: string;
|
||||
tags: string;
|
||||
isYoutube: boolean;
|
||||
isInternationalMedia: boolean;
|
||||
attachFromScheduleId?: number;
|
||||
} = {
|
||||
...data,
|
||||
let requestData: any = {
|
||||
title: finalTitle,
|
||||
description: htmlToString(finalDescription),
|
||||
htmlDescription: finalDescription,
|
||||
description: htmlToString(finalDescription), // versi plain text
|
||||
htmlDescription: finalDescription, // versi HTML
|
||||
fileTypeId,
|
||||
categoryId: selectedCategory,
|
||||
subCategoryId: selectedCategory,
|
||||
|
|
@ -594,11 +621,43 @@ export default function FormImage() {
|
|||
statusId: "1",
|
||||
publishedFor: publishedFor.join(","),
|
||||
creatorName: data.creatorName,
|
||||
tags: finalTags,
|
||||
tags: tags.join(", "),
|
||||
isYoutube: false,
|
||||
isInternationalMedia: false,
|
||||
};
|
||||
|
||||
// let requestData: {
|
||||
// title: string;
|
||||
// description: string;
|
||||
// htmlDescription: string;
|
||||
// fileTypeId: string;
|
||||
// categoryId: any;
|
||||
// subCategoryId: any;
|
||||
// uploadedBy: string;
|
||||
// statusId: string;
|
||||
// publishedFor: string;
|
||||
// creatorName: string;
|
||||
// tags: string;
|
||||
// isYoutube: boolean;
|
||||
// isInternationalMedia: boolean;
|
||||
// attachFromScheduleId?: number;
|
||||
// } = {
|
||||
// ...data,
|
||||
// title: finalTitle,
|
||||
// description: htmlToString(finalDescription), // plain text
|
||||
// htmlDescription: finalDescription, // versi HTML
|
||||
// fileTypeId,
|
||||
// categoryId: selectedCategory,
|
||||
// subCategoryId: selectedCategory,
|
||||
// uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
// statusId: "1",
|
||||
// publishedFor: publishedFor.join(","),
|
||||
// creatorName: data.creatorName,
|
||||
// tags: finalTags,
|
||||
// isYoutube: false,
|
||||
// isInternationalMedia: false,
|
||||
// };
|
||||
|
||||
let id = Cookies.get("idCreate");
|
||||
|
||||
if (scheduleId !== undefined) {
|
||||
|
|
@ -611,6 +670,7 @@ export default function FormImage() {
|
|||
|
||||
Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
||||
id = response?.data?.data;
|
||||
|
||||
const formMedia = new FormData();
|
||||
const thumbnail = files[0];
|
||||
formMedia.append("file", thumbnail);
|
||||
|
|
@ -620,6 +680,7 @@ export default function FormImage() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const progressInfoArr = files.map((item) => ({
|
||||
percentage: 0,
|
||||
fileName: item.name,
|
||||
|
|
@ -628,13 +689,13 @@ export default function FormImage() {
|
|||
setIsStartUpload(true);
|
||||
setProgressList(progressInfoArr);
|
||||
|
||||
close();
|
||||
// close();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
String(id),
|
||||
item,
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -661,7 +722,7 @@ export default function FormImage() {
|
|||
idx: number,
|
||||
id: string,
|
||||
file: any,
|
||||
duration: string
|
||||
duration: string,
|
||||
) {
|
||||
console.log(idx, id, file, duration);
|
||||
|
||||
|
|
@ -697,7 +758,7 @@ export default function FormImage() {
|
|||
onChunkComplete: (
|
||||
chunkSize: any,
|
||||
bytesAccepted: any,
|
||||
bytesTotal: any
|
||||
bytesTotal: any,
|
||||
) => {
|
||||
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
progressInfo[idx].percentage = uploadPersen;
|
||||
|
|
@ -739,6 +800,7 @@ export default function FormImage() {
|
|||
}
|
||||
if (counter == progressInfo.length) {
|
||||
setIsStartUpload(false);
|
||||
close();
|
||||
// hideProgress();
|
||||
Cookies.remove("idCreate");
|
||||
successSubmit("/in/contributor/content/image");
|
||||
|
|
@ -868,8 +930,84 @@ export default function FormImage() {
|
|||
{t("form-image", { defaultValue: "Form Image" })}
|
||||
</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<div className="py-3 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslateTitle(true);
|
||||
const res = await translateText({
|
||||
text: getValues("title"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text || "";
|
||||
setTranslatedTitle(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Translate title gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslateTitle(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslateTitle
|
||||
? "Translating..."
|
||||
: "Translate to English"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{/* Title Indonesia */}
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
Indonesian Title
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukkan Judul Bahasa Indonesia"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Title English (hanya muncul setelah translate) */}
|
||||
{translatedTitle && (
|
||||
<div className="mt-4">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Title
|
||||
</Label>
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={translatedTitle}
|
||||
onChange={(e) => setTranslatedTitle(e.target.value)}
|
||||
placeholder="English version"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="space-y-2 py-3">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -889,7 +1027,7 @@ export default function FormImage() {
|
|||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* <div className="flex items-center">
|
||||
<div className="py-3 space-y-2 w-full">
|
||||
|
|
@ -1220,18 +1358,11 @@ export default function FormImage() {
|
|||
<RadioGroup
|
||||
onValueChange={(value) => setSelectedFileType(value)}
|
||||
value={selectedFileType}
|
||||
className=" grid-cols-1"
|
||||
className="grid-cols-1"
|
||||
>
|
||||
<div className="">
|
||||
<RadioGroupItem value="original" id="original-file" />
|
||||
<Label htmlFor="original-file">
|
||||
Select Original Description
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="py-3 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>
|
||||
<Label className="text-[15px]">
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
|
||||
|
|
@ -1252,7 +1383,6 @@ export default function FormImage() {
|
|||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text ||
|
||||
"";
|
||||
|
||||
setTranslatedContent(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -1271,156 +1401,45 @@ export default function FormImage() {
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pilihan bahasa untuk posting */}
|
||||
{roleId === "14" && (
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value="id"
|
||||
checked={selectedLang === "id"}
|
||||
onChange={() => setSelectedLang("id")}
|
||||
/>
|
||||
<span>Gunakan Bahasa Indonesia</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Editor Bahasa Indonesia */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
initialData={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Editor Bahasa Inggris */}
|
||||
{translatedContent && (
|
||||
<div className="mt-4">
|
||||
<div className="flex flex-col">
|
||||
<Label className="text-[15px]">
|
||||
English Version
|
||||
</Label>{" "}
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value="en"
|
||||
checked={selectedLang === "en"}
|
||||
onChange={() => setSelectedLang("en")}
|
||||
disabled={!translatedContent} // kalau belum translate, disable
|
||||
{/* Editor side-by-side */}
|
||||
<div className="flex flex-col gap-3 mt-3">
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
Indonesian Version
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
initialData={value}
|
||||
/>
|
||||
<span>Gunakan Bahasa Inggris</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<CustomEditor
|
||||
onChange={(val: any) => setTranslatedContent(val)}
|
||||
initialData={translatedContent}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{translatedContent && (
|
||||
<div className="mt-5">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Version
|
||||
</Label>
|
||||
<CustomEditor
|
||||
onChange={(val: any) => setTranslatedContent(val)}
|
||||
initialData={translatedContent}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{errors.description?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* <div className="py-3 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslate(true);
|
||||
const res = await translateText({
|
||||
text: getValues("descriptionOri"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
console.log("RRRR", res);
|
||||
|
||||
if (!res.error) {
|
||||
// setLocalContent(res.data.translatedText);
|
||||
setValue(
|
||||
"descriptionOri",
|
||||
res?.data?.data?.translations[0]?.text || ""
|
||||
);
|
||||
// setEditorContent(res.data.translatedText);
|
||||
}
|
||||
} catch (err) {
|
||||
close();
|
||||
console.error("Translate gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslate(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslate
|
||||
? "Translating..."
|
||||
: "Translate to English"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CustomEditor
|
||||
onChange={(val: any) => {
|
||||
onChange(val);
|
||||
// setLocalContent(val);
|
||||
// setEditorContent(val);
|
||||
}}
|
||||
initialData={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{errors.description?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div> */}
|
||||
{/* <div className="py-3 space-y-2">
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CustomEditor
|
||||
onChange={(value: any) => {
|
||||
onChange(value);
|
||||
setEditorContent(value);
|
||||
}}
|
||||
initialData={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.description?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div> */}
|
||||
<p className="text-sm font-semibold">Content Rewrite</p>
|
||||
<p className="text-sm font-semibold mt-3">
|
||||
Content Rewrite
|
||||
</p>
|
||||
<div className="my-2">
|
||||
<Button
|
||||
size="sm"
|
||||
|
|
@ -1431,6 +1450,7 @@ export default function FormImage() {
|
|||
Content Rewrite
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showRewriteEditor && (
|
||||
<div>
|
||||
{isGeneratedArticle && (
|
||||
|
|
@ -1442,7 +1462,7 @@ export default function FormImage() {
|
|||
className={`mr-3 px-3 py-2 rounded-md ${
|
||||
selectedArticleId === id
|
||||
? "bg-green-500 text-white"
|
||||
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
|
||||
: "border-2 border-green-500 bg-white text-green-500 hover:bg-green-500 hover:text-white hover:border-green-500"
|
||||
}`}
|
||||
onClick={() => handleArticleIdClick(id)}
|
||||
>
|
||||
|
|
@ -1451,12 +1471,6 @@ export default function FormImage() {
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center space-x-2 mt-3">
|
||||
<RadioGroupItem value="rewrite" id="rewrite-file" />
|
||||
<Label htmlFor="rewrite-file">
|
||||
Select Description Rewrite
|
||||
</Label>
|
||||
</div>
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>
|
||||
{t("file-rewrite", {
|
||||
|
|
@ -1490,6 +1504,7 @@ export default function FormImage() {
|
|||
</RadioGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="py-3 space-y-2">
|
||||
<Label>
|
||||
{t("select-file", { defaultValue: "Select File" })}
|
||||
|
|
@ -1726,7 +1741,7 @@ export default function FormImage() {
|
|||
{/* <Button type="submit" color="primary">
|
||||
{t("submit", { defaultValue: "Submit" })}
|
||||
</Button> */}
|
||||
{levelNumber !== "2" && levelNumber !== "3" && (
|
||||
{levelNumber !== "2" && (
|
||||
<Button type="submit" color="primary">
|
||||
{t("submit", { defaultValue: "Submit" })}
|
||||
</Button>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -27,6 +27,7 @@ import {
|
|||
createMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
rejectFiles,
|
||||
submitApproval,
|
||||
} from "@/service/content/content";
|
||||
|
|
@ -113,7 +114,7 @@ const ViewEditor = dynamic(
|
|||
() => {
|
||||
return import("@/components/editor/view-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
interface Destination {
|
||||
|
|
@ -190,6 +191,12 @@ export default function FormTeksDetail() {
|
|||
satker: boolean;
|
||||
}>
|
||||
>([]);
|
||||
const [creatorLevelNumber, setCreatorLevelNumber] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const isContentFromSatker = creatorLevelNumber === 3;
|
||||
const isContentFromMabesOrPolda =
|
||||
creatorLevelNumber === 1 || creatorLevelNumber === 2;
|
||||
|
||||
useEffect(() => {
|
||||
if (Number(userLevelId) === 216 && Number(roleId) === 3) {
|
||||
|
|
@ -201,7 +208,7 @@ export default function FormTeksDetail() {
|
|||
const handleFileUnitChange = (
|
||||
fileIndex: number,
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
value: boolean,
|
||||
) => {
|
||||
setFileUnitSelections((prev) => {
|
||||
const newSelections = [...prev];
|
||||
|
|
@ -221,7 +228,7 @@ export default function FormTeksDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[fileIndex] || new Set()
|
||||
newArray[fileIndex] || new Set(),
|
||||
);
|
||||
|
||||
if (value) {
|
||||
|
|
@ -252,12 +259,12 @@ export default function FormTeksDetail() {
|
|||
(item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
currentFileCheckedLevels.has(Number(item.id))
|
||||
currentFileCheckedLevels.has(Number(item.id)),
|
||||
);
|
||||
|
||||
if (!hasSelectedPolda) {
|
||||
alert(
|
||||
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."
|
||||
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES.",
|
||||
);
|
||||
return prev; // Batalkan perubahan
|
||||
}
|
||||
|
|
@ -296,7 +303,7 @@ export default function FormTeksDetail() {
|
|||
useEffect(() => {
|
||||
if (detail?.assignedToTopLevel) {
|
||||
const outputSet = new Set(
|
||||
detail.assignedToTopLevel.split(",").map(Number)
|
||||
detail.assignedToTopLevel.split(",").map(Number),
|
||||
);
|
||||
setUnitSelection({
|
||||
semua: outputSet.has(0),
|
||||
|
|
@ -321,7 +328,7 @@ export default function FormTeksDetail() {
|
|||
acc[polda.id] = false;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
{},
|
||||
);
|
||||
setExpandedPolda(initialExpandedState);
|
||||
console.log("polres", initialExpandedState);
|
||||
|
|
@ -356,7 +363,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
value: boolean,
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
|
|
@ -435,7 +442,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -449,7 +456,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -457,7 +464,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
o.name.toLowerCase().includes("pers rilis")
|
||||
o.name.toLowerCase().includes("pers rilis"),
|
||||
);
|
||||
|
||||
if (findCategory) {
|
||||
|
|
@ -506,6 +513,9 @@ export default function FormTeksDetail() {
|
|||
console.log("detail", details);
|
||||
setFiles(details?.files);
|
||||
setDetail(details);
|
||||
if (details?.uploadedBy?.userLevel?.levelNumber) {
|
||||
setCreatorLevelNumber(details.uploadedBy.userLevel.levelNumber);
|
||||
}
|
||||
setMain({
|
||||
type: details?.fileType.name,
|
||||
url: details?.files[0]?.url,
|
||||
|
|
@ -516,14 +526,14 @@ export default function FormTeksDetail() {
|
|||
|
||||
if (details?.assignedToLevel) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
details.assignedToLevel.split(",").map(Number),
|
||||
);
|
||||
setCheckedLevels(levels);
|
||||
}
|
||||
|
||||
if (details?.publishedForObject) {
|
||||
const publisherIds = details?.publishedForObject?.map(
|
||||
(obj: any) => obj.id
|
||||
(obj: any) => obj.id,
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
}
|
||||
|
|
@ -695,7 +705,7 @@ export default function FormTeksDetail() {
|
|||
type: string,
|
||||
url: string,
|
||||
names: string,
|
||||
format: string
|
||||
format: string,
|
||||
) => {
|
||||
console.log("Test 3 :", type, url, names, format);
|
||||
setMain({
|
||||
|
|
@ -722,7 +732,7 @@ export default function FormTeksDetail() {
|
|||
const setupPlacement = (
|
||||
index: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
checked: boolean,
|
||||
) => {
|
||||
let temp = [...filePlacements];
|
||||
if (checked) {
|
||||
|
|
@ -733,7 +743,7 @@ export default function FormTeksDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[index] || new Set()
|
||||
newArray[index] || new Set(),
|
||||
);
|
||||
|
||||
// Checklist semua item di modal
|
||||
|
|
@ -783,7 +793,7 @@ export default function FormTeksDetail() {
|
|||
// Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist
|
||||
// JANGAN include satker dalam perhitungan auto "all"
|
||||
const nonSatkerItems = now.filter(
|
||||
(item) => item !== "satker" && item !== "all"
|
||||
(item) => item !== "satker" && item !== "all",
|
||||
);
|
||||
if (nonSatkerItems.length === 3 && !now.includes("all")) {
|
||||
now.push("all");
|
||||
|
|
@ -798,7 +808,7 @@ export default function FormTeksDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[index] || new Set()
|
||||
newArray[index] || new Set(),
|
||||
);
|
||||
|
||||
// Unchecklist semua item di modal
|
||||
|
|
@ -832,7 +842,7 @@ export default function FormTeksDetail() {
|
|||
// Hapus "all" jika tidak semua item ter-checklist
|
||||
if (now.includes("all")) {
|
||||
const nonSatkerItems = now.filter(
|
||||
(item) => item !== "satker" && item !== "all"
|
||||
(item) => item !== "satker" && item !== "all",
|
||||
);
|
||||
if (nonSatkerItems.length < 3) {
|
||||
const newData = now.filter((b) => b !== "all");
|
||||
|
|
@ -850,7 +860,7 @@ export default function FormTeksDetail() {
|
|||
const updateModalChecklistLevels = (
|
||||
fileIndex: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
checked: boolean,
|
||||
) => {
|
||||
if (!listDest || listDest.length === 0) return;
|
||||
|
||||
|
|
@ -883,7 +893,7 @@ export default function FormTeksDetail() {
|
|||
} else if (placement === "satker") {
|
||||
// Checklist SATKER POLRI dan semua sub-item di bawahnya
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.add(Number(satkerItem.id));
|
||||
|
|
@ -919,7 +929,7 @@ export default function FormTeksDetail() {
|
|||
} else if (placement === "satker") {
|
||||
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.delete(Number(satkerItem.id));
|
||||
|
|
@ -952,7 +962,7 @@ export default function FormTeksDetail() {
|
|||
// Fungsi untuk mengupdate checklist levels untuk file tertentu
|
||||
const handleFileCheckboxChangePlacement = (
|
||||
fileIndex: number,
|
||||
levelId: number
|
||||
levelId: number,
|
||||
) => {
|
||||
setFileCheckedLevels((prev) => {
|
||||
const newArray = [...prev];
|
||||
|
|
@ -964,7 +974,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
|
||||
const poldaItem = listDest.find(
|
||||
(item: any) => Number(item.id) === levelId
|
||||
(item: any) => Number(item.id) === levelId,
|
||||
) as any;
|
||||
if (
|
||||
poldaItem &&
|
||||
|
|
@ -987,7 +997,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => Number(item.id) === levelId
|
||||
(item: any) => Number(item.id) === levelId,
|
||||
) as any;
|
||||
if (satkerItem && satkerItem.name === "SATKER POLRI") {
|
||||
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
|
||||
|
|
@ -1018,7 +1028,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
// Hitung total POLDA yang ada (bukan SATKER POLRI)
|
||||
const totalPoldaCount = listDest.filter(
|
||||
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
|
||||
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI",
|
||||
).length;
|
||||
|
||||
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
|
||||
|
|
@ -1045,7 +1055,7 @@ export default function FormTeksDetail() {
|
|||
}
|
||||
return total;
|
||||
},
|
||||
0
|
||||
0,
|
||||
);
|
||||
|
||||
// Hitung berapa banyak POLRES yang ter-checklist
|
||||
|
|
@ -1055,7 +1065,7 @@ export default function FormTeksDetail() {
|
|||
return (
|
||||
total +
|
||||
item.subDestination.filter((sub: any) =>
|
||||
currentFileLevels.has(Number(sub.id))
|
||||
currentFileLevels.has(Number(sub.id)),
|
||||
).length
|
||||
);
|
||||
}
|
||||
|
|
@ -1064,7 +1074,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
// Cek apakah SATKER POLRI ter-checklist
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
const isSatkerChecked =
|
||||
satkerItem && currentFileLevels.has(Number(satkerItem.id));
|
||||
|
|
@ -1098,7 +1108,7 @@ export default function FormTeksDetail() {
|
|||
|
||||
// Cek apakah semua sub-items sudah ter-checklist
|
||||
const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
|
||||
currentFileLevels.has(Number(sub.id))
|
||||
currentFileLevels.has(Number(sub.id)),
|
||||
);
|
||||
|
||||
if (allSubItemsChecked) {
|
||||
|
|
@ -1181,7 +1191,7 @@ export default function FormTeksDetail() {
|
|||
{detail &&
|
||||
!categories.find(
|
||||
(cat) =>
|
||||
String(cat.id) === String(detail.category.id)
|
||||
String(cat.id) === String(detail.category.id),
|
||||
) && (
|
||||
<SelectItem
|
||||
key={String(detail.category.id)}
|
||||
|
|
@ -1464,7 +1474,7 @@ export default function FormTeksDetail() {
|
|||
Tingkat Distribusi:
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{/* {[
|
||||
{ key: "semua", label: "Semua" },
|
||||
{ key: "nasional", label: "Nasional" },
|
||||
{ key: "wilayah", label: "Wilayah" },
|
||||
|
|
@ -1472,6 +1482,39 @@ export default function FormTeksDetail() {
|
|||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
] */}
|
||||
{[
|
||||
{ key: "semua", label: "Semua" },
|
||||
|
||||
...(isContentFromMabesOrPolda
|
||||
? [
|
||||
{
|
||||
key: "nasional",
|
||||
label: "Nasional",
|
||||
},
|
||||
{
|
||||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
{
|
||||
key: "wilayah",
|
||||
label: "Wilayah",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(isContentFromSatker
|
||||
? [
|
||||
{
|
||||
key: "nasional",
|
||||
label: "Nasional",
|
||||
},
|
||||
{
|
||||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
|
|
@ -1488,12 +1531,12 @@ export default function FormTeksDetail() {
|
|||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
value as boolean,
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
Boolean(value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -1509,251 +1552,262 @@ export default function FormTeksDetail() {
|
|||
</div>
|
||||
|
||||
{/* Detail Wilayah */}
|
||||
{fileUnitSelections[index]?.wilayah && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
{/* {fileUnitSelections[index]?.wilayah && ( */}
|
||||
{!isContentFromSatker &&
|
||||
fileUnitSelections[index]?.wilayah && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{ key: "polres", label: "POLRES" },
|
||||
{ key: "satker", label: "SATKER" },
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{ key: "polres", label: "POLRES" },
|
||||
{ key: "satker", label: "SATKER" },
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean,
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan POLRES
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map((polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(polda.id)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(polda.id)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{polda.subDestination && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id
|
||||
);
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Sub-items */}
|
||||
{polda.subDestination &&
|
||||
expandedPolda[polda.id] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(sub: any) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
)
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
polda
|
||||
)
|
||||
}
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={12}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={12}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan
|
||||
POLRES
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map((polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(polda.id),
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(polda.id),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{polda.subDestination && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id,
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(sub: any) => (
|
||||
<Label
|
||||
key={sub.id}
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sub-items */}
|
||||
{polda.subDestination &&
|
||||
expandedPolda[
|
||||
polda.id
|
||||
] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(sub: any) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
sub.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
polda,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{sub.name}
|
||||
</span>
|
||||
</Label>
|
||||
)
|
||||
)}
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal
|
||||
Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih
|
||||
Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(sub: any) => (
|
||||
<Label
|
||||
key={sub.id}
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id,
|
||||
),
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(
|
||||
sub.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{sub.name}
|
||||
</span>
|
||||
</Label>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>
|
||||
{/* {t("save", {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>
|
||||
{/* {t("save", {
|
||||
defaultValue: "Simpan",
|
||||
})} */}
|
||||
Simpan
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
Simpan
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -1832,7 +1886,7 @@ export default function FormTeksDetail() {
|
|||
>
|
||||
{template}
|
||||
</Button>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1863,7 +1917,72 @@ export default function FormTeksDetail() {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</Card>
|
||||
{Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
{Number(roleId) === 14 ? (
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<Button
|
||||
onClick={() => actionApproval("2")}
|
||||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" />
|
||||
{t("accept", { defaultValue: "Accept" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision", { defaultValue: "Revision" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
color="destructive"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
{t("reject", { defaultValue: "Reject" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : (Number(detail?.needApprovalFromLevel) ===
|
||||
Number(userLevelId) ||
|
||||
(detail?.isInternationalMedia === true &&
|
||||
detail?.isForwardFromNational === true &&
|
||||
Number(detail?.statusId) === 1)) &&
|
||||
(Number(detail?.uploadedById) === Number(userId) ||
|
||||
Number(detail?.needApprovalFromLevel) ===
|
||||
Number(userLevelId)) ? (
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<Button
|
||||
onClick={() => actionApproval("2")}
|
||||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" />
|
||||
{t("accept", { defaultValue: "Accept" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision", { defaultValue: "Revision" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
color="destructive"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
{t("reject", { defaultValue: "Reject" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{/* {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
Number(detail?.uploadedById) == Number(userId) ? (
|
||||
""
|
||||
) : (
|
||||
|
|
@ -1896,7 +2015,7 @@ export default function FormTeksDetail() {
|
|||
)
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -26,14 +26,13 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
|
||||
import { register } from "module";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
uploadThumbnail,
|
||||
} from "@/service/content/content";
|
||||
import { uploadThumbnailBlog } from "@/service/blog/blog";
|
||||
|
|
@ -140,13 +139,12 @@ export default function FormTeks() {
|
|||
let uploadPersen = 0;
|
||||
const [isStartUpload, setIsStartUpload] = useState(false);
|
||||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
|
||||
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
|
||||
const [translatedContent, setTranslatedContent] = React.useState("");
|
||||
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
||||
const [translatedTitle, setTranslatedTitle] = useState("");
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
|
|
@ -484,7 +482,7 @@ export default function FormTeks() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -542,6 +540,102 @@ export default function FormTeks() {
|
|||
}
|
||||
}, [articleBody, setValue]);
|
||||
|
||||
// const save = async (data: TeksSchema) => {
|
||||
// loading();
|
||||
|
||||
// if (files.length === 0) {
|
||||
// MySwal.fire("Error", "Minimal 1 file harus diunggah.", "error");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const finalTags = tags.join(", ");
|
||||
// const finalTitle = isSwitchOn ? title : data.title;
|
||||
// // const finalDescription = articleBody || data.description;
|
||||
// const finalDescription = isSwitchOn
|
||||
// ? data.description
|
||||
// : selectedFileType === "rewrite"
|
||||
// ? data.rewriteDescription
|
||||
// : data.descriptionOri;
|
||||
|
||||
// if (!finalDescription?.trim()) {
|
||||
// MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// let requestData: {
|
||||
// title: string;
|
||||
// description: string;
|
||||
// htmlDescription: string;
|
||||
// fileTypeId: string;
|
||||
// categoryId: any;
|
||||
// subCategoryId: any;
|
||||
// uploadedBy: string;
|
||||
// statusId: string;
|
||||
// publishedFor: string;
|
||||
// creatorName: string;
|
||||
// tags: string;
|
||||
// isYoutube: boolean;
|
||||
// isInternationalMedia: boolean;
|
||||
// attachFromScheduleId?: number;
|
||||
// } = {
|
||||
// ...data,
|
||||
// title: finalTitle,
|
||||
// description: htmlToString(finalDescription),
|
||||
// htmlDescription: finalDescription,
|
||||
// fileTypeId,
|
||||
// categoryId: selectedCategory,
|
||||
// subCategoryId: selectedCategory,
|
||||
// uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
|
||||
// statusId: "1",
|
||||
// publishedFor: publishedFor.join(","),
|
||||
// creatorName: data.creatorName,
|
||||
// tags: finalTags,
|
||||
// isYoutube: false,
|
||||
// isInternationalMedia: false,
|
||||
// };
|
||||
|
||||
// let id = Cookies.get("idCreate");
|
||||
|
||||
// if (scheduleId !== undefined) {
|
||||
// requestData.attachFromScheduleId = Number(scheduleId);
|
||||
// }
|
||||
|
||||
// if (id == undefined) {
|
||||
// const response = await createMedia(requestData);
|
||||
// console.log("Form Data Submitted:", requestData);
|
||||
|
||||
// Cookies.set("idCreate", response?.data?.data, { expires: 1 });
|
||||
// id = response?.data?.data;
|
||||
// const formMedia = new FormData();
|
||||
// const thumbnail = files[0];
|
||||
// formMedia.append("file", thumbnail);
|
||||
// const responseThumbnail = await uploadThumbnail(id, formMedia);
|
||||
// if (responseThumbnail?.error == true) {
|
||||
// error(responseThumbnail?.message);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// const progressInfoArr = files.map((item) => ({
|
||||
// percentage: 0,
|
||||
// fileName: item.name,
|
||||
// }));
|
||||
// progressInfo = progressInfoArr;
|
||||
// setIsStartUpload(true);
|
||||
// setProgressList(progressInfoArr);
|
||||
|
||||
// close();
|
||||
// files.map(async (item: any, index: number) => {
|
||||
// await uploadResumableFile(
|
||||
// index,
|
||||
// String(id),
|
||||
// item,
|
||||
// fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
|
||||
// );
|
||||
// });
|
||||
|
||||
// Cookies.remove("idCreate");
|
||||
// };
|
||||
|
||||
const save = async (data: TeksSchema) => {
|
||||
loading();
|
||||
|
||||
|
|
@ -551,19 +645,49 @@ export default function FormTeks() {
|
|||
}
|
||||
|
||||
const finalTags = tags.join(", ");
|
||||
const finalTitle = isSwitchOn ? title : data.title;
|
||||
// const finalTitle = isSwitchOn ? title : data.title;
|
||||
// const finalDescription = articleBody || data.description;
|
||||
const finalDescription = isSwitchOn
|
||||
? data.description
|
||||
: selectedFileType === "rewrite"
|
||||
? data.rewriteDescription
|
||||
: data.descriptionOri;
|
||||
// const finalDescription = isSwitchOn
|
||||
// ? data.description
|
||||
// : selectedFileType === "rewrite"
|
||||
// ? data.rewriteDescription
|
||||
// : data.descriptionOri;
|
||||
|
||||
// if (!finalDescription?.trim()) {
|
||||
// MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
// return;
|
||||
// }
|
||||
|
||||
const finalTitle =
|
||||
translatedTitle && translatedTitle.trim() !== ""
|
||||
? translatedTitle
|
||||
: isSwitchOn
|
||||
? title
|
||||
: data.title;
|
||||
|
||||
const finalDescription =
|
||||
translatedContent && translatedContent.trim() !== ""
|
||||
? translatedContent
|
||||
: isSwitchOn
|
||||
? data.description
|
||||
: selectedFileType === "rewrite"
|
||||
? data.rewriteDescription
|
||||
: data.descriptionOri;
|
||||
|
||||
if (!finalDescription?.trim()) {
|
||||
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// 👉 tempelkan hasil translate ke field form agar ikut terkirim
|
||||
if (translatedContent) {
|
||||
data.descriptionOri = translatedContent;
|
||||
console.log(
|
||||
"🌍 Translate dimasukkan ke descriptionOri:",
|
||||
translatedContent
|
||||
);
|
||||
}
|
||||
|
||||
let requestData: {
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
@ -625,7 +749,7 @@ export default function FormTeks() {
|
|||
setIsStartUpload(true);
|
||||
setProgressList(progressInfoArr);
|
||||
|
||||
close();
|
||||
// close();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(
|
||||
index,
|
||||
|
|
@ -737,6 +861,7 @@ export default function FormTeks() {
|
|||
}
|
||||
if (counter == progressInfo.length) {
|
||||
setIsStartUpload(false);
|
||||
close();
|
||||
// hideProgress();
|
||||
Cookies.remove("idCreate");
|
||||
successSubmit("/in/contributor/content/teks");
|
||||
|
|
@ -869,6 +994,80 @@ export default function FormTeks() {
|
|||
<div className="gap-5 mb-5">
|
||||
{/* Input Title */}
|
||||
<div className="space-y-2 py-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslate(true);
|
||||
const res = await translateText({
|
||||
text: getValues("title"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text || "";
|
||||
setTranslatedTitle(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
close();
|
||||
console.error("Translate title gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslate(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslate
|
||||
? "Translating..."
|
||||
: "Translate Title"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Title Bahasa Indonesia */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukkan Judul Bahasa Indonesia"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Versi English (muncul setelah translate) */}
|
||||
{translatedTitle && (
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Title
|
||||
</Label>
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={translatedTitle}
|
||||
onChange={(e) => setTranslatedTitle(e.target.value)}
|
||||
placeholder="Translated Title"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="space-y-2 py-3">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -886,7 +1085,7 @@ export default function FormTeks() {
|
|||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full space-y-2">
|
||||
|
|
@ -1212,12 +1411,10 @@ export default function FormTeks() {
|
|||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text ||
|
||||
"";
|
||||
|
||||
setTranslatedContent(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -1232,57 +1429,34 @@ export default function FormTeks() {
|
|||
>
|
||||
{isLoadingTranslate
|
||||
? "Translating..."
|
||||
: "Translate to English"}
|
||||
: "Translate Description"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pilihan bahasa untuk posting */}
|
||||
{roleId === "14" && (
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value="id"
|
||||
checked={selectedLang === "id"}
|
||||
onChange={() => setSelectedLang("id")}
|
||||
{/* Deskripsi Bahasa Indonesia */}
|
||||
<div className="mt-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
Indonesian Version
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
initialData={value}
|
||||
/>
|
||||
<span>Gunakan Bahasa Indonesia</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Editor Bahasa Indonesia */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
initialData={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Editor Bahasa Inggris */}
|
||||
{/* Versi Inggris muncul setelah translate */}
|
||||
{translatedContent && (
|
||||
<div className="mt-4">
|
||||
<div className="flex flex-col">
|
||||
<Label className="text-[15px]">
|
||||
English Version
|
||||
</Label>{" "}
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value="en"
|
||||
checked={selectedLang === "en"}
|
||||
onChange={() => setSelectedLang("en")}
|
||||
disabled={!translatedContent} // kalau belum translate, disable
|
||||
/>
|
||||
<span>Gunakan Bahasa Inggris</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Version
|
||||
</Label>
|
||||
<CustomEditor
|
||||
onChange={(val: any) => setTranslatedContent(val)}
|
||||
initialData={translatedContent}
|
||||
|
|
@ -1296,6 +1470,7 @@ export default function FormTeks() {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="py-3 space-y-2">
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
|
|
@ -1601,7 +1776,7 @@ export default function FormTeks() {
|
|||
{/* <Button type="submit" color="primary">
|
||||
{t("submit", { defaultValue: "Submit" })}
|
||||
</Button> */}
|
||||
{levelNumber !== "2" && levelNumber !== "3" && (
|
||||
{levelNumber !== "2" && (
|
||||
<Button type="submit" color="primary">
|
||||
{t("submit", { defaultValue: "Submit" })}
|
||||
</Button>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -27,6 +27,7 @@ import {
|
|||
createMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
rejectFiles,
|
||||
submitApproval,
|
||||
} from "@/service/content/content";
|
||||
|
|
@ -114,7 +115,7 @@ const ViewEditor = dynamic(
|
|||
() => {
|
||||
return import("@/components/editor/view-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
interface Destination {
|
||||
|
|
@ -189,6 +190,12 @@ export default function FormVideoDetail() {
|
|||
satker: boolean;
|
||||
}>
|
||||
>([]);
|
||||
const [creatorLevelNumber, setCreatorLevelNumber] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const isContentFromSatker = creatorLevelNumber === 3;
|
||||
const isContentFromMabesOrPolda =
|
||||
creatorLevelNumber === 1 || creatorLevelNumber === 2;
|
||||
|
||||
useEffect(() => {
|
||||
if (Number(userLevelId) === 216 && Number(roleId) === 3) {
|
||||
|
|
@ -200,7 +207,7 @@ export default function FormVideoDetail() {
|
|||
const handleFileUnitChange = (
|
||||
fileIndex: number,
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
value: boolean,
|
||||
) => {
|
||||
setFileUnitSelections((prev) => {
|
||||
const newSelections = [...prev];
|
||||
|
|
@ -220,7 +227,7 @@ export default function FormVideoDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[fileIndex] || new Set()
|
||||
newArray[fileIndex] || new Set(),
|
||||
);
|
||||
|
||||
if (value) {
|
||||
|
|
@ -251,12 +258,12 @@ export default function FormVideoDetail() {
|
|||
(item: any) =>
|
||||
item.levelNumber === 2 &&
|
||||
item.name !== "SATKER POLRI" &&
|
||||
currentFileCheckedLevels.has(Number(item.id))
|
||||
currentFileCheckedLevels.has(Number(item.id)),
|
||||
);
|
||||
|
||||
if (!hasSelectedPolda) {
|
||||
alert(
|
||||
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."
|
||||
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES.",
|
||||
);
|
||||
return prev; // Batalkan perubahan
|
||||
}
|
||||
|
|
@ -295,7 +302,7 @@ export default function FormVideoDetail() {
|
|||
useEffect(() => {
|
||||
if (detail?.assignedToTopLevel) {
|
||||
const outputSet = new Set(
|
||||
detail.assignedToTopLevel.split(",").map(Number)
|
||||
detail.assignedToTopLevel.split(",").map(Number),
|
||||
);
|
||||
setUnitSelection({
|
||||
semua: outputSet.has(0),
|
||||
|
|
@ -320,7 +327,7 @@ export default function FormVideoDetail() {
|
|||
acc[polda.id] = false;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
{},
|
||||
);
|
||||
setExpandedPolda(initialExpandedState);
|
||||
console.log("polres", initialExpandedState);
|
||||
|
|
@ -355,7 +362,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
const handleUnitChange = (
|
||||
key: keyof typeof unitSelection,
|
||||
value: boolean
|
||||
value: boolean,
|
||||
) => {
|
||||
if (key === "semua") {
|
||||
const newState = {
|
||||
|
|
@ -434,7 +441,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
const handleCheckboxChange = (id: number) => {
|
||||
setSelectedPublishers((prev) =>
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -448,7 +455,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -456,7 +463,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
o.name.toLowerCase().includes("pers rilis")
|
||||
o.name.toLowerCase().includes("pers rilis"),
|
||||
);
|
||||
|
||||
if (findCategory) {
|
||||
|
|
@ -479,6 +486,10 @@ export default function FormVideoDetail() {
|
|||
console.log("detail", details);
|
||||
setFiles(details?.files);
|
||||
setDetail(details);
|
||||
if (details?.uploadedBy?.userLevel?.levelNumber) {
|
||||
setCreatorLevelNumber(details.uploadedBy.userLevel.levelNumber);
|
||||
}
|
||||
|
||||
setMain({
|
||||
type: details?.fileType.name,
|
||||
url: details?.files[0]?.url,
|
||||
|
|
@ -488,14 +499,14 @@ export default function FormVideoDetail() {
|
|||
|
||||
if (details?.assignedToLevel) {
|
||||
const levels = new Set(
|
||||
details.assignedToLevel.split(",").map(Number)
|
||||
details.assignedToLevel.split(",").map(Number),
|
||||
);
|
||||
setCheckedLevels(levels);
|
||||
}
|
||||
|
||||
if (details?.publishedForObject) {
|
||||
const publisherIds = details?.publishedForObject?.map(
|
||||
(obj: any) => obj.id
|
||||
(obj: any) => obj.id,
|
||||
);
|
||||
setSelectedPublishers(publisherIds);
|
||||
}
|
||||
|
|
@ -505,7 +516,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
const filesData = details?.files || [];
|
||||
const fileUrls = filesData.map((files: { url: string }) =>
|
||||
files.url ? files.url : "default-image.jpg"
|
||||
files.url ? files.url : "default-image.jpg",
|
||||
);
|
||||
setDetailVideo(fileUrls);
|
||||
|
||||
|
|
@ -683,7 +694,7 @@ export default function FormVideoDetail() {
|
|||
const setupPlacement = (
|
||||
index: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
checked: boolean,
|
||||
) => {
|
||||
let temp = [...filePlacements];
|
||||
if (checked) {
|
||||
|
|
@ -694,7 +705,7 @@ export default function FormVideoDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[index] || new Set()
|
||||
newArray[index] || new Set(),
|
||||
);
|
||||
|
||||
// Checklist semua item di modal
|
||||
|
|
@ -744,7 +755,7 @@ export default function FormVideoDetail() {
|
|||
// Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist
|
||||
// JANGAN include satker dalam perhitungan auto "all"
|
||||
const nonSatkerItems = now.filter(
|
||||
(item) => item !== "satker" && item !== "all"
|
||||
(item) => item !== "satker" && item !== "all",
|
||||
);
|
||||
if (nonSatkerItems.length === 3 && !now.includes("all")) {
|
||||
now.push("all");
|
||||
|
|
@ -759,7 +770,7 @@ export default function FormVideoDetail() {
|
|||
setFileCheckedLevels((prevLevels) => {
|
||||
const newArray = [...prevLevels];
|
||||
const currentFileLevels = new Set<number>(
|
||||
newArray[index] || new Set()
|
||||
newArray[index] || new Set(),
|
||||
);
|
||||
|
||||
// Unchecklist semua item di modal
|
||||
|
|
@ -793,7 +804,7 @@ export default function FormVideoDetail() {
|
|||
// Hapus "all" jika tidak semua item ter-checklist
|
||||
if (now.includes("all")) {
|
||||
const nonSatkerItems = now.filter(
|
||||
(item) => item !== "satker" && item !== "all"
|
||||
(item) => item !== "satker" && item !== "all",
|
||||
);
|
||||
if (nonSatkerItems.length < 3) {
|
||||
const newData = now.filter((b) => b !== "all");
|
||||
|
|
@ -811,7 +822,7 @@ export default function FormVideoDetail() {
|
|||
const updateModalChecklistLevels = (
|
||||
fileIndex: number,
|
||||
placement: string,
|
||||
checked: boolean
|
||||
checked: boolean,
|
||||
) => {
|
||||
if (!listDest || listDest.length === 0) return;
|
||||
|
||||
|
|
@ -844,7 +855,7 @@ export default function FormVideoDetail() {
|
|||
} else if (placement === "satker") {
|
||||
// Checklist SATKER POLRI dan semua sub-item di bawahnya
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.add(Number(satkerItem.id));
|
||||
|
|
@ -880,7 +891,7 @@ export default function FormVideoDetail() {
|
|||
} else if (placement === "satker") {
|
||||
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya
|
||||
const satkerItem: any = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
if (satkerItem) {
|
||||
currentFileLevels.delete(Number(satkerItem.id));
|
||||
|
|
@ -909,7 +920,7 @@ export default function FormVideoDetail() {
|
|||
type: string,
|
||||
url: string,
|
||||
names: string,
|
||||
format: string
|
||||
format: string,
|
||||
) => {
|
||||
console.log("Test 3 :", type, url, names, format);
|
||||
setMain({
|
||||
|
|
@ -950,7 +961,7 @@ export default function FormVideoDetail() {
|
|||
// Fungsi untuk mengupdate checklist levels untuk file tertentu
|
||||
const handleFileCheckboxChangePlacement = (
|
||||
fileIndex: number,
|
||||
levelId: number
|
||||
levelId: number,
|
||||
) => {
|
||||
setFileCheckedLevels((prev) => {
|
||||
const newArray = [...prev];
|
||||
|
|
@ -962,7 +973,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
|
||||
const poldaItem = listDest.find(
|
||||
(item: any) => Number(item.id) === levelId
|
||||
(item: any) => Number(item.id) === levelId,
|
||||
) as any;
|
||||
if (
|
||||
poldaItem &&
|
||||
|
|
@ -985,7 +996,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => Number(item.id) === levelId
|
||||
(item: any) => Number(item.id) === levelId,
|
||||
) as any;
|
||||
if (satkerItem && satkerItem.name === "SATKER POLRI") {
|
||||
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
|
||||
|
|
@ -1016,7 +1027,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
// Hitung total POLDA yang ada (bukan SATKER POLRI)
|
||||
const totalPoldaCount = listDest.filter(
|
||||
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
|
||||
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI",
|
||||
).length;
|
||||
|
||||
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
|
||||
|
|
@ -1043,7 +1054,7 @@ export default function FormVideoDetail() {
|
|||
}
|
||||
return total;
|
||||
},
|
||||
0
|
||||
0,
|
||||
);
|
||||
|
||||
// Hitung berapa banyak POLRES yang ter-checklist
|
||||
|
|
@ -1053,7 +1064,7 @@ export default function FormVideoDetail() {
|
|||
return (
|
||||
total +
|
||||
item.subDestination.filter((sub: any) =>
|
||||
currentFileLevels.has(Number(sub.id))
|
||||
currentFileLevels.has(Number(sub.id)),
|
||||
).length
|
||||
);
|
||||
}
|
||||
|
|
@ -1062,7 +1073,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
// Cek apakah SATKER POLRI ter-checklist
|
||||
const satkerItem = listDest.find(
|
||||
(item: any) => item.name === "SATKER POLRI"
|
||||
(item: any) => item.name === "SATKER POLRI",
|
||||
);
|
||||
const isSatkerChecked =
|
||||
satkerItem && currentFileLevels.has(Number(satkerItem.id));
|
||||
|
|
@ -1096,7 +1107,7 @@ export default function FormVideoDetail() {
|
|||
|
||||
// Cek apakah semua sub-items sudah ter-checklist
|
||||
const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
|
||||
currentFileLevels.has(Number(sub.id))
|
||||
currentFileLevels.has(Number(sub.id)),
|
||||
);
|
||||
|
||||
if (allSubItemsChecked) {
|
||||
|
|
@ -1179,7 +1190,7 @@ export default function FormVideoDetail() {
|
|||
{detail &&
|
||||
!categories.find(
|
||||
(cat) =>
|
||||
String(cat.id) === String(detail.category.id)
|
||||
String(cat.id) === String(detail.category.id),
|
||||
) && (
|
||||
<SelectItem
|
||||
key={String(detail.category.id)}
|
||||
|
|
@ -1464,7 +1475,7 @@ export default function FormVideoDetail() {
|
|||
Tingkat Distribusi:
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{/* {[
|
||||
{ key: "semua", label: "Semua" },
|
||||
{ key: "nasional", label: "Nasional" },
|
||||
{ key: "wilayah", label: "Wilayah" },
|
||||
|
|
@ -1472,6 +1483,39 @@ export default function FormVideoDetail() {
|
|||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
] */}
|
||||
{[
|
||||
{ key: "semua", label: "Semua" },
|
||||
|
||||
...(isContentFromMabesOrPolda
|
||||
? [
|
||||
{
|
||||
key: "nasional",
|
||||
label: "Nasional",
|
||||
},
|
||||
{
|
||||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
{
|
||||
key: "wilayah",
|
||||
label: "Wilayah",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(isContentFromSatker
|
||||
? [
|
||||
{
|
||||
key: "nasional",
|
||||
label: "Nasional",
|
||||
},
|
||||
{
|
||||
key: "international",
|
||||
label: "Internasional",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
|
|
@ -1488,12 +1532,12 @@ export default function FormVideoDetail() {
|
|||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
value as boolean,
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
Boolean(value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -1509,251 +1553,262 @@ export default function FormVideoDetail() {
|
|||
</div>
|
||||
|
||||
{/* Detail Wilayah */}
|
||||
{fileUnitSelections[index]?.wilayah && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
{/* {fileUnitSelections[index]?.wilayah && ( */}
|
||||
{!isContentFromSatker &&
|
||||
fileUnitSelections[index]?.wilayah && (
|
||||
<div className="border-t border-gray-200 pt-2">
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">
|
||||
Detail Wilayah:
|
||||
</p>
|
||||
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{ key: "polres", label: "POLRES" },
|
||||
{ key: "satker", label: "SATKER" },
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ key: "polda", label: "POLDA" },
|
||||
{ key: "polres", label: "POLRES" },
|
||||
{ key: "satker", label: "SATKER" },
|
||||
].map((item, idx) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
<Checkbox
|
||||
id={`${item.key}-${index}`}
|
||||
checked={
|
||||
fileUnitSelections[index]?.[
|
||||
item.key as keyof typeof unitSelection
|
||||
] || false
|
||||
}
|
||||
onCheckedChange={(value) => {
|
||||
handleFileUnitChange(
|
||||
index,
|
||||
item.key as keyof typeof unitSelection,
|
||||
value as boolean,
|
||||
);
|
||||
setupPlacement(
|
||||
index,
|
||||
item.key,
|
||||
Boolean(value),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`${item.key}-${index}`}
|
||||
className="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan POLRES
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map((polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(polda.id)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(polda.id)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{polda.subDestination && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id
|
||||
);
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Sub-items */}
|
||||
{polda.subDestination &&
|
||||
expandedPolda[polda.id] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(sub: any) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
)
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
polda
|
||||
)
|
||||
}
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={12}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={12}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{/* Tombol Kustom sejajar dengan checkbox */}
|
||||
<div className="flex items-center justify-center p-3">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
>
|
||||
<Icon
|
||||
icon="material-symbols:tune"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
{t("custom", {
|
||||
defaultValue: "Kustom",
|
||||
})}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
|
||||
<DialogHeader className="border-b border-gray-200 pb-4">
|
||||
<DialogTitle className="text-lg font-semibold">
|
||||
Daftar Wilayah POLDA dan
|
||||
POLRES
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
|
||||
{listDest.map((polda: any) => (
|
||||
<div
|
||||
key={polda.id}
|
||||
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
|
||||
>
|
||||
{/* Header POLDA */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(polda.id),
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(polda.id),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="font-semibold text-gray-900 text-sm">
|
||||
{polda.name}
|
||||
</span>
|
||||
</Label>
|
||||
{polda.subDestination && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleExpand(
|
||||
polda.id,
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(sub: any) => (
|
||||
<Label
|
||||
key={sub.id}
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
}}
|
||||
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
expandedPolda[
|
||||
polda.id
|
||||
]
|
||||
? "mdi:chevron-up"
|
||||
: "mdi:chevron-down"
|
||||
}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sub-items */}
|
||||
{polda.subDestination &&
|
||||
expandedPolda[
|
||||
polda.id
|
||||
] && (
|
||||
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
|
||||
{/* Tombol Pilih Semua untuk sub-items */}
|
||||
<div className="mb-2 flex justify-start">
|
||||
{(() => {
|
||||
const allSubItemsChecked =
|
||||
polda.subDestination?.every(
|
||||
(sub: any) =>
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
sub.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs h-6 px-2"
|
||||
onClick={() =>
|
||||
handleSelectAllSubItems(
|
||||
index,
|
||||
Number(
|
||||
sub.id
|
||||
)
|
||||
polda,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{sub.name}
|
||||
</span>
|
||||
</Label>
|
||||
)
|
||||
)}
|
||||
>
|
||||
{allSubItemsChecked ? (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-indeterminate-small"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Batal
|
||||
Semua
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="material-symbols:check-all"
|
||||
width={
|
||||
12
|
||||
}
|
||||
height={
|
||||
12
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
Pilih
|
||||
Semua
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{polda.subDestination.map(
|
||||
(sub: any) => (
|
||||
<Label
|
||||
key={sub.id}
|
||||
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
|
||||
>
|
||||
<Checkbox
|
||||
checked={
|
||||
fileCheckedLevels[
|
||||
index
|
||||
]?.has(
|
||||
Number(
|
||||
sub.id,
|
||||
),
|
||||
) || false
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleFileCheckboxChangePlacement(
|
||||
index,
|
||||
Number(
|
||||
sub.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-gray-700">
|
||||
{sub.name}
|
||||
</span>
|
||||
</Label>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>
|
||||
{/* {t("save", {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
{t("cancel", {
|
||||
defaultValue: "Batal",
|
||||
})}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button>
|
||||
{/* {t("save", {
|
||||
defaultValue: "Simpan",
|
||||
})} */}
|
||||
Simpan
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
Simpan
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -1832,7 +1887,7 @@ export default function FormVideoDetail() {
|
|||
>
|
||||
{template}
|
||||
</Button>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1863,7 +1918,73 @@ export default function FormVideoDetail() {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</Card>
|
||||
{Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
{Number(roleId) === 14 ? (
|
||||
// 🔹 Jika roleId = 14 → selalu tampilkan tombol tanpa pengecekan tambahan
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<Button
|
||||
onClick={() => actionApproval("2")}
|
||||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" />
|
||||
{t("accept", { defaultValue: "Accept" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision", { defaultValue: "Revision" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
color="destructive"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
{t("reject", { defaultValue: "Reject" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : (Number(detail?.needApprovalFromLevel) ===
|
||||
Number(userLevelId) ||
|
||||
(detail?.isInternationalMedia === true &&
|
||||
detail?.isForwardFromNational === true &&
|
||||
Number(detail?.statusId) === 1)) &&
|
||||
(Number(detail?.uploadedById) === Number(userId) ||
|
||||
Number(detail?.needApprovalFromLevel) ===
|
||||
Number(userLevelId)) ? (
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
<Button
|
||||
onClick={() => actionApproval("2")}
|
||||
color="primary"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:check" className="mr-3" />
|
||||
{t("accept", { defaultValue: "Accept" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("3")}
|
||||
className="bg-orange-400 hover:bg-orange-300"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:comment-o" className="mr-3" />{" "}
|
||||
{t("revision", { defaultValue: "Revision" })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => actionApproval("4")}
|
||||
color="destructive"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="fa:times" className="mr-3" />
|
||||
{t("reject", { defaultValue: "Reject" })}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
||||
{/* {Number(detail?.needApprovalFromLevel) == Number(userLevelId) ? (
|
||||
Number(detail?.uploadedById) == Number(userId) ? (
|
||||
""
|
||||
) : (
|
||||
|
|
@ -1896,7 +2017,7 @@ export default function FormVideoDetail() {
|
|||
)
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -26,17 +26,15 @@ import {
|
|||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
|
||||
import { register } from "module";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import Cookies from "js-cookie";
|
||||
import {
|
||||
createMedia,
|
||||
getTagsBySubCategoryId,
|
||||
listEnableCategory,
|
||||
listEnableCategoryNew,
|
||||
uploadThumbnail,
|
||||
} from "@/service/content/content";
|
||||
import { uploadThumbnailBlog } from "@/service/blog/blog";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
generateDataArticle,
|
||||
|
|
@ -52,10 +50,8 @@ import { Icon } from "@iconify/react";
|
|||
import { CloudUpload } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { close, error, loading } from "@/config/swal";
|
||||
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";
|
||||
import { htmlToString } from "@/utils/globals";
|
||||
|
||||
|
|
@ -63,7 +59,7 @@ const CustomEditor = dynamic(
|
|||
() => {
|
||||
return import("@/components/editor/custom-editor");
|
||||
},
|
||||
{ ssr: false }
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
interface FileWithPreview extends File {
|
||||
|
|
@ -115,11 +111,11 @@ export default function FormVideo() {
|
|||
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
|
||||
const [articleBody, setArticleBody] = useState<string>("");
|
||||
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
|
||||
const [publishedForError, setPublishedForError] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [selectedSize, setSelectedSize] = useState("");
|
||||
const [detailData, setDetailData] = useState<any>(null);
|
||||
|
|
@ -146,10 +142,12 @@ export default function FormVideo() {
|
|||
const [counterProgress, setCounterProgress] = useState(0);
|
||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||
const [files, setFiles] = useState<FileWithPreview[]>([]);
|
||||
|
||||
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
|
||||
const [translatedContent, setTranslatedContent] = React.useState("");
|
||||
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
|
||||
// 🔹 State untuk translate Title
|
||||
const [translatedTitle, setTranslatedTitle] = useState("");
|
||||
const [isLoadingTranslateTitle, setIsLoadingTranslateTitle] = useState(false);
|
||||
|
||||
const options: Option[] = [
|
||||
{ id: "all", label: "SEMUA" },
|
||||
|
|
@ -186,7 +184,7 @@ export default function FormVideo() {
|
|||
}
|
||||
|
||||
const filesWithPreview = acceptedFiles.map((file) =>
|
||||
Object.assign(file, { preview: URL.createObjectURL(file) })
|
||||
Object.assign(file, { preview: URL.createObjectURL(file) }),
|
||||
);
|
||||
|
||||
setFiles((prev) => {
|
||||
|
|
@ -213,11 +211,11 @@ export default function FormVideo() {
|
|||
.refine(
|
||||
(files) =>
|
||||
files.every((file: File) => ACCEPTED_FILE_TYPES.includes(file.type)),
|
||||
{ message: "File harus berformat mp4 atau mov" }
|
||||
{ message: "File harus berformat mp4 atau mov" },
|
||||
)
|
||||
.refine(
|
||||
(files) => files.every((file: File) => file.size <= MAX_FILE_SIZE),
|
||||
{ message: "Ukuran file maksimal 100 MB" }
|
||||
{ message: "Ukuran file maksimal 100 MB" },
|
||||
),
|
||||
publishedFor: z
|
||||
.array(z.string())
|
||||
|
|
@ -425,7 +423,7 @@ export default function FormVideo() {
|
|||
const articleData = await waitForStatusUpdate();
|
||||
const cleanArticleBody = articleData?.articleBody?.replace(
|
||||
/<img[^>]*>/g,
|
||||
""
|
||||
"",
|
||||
);
|
||||
const articleImagesData = articleData?.imagesUrl?.split(",");
|
||||
setValue("description", cleanArticleBody || "");
|
||||
|
|
@ -473,7 +471,7 @@ export default function FormVideo() {
|
|||
|
||||
const getCategories = async () => {
|
||||
try {
|
||||
const category = await listEnableCategory(fileTypeId);
|
||||
const category = await listEnableCategoryNew(fileTypeId);
|
||||
const resCategory: Category[] = category?.data?.data?.content;
|
||||
|
||||
setCategories(resCategory);
|
||||
|
|
@ -481,7 +479,7 @@ export default function FormVideo() {
|
|||
|
||||
if (scheduleId && scheduleType === "3") {
|
||||
const findCategory = resCategory.find((o) =>
|
||||
o.name.toLowerCase().includes("pers rilis")
|
||||
o.name.toLowerCase().includes("pers rilis"),
|
||||
);
|
||||
|
||||
if (findCategory) {
|
||||
|
|
@ -504,7 +502,7 @@ export default function FormVideo() {
|
|||
setPublishedFor(
|
||||
options
|
||||
.filter((opt: any) => opt.id !== "all")
|
||||
.map((opt: any) => opt.id)
|
||||
.map((opt: any) => opt.id),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -536,19 +534,54 @@ export default function FormVideo() {
|
|||
}
|
||||
loading();
|
||||
const finalTags = data.tags.join(", ");
|
||||
const finalTitle = isSwitchOn ? title : data.title;
|
||||
// const finalTitle = isSwitchOn ? title : data.title;
|
||||
// const finalDescription = articleBody || data.description;
|
||||
const finalDescription = isSwitchOn
|
||||
? data.description
|
||||
: selectedFileType === "rewrite"
|
||||
? data.rewriteDescription
|
||||
: data.descriptionOri;
|
||||
// const finalDescription = isSwitchOn
|
||||
// ? data.description
|
||||
// : selectedFileType === "rewrite"
|
||||
// ? data.rewriteDescription
|
||||
// : data.descriptionOri;
|
||||
|
||||
// if (!finalDescription?.trim()) {
|
||||
// MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // 👉 tempelkan hasil translate ke form agar ikut terkirim
|
||||
// if (translatedContent) {
|
||||
// data.descriptionOri = translatedContent;
|
||||
// console.log(
|
||||
// "🌍 Translate dimasukkan ke descriptionOri:",
|
||||
// translatedContent
|
||||
// );
|
||||
// }
|
||||
|
||||
// Tentukan title final (gunakan versi translate kalau ada)
|
||||
const finalTitle =
|
||||
translatedTitle && translatedTitle.trim() !== ""
|
||||
? translatedTitle
|
||||
: isSwitchOn
|
||||
? title
|
||||
: data.title;
|
||||
|
||||
// Tentukan deskripsi final:
|
||||
// Jika ada hasil translate, kirim itu ke backend
|
||||
const finalDescription =
|
||||
translatedContent && translatedContent.trim() !== ""
|
||||
? translatedContent
|
||||
: isSwitchOn
|
||||
? data.description
|
||||
: selectedFileType === "rewrite"
|
||||
? data.rewriteDescription
|
||||
: data.descriptionOri;
|
||||
|
||||
if (!finalDescription?.trim()) {
|
||||
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("📝 Deskripsi final yang dikirim:", finalDescription);
|
||||
|
||||
let requestData: {
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
@ -616,7 +649,16 @@ export default function FormVideo() {
|
|||
setIsStartUpload(true);
|
||||
setProgressList(progressInfoArr);
|
||||
|
||||
close();
|
||||
MySwal.fire({
|
||||
title: "Mengunggah Video...",
|
||||
text: "Mohon tunggu hingga proses upload selesai.",
|
||||
allowOutsideClick: false,
|
||||
allowEscapeKey: false,
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
},
|
||||
});
|
||||
// close();
|
||||
// showProgress();
|
||||
files.map(async (item: any, index: number) => {
|
||||
await uploadResumableFile(index, String(id), item, "0");
|
||||
|
|
@ -653,7 +695,7 @@ export default function FormVideo() {
|
|||
idx: number,
|
||||
id: string,
|
||||
file: any,
|
||||
duration: string
|
||||
duration: string,
|
||||
) {
|
||||
console.log(idx, id, file, duration);
|
||||
|
||||
|
|
@ -689,7 +731,7 @@ export default function FormVideo() {
|
|||
onChunkComplete: (
|
||||
chunkSize: any,
|
||||
bytesAccepted: any,
|
||||
bytesTotal: any
|
||||
bytesTotal: any,
|
||||
) => {
|
||||
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
|
||||
progressInfo[idx].percentage = uploadPersen;
|
||||
|
|
@ -729,19 +771,28 @@ export default function FormVideo() {
|
|||
counter++;
|
||||
}
|
||||
}
|
||||
if (counter == progressInfo.length) {
|
||||
if (counter === progressInfo.length) {
|
||||
setIsStartUpload(false);
|
||||
// hideProgress();
|
||||
Cookies.remove("idCreate");
|
||||
|
||||
close();
|
||||
|
||||
successSubmit("/in/contributor/content/video");
|
||||
}
|
||||
|
||||
// if (counter == progressInfo.length) {
|
||||
// setIsStartUpload(false);
|
||||
// // hideProgress();
|
||||
// Cookies.remove("idCreate");
|
||||
// successSubmit("/in/contributor/content/video");
|
||||
// }
|
||||
}
|
||||
|
||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setThumbnail(file); // Simpan file asli tanpa dimodifikasi
|
||||
setPreview(URL.createObjectURL(file)); // Simpan preview string terpisah
|
||||
setThumbnail(file);
|
||||
setPreview(URL.createObjectURL(file));
|
||||
console.log("Selected Thumbnail:", file);
|
||||
}
|
||||
};
|
||||
|
|
@ -874,7 +925,81 @@ export default function FormVideo() {
|
|||
{t("form-video", { defaultValue: "Form Video" })}
|
||||
</p>
|
||||
<div className="gap-5 mb-5">
|
||||
{/* Input Title dengan tombol translate */}
|
||||
<div className="space-y-2 py-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslateTitle(true);
|
||||
const res = await translateText({
|
||||
text: getValues("title"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text || "";
|
||||
setTranslatedTitle(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Translate title gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslateTitle(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslateTitle
|
||||
? "Translating..."
|
||||
: "Translate to English"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Judul Bahasa Indonesia */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="Masukkan Judul Bahasa Indonesia"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Versi English (muncul setelah klik translate) */}
|
||||
{translatedTitle && (
|
||||
<div className="mt-3">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Title
|
||||
</Label>
|
||||
<Input
|
||||
size="md"
|
||||
type="text"
|
||||
value={translatedTitle}
|
||||
onChange={(e) => setTranslatedTitle(e.target.value)}
|
||||
placeholder="English version"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="space-y-2 py-3">
|
||||
<Label>{t("title", { defaultValue: "Title" })}</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
|
|
@ -892,7 +1017,7 @@ export default function FormVideo() {
|
|||
{errors.title?.message && (
|
||||
<p className="text-red-400 text-sm">{errors.title.message}</p>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="py-3 w-full space-y-2">
|
||||
|
|
@ -1201,7 +1326,86 @@ export default function FormVideo() {
|
|||
</Label>
|
||||
</div>
|
||||
|
||||
{/* Deskripsi dengan translate atas-bawah */}
|
||||
<div className="py-3 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
</Label>
|
||||
|
||||
{roleId === "14" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
try {
|
||||
loading();
|
||||
setIsLoadingTranslate(true);
|
||||
const res = await translateText({
|
||||
text: getValues("descriptionOri"),
|
||||
sourceLang: "ID",
|
||||
targetLang: "EN",
|
||||
});
|
||||
|
||||
if (!res.error) {
|
||||
const resultText =
|
||||
res?.data?.data?.translations?.[0]?.text ||
|
||||
"";
|
||||
setTranslatedContent(resultText);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Translate gagal:", err);
|
||||
} finally {
|
||||
close();
|
||||
setIsLoadingTranslate(false);
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
>
|
||||
{isLoadingTranslate
|
||||
? "Translating..."
|
||||
: "Translate to English"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Editor Bahasa Indonesia */}
|
||||
<div className="mt-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
Indonesian Version
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<CustomEditor
|
||||
onChange={onChange}
|
||||
initialData={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Editor Bahasa Inggris (muncul setelah klik translate) */}
|
||||
{translatedContent && (
|
||||
<div className="mt-5">
|
||||
<Label className="text-sm font-semibold">
|
||||
English Version
|
||||
</Label>
|
||||
<CustomEditor
|
||||
onChange={(val: any) => setTranslatedContent(val)}
|
||||
initialData={translatedContent}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.description?.message && (
|
||||
<p className="text-red-400 text-sm">
|
||||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="py-3 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<Label>
|
||||
{t("description", { defaultValue: "Description" })}
|
||||
|
|
@ -1244,7 +1448,6 @@ export default function FormVideo() {
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Pilihan bahasa untuk posting */}
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
|
|
@ -1257,7 +1460,6 @@ export default function FormVideo() {
|
|||
</label>
|
||||
</div>
|
||||
|
||||
{/* Editor Bahasa Indonesia */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="descriptionOri"
|
||||
|
|
@ -1269,7 +1471,6 @@ export default function FormVideo() {
|
|||
)}
|
||||
/>
|
||||
|
||||
{/* Editor Bahasa Inggris */}
|
||||
{translatedContent && (
|
||||
<div className="mt-4">
|
||||
<div className="flex flex-col">
|
||||
|
|
@ -1282,7 +1483,7 @@ export default function FormVideo() {
|
|||
value="en"
|
||||
checked={selectedLang === "en"}
|
||||
onChange={() => setSelectedLang("en")}
|
||||
disabled={!translatedContent} // kalau belum translate, disable
|
||||
disabled={!translatedContent}
|
||||
/>
|
||||
<span>Gunakan Bahasa Inggris</span>
|
||||
</label>
|
||||
|
|
@ -1300,7 +1501,7 @@ export default function FormVideo() {
|
|||
{errors.description.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* <div className="py-3 space-y-2">
|
||||
<Label>
|
||||
|
|
@ -1531,7 +1732,7 @@ export default function FormVideo() {
|
|||
type="button"
|
||||
onClick={() => {
|
||||
const updatedTags = field.value.filter(
|
||||
(_, i) => i !== index
|
||||
(_, i) => i !== index,
|
||||
);
|
||||
field.onChange(updatedTags);
|
||||
}}
|
||||
|
|
@ -1624,7 +1825,7 @@ export default function FormVideo() {
|
|||
{/* <Button type="submit" color="primary">
|
||||
{t("submit", { defaultValue: "Submit" })}
|
||||
</Button> */}
|
||||
{levelNumber !== "2" && levelNumber !== "3" && (
|
||||
{levelNumber !== "2" && (
|
||||
<Button type="submit" color="primary">
|
||||
{t("submit", { defaultValue: "Submit" })}
|
||||
</Button>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue