Compare commits

..

No commits in common. "main" and "dev-noProd" have entirely different histories.

123 changed files with 4724 additions and 7596 deletions

View File

@ -1,46 +0,0 @@
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
- main
- 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:
- main
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
View File

@ -1,3 +1,3 @@
NEXT_PUBLIC_API=https://new.netidhub.com/api NEXT_PUBLIC_API=https://netidhub.com/api
NEXT_PUBLIC=https://new.netidhub.com NEXT_PUBLIC=https://netidhub.com
NEXT_PUBLIC_TINYMCE_API_KEY=bhteuja26yz5p0aubxry9b95hs33amgn65kjv5km0fd5iuev NEXT_PUBLIC_TINYMCE_API_KEY=bhteuja26yz5p0aubxry9b95hs33amgn65kjv5km0fd5iuev

View File

@ -12,12 +12,12 @@ build-dev:
name: docker:25.0.3-cli name: docker:25.0.3-cli
services: services:
- name: docker:25.0.3-dind - name: docker:25.0.3-dind
command: ["--insecure-registry=38.47.185.86:8900"] command: ["--insecure-registry=103.82.242.92:8900"]
script: script:
- docker logout - docker logout
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900 - docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
- docker build -t 38.47.185.86:8900/mediahub/new-mediahub-fe:dev . - docker build -t 103.82.242.92:8900/mediahub/new-mediahub-fe:dev .
- docker push 38.47.185.86:8900/mediahub/new-mediahub-fe:dev - docker push 103.82.242.92:8900/mediahub/new-mediahub-fe:dev
auto-deploy: auto-deploy:
stage: deploy stage: deploy
@ -26,5 +26,7 @@ auto-deploy:
- main - main
- dev-landing-v2 - dev-landing-v2
image: curlimages/curl:latest image: curlimages/curl:latest
services:
- docker:dind
script: script:
- curl --user admin:$JENKINS_PWD http://38.47.185.86:8080/job/auto-deploy-new-mediahub-fe/build?token=autodeploynewmediahub - curl --user admin:$JENKINS_PWD http://38.47.180.165:8080/job/auto-deploy-new-mediahub-fe/build?token=autodeploynewmediahub

View File

@ -18,7 +18,7 @@ import { useRouter } from "next/navigation";
import { deleteUser } from "@/service/management-user/management-user"; import { deleteUser } from "@/service/management-user/management-user";
import { stringify } from "querystring"; import { stringify } from "querystring";
const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[] => [ const columns: ColumnDef<any>[] = [
{ {
accessorKey: "no", accessorKey: "no",
header: "No", header: "No",
@ -30,13 +30,11 @@ const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[]
header: "Nama", header: "Nama",
cell: ({ row }) => <span>{row.getValue("fullname")}</span>, cell: ({ row }) => <span>{row.getValue("fullname")}</span>,
}, },
{ {
accessorKey: "address", accessorKey: "address",
header: "Wilayah", header: "Wilayah",
cell: () => <span>MABES</span>, cell: ({ row }) => <span>MABES</span>,
}, },
{ {
accessorKey: "userRolePlacements", accessorKey: "userRolePlacements",
header: "Posisi", header: "Posisi",
@ -54,7 +52,6 @@ const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[]
return <span>{posisi}</span>; return <span>{posisi}</span>;
}, },
}, },
{ {
accessorKey: "role.name", accessorKey: "role.name",
header: "Bidang Keahlian", header: "Bidang Keahlian",
@ -84,77 +81,72 @@ const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[]
{ {
id: "actions", id: "actions",
accessorKey: "action",
header: "Actions", header: "Actions",
enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const { toast } = useToast(); const { toast } = useToast();
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter();
const doDelete = async (id: number) => { const doDelete = async (id: number) => {
Swal.fire({
title: "Menghapus user...",
text: "Mohon tunggu",
allowOutsideClick: false,
didOpen: () => Swal.showLoading(),
});
const response = await deleteUser(id); const response = await deleteUser(id);
Swal.close();
if (response?.error) { if (response?.error) {
toast({ toast({
title: stringify(response?.message), title: stringify(response?.message),
variant: "destructive", variant: "destructive",
}); });
return;
} }
toast({
title: "Success delete",
});
toast({ title: "Berhasil menghapus user" }); router.push("?dataChange=true");
// ⬅️ INI YANG PENTING → REFRESH TABLE TANPA RELOAD
onRefresh();
}; };
const handleDelete = (id: number) => { const handleDelete = (id: number) => {
MySwal.fire({ MySwal.fire({
title: "Hapus user ini?", title: "Apakah anda ingin menghapus data user?",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: "#dc3545", confirmButtonColor: "#dc3545",
confirmButtonText: "Iya", confirmButtonText: "Iya",
cancelButtonText: "Tidak", cancelButtonText: "Tidak",
}).then((res) => { }).then((result) => {
if (res.isConfirmed) doDelete(id); if (result.isConfirmed) {
doDelete(id);
}
}); });
}; };
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button size="icon" variant="ghost"> <Button
<MoreVertical className="h-4 w-4" /> 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> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/add-experts/detail/${row?.original?.id}`}>
<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">
<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" />
<Eye className="w-4 h-4 me-1.5" /> View View
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<Link href={`/admin/add-experts/update/${row.original.id}`}> <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"> <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 <SquarePen className="w-4 h-4 me-1.5" />
Edit
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<DropdownMenuItem <DropdownMenuItem
onClick={() => handleDelete(row.original.userKeycloakId)} onClick={() => handleDelete(row.original.userKeycloakId)}
className="text-red-600 cursor-pointer hover:bg-red-300" 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 <Trash2 className="w-4 h-4 me-1.5" />
Delete
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );
@ -162,4 +154,4 @@ const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef<any>[]
}, },
]; ];
export default getColumns; export default columns;

View File

@ -2,6 +2,7 @@
import * as React from "react"; import * as React from "react";
import { import {
ColumnDef,
ColumnFiltersState, ColumnFiltersState,
PaginationState, PaginationState,
SortingState, SortingState,
@ -14,6 +15,7 @@ import {
useReactTable, useReactTable,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Table, Table,
TableBody, TableBody,
@ -23,6 +25,7 @@ import {
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { UserIcon } from "lucide-react"; import { UserIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -32,14 +35,43 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input"; 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 { useRouter, useSearchParams } from "next/navigation";
import TablePagination from "@/components/table/table-pagination"; import TablePagination from "@/components/table/table-pagination";
// import columns from "./column"; import columns from "./column";
import getColumns from "./column"; import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { listDataMedia } from "@/service/broadcast/broadcast";
import { listEnableCategory } from "@/service/content/content"; import { listEnableCategory } from "@/service/content/content";
import { Checkbox } from "@/components/ui/checkbox";
import { close, loading } from "@/config/swal";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { listDataExperts } from "@/service/experts/experts"; 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 AddExpertTable = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@ -65,8 +97,7 @@ const AddExpertTable = () => {
const [limit, setLimit] = React.useState(10); const [limit, setLimit] = React.useState(10);
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
// columns, columns,
columns: getColumns({ onRefresh: fetchData }),
onSortingChange: setSorting, onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters, onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
@ -252,11 +283,7 @@ const AddExpertTable = () => {
)) ))
) : ( ) : (
<TableRow> <TableRow>
<TableCell <TableCell colSpan={columns.length} className="h-24 text-center">
// colSpan={columns.length}
colSpan={table.getAllLeafColumns().length}
className="h-24 text-center"
>
No results. No results.
</TableCell> </TableCell>
</TableRow> </TableRow>

View File

@ -35,61 +35,32 @@ import {
import { error, loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { Eye, EyeOff } from "lucide-react"; 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({ const FormSchema = z.object({
name: z.string({ required_error: "Required" }), name: z.string({
username: z required_error: "Required",
.string({ required_error: "Required" }) }),
.refine((val) => !/\s/.test(val), { username: z.string({
message: "Username tidak boleh mengandung spasi", required_error: "Required",
}), }),
// .transform((val) => val.toLowerCase()), password: z.string({
required_error: "Required",
password: z }),
.string({ required_error: "Required" }) phoneNumber: z.string({
.min(8, "Minimal 8 karakter") required_error: "Required",
.regex(/[A-Z]/, "Harus mengandung huruf besar (A-Z)") }),
.regex(/[0-9]/, "Harus mengandung angka (0-9)") email: z.string({
.regex(/[^A-Za-z0-9]/, "Harus mengandung karakter spesial (!@#$%^&*)"), required_error: "Required",
}),
// confirmPassword: z.string({ required_error: "Required" }), skills: z.string({
required_error: "Required",
phoneNumber: z.string({ required_error: "Required" }), }),
email: z.string({ required_error: "Required" }), experiences: z.string({
skills: z.string({ required_error: "Required" }), required_error: "Required",
experiences: z.string({ required_error: "Required" }), }),
company: 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 = { export type Placements = {
index: number; index: number;
@ -103,7 +74,6 @@ export default function AddExpertForm() {
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
}); });
const [passwordStrength, setPasswordStrength] = useState("");
const [incrementId, setIncrementId] = useState(1); const [incrementId, setIncrementId] = useState(1);
const [placementRows, setPlacementRows] = useState<Placements[]>([ const [placementRows, setPlacementRows] = useState<Placements[]>([
{ index: 0, roleId: "", userLevelId: 0 }, { index: 0, roleId: "", userLevelId: 0 },
@ -291,19 +261,6 @@ 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 ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
@ -331,39 +288,6 @@ export default function AddExpertForm() {
)} )}
/> />
<FormField <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} control={form.control}
name="username" name="username"
render={({ field }) => ( render={({ field }) => (
@ -379,7 +303,7 @@ export default function AddExpertForm() {
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> */} />
<FormField <FormField
control={form.control} control={form.control}
name="phoneNumber" name="phoneNumber"
@ -425,69 +349,6 @@ export default function AddExpertForm() {
)} )}
/> />
<FormField <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} control={form.control}
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
@ -512,7 +373,7 @@ export default function AddExpertForm() {
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> */} />
<FormField <FormField
control={form.control} control={form.control}
name="skills" name="skills"

View File

@ -37,43 +37,32 @@ import { Eye, EyeOff } from "lucide-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
const FormSchema = z.object({ const FormSchema = z.object({
name: z.string().optional(), name: z.string({
username: z.string().optional(), required_error: "Required",
password: z.string().optional(), }),
phoneNumber: z.string().optional(), username: z.string({
email: z.string().optional(), required_error: "Required",
skills: z.string().optional(), }),
experiences: z.string().optional(), password: z.string({
company: z.string().optional(), 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",
// }),
// });
export type Placements = { export type Placements = {
index: number; index: number;
roleId?: string; roleId?: string;
@ -107,10 +96,6 @@ interface Detail {
createdAt: string; createdAt: string;
}; };
}; };
userRolePlacements?: {
roleId: number;
userLevelId: number;
}[];
} }
export default function UpdateExpertForm() { export default function UpdateExpertForm() {
@ -164,39 +149,6 @@ export default function UpdateExpertForm() {
initState(); initState();
}, [id]); }, [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>; if (!detail) return <div>Loading...</div>;
const togglePasswordType = () => { const togglePasswordType = () => {
@ -237,35 +189,18 @@ export default function UpdateExpertForm() {
const dataReq = { const dataReq = {
id: detail?.id, id: detail?.id,
firstName: data.name || detail.fullname, firstName: data.name,
username: data.username || detail.username, username: data.username,
email: data.email || detail.email, email: data.email,
password: data.password || undefined, password: data.password,
address: "", address: "",
roleId: "EXP-ID", roleId: "EXP-ID",
phoneNumber: data.phoneNumber || detail.phoneNumber, phoneNumber: data.phoneNumber,
userCompetencyId: userCompetencyId: data.skills,
data.skills || detail.userProfilesAdditional?.userCompetency?.id, userExperienceId: data.experiences,
userExperienceId: companyName: data.company,
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(); loading();
const res = await saveUserInternal(dataReq); const res = await saveUserInternal(dataReq);
const resData = res?.data?.data; const resData = res?.data?.data;
@ -387,15 +322,10 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Nama Lengkap</FormLabel> <FormLabel>Nama Lengkap</FormLabel>
{/* <Input <Input
defaultValue={detail?.fullname} defaultValue={detail?.fullname}
placeholder="Masukkan Nama Lengkap" placeholder="Masukkan Nama Lengkap"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
defaultValue={detail?.fullname}
placeholder="Masukkan Nama Lengkap"
/> />
<FormMessage /> <FormMessage />
@ -403,24 +333,18 @@ export default function UpdateExpertForm() {
)} )}
/> />
<FormField <FormField
disabled
control={form.control} control={form.control}
name="username" name="username"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
{/* <Input <Input
type="text" type="text"
defaultValue={detail?.username} defaultValue={detail?.username}
placeholder="Masukkan" placeholder="Masukkan"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="text"
defaultValue={detail?.username}
placeholder="Masukkan"
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@ -431,17 +355,11 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>No. HP</FormLabel> <FormLabel>No. HP</FormLabel>
{/* <Input <Input
type="number" type="number"
defaultValue={detail?.phoneNumber} defaultValue={detail?.phoneNumber}
placeholder="Masukkan No.Hp" placeholder="Masukkan No.Hp"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="number"
defaultValue={detail?.phoneNumber}
placeholder="Masukkan"
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -453,46 +371,17 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>Email</FormLabel>
{/* <Input <Input
type="email" type="email"
defaultValue={detail?.email} defaultValue={detail?.email}
placeholder="Masukkan email" placeholder="Masukkan email"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="email"
defaultValue={detail?.email}
placeholder="Masukkan email"
/> />
<FormMessage /> <FormMessage />
</FormItem> </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} control={form.control}
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
@ -517,7 +406,7 @@ export default function UpdateExpertForm() {
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> */} />
<FormField <FormField
control={form.control} control={form.control}
name="skills" name="skills"
@ -592,21 +481,12 @@ export default function UpdateExpertForm() {
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Nama Institusi/Perusahaan</FormLabel> <FormLabel>Nama Institusi/Perusahaan</FormLabel>
{/* <Input <Input
type="text" type="text"
value={detail?.userProfilesAdditional?.companyName || ""} value={detail?.userProfilesAdditional?.companyName || ""}
placeholder="Nama Institusi/Perusahaan" placeholder="Nama Institusi/Perusahaan"
onChange={field.onChange} onChange={field.onChange}
/> */}
<Input
{...field}
type="text"
defaultValue={
detail?.userProfilesAdditional?.companyName || ""
}
placeholder="Nama Institusi/Perusahaan"
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
@ -617,7 +497,6 @@ export default function UpdateExpertForm() {
{placementRows?.map((row: any) => ( {placementRows?.map((row: any) => (
<div key={row.index} className="flex items-center gap-2 my-2"> <div key={row.index} className="flex items-center gap-2 my-2">
<Select <Select
value={row.roleId}
onValueChange={(e) => onValueChange={(e) =>
handleSelectionChange(row.index, "roleId", e) handleSelectionChange(row.index, "roleId", e)
} }
@ -654,7 +533,6 @@ export default function UpdateExpertForm() {
</SelectContent> </SelectContent>
</Select> */} </Select> */}
<Select <Select
value={row.userLevelId}
onValueChange={(e) => onValueChange={(e) =>
handleSelectionChange(row.index, "userLevelId", e) handleSelectionChange(row.index, "userLevelId", e)
} }
@ -684,7 +562,7 @@ export default function UpdateExpertForm() {
type="button" type="button"
size="md" size="md"
onClick={handleAddRow} onClick={handleAddRow}
disabled={placementRows.length >= 2} disabled={placementRows.length >= 2} // optional: disable button if already 1 row added
> >
Tambah Tambah
</Button> </Button>

View File

@ -59,12 +59,8 @@ import {
deleteMediaBlastCampaignAccount, deleteMediaBlastCampaignAccount,
saveMediaBlastCampaignAccountBulk, saveMediaBlastCampaignAccountBulk,
} from "@/service/broadcast/broadcast"; } from "@/service/broadcast/broadcast";
import { import { AdministrationUserList, getUserListAll } from "@/service/management-user/management-user";
AdministrationUserList,
getUserListAll,
} from "@/service/management-user/management-user";
import { close, loading, error, success, successCallback } from "@/config/swal"; import { close, loading, error, success, successCallback } from "@/config/swal";
import { Link } from "@/i18n/routing";
// Mock data for available accounts - replace with actual API call // Mock data for available accounts - replace with actual API call
const availableAccounts = [ const availableAccounts = [
@ -102,8 +98,7 @@ const AccountListTable = () => {
const [accountCategory, setAccountCategory] = React.useState<string>(""); const [accountCategory, setAccountCategory] = React.useState<string>("");
const [selectedAccount, setSelectedAccount] = React.useState<any[]>([]); const [selectedAccount, setSelectedAccount] = React.useState<any[]>([]);
const [selectedCategory, setSelectedCategory] = React.useState<string>(""); const [selectedCategory, setSelectedCategory] = React.useState<string>("");
const [availableAccountsList, setAvailableAccountsList] = const [availableAccountsList, setAvailableAccountsList] = React.useState<any[]>(availableAccounts);
React.useState<any[]>(availableAccounts);
const [usersList, setUsersList] = React.useState<any[]>([]); const [usersList, setUsersList] = React.useState<any[]>([]);
const table = useReactTable({ const table = useReactTable({
@ -221,7 +216,7 @@ const AccountListTable = () => {
// Handle custom selection - send campaignId and selected user IDs // Handle custom selection - send campaignId and selected user IDs
const request = { const request = {
mediaBlastCampaignId: campaignId, mediaBlastCampaignId: campaignId,
mediaBlastAccountIds: selectedAccount.map((acc) => acc.id), mediaBlastAccountIds: selectedAccount.map(acc => acc.id),
}; };
const response = await saveMediaBlastCampaignAccountBulk(request); const response = await saveMediaBlastCampaignAccountBulk(request);
if (response?.error) { if (response?.error) {
@ -270,15 +265,15 @@ const AccountListTable = () => {
setFiltered(temp); setFiltered(temp);
}; };
const removeSelectedAccount = (accountId: string) => { const removeSelectedAccount = (accountId: string) => {
setSelectedAccount(selectedAccount.filter((acc) => acc.id !== accountId)); setSelectedAccount(selectedAccount.filter(acc => acc.id !== accountId));
}; };
const getFilteredAccounts = () => { const getFilteredAccounts = () => {
if (accountCategory === "kategori" && selectedCategory) { if (accountCategory === "kategori" && selectedCategory) {
return availableAccountsList.filter( return availableAccountsList.filter(acc => acc.category === selectedCategory);
(acc) => acc.category === selectedCategory
);
} }
return availableAccountsList; return availableAccountsList;
}; };
@ -296,10 +291,7 @@ const AccountListTable = () => {
Pilih Akun Pilih Akun
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent <DialogContent size="md" className="max-w-xl max-h-[80vh] overflow-y-auto">
size="md"
className="max-w-xl max-h-[80vh] overflow-y-auto"
>
<DialogHeader> <DialogHeader>
<DialogTitle>Pilih Akun Untuk Campaign Ini</DialogTitle> <DialogTitle>Pilih Akun Untuk Campaign Ini</DialogTitle>
</DialogHeader> </DialogHeader>
@ -358,17 +350,15 @@ const AccountListTable = () => {
options={usersList.map((user: any) => ({ options={usersList.map((user: any) => ({
value: user.id, value: user.id,
label: `${user.fullname} (${user.role?.name})`, label: `${user.fullname} (${user.role?.name})`,
user: user, user: user
}))} }))}
value={selectedAccount.map((acc: any) => ({ value={selectedAccount.map((acc: any) => ({
value: acc.id, value: acc.id,
label: `${acc.fullname} (${acc.role?.name})`, label: `${acc.fullname} (${acc.role?.name})`,
user: acc, user: acc
}))} }))}
onChange={(selectedOptions: any) => { onChange={(selectedOptions: any) => {
const selectedUsers = selectedOptions const selectedUsers = selectedOptions ? selectedOptions.map((option: any) => option.user) : [];
? selectedOptions.map((option: any) => option.user)
: [];
setSelectedAccount(selectedUsers); setSelectedAccount(selectedUsers);
}} }}
placeholder="Cari dan pilih user..." placeholder="Cari dan pilih user..."
@ -386,10 +376,7 @@ const AccountListTable = () => {
<Label>User Terpilih ({selectedAccount.length}):</Label> <Label>User Terpilih ({selectedAccount.length}):</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{selectedAccount.map((acc) => ( {selectedAccount.map((acc) => (
<Badge <Badge key={acc.id} className="flex items-center gap-1">
key={acc.id}
className="flex items-center gap-1"
>
{acc.fullname} {acc.fullname}
<X <X
className="h-3 w-3 cursor-pointer" className="h-3 w-3 cursor-pointer"
@ -416,8 +403,7 @@ const AccountListTable = () => {
{accountCategory === "kategori" && selectedCategory && ( {accountCategory === "kategori" && selectedCategory && (
<div className="p-3 bg-green-50 rounded-md"> <div className="p-3 bg-green-50 rounded-md">
<p className="text-sm text-green-700"> <p className="text-sm text-green-700">
Semua akun dengan role "{selectedCategory.toUpperCase()}" Semua akun dengan role "{selectedCategory.toUpperCase()}" akan ditambahkan.
akan ditambahkan.
</p> </p>
</div> </div>
)} )}
@ -426,8 +412,7 @@ const AccountListTable = () => {
{accountCategory === "custom" && ( {accountCategory === "custom" && (
<div className="p-3 bg-purple-50 rounded-md"> <div className="p-3 bg-purple-50 rounded-md">
<p className="text-sm text-purple-700"> <p className="text-sm text-purple-700">
{selectedAccount.length} user terpilih akan ditambahkan ke {selectedAccount.length} user terpilih akan ditambahkan ke campaign ini.
campaign ini.
</p> </p>
</div> </div>
)} )}
@ -438,8 +423,7 @@ const AccountListTable = () => {
onClick={saveCampaignAccount} onClick={saveCampaignAccount}
disabled={ disabled={
!accountCategory || !accountCategory ||
(accountCategory === "custom" && (accountCategory === "custom" && selectedAccount.length < 1) ||
selectedAccount.length < 1) ||
(accountCategory === "kategori" && !selectedCategory) (accountCategory === "kategori" && !selectedCategory)
} }
> >
@ -457,47 +441,7 @@ const AccountListTable = () => {
</div> </div>
{/* === Filter Akun === */} {/* === Filter Akun === */}
<div className="flex flex-row justify-end"> <div className="flex 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> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button size="md" variant="outline"> <Button size="md" variant="outline">

View File

@ -6,6 +6,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { import {
Form, Form,
FormControl, FormControl,
FormDescription,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
@ -13,80 +14,55 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { 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 { import {
getMediaBlastCampaignPage,
saveMediaBlastAccount, saveMediaBlastAccount,
saveMediaBlastCampaign,
} from "@/service/broadcast/broadcast"; } from "@/service/broadcast/broadcast";
import { error } from "@/config/swal"; import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing"; import { useRouter } from "@/i18n/routing";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useEffect, useState } from "react"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
// ----------------------------
// ZOD SCHEMA (dinamis)
// ----------------------------
const FormSchema = z.object({ const FormSchema = z.object({
name: z.string({ required_error: "Required" }), name: z.string({
required_error: "Required",
}),
accountType: z accountType: z
.array(z.string()) .array(z.string())
.min(1, "Pilih minimal satu tipe akun"), .refine((value) => value.some((item) => item), {
message: "Required",
email: z.string().optional(), }),
whatsapp: z.string().optional(), accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], {
required_error: "Required",
campaignId: z.string({ required_error: "Required" }), }),
}).refine( email: z.string({
(data) => { required_error: "Required",
if (data.accountType.includes("email") && !data.email) return false; }),
return true; whatsapp: z.string({
}, required_error: "Required",
{ 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() { export default function CreateAccountForBroadcast() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
defaultValues: { defaultValues: { accountType: [] },
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>) => { const onSubmit = async (data: z.infer<typeof FormSchema>) => {
MySwal.fire({ MySwal.fire({
title: "Simpan Data", title: "Simpan Data",
@ -109,8 +85,10 @@ export default function CreateAccountForBroadcast() {
icon: "success", icon: "success",
confirmButtonColor: "#3085d6", confirmButtonColor: "#3085d6",
confirmButtonText: "OK", confirmButtonText: "OK",
}).then(() => { }).then((result) => {
router.push("/admin/broadcast/campaign-list/account-list"); if (result.isConfirmed) {
router.push("/admin/broadcast/campaign-list/account-list");
}
}); });
} }
@ -118,21 +96,20 @@ export default function CreateAccountForBroadcast() {
const reqData = { const reqData = {
accountName: data.name, accountName: data.name,
accountType: data.accountType.join(","), accountType: data.accountType.join(","),
emailAddress: data.email ?? "", accountCategory: data.accountCategory,
whatsappNumber: data.whatsapp ?? "", emailAddress: data.email,
campaignId: data.campaignId, whatsappNumber: data.whatsapp,
}; };
console.log("data", data);
console.log("REQ:", reqData);
const response = await saveMediaBlastAccount(reqData); const response = await saveMediaBlastAccount(reqData);
if (response?.error) { if (response?.error) {
error(response.message); error(response.message);
return; return false;
} }
successSubmit(); successSubmit();
}; };
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
@ -141,9 +118,7 @@ export default function CreateAccountForBroadcast() {
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4" className="space-y-3 bg-white rounded-sm p-4"
> >
<p className="font-semibold">Account</p> <p className="fonnt-semibold">Account</p>
{/* NAMA */}
<FormField <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -155,125 +130,172 @@ export default function CreateAccountForBroadcast() {
placeholder="Masukkan nama" placeholder="Masukkan nama"
onChange={field.onChange} onChange={field.onChange}
/> />
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
{/* CHECKBOX TIPE AKUN */}
<FormField <FormField
control={form.control} control={form.control}
name="accountType" name="accountType"
render={({ field }) => ( render={() => (
<FormItem> <FormItem>
<FormLabel>Tipe Akun</FormLabel> <FormLabel>Tipe Akun</FormLabel>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-2">
{/* WA */} {" "}
<div className="flex items-center gap-2"> <FormField
<Checkbox key="wa"
checked={field.value.includes("wa")} control={form.control}
onCheckedChange={(checked) => name="accountType"
checked render={({ field }) => {
? field.onChange([...field.value, "wa"]) return (
: field.onChange(field.value.filter((v) => v !== "wa")) <FormItem
} key="wa"
/> className="flex flex-row items-start space-x-3 space-y-0"
<label>Whatsapp</label> >
</div> <FormControl>
<Checkbox
{/* EMAIL */} checked={field.value?.includes("wa")}
<div className="flex items-center gap-2"> onCheckedChange={(checked) => {
<Checkbox return checked
checked={field.value.includes("email")} ? field.onChange([...field.value, "wa"])
onCheckedChange={(checked) => : field.onChange(
checked field.value?.filter(
? field.onChange([...field.value, "email"]) (value) => value !== "wa"
: field.onChange( )
field.value.filter((v) => v !== "email") );
) }}
} />
/> </FormControl>
<label>Email</label> <FormLabel className="font-normal">
</div> 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>
<FormMessage /> <FormMessage />
</FormItem> </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 <FormField
control={form.control} control={form.control}
name="campaignId" name="accountCategory"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem className="space-y-3">
<FormLabel>Campaign</FormLabel> <FormLabel>Kategori</FormLabel>
<FormControl> <FormControl>
<select <RadioGroup
className="w-full border rounded-md p-2 text-sm" onValueChange={field.onChange}
value={field.value} defaultValue={field.value}
onChange={field.onChange} className="flex flex-row gap-2"
> >
<option value="" className="text-slate-400"> <FormItem className="flex items-center space-x-3 space-y-0">
Pilih campaign <FormControl>
</option> <RadioGroupItem value="polri" />
</FormControl>
{campaigns.map((c: any) => ( <FormLabel className="font-normal">POLRI</FormLabel>
<option key={c.id} value={c.id}> </FormItem>
{c.title || `Campaign ${c.id}`} <FormItem className="flex items-center space-x-3 space-y-0">
</option> <FormControl>
))} <RadioGroupItem value="jurnalis" />
</select> </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> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </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}
/>
{/* BUTTON */} <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>
)}
/>
<div className="flex flex-row gap-2 mt-4 pt-4"> <div className="flex flex-row gap-2 mt-4 pt-4">
<Button type="button" variant="outline" color="destructive"> <Button
size="md"
type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel Cancel
</Button> </Button>
<Button type="submit" color="primary"> <Button size="md" type="submit" color="primary" className="text-xs">
Submit Submit
</Button> </Button>
</div> </div>
@ -282,380 +304,3 @@ export default function CreateAccountForBroadcast() {
</div> </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>
// );
// }

View File

@ -152,12 +152,12 @@ const CampaignListTable = () => {
<div className="flex justify-between mb-10 items-center"> <div className="flex justify-between mb-10 items-center">
<p className="text-xl font-medium text-default-900">Daftar Campaign</p> <p className="text-xl font-medium text-default-900">Daftar Campaign</p>
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
{/* <Link href="/admin/broadcast/campaign-list/account-list"> <Link href="/admin/broadcast/campaign-list/account-list">
<Button color="primary" size="md" className="text-sm"> <Button color="primary" size="md" className="text-sm">
<UserIcon /> <UserIcon />
Daftar Akun Daftar Akun
</Button> </Button>
</Link> */} </Link>
<Link href="/admin/broadcast/campaign-list/create"> <Link href="/admin/broadcast/campaign-list/create">
<Button color="primary" size="md" className="text-sm"> <Button color="primary" size="md" className="text-sm">
<NewCampaignIcon size={23} /> <NewCampaignIcon size={23} />

View File

@ -5,7 +5,7 @@ export default function CreateEmailBlast() {
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
<ContentBlast /> <ContentBlast type="email" />
</div> </div>
); );
} }

View File

@ -90,16 +90,16 @@ const columns: ColumnDef<any>[] = [
Detail Detail
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
<Link href={`/admin/broadcast/create/${row.original.id}`}> <Link href={`/admin/broadcast/email/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
Email & Whatsapp Blast Email Blast
</DropdownMenuItem> </DropdownMenuItem>
</Link> </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"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none cursor-pointer">
Whatsapp Blast Whatsapp Blast
</DropdownMenuItem> </DropdownMenuItem>
</Link> */} </Link>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import SiteBreadcrumb from "@/components/site-breadcrumb"; import SiteBreadcrumb from "@/components/site-breadcrumb";
import BroadcastTable from "./create/component/table"; import BroadcastTable from "./email/component/table";
import { PlusIcon } from "lucide-react"; import { PlusIcon } from "lucide-react";
import EscalationTable from "../../shared/communication/escalation/components/escalation-table"; 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 { useState } from "react";
import { Link } from "@/i18n/routing"; import { Link } from "@/i18n/routing";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import BroadcastEmailTable from "./create/component/table"; import BroadcastEmailTable from "./email/component/table";
import BroadcastWhatsAppTable from "./whatsapp/component/table"; import BroadcastWhatsAppTable from "./whatsapp/component/table";
export default function AdminBroadcast() { export default function AdminBroadcast() {

View File

@ -5,7 +5,7 @@ export default function CreateWABlast() {
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
{/* <ContentBlast /> */} <ContentBlast type="wa" />
</div> </div>
); );
} }

View File

@ -31,8 +31,6 @@ import {
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible"; 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>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -54,132 +52,12 @@ const columns: ColumnDef<any>[] = [
<span className="normal-case">{row.getValue("title")}</span> <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", accessorKey: "link",
header: "Link Berita", header: "Link Berita",
cell: ({ row }) => { cell: ({ row }) => (
const link = row.getValue<string>("link"); <span className="normal-case">{row.getValue("link")}</span>
),
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>
);
},
}, },
]; ];

View File

@ -116,18 +116,6 @@ const NewsDetailTable = () => {
onColumnVisibilityChange: setColumnVisibility, onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection, onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination, onPaginationChange: setPagination,
meta: {
updateData: (rowIndex: number, value: Partial<any>) => {
setDataTable((old) =>
old.map((row, index) =>
index === rowIndex ? { ...row, ...value } : row
)
);
},
refetchData: () => {
fetchData();
},
},
state: { state: {
sorting, sorting,
columnFilters, columnFilters,
@ -166,7 +154,7 @@ const NewsDetailTable = () => {
pageIndex: 0, pageIndex: 0,
pageSize: Number(showData), pageSize: Number(showData),
}); });
}, [page, showData, id]); }, [page, showData]);
async function fetchData() { async function fetchData() {
try { try {

View File

@ -1,15 +1,7 @@
import * as React from "react"; import * as React from "react";
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import { exportMediaTrackingToExcel } from "@/utils/export-media-tracking";
import { loading, close } from "@/config/swal"; import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { error } from "@/lib/swal";
import {
DownloadIcon,
Eye,
MoreVertical,
SquarePen,
Trash2,
} from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
DropdownMenu, DropdownMenu,
@ -53,82 +45,32 @@ const columns: ColumnDef<any>[] = [
cell: ({ row }) => <span>{row.getValue("title")}</span>, cell: ({ row }) => <span>{row.getValue("title")}</span>,
}, },
{ {
accessorKey: "resultTotal", accessorKey: "link",
header: () => <div className="text-center w-full">Total Artikel</div>, header: "Jumlah Amplifikasi",
cell: ({ row }) => { cell: ({ row }) => <span>{row.getValue("link")}</span>,
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: "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", // accessorKey: "status",
// header: "Status", // header: "Status",
// cell: ({ row }) => <span>{row.getValue("status")}</span>, // 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: "isProcessing", accessorKey: "status",
header: () => <div className="text-center">Status</div>, header: () => <div className="text-center">Status</div>,
cell: ({ row }) => { cell: ({ row }) => {
const raw = Boolean(row.getValue("isProcessing")); const raw = row.getValue("status");
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 ( let value: string | number = "-";
<div className="text-center">
<span if (typeof raw === "string" || typeof raw === "number") {
className={`px-2 py-1 rounded text-xs font-medium inline-block ${colorClass}`} value = raw;
> } else if (raw && typeof raw === "object") {
{statusText} value = JSON.stringify(raw);
</span> }
</div>
); return <div className="text-center">{value}</div>;
}, },
}, },
{ {
accessorKey: "createdAt", accessorKey: "createdAt",
header: () => <div className="text-center">Tanggal Penarikan</div>, header: () => <div className="text-center">Tanggal Penarikan</div>,
@ -168,31 +110,13 @@ const columns: ColumnDef<any>[] = [
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
<Link href={`/admin/media-tracking/detail/${row.original.id}`}> <Link href={`/admin/media-tracking/detail/${row.original.id}`}>
<DropdownMenuItem className="p-2 border-b cursor-pointer text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground items-center rounded-none">
<Eye className="w-4 h-4 me-1.5" /> <Eye className="w-4 h-4 me-1.5" />
View&nbsp; View&nbsp;
{row.original.mediaUpload.fileType.secondaryName && {row.original.mediaUpload.fileType.secondaryName &&
row.original.mediaUpload.fileType.secondaryName.toLowerCase()} row.original.mediaUpload.fileType.secondaryName.toLowerCase()}
</DropdownMenuItem> </DropdownMenuItem>
</Link> </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> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );

View File

@ -40,7 +40,7 @@ export default function TrackingBeritaCard() {
const initFecth = async () => { const initFecth = async () => {
loading(); loading();
const response = await listDataTracking(Number(showData), page - 1, search); const response = await listDataTracking(showData, page - 1);
const data = response?.data?.data; const data = response?.data?.data;
const newData = data?.content; const newData = data?.content;
setTotalPage(data?.totalPages || 1); setTotalPage(data?.totalPages || 1);
@ -56,85 +56,23 @@ export default function TrackingBeritaCard() {
setContent(response?.data?.data?.content || []); setContent(response?.data?.data?.content || []);
}; };
const handleInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value; const value = e.target.value;
setSearch(value); setSearch(value);
const response = await listDataTracking(Number(showData), 0, value); if (value.trim() === "") {
setContent(response?.data?.data?.content || []); initFecth();
} else {
fecthAll(value);
}
}; };
// const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// const value = e.target.value;
// setSearch(value);
// if (value.trim() === "") {
// initFecth();
// } else {
// fecthAll(value);
// }
// };
const handleSelect = (id: number) => { const handleSelect = (id: number) => {
setSelectedItems((prev) => setSelectedItems((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id] 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 () => { // const doSave = async () => {
// if (selectedItems.length === 0) { // if (selectedItems.length === 0) {
// toast("Pilih minimal 1 berita untuk disimpan."); // toast("Pilih minimal 1 berita untuk disimpan.");
@ -161,63 +99,48 @@ export default function TrackingBeritaCard() {
// } // }
// }; // };
// const doSave = async () => { const doSave = async () => {
// if (selectedItems.length === 0) { if (selectedItems.length === 0) {
// MySwal.fire( MySwal.fire(
// "Peringatan", "Peringatan",
// "Pilih minimal 1 berita untuk disimpan.", "Pilih minimal 1 berita untuk disimpan.",
// "warning" "warning"
// ); );
// return; return;
// } }
// try { try {
// loading(); loading();
// const promises = selectedItems.map((id) => const promises = selectedItems.map((id) =>
// mediaTrackingSave({ mediaTrackingSave({
// mediaUploadId: id, mediaUploadId: id,
// duration: 24, duration: 24,
// scrapingPeriod: 3, scrapingPeriod: 3,
// }) })
// ); );
// await Promise.all(promises); await Promise.all(promises);
// close(); close();
// await MySwal.fire({ await MySwal.fire({
// icon: "success", icon: "success",
// title: "Berhasil!", title: "Berhasil!",
// text: "Tracking berita berhasil ditambahkan.", text: "Tracking berita berhasil ditambahkan.",
// confirmButtonColor: "#2563eb", confirmButtonColor: "#2563eb",
// }); });
// setSelectedItems([]); setSelectedItems([]);
// initFecth(); initFecth();
// } catch (err: any) { } catch (err: any) {
// close(); close();
// MySwal.fire({ MySwal.fire({
// icon: "error", icon: "error",
// title: "Gagal!", title: "Gagal!",
// text: err?.message || "Terjadi kesalahan saat menyimpan data.", text: err?.message || "Terjadi kesalahan saat menyimpan data.",
// confirmButtonColor: "#dc2626", 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 ( return (
@ -265,7 +188,7 @@ export default function TrackingBeritaCard() {
<div className="text-sm text-blue-600 font-medium"> <div className="text-sm text-blue-600 font-medium">
{selectedItems.length} Item Terpilih{" "} {selectedItems.length} Item Terpilih{" "}
<span className="text-black"> <span className="text-black">
/ Tracking Berita tersisa {5 - selectedItems.length} / Tracking Berita tersisa {29 - selectedItems.length}
</span> </span>
</div> </div>
<Button className="bg-blue-600 text-white" onClick={doSave}> <Button className="bg-blue-600 text-white" onClick={doSave}>
@ -275,48 +198,6 @@ export default function TrackingBeritaCard() {
)} )}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"> <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?.length > 1 &&
content.map((item: any) => ( content.map((item: any) => (
<Card <Card
@ -341,7 +222,7 @@ export default function TrackingBeritaCard() {
</p> </p>
</Card> </Card>
))} ))}
</div> */} </div>
<div className="mt-3"> <div className="mt-3">
{content && content?.length > 0 ? ( {content && content?.length > 0 ? (
<CustomPagination <CustomPagination

View File

@ -109,9 +109,7 @@ const EventModal = ({
const pathname = usePathname(); const pathname = usePathname();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set()); 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 [audioFile, setAudioFile] = useState<File | null>(null);
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
@ -155,11 +153,9 @@ const EventModal = ({
}); });
// State untuk melacak apakah perubahan berasal dari checkbox Jenis Agenda // State untuk melacak apakah perubahan berasal dari checkbox Jenis Agenda
const [isUpdatingFromJenisAgenda, setIsUpdatingFromJenisAgenda] = const [isUpdatingFromJenisAgenda, setIsUpdatingFromJenisAgenda] = useState(false);
useState(false);
// State untuk melacak jenis perubahan spesifik // State untuk melacak jenis perubahan spesifik
const [jenisAgendaChangeType, setJenisAgendaChangeType] = const [jenisAgendaChangeType, setJenisAgendaChangeType] = useState<string>("");
useState<string>("");
const levelNumber = Number(getCookiesDecrypt("ulne")) || 0; const levelNumber = Number(getCookiesDecrypt("ulne")) || 0;
const userLevelId = getCookiesDecrypt("ulie"); const userLevelId = getCookiesDecrypt("ulie");
@ -265,11 +261,7 @@ const EventModal = ({
// useEffect untuk sinkronisasi checkbox modal dengan Jenis Agenda // useEffect untuk sinkronisasi checkbox modal dengan Jenis Agenda
useEffect(() => { useEffect(() => {
if ( if (listDest.length > 0 && isUpdatingFromJenisAgenda && jenisAgendaChangeType) {
listDest.length > 0 &&
isUpdatingFromJenisAgenda &&
jenisAgendaChangeType
) {
syncModalWithJenisAgenda(); syncModalWithJenisAgenda();
} }
}, [isUpdatingFromJenisAgenda, jenisAgendaChangeType]); }, [isUpdatingFromJenisAgenda, jenisAgendaChangeType]);
@ -281,39 +273,29 @@ const EventModal = ({
} }
}, [checkedLevels, isUpdatingFromJenisAgenda]); }, [checkedLevels, isUpdatingFromJenisAgenda]);
// Fungsi untuk update wilayahPublish berdasarkan checkbox modal // Fungsi untuk update wilayahPublish berdasarkan checkbox modal
const updateWilayahPublishFromModal = () => { const updateWilayahPublishFromModal = () => {
// Hanya update jika tidak sedang dalam proses update dari Jenis Agenda // Hanya update jika tidak sedang dalam proses update dari Jenis Agenda
if (!isUpdatingFromJenisAgenda && listDest.length > 0) { if (!isUpdatingFromJenisAgenda && listDest.length > 0) {
// Hitung item yang dipilih berdasarkan checkedLevels // Hitung item yang dipilih berdasarkan checkedLevels
const checkedPoldaCount = listDest.filter( const checkedPoldaCount = listDest.filter((item: any) =>
(item: any) => item.levelNumber === 2 &&
item.levelNumber === 2 && item.name !== "SATKER POLRI" &&
item.name !== "SATKER POLRI" && checkedLevels.has(Number(item.id))
checkedLevels.has(Number(item.id))
).length; ).length;
const checkedPolresCount = listDest.reduce((total: number, item: any) => { const checkedPolresCount = listDest.reduce((total: number, item: any) => {
if (item.subDestination) { if (item.subDestination) {
return ( return total + item.subDestination.filter((sub: any) => checkedLevels.has(Number(sub.id))).length;
total +
item.subDestination.filter((sub: any) =>
checkedLevels.has(Number(sub.id))
).length
);
} }
return total; return total;
}, 0); }, 0);
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
(item: any) => item.name === "SATKER POLRI" const checkedSatkerCount = satkerItem ? (
); (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
const checkedSatkerCount = satkerItem (satkerItem.subDestination?.filter((sub: any) => checkedLevels.has(Number(sub.id))).length || 0)
? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) + ) : 0;
(satkerItem.subDestination?.filter((sub: any) =>
checkedLevels.has(Number(sub.id))
).length || 0)
: 0;
// Checkbox aktif jika ADA item yang dipilih dalam kategori tersebut // Checkbox aktif jika ADA item yang dipilih dalam kategori tersebut
const hasSelectedPolda = checkedPoldaCount > 0; const hasSelectedPolda = checkedPoldaCount > 0;
@ -322,11 +304,10 @@ const EventModal = ({
// Update arrays untuk backend // Update arrays untuk backend
const newSelectedPolda = listDest const newSelectedPolda = listDest
.filter( .filter((item: any) =>
(item: any) => item.levelNumber === 2 &&
item.levelNumber === 2 && item.name !== "SATKER POLRI" &&
item.name !== "SATKER POLRI" && checkedLevels.has(Number(item.id))
checkedLevels.has(Number(item.id))
) )
.map((item: any) => String(item.id)); .map((item: any) => String(item.id));
@ -361,7 +342,7 @@ const EventModal = ({
setSelectedSatker(newSelectedSatker); setSelectedSatker(newSelectedSatker);
// Update wilayahPublish berdasarkan yang dipilih di modal // Update wilayahPublish berdasarkan yang dipilih di modal
setWilayahPublish((prev) => { setWilayahPublish(prev => {
const newState = { ...prev }; const newState = { ...prev };
// Update individual checkboxes // Update individual checkboxes
@ -372,12 +353,7 @@ const EventModal = ({
// Update checkbox "semua" berdasarkan level user // Update checkbox "semua" berdasarkan level user
if (levelNumber === 1) { if (levelNumber === 1) {
// Level 1: semua checkbox harus aktif (nasional, polda, polres, satker, international) // Level 1: semua checkbox harus aktif (nasional, polda, polres, satker, international)
newState.semua = newState.semua = newState.nasional && hasSelectedPolda && hasSelectedPolres && hasSelectedSatker && newState.international;
newState.nasional &&
hasSelectedPolda &&
hasSelectedPolres &&
hasSelectedSatker &&
newState.international;
} else if (levelNumber === 2) { } else if (levelNumber === 2) {
// Level 2: hanya polres yang perlu aktif // Level 2: hanya polres yang perlu aktif
newState.semua = hasSelectedPolres; newState.semua = hasSelectedPolres;
@ -398,7 +374,7 @@ const EventModal = ({
} }
}; };
// Fungsi untuk sinkronisasi checkbox modal dengan Jenis Agenda // Fungsi untuk sinkronisasi checkbox modal dengan Jenis Agenda
const syncModalWithJenisAgenda = () => { const syncModalWithJenisAgenda = () => {
// Hanya jalankan sinkronisasi jika perubahan berasal dari checkbox Jenis Agenda // Hanya jalankan sinkronisasi jika perubahan berasal dari checkbox Jenis Agenda
if (isUpdatingFromJenisAgenda) { if (isUpdatingFromJenisAgenda) {
@ -415,11 +391,7 @@ const EventModal = ({
} else if (jenisAgendaChangeType === "polres_checked") { } else if (jenisAgendaChangeType === "polres_checked") {
// Checklist semua polres, tapi hanya yang poldanya sudah di-checklist // Checklist semua polres, tapi hanya yang poldanya sudah di-checklist
listDest.forEach((item: any) => { listDest.forEach((item: any) => {
if ( if (item.levelNumber === 2 && item.name !== "SATKER POLRI" && newCheckedLevels.has(Number(item.id))) {
item.levelNumber === 2 &&
item.name !== "SATKER POLRI" &&
newCheckedLevels.has(Number(item.id))
) {
if (item.subDestination) { if (item.subDestination) {
item.subDestination.forEach((polres: any) => { item.subDestination.forEach((polres: any) => {
newCheckedLevels.add(Number(polres.id)); newCheckedLevels.add(Number(polres.id));
@ -429,9 +401,7 @@ const EventModal = ({
}); });
} else if (jenisAgendaChangeType === "satker_checked") { } else if (jenisAgendaChangeType === "satker_checked") {
// Checklist satker // Checklist satker
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) { if (satkerItem) {
newCheckedLevels.add(Number(satkerItem.id)); newCheckedLevels.add(Number(satkerItem.id));
if (satkerItem.subDestination) { if (satkerItem.subDestination) {
@ -464,12 +434,10 @@ const EventModal = ({
} }
} }
}); });
setWilayahPublish((prev) => ({ ...prev, polres: false })); setWilayahPublish(prev => ({ ...prev, polres: false }));
} else if (jenisAgendaChangeType === "satker_unchecked") { } else if (jenisAgendaChangeType === "satker_unchecked") {
// Clear satker dari checkedLevels // Clear satker dari checkedLevels
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) { if (satkerItem) {
newCheckedLevels.delete(Number(satkerItem.id)); newCheckedLevels.delete(Number(satkerItem.id));
if (satkerItem.subDestination) { if (satkerItem.subDestination) {
@ -521,9 +489,7 @@ const EventModal = ({
updatedLevels.delete(levelId); updatedLevels.delete(levelId);
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
const poldaItem = listDest.find( const poldaItem = listDest.find((item: any) => Number(item.id) === levelId) as any;
(item: any) => Number(item.id) === levelId
) as any;
if (poldaItem && poldaItem.subDestination) { if (poldaItem && poldaItem.subDestination) {
poldaItem.subDestination.forEach((polres: any) => { poldaItem.subDestination.forEach((polres: any) => {
updatedLevels.delete(Number(polres.id)); updatedLevels.delete(Number(polres.id));
@ -548,12 +514,7 @@ const EventModal = ({
const toggleWilayah = (key: string) => { const toggleWilayah = (key: string) => {
// Set flag bahwa perubahan berasal dari checkbox Jenis Agenda // Set flag bahwa perubahan berasal dari checkbox Jenis Agenda
setIsUpdatingFromJenisAgenda(true); setIsUpdatingFromJenisAgenda(true);
setJenisAgendaChangeType( setJenisAgendaChangeType(key + (wilayahPublish[key as keyof typeof wilayahPublish] ? "_unchecked" : "_checked"));
key +
(wilayahPublish[key as keyof typeof wilayahPublish]
? "_unchecked"
: "_checked")
);
setWilayahPublish((prev: any) => { setWilayahPublish((prev: any) => {
let newState = { ...prev }; let newState = { ...prev };
@ -590,21 +551,18 @@ const EventModal = ({
return newState; return newState;
} }
// Validasi khusus untuk POLRES // Validasi khusus untuk POLRES
if (key === "polres" && !prev[key]) { if (key === "polres" && !prev[key]) {
// Cek apakah ada POLDA yang sudah dipilih di modal // Cek apakah ada POLDA yang sudah dipilih di modal
const hasSelectedPolda = listDest.some( const hasSelectedPolda = listDest.some((item: any) =>
(item: any) => item.levelNumber === 2 &&
item.levelNumber === 2 && item.name !== "SATKER POLRI" &&
item.name !== "SATKER POLRI" && checkedLevels.has(Number(item.id))
checkedLevels.has(Number(item.id))
); );
if (!hasSelectedPolda) { if (!hasSelectedPolda) {
// Jika tidak ada POLDA yang dipilih, tampilkan peringatan dan batalkan // Jika tidak ada POLDA yang dipilih, tampilkan peringatan dan batalkan
alert( alert("Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES.");
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."
);
// Reset flag karena perubahan dibatalkan // Reset flag karena perubahan dibatalkan
setIsUpdatingFromJenisAgenda(false); setIsUpdatingFromJenisAgenda(false);
setJenisAgendaChangeType(""); setJenisAgendaChangeType("");
@ -640,9 +598,7 @@ const EventModal = ({
}); });
} else if (key === "satker") { } else if (key === "satker") {
// Clear satker // Clear satker
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI");
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) { if (satkerItem) {
newCheckedLevels.delete(Number(satkerItem.id)); newCheckedLevels.delete(Number(satkerItem.id));
if (satkerItem.subDestination) { if (satkerItem.subDestination) {
@ -659,12 +615,7 @@ const EventModal = ({
// Untuk level 1: semua, nasional, polda, polres, satker, international harus aktif // 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) { if (levelNumber === 1) {
newState.semua = newState.semua = newState.nasional && newState.polda && newState.polres && newState.satker && newState.international;
newState.nasional &&
newState.polda &&
newState.polres &&
newState.satker &&
newState.international;
} else if (levelNumber === 2) { } else if (levelNumber === 2) {
newState.semua = newState.polres; newState.semua = newState.polres;
} else { } else {
@ -819,7 +770,7 @@ const EventModal = ({
const onDeleteEventAction = async () => { const onDeleteEventAction = async () => {
try { try {
} catch (error) {} } catch (error) { }
}; };
const handleOpenDeleteModal = (eventId: string) => { const handleOpenDeleteModal = (eventId: string) => {
@ -894,6 +845,7 @@ const EventModal = ({
const resCsrf = await getCsrfToken(); const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token; const csrfToken = resCsrf?.data?.token;
console.log("CSRF TOKEN : ", csrfToken);
const headers = { const headers = {
"X-XSRF-TOKEN": csrfToken, "X-XSRF-TOKEN": csrfToken,
}; };
@ -998,7 +950,7 @@ const EventModal = ({
); );
}; };
const handleRemoveFile = (id: number) => {}; const handleRemoveFile = (id: number) => { };
async function doDelete(id: any) { async function doDelete(id: any) {
loading(); loading();
@ -1154,42 +1106,40 @@ const EventModal = ({
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label htmlFor="wilayahPublish">Jenis Agenda</Label> <Label htmlFor="wilayahPublish">Jenis Agenda</Label>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<div>
<Checkbox <div>
id="semua" <Checkbox
checked={wilayahPublish.semua} id="semua"
onCheckedChange={() => toggleWilayah("semua")} checked={wilayahPublish.semua}
/> onCheckedChange={() => toggleWilayah("semua")}
<label htmlFor="semua" className="ml-2 text-sm"> />
Semua <label htmlFor="semua" className="ml-2 text-sm">
</label> Semua
</div> </label>
</div>
{levelNumber === 1 && ( {levelNumber === 1 && (
<> <>
<div> <div>
<Checkbox <Checkbox
id="nasional" id="nasional"
checked={wilayahPublish.nasional} checked={wilayahPublish.nasional}
onCheckedChange={() => toggleWilayah("nasional")} onCheckedChange={() => toggleWilayah("nasional")}
/> />
<label <label htmlFor="nasional" className="ml-2 text-sm mr-2">
htmlFor="nasional" Nasional
className="ml-2 text-sm mr-2" </label>
> </div>
Nasional <div>
</label> <Checkbox
</div> id="polda"
<div> checked={wilayahPublish.polda}
<Checkbox onCheckedChange={() => toggleWilayah("polda")}
id="polda" />
checked={wilayahPublish.polda} <label htmlFor="polda" className="mx-2 text-sm mr-2">
onCheckedChange={() => toggleWilayah("polda")} Polda
/> </label>
<label htmlFor="polda" className="mx-2 text-sm mr-2"> </div>
Polda
</label>
</div>
</> </>
)} )}
@ -1208,31 +1158,29 @@ const EventModal = ({
{levelNumber === 1 && ( {levelNumber === 1 && (
<> <>
<div> <div>
<Checkbox <Checkbox
id="satker" id="satker"
checked={wilayahPublish.satker} checked={wilayahPublish.satker}
onCheckedChange={() => toggleWilayah("satker")} onCheckedChange={() => toggleWilayah("satker")}
/> />
<label htmlFor="satker" className="mx-2 text-sm mr-2"> <label htmlFor="satker" className="mx-2 text-sm mr-2">
Satker Satker
</label> </label>
</div> </div>
<div> <div>
<Checkbox <Checkbox
id="international" id="international"
checked={wilayahPublish.international} checked={wilayahPublish.international}
onCheckedChange={() => onCheckedChange={() => toggleWilayah("international")}
toggleWilayah("international") />
} <label
/> htmlFor="international"
<label className="ml-2 text-sm mr-2"
htmlFor="international" >
className="ml-2 text-sm mr-2" Internasional
> </label>
Internasional </div>
</label>
</div>
</> </>
)} )}
@ -1254,9 +1202,7 @@ const EventModal = ({
<div key={polda.id} className="border p-2"> <div key={polda.id} className="border p-2">
<Label className="flex items-center"> <Label className="flex items-center">
<Checkbox <Checkbox
checked={checkedLevels.has( checked={checkedLevels.has(Number(polda.id))}
Number(polda.id)
)}
onCheckedChange={() => onCheckedChange={() =>
handleCheckboxChange(Number(polda.id)) handleCheckboxChange(Number(polda.id))
} }
@ -1294,13 +1240,9 @@ const EventModal = ({
polda?.subDestination?.forEach( polda?.subDestination?.forEach(
(polres: any) => { (polres: any) => {
if (isChecked) { if (isChecked) {
updatedLevels.add( updatedLevels.add(Number(polres.id));
Number(polres.id)
);
} else { } else {
updatedLevels.delete( updatedLevels.delete(Number(polres.id));
Number(polres.id)
);
} }
} }
); );
@ -1310,27 +1252,18 @@ const EventModal = ({
/> />
Pilih Semua Pilih Semua
</Label> </Label>
{polda?.subDestination?.map( {polda?.subDestination?.map((polres: any) => (
(polres: any) => ( <Label key={polres.id} className="block mt-1">
<Label <Checkbox
key={polres.id} checked={checkedLevels.has(Number(polres.id))}
className="block mt-1" onCheckedChange={() =>
> handleCheckboxChange(Number(polres.id))
<Checkbox }
checked={checkedLevels.has( className="mr-2"
Number(polres.id) />
)} {polres.name}
onCheckedChange={() => </Label>
handleCheckboxChange( ))}
Number(polres.id)
)
}
className="mr-2"
/>
{polres.name}
</Label>
)
)}
</div> </div>
)} )}
</div> </div>
@ -1371,7 +1304,8 @@ const EventModal = ({
<Label>Video</Label> <Label>Video</Label>
<FileUploader <FileUploader
accept={{ accept={{
"video/*": [], "mp4/*": [],
"mov/*": [],
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .mp4 atau .mov." label="Upload file dengan format .mp4 atau .mov."
@ -1383,7 +1317,7 @@ const EventModal = ({
className="object-fill h-full w-full rounded-md" className="object-fill h-full w-full rounded-md"
src={file.url} src={file.url}
controls controls
title={`Video ${file.id}`} title={`Video ${file.id}`} // Mengganti alt dengan title
/> />
<div <div
key={index} key={index}
@ -1462,9 +1396,10 @@ const EventModal = ({
</div> </div>
<div> <div>
<Label>Teks</Label> <Label>Teks</Label>
<FileUploader <FileUploader
accept={{ accept={{
"application/pdf": [], "pdf/*": [],
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .pdf." label="Upload file dengan format .pdf."
@ -1520,7 +1455,8 @@ const EventModal = ({
/> />
<FileUploader <FileUploader
accept={{ accept={{
"audio/*": [], "mp3/*": [],
"wav/*": [],
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .mp3 atau .wav." label="Upload file dengan format .mp3 atau .wav."
@ -1546,8 +1482,7 @@ const EventModal = ({
type="button" type="button"
onClick={onPlayPause} onClick={onPlayPause}
disabled={isPlaying} disabled={isPlaying}
className={`flex items-center gap-2 ${ className={`flex items-center gap-2 ${isPlaying
isPlaying
? "bg-gray-300 cursor-not-allowed" ? "bg-gray-300 cursor-not-allowed"
: "bg-primary text-white" : "bg-primary text-white"
} p-2 rounded`} } p-2 rounded`}

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormAudioDetail from "@/components/form/content/audio-detail-form";
const AudioDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormAudioDetail />
</div>
</div>
);
};
export default AudioDetailPage;

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormAudioUpdate from "@/components/form/content/audio-update-form";
const AudioUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormAudioUpdate />
</div>
</div>
);
};
export default AudioUpdatePage;

View File

@ -0,0 +1,498 @@
import * as React from "react";
import { ColumnDef } from "@tanstack/react-table";
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { cn, getCookiesDecrypt } from "@/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns";
import { Link } from "@/components/navigation";
import { deleteMedia } from "@/service/content/content";
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");
const MySwal = withReactContent(Swal);
const userLevelId = getCookiesDecrypt("ulie");
const columns: ColumnDef<any>[] = [
{
accessorKey: "no",
header: t("no", { defaultValue: "No" }),
cell: ({ row }) => (
<div className="flex items-center gap-5">
<div className="flex-1 text-start">
<h4 className="text-sm font-medium text-default-600 whitespace-nowrap mb-1">
{row.getValue("no")}
</h4>
</div>
</div>
),
},
{
accessorKey: "title",
header: t("title", { defaultValue: "Title" }),
cell: ({ row }: { row: { getValue: (key: string) => string } }) => {
const title: string = row.getValue("title");
return (
<span className="whitespace-nowrap">
{title.length > 50 ? `${title.slice(0, 30)}...` : title}
</span>
);
},
},
{
accessorKey: "categoryName",
header: t("category-name", { defaultValue: "Category Name" }),
cell: ({ row }) => (
<span className="whitespace-nowrap">
{row.getValue("categoryName")}
</span>
),
},
{
accessorKey: "createdAt",
header: t("upload-date", { defaultValue: "Upload Date" }),
cell: ({ row }) => {
const createdAt = row.getValue("createdAt") as
| string
| number
| undefined;
const formattedDate =
createdAt && !isNaN(new Date(createdAt).getTime())
? format(new Date(createdAt), "dd-MM-yyyy HH:mm:ss")
: "-";
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
{
accessorKey: "creatorName",
header: t("creator-group", { defaultValue: "Creator Group" }),
cell: ({ row }) => (
<span className="whitespace-nowrap">{row.getValue("creatorName")}</span>
),
},
{
accessorKey: "fileTypeId",
header: "Jenis Konten",
cell: ({ row }) => {
const type = Number(row.getValue("fileTypeId"));
const name = row.original.fileTypeName;
const label =
type === 1
? "Image"
: type === 2
? "Audio Visual"
: type === 3
? "Text"
: type === 4
? "Audio"
: name || "-";
const color =
type === 1
? "bg-blue-100 text-blue-600"
: type === 2
? "bg-red-100 text-red-600"
: type === 3
? "bg-green-100 text-green-600"
: type === 4
? "bg-yellow-100 text-yellow-600"
: "bg-gray-200 text-gray-600";
return (
<Badge className={`${color} whitespace-nowrap rounded-full px-3`}>
{label}
</Badge>
);
},
},
{
accessorKey: "creatorGroupLevelName",
header: t("source", { defaultValue: "Source" }),
cell: ({ row }) => (
<span className="whitespace-nowrap">
{row.getValue("creatorGroupLevelName")}
</span>
),
},
{
accessorKey: "publishedOn",
header: t("published", { defaultValue: "Published" }),
cell: ({ row }) => {
const isPublish = row.original.isPublish;
const isPublishOnPolda = row.original.isPublishOnPolda;
const creatorGroupParentLevelId =
row.original.creatorGroupParentLevelId;
let displayText = "-";
if (isPublish && !isPublishOnPolda) {
displayText = "Mabes";
} else if (isPublish && isPublishOnPolda) {
if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Mabes & Satker";
} else {
displayText = "Mabes & Polda";
}
} else if (!isPublish && isPublishOnPolda) {
if (Number(creatorGroupParentLevelId) == 761) {
displayText = "Satker";
} else {
displayText = "Polda";
}
}
return (
<div className="text-center whitespace-nowrap" title={displayText}>
{displayText}
</div>
);
},
},
{
accessorKey: "statusName",
header: "Status",
cell: ({ row }) => {
const statusColors: Record<string, string> = {
diterima: "bg-green-100 text-green-600",
"menunggu review": "bg-orange-100 text-orange-600",
};
const colors = [
"bg-orange-100 text-orange-600",
"bg-orange-100 text-orange-600",
"bg-green-100 text-green-600",
"bg-blue-100 text-blue-600",
"bg-red-200 text-red-600",
];
const status =
Number(row.original?.statusId) == 2 &&
row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) &&
Number(row.original?.creatorGroupLevelId) != Number(userLevelId)
? "1"
: row.original?.statusId;
const statusStyles =
colors[Number(status)] || "bg-red-200 text-red-600";
// const statusStyles = statusColors[status] || "bg-red-200 text-red-600";
return (
<Badge
className={cn(
"rounded-full px-5 w-full whitespace-nowrap",
statusStyles
)}
>
{(Number(row.original?.statusId) == 2 &&
!row.original?.reviewedAtLevel !== null &&
!row.original?.reviewedAtLevel?.includes(
`:${Number(userLevelId)}:`
) &&
Number(row.original?.creatorGroupLevelId) !=
Number(userLevelId)) ||
(Number(row.original?.statusId) == 1 &&
Number(row.original?.needApprovalFromLevel) ==
Number(userLevelId))
? "Menunggu Review"
: row.original?.statusName}{" "}
</Badge>
);
},
},
{
id: "actions",
accessorKey: "action",
header: t("action", { defaultValue: "Action" }),
enableHiding: false,
cell: ({ row }) => {
const router = useRouter();
const MySwal = withReactContent(Swal);
const typeId = Number(row.original.fileTypeId);
// mapping route detail
const detailRoute =
typeId === 1
? `/contributor/content/satker/image/detail/${row.original.id}`
: typeId === 2
? `/contributor/content/satker/video/detail/${row.original.id}`
: typeId === 3
? `/contributor/content/satker/text/detail/${row.original.id}`
: typeId === 4
? `/contributor/content/satker/audio/detail/${row.original.id}`
: `/contributor/content/satker/detail/${row.original.id}`;
// mapping route update
const updateRoute =
typeId === 1
? `/contributor/content/satker/image/update/${row.original.id}`
: typeId === 2
? `/contributor/content/satker/video/update/${row.original.id}`
: typeId === 3
? `/contributor/content/satker/text/update/${row.original.id}`
: typeId === 4
? `/contributor/content/satker/audio/update/${row.original.id}`
: `/contributor/content/satker/update/${row.original.id}`;
async function doDelete(id: any) {
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 = Number(getCookiesDecrypt("urie")); // pastikan jadi number
React.useEffect(() => {
if (userLevelId !== undefined && roleId !== undefined) {
setIsMabesApprover(
Number(userLevelId) === 216 && Number(roleId) === 3
);
}
}, [userLevelId, roleId]);
const canEdit =
Number(row.original.uploadedById) === Number(userId) ||
isMabesApprover ||
roleId === 14;
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/satker/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={detailRoute}>
<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>
{/* {canEdit && (
<Link
href={`/contributor/content/satker/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>
)} */}
{canEdit && (
<Link href={updateRoute}>
<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>
</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;
};
export default useTableColumns;

View File

@ -0,0 +1,572 @@
"use client";
import * as React from "react";
import {
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ChevronDown, Search } from "lucide-react";
import { getCookiesDecrypt } from "@/lib/utils";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupText } from "@/components/ui/input-group";
import TablePagination from "@/components/table/table-pagination";
import { listDataAll, listDataSatker, listEnableCategory } from "@/service/content/content";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { format } from "date-fns";
import useTableColumns from "./columns";
import { Label } from "@/components/ui/label";
import { useRouter } from "@/i18n/routing";
import { useSearchParams } from "next/navigation";
const TableSatker = () => {
const router = useRouter();
const searchParams = useSearchParams();
const MySwal = withReactContent(Swal);
const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1);
const [totalPage, setTotalPage] = React.useState(1);
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("10");
const [page, setPage] = React.useState(1);
const [search, setSearch] = React.useState("");
const searchTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
const [categories, setCategories] = React.useState<any[]>([]);
const [selectedCategories, setSelectedCategories] = React.useState<number[]>(
[]
);
const [categoryFilter, setCategoryFilter] = React.useState<string>("");
const [statusFilter, setStatusFilter] = React.useState<any[]>([]);
const [startDate, setStartDate] = React.useState("");
const [endDate, setEndDate] = React.useState("");
const [filterByCreator, setFilterByCreator] = React.useState("");
const [filterBySource, setFilterBySource] = React.useState("");
const [filterByCreatorGroup, setFilterByCreatorGroup] = React.useState("");
const [typeId, setTypeId] = React.useState<string>("");
const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie");
const columns = useTableColumns();
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: Number(showData),
});
const table = useReactTable({
data: dataTable,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination,
},
});
// Sync page dari URL (?page=)
React.useEffect(() => {
const pageFromUrl = searchParams?.get("page");
if (pageFromUrl) setPage(Number(pageFromUrl));
}, [searchParams]);
// Ambil kategori sekali di awal
React.useEffect(() => {
getCategories();
}, []);
// Fetch data ketika filter/pagination berubah
React.useEffect(() => {
fetchData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
categoryFilter,
statusFilter,
startDate,
endDate,
showData,
page,
typeId,
filterByCreatorGroup,
]);
async function getCategories() {
try {
// ini mengikuti kode awal, type "2" (misal: kategori video)
const category = await listEnableCategory("2");
const resCategory = category?.data?.data?.content;
setCategories(resCategory || []);
} catch (error) {
console.error("Error fetching categories:", error);
}
}
async function fetchData(showLoader = false, customSearch?: string) {
const formattedStartDate = startDate
? format(new Date(startDate), "yyyy-MM-dd")
: "";
const formattedEndDate = endDate
? format(new Date(endDate), "yyyy-MM-dd")
: "";
try {
if (showLoader) {
MySwal.fire({
title: "Memuat data...",
html: "Mohon tunggu sebentar",
allowOutsideClick: false,
didOpen: () => MySwal.showLoading(),
});
}
const isForSelf = Number(roleId) === 4;
const isApproval = !isForSelf;
const res = await listDataSatker(
isForSelf,
isApproval,
page - 1, // page (0-based)
parseInt(showData) || 10, // limit
search, // search (kalau diperlukan di backend)
typeId, // typeId: 1=image, 2=video, 3=text, 4=audio
statusFilter?.join(","), // statusId
statusFilter?.join(",")?.includes("1") ? userLevelId : "",
filterByCreator,
filterBySource,
formattedStartDate,
formattedEndDate,
customSearch ?? search // title
);
const data = res?.data?.data;
const contentData = data?.content || [];
const newData = contentData.map((item: any, index: number) => ({
...item,
no: (page - 1) * Number(showData) + index + 1,
}));
setDataTable([...newData]);
setTotalData(data?.totalElements || 0);
setTotalPage(data?.totalPages || 1);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
MySwal.close();
}
}
// === HANDLERS ===
const handleCheckboxChange = (categoryId: number) => {
setSelectedCategories((prev) =>
prev.includes(categoryId)
? prev.filter((id) => id !== categoryId)
: [...prev, categoryId]
);
setCategoryFilter((prev) => {
const updated = prev.split(",").filter(Boolean).map(Number);
const newList = updated.includes(categoryId)
? updated.filter((id) => id !== categoryId)
: [...updated, categoryId];
return newList.join(",");
});
};
function handleStatusCheckboxChange(value: any) {
setStatusFilter((prev: any) =>
prev.includes(value)
? prev.filter((status: any) => status !== value)
: [...prev, value]
);
}
// Search dengan debounce 2 detik
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearch(value);
if (searchTimeoutRef.current) {
clearTimeout(searchTimeoutRef.current);
}
searchTimeoutRef.current = setTimeout(() => {
// setiap kali search, reset ke page 1
setPage(1);
fetchData(true, value);
}, 2000);
};
const handleSearchFilterByCreator = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const value = e.target.value;
setFilterByCreator(value);
setPage(1);
fetchData(true);
};
const handleSearchFilterBySource = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const value = e.target.value;
setFilterBySource(value);
setPage(1);
fetchData(true);
};
// 🧹 Reset semua filter
const handleResetFilters = () => {
setSelectedCategories([]);
setCategoryFilter("");
setStatusFilter([]);
setStartDate("");
setEndDate("");
setFilterByCreator("");
setFilterBySource("");
setFilterByCreatorGroup("");
setTypeId("");
setPage(1);
fetchData(true);
};
return (
<div className="w-full overflow-x-auto">
<div className="flex flex-col md:flex-row md:justify-between items-center md:px-5">
{/* 🔍 Search bar */}
<div className="w-full md:w-[200px] lg:w-[300px] px-2">
<InputGroup merged>
<InputGroupText className="bg-transparent dark:border-secondary dark:group-focus-within:border-secondary">
<Search className="h-6 w-6 dark:text-white" />
</InputGroupText>
<Input
type="text"
placeholder="Cari Judul..."
className="bg-transparent dark:border-secondary dark:placeholder-white/80 dark:focus:border-secondary dark:text-white text-[16px]"
value={search}
onChange={handleSearch}
/>
</InputGroup>
</div>
{/* Filter & Columns & ShowData */}
<div className="flex flex-wrap items-center gap-3 mt-2 md:mt-0">
{/* Show Data Dropdown */}
<div className="mx-3">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="md" variant="outline">
{showData} Data
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={showData}
onValueChange={(value) => {
setShowData(value);
setPagination((prev) => ({
...prev,
pageSize: Number(value),
pageIndex: 0,
}));
setPage(1);
}}
>
<DropdownMenuRadioItem value="10">
10 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="50">
50 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="100">
100 Data
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="250">
250 Data
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* Jenis Konten (typeId) */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="md">
Jenis Konten <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 text-sm">
<DropdownMenuRadioGroup
value={typeId}
onValueChange={(value) => {
setTypeId(value);
setPage(1);
fetchData(true);
}}
>
<DropdownMenuRadioItem value="">
Semua Jenis
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="1">
Gambar / Image
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="2">
Video
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="3">
Teks / Artikel
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="4">
Audio
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
{/* Filter Dropdown */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="md">
Filter <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-64 h-[380px] overflow-y-auto"
>
{/* 🧹 Reset All Button */}
<div className="flex justify-end px-3 pt-2 pb-1">
<Button
variant="ghost"
size="sm"
className="text-xs text-gray-700 hover:bg-red-200 dark:text-white"
onClick={handleResetFilters}
>
Reset Semua Filter
</Button>
</div>
<Label className="ml-2">Kategori</Label>
{categories.length > 0 ? (
categories.map((category) => (
<div
key={category.id}
className="flex items-center px-4 py-1"
>
<input
type="checkbox"
id={`category-${category.id}`}
className="mr-2"
checked={selectedCategories.includes(category.id)}
onChange={() => handleCheckboxChange(category.id)}
/>
<label
htmlFor={`category-${category.id}`}
className="text-sm"
>
{category.name}
</label>
</div>
))
) : (
<p className="text-sm text-gray-500 px-4 py-2">
Tidak ada kategori.
</p>
)}
<div className="mx-2 my-1">
<Label>Tanggal Awal</Label>
<Input
type="date"
value={startDate}
onChange={(e) => {
setStartDate(e.target.value);
setPage(1);
}}
/>
</div>
<div className="mx-2 my-1">
<Label>Tanggal Akhir</Label>
<Input
type="date"
value={endDate}
onChange={(e) => {
setEndDate(e.target.value);
setPage(1);
}}
/>
</div>
<div className="mx-2 my-1">
<Label>Kreator</Label>
<Input
placeholder="Nama kreator..."
value={filterByCreator}
onChange={handleSearchFilterByCreator}
/>
</div>
<div className="mx-2 my-1">
<Label>Sumber</Label>
<Input
placeholder="Nama sumber..."
value={filterBySource}
onChange={handleSearchFilterBySource}
/>
</div>
<Label className="ml-2 mt-2">Status</Label>
{[1, 2, 3, 4].map((id) => {
const label =
id === 1
? "Menunggu Review"
: id === 2
? "Diterima"
: id === 3
? "Minta Update"
: "Ditolak";
return (
<div key={id} className="flex items-center px-4 py-1">
<input
type="checkbox"
id={`status-${id}`}
className="mr-2"
checked={statusFilter.includes(id)}
onChange={() => handleStatusCheckboxChange(id)}
/>
<label htmlFor={`status-${id}`} className="text-sm">
{label}
</label>
</div>
);
})}
</DropdownMenuContent>
</DropdownMenu>
{/* Columns Toggle */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="md">
Columns <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* === TABLE === */}
<Table className="overflow-hidden mt-3">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="bg-default-200">
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
className="h-[75px]"
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Tidak ada hasil ditemukan.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<TablePagination
table={table}
totalData={totalData}
totalPage={totalPage}
/>
</div>
);
};
export default TableSatker;

View File

@ -0,0 +1,15 @@
import FormVideo from "@/components/form/content/video-form";
import SiteBreadcrumb from "@/components/site-breadcrumb";
const VideoCreatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormVideo />
</div>
</div>
);
};
export default VideoCreatePage;

View File

@ -0,0 +1,15 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
const ImageDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormImageDetail />
</div>
</div>
);
};
export default ImageDetailPage;

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
const ImageUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormImageUpdate />
</div>
</div>
);
};
export default ImageUpdatePage;

View File

@ -0,0 +1,11 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Media Hub | POLRI",
description: "Media Hub merupakan situs resmi milik Divisi Humas Polri di mana di dalamnya berisi konten-konten yang dapat diakses secara gratis oleh Internal Polri, Jurnalis, Masyarakat Umum, dan KSP.",
};
const Layout = ({ children }: { children: React.ReactNode }) => {
return <>{children}</>;
};
export default Layout;

View File

@ -0,0 +1,88 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import SiteBreadcrumb from "@/components/site-breadcrumb";
import TableImage from "./components/table-satker";
import { UploadIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Icon } from "@iconify/react/dist/iconify.js";
import TableVideo from "./components/table-satker";
import { Link } from "@/components/navigation";
import { useTranslations } from "next-intl";
const ReactTableVideoPage = () => {
const t = useTranslations("AnalyticsDashboard");
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<Card className="py-4 px-3">
<div className="flex flex-wrap justify-between items-center px-5">
<div className="flex flex-row items-center text-xl font-medium text-default-900 gap-2">
<div>
<Icon icon="icon-park-outline:check-one" />
</div>
<div>
<p>
<span className="text-red-500">{t("average", { defaultValue: "Average" })} :0</span>
</p>
<p className="text-sm">
{t("Hasil_unggah_disetujui_hari_ini", { defaultValue: "Hasil Unggah Disetujui Hari Ini" })}
</p>
</div>
</div>
<div className="flex flex-row items-center text-xl font-medium text-default-900 gap-2">
<div>
<Icon icon="material-symbols:settings-backup-restore-rounded" />
</div>
<div>
<p>
<span className="text-red-500">{t("average", { defaultValue: "Average" })} :0</span>
</p>
<p className="text-sm">{t("Hasil_unggah_direvisi_hari_ini", { defaultValue: "Hasil Unggah Direvisi Hari Ini" })}</p>
</div>
</div>
<div className="flex flex-row items-center text-xl font-medium text-default-900 gap-2">
<div>
<Icon icon="healthicons:no-outline" />
</div>
<div>
<p>
<span className="text-red-500">{t("average", { defaultValue: "Average" })} :0</span>
</p>
<p className="text-sm">{t("Hasil_unggah_ditolak_hari_ini", { defaultValue: "Hasil Unggah Ditolak Hari Ini" })}</p>
</div>
</div>
</div>
</Card>
<Card>
<CardHeader className="border-b border-solid border-default-200 mb-6">
<CardTitle>
<div className="flex items-center">
<div className="flex-1 text-xl font-medium text-default-900">
Satker
</div>
<div className="flex-none">
{/* <Link href={"/contributor/content/video/create"}>
<Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" />
{t("create-video", { defaultValue: "Create Video" })}
</Button>
</Link> */}
{/* <Button color="primary" className="text-white ml-3">
<UploadIcon />
Unggah Video Dengan AI
</Button> */}
</div>
</div>
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<TableVideo />
</CardContent>
</Card>
</div>
</div>
);
};
export default ReactTableVideoPage;

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormTeksDetail from "@/components/form/content/teks-detail-form";
const TeksDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTeksDetail />
</div>
</div>
);
};
export default TeksDetailPage;

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormTeksUpdate from "@/components/form/content/teks-update-form";
const TeksUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormTeksUpdate />
</div>
</div>
);
};
export default TeksUpdatePage;

View File

@ -0,0 +1,18 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormVideoUpdate from "@/components/form/content/video-update-form";
import FormVideoSeo from "@/components/form/content/video-update-seo";
const VideoUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormVideoSeo />
</div>
</div>
);
};
export default VideoUpdatePage;

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormVideoUpdate from "@/components/form/content/video-update-form";
const VideoUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormVideoUpdate />
</div>
</div>
);
};
export default VideoUpdatePage;

View File

@ -0,0 +1,16 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormVideoDetail from "@/components/form/content/video-detail-form";
const VideoDetailPage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormVideoDetail />
</div>
</div>
);
};
export default VideoDetailPage;

View File

@ -0,0 +1,17 @@
import SiteBreadcrumb from "@/components/site-breadcrumb";
import FormImageDetail from "@/components/form/content/image-detail-form";
import FormImageUpdate from "@/components/form/content/image-update-form";
import FormVideoUpdate from "@/components/form/content/video-update-form";
const VideoUpdatePage = async () => {
return (
<div>
<SiteBreadcrumb />
<div className="space-y-4">
<FormVideoUpdate />
</div>
</div>
);
};
export default VideoUpdatePage;

View File

@ -153,7 +153,7 @@ const useTableColumns = ({
// try { // try {
// loading(); // loading();
// const response = await axios.get( // const response = await axios.get(
// `https://new.netidhub.com/api/media/report/download?id=${id}`, // `https://netidhub.com/api/media/report/download?id=${id}`,
// { // {
// responseType: "blob", // responseType: "blob",
// } // }

View File

@ -22,9 +22,7 @@ import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
const useTableColumns = ( const useTableColumns = () => {
activeTab: "ta" | "daily" | "special" | "mabes-koor",
) => {
const t = useTranslations("Table"); const t = useTranslations("Table");
const columns: ColumnDef<any>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -115,31 +113,6 @@ const useTableColumns = (
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const roleId = Number(getCookiesDecrypt("urie")) || 0; 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) { async function deleteProcess(id: any) {
loading(); loading();
const resDelete = await deleteTaskTa(id); const resDelete = await deleteTaskTa(id);
@ -192,11 +165,7 @@ const useTableColumns = (
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <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}`}> <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"> <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" /> <Eye className="w-4 h-4 me-1.5" />
@ -204,16 +173,15 @@ const useTableColumns = (
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
)} )}
{roleId == 11 || {roleId == 11 && (
(roleId == 3 && ( <Link href={`/contributor/task-ta/update/${row.original.id}`}>
<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">
<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" />
<SquarePen className="w-4 h-4 me-1.5" /> Edit
Edit </DropdownMenuItem>
</DropdownMenuItem> </Link>
</Link> )}
))} {(roleId == 11 || roleId == 12 || roleId == 19) && (
{(roleId == 12 || roleId == 19) && (
<Link <Link
href={`/contributor/task-ta/upload-task/${row.original.id}`} href={`/contributor/task-ta/upload-task/${row.original.id}`}
> >
@ -223,16 +191,15 @@ const useTableColumns = (
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
)} )}
{roleId == 11 || {roleId == 11 && (
(roleId == 3 && ( <DropdownMenuItem
<DropdownMenuItem onClick={() => TaskDelete(row.original.id)}
onClick={() => TaskDelete(row.original.id)} className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
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" />
<Trash2 className="w-4 h-4 me-1.5" /> Delete
Delete </DropdownMenuItem>
</DropdownMenuItem> )}
))}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );

View File

@ -35,19 +35,10 @@ const TaskTaPage = () => {
<CardTitle> <CardTitle>
<div className="flex flex-col sm:flex-row lg:flex-row lg:items-center"> <div className="flex flex-col sm:flex-row lg:flex-row lg:items-center">
<div className="flex-1 text-xl font-medium text-default-900"> <div className="flex-1 text-xl font-medium text-default-900">
{t("tabel", { defaultValue: "Tabel" })}{" "} {t("tabel", { defaultValue: "Tabel" })} {t("task-ta", { defaultValue: "Task Ta" })}
{t("task-ta", { defaultValue: "Task Ta" })}
</div> </div>
<div className="flex-none"> <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"}> <Link href={"/contributor/task-ta/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" /> <UploadIcon size={18} className="mr-2" />

View File

@ -5,11 +5,7 @@ import { Button } from "@/components/ui/button";
import { UploadIcon } from "lucide-react"; import { UploadIcon } from "lucide-react";
import SiteBreadcrumb from "@/components/site-breadcrumb"; import SiteBreadcrumb from "@/components/site-breadcrumb";
import { Link } from "@/components/navigation"; import { Link } from "@/components/navigation";
import { import { checkAuthorization, checkLoginSession } from "@/lib/utils";
checkAuthorization,
checkLoginSession,
getCookiesDecrypt,
} from "@/lib/utils";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@ -23,7 +19,6 @@ const TaskPage = () => {
initState(); initState();
}, []); }, []);
const levelNumber = getCookiesDecrypt("ulne");
return ( return (
<div> <div>
@ -34,28 +29,16 @@ const TaskPage = () => {
<CardTitle> <CardTitle>
<div className="flex flex-col sm:flex-row lg:flex-row lg:items-center"> <div className="flex flex-col sm:flex-row lg:flex-row lg:items-center">
<div className="flex-1 text-xl font-medium text-default-900"> <div className="flex-1 text-xl font-medium text-default-900">
{t("tabel", { defaultValue: "Tabel" })}{" "} {t("tabel", { defaultValue: "Tabel" })} {t("task", { defaultValue: "Task" })}
{t("task", { defaultValue: "Task" })}
</div> </div>
{Number(levelNumber) !== 3 && ( <div className="flex-none">
<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"}> <Link href={"/contributor/task/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" /> <UploadIcon size={18} className="mr-2" />
{t("create-task", { defaultValue: "Create Task" })} {t("create-task", { defaultValue: "Create Task" })}
</Button> </Button>
</Link> </Link>
</div> */} </div>
</div> </div>
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>

View File

@ -24,7 +24,6 @@ import ReportTable from "../contributor/report/components/report-table";
const DashboardPage = () => { const DashboardPage = () => {
const t = useTranslations("AnalyticsDashboard"); const t = useTranslations("AnalyticsDashboard");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const levelNumber = Number(getCookiesDecrypt("ulne"));
return Number(roleId) == 2 || Number(roleId) == 11 || Number(roleId) == 12 ? ( return Number(roleId) == 2 || Number(roleId) == 11 || Number(roleId) == 12 ? (
<div> <div>
@ -55,23 +54,18 @@ const DashboardPage = () => {
> >
{t("schedule", { defaultValue: "Schedule" })} {t("schedule", { defaultValue: "Schedule" })}
</TabsTrigger> </TabsTrigger>
{levelNumber !== 3 && ( <TabsTrigger
<> value="indeks"
<TabsTrigger className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
value="indeks" >
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6" {t("indeks", { defaultValue: "Indeks" })}
> </TabsTrigger>
{t("indeks", { defaultValue: "Indeks" })} <TabsTrigger
</TabsTrigger> value="report"
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6"
<TabsTrigger >
value="report" {t("report", { defaultValue: "Report" })}
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground rounded-md px-6" </TabsTrigger>
>
{t("report", { defaultValue: "Report" })}
</TabsTrigger>
</>
)}
</TabsList> </TabsList>
</Card> </Card>
<TabsContent value="routine-task"> <TabsContent value="routine-task">
@ -81,24 +75,18 @@ const DashboardPage = () => {
<CardContent className="p-4"> <CardContent className="p-4">
<div className="grid md:grid-cols-3 gap-4"> <div className="grid md:grid-cols-3 gap-4">
<StatisticsBlock <StatisticsBlock
title={t("Hasil_unggah_disetujui_hari_ini", { title={t("Hasil_unggah_disetujui_hari_ini", { defaultValue: "Hasil Unggah Disetujui Hari Ini" })}
defaultValue: "Hasil Unggah Disetujui Hari Ini",
})}
total="3,564" total="3,564"
className="bg-info/10 border-none shadow-none" className="bg-info/10 border-none shadow-none"
/> />
<StatisticsBlock <StatisticsBlock
title={t("Hasil_unggah_direvisi_hari_ini", { title={t("Hasil_unggah_direvisi_hari_ini", { defaultValue: "Hasil Unggah Direvisi Hari Ini" })}
defaultValue: "Hasil Unggah Direvisi Hari Ini",
})}
total="564" total="564"
className="bg-warning/10 border-none shadow-none" className="bg-warning/10 border-none shadow-none"
chartColor="#FB8F65" chartColor="#FB8F65"
/> />
<StatisticsBlock <StatisticsBlock
title={t("Hasil_unggah_ditolak_hari_ini", { title={t("Hasil_unggah_ditolak_hari_ini", { defaultValue: "Hasil Unggah Ditolak Hari Ini" })}
defaultValue: "Hasil Unggah Ditolak Hari Ini",
})}
total="+5.0%" total="+5.0%"
className="bg-primary/10 border-none shadow-none" className="bg-primary/10 border-none shadow-none"
chartColor="#2563eb" chartColor="#2563eb"
@ -113,9 +101,7 @@ const DashboardPage = () => {
<Card> <Card>
<CardHeader className="flex flex-row items-center"> <CardHeader className="flex flex-row items-center">
<CardTitle className="flex-1 text-lg"> <CardTitle className="flex-1 text-lg">
{t("Total-Content-Production", { {t("Total-Content-Production", { defaultValue: "Total Content Production" })}
defaultValue: "Total Content Production",
})}
</CardTitle> </CardTitle>
<DashboardDropdown /> <DashboardDropdown />
</CardHeader> </CardHeader>
@ -127,9 +113,7 @@ const DashboardPage = () => {
<div className="lg:col-span-8 col-span-12"> <div className="lg:col-span-8 col-span-12">
<Card> <Card>
<CardHeader className="flex flex-row items-center"> <CardHeader className="flex flex-row items-center">
<CardTitle className="flex-1"> <CardTitle className="flex-1">{t("tabel", { defaultValue: "Tabel" })}</CardTitle>
{t("tabel", { defaultValue: "Tabel" })}
</CardTitle>
{/* <DashboardDropdown /> */} {/* <DashboardDropdown /> */}
</CardHeader> </CardHeader>
<CardContent className="p-0"> <CardContent className="p-0">
@ -148,26 +132,14 @@ const DashboardPage = () => {
<div className="flex-1 text-xl font-medium text-default-900"> <div className="flex-1 text-xl font-medium text-default-900">
Tabel Penugasan Tabel Penugasan
</div> </div>
{Number(levelNumber) !== 3 && ( <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>
<Link href={"/contributor/task/create"}> <Link href={"/contributor/task/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon /> <UploadIcon />
Buat Penugasan Buat Penugasan
</Button> </Button>
</Link> </Link>
</div> */} </div>
</div> </div>
</Card> </Card>
<CardContent className="p-0 mt-3"> <CardContent className="p-0 mt-3">

View File

@ -1,6 +1,8 @@
import * as React from "react"; import * as React from "react";
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react"; import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -8,42 +10,13 @@ import {
DropdownMenuItem, DropdownMenuItem,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { format } from "date-fns"; import { format } from "date-fns";
import { Link } from "@/components/navigation"; 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"; import { useTranslations } from "next-intl";
const MySwal = withReactContent(Swal); const useTableColumns = () => {
const t = useTranslations("Table"); // Panggil di dalam hook
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>[] = [ const columns: ColumnDef<any>[] = [
{ {
@ -94,46 +67,46 @@ const useTableColumns = (onDeleteSuccess?: () => void) => {
}, },
{ {
id: "actions", id: "actions",
header: t("action"), accessorKey: "action",
cell: ({ row }) => ( header: t("action", { defaultValue: "Action" }),
<DropdownMenu> enableHiding: false,
<DropdownMenuTrigger asChild> cell: ({ row }) => {
<Button size="icon" className="bg-transparent hover:bg-transparent"> return (
<MoreVertical className="h-4 w-4 text-default-800" /> <DropdownMenu>
</Button> <DropdownMenuTrigger asChild>
</DropdownMenuTrigger> <Button
<DropdownMenuContent align="end" className="p-0"> size="icon"
{/* View */} className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
<Link >
href={`/shared/communication/internal/detail/${row.original.id}`} <span className="sr-only">Open menu</span>
> <MoreVertical className="h-4 w-4 text-default-800" />
<DropdownMenuItem className="p-2 border-b cursor-pointer hover:bg-slate-100"> </Button>
<Eye className="w-4 h-4 me-1.5" /> </DropdownMenuTrigger>
View <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
</DropdownMenuItem> </DropdownMenuItem>
</Link> </DropdownMenuContent>
</DropdownMenu>
{/* 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>
),
}, },
]; ];

View File

@ -68,6 +68,7 @@ import useTableColumns from "./columns";
const InternalTable = () => { const InternalTable = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [dataTable, setDataTable] = React.useState<any[]>([]); const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1); const [totalData, setTotalData] = React.useState<number>(1);
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
@ -78,6 +79,7 @@ const InternalTable = () => {
React.useState<VisibilityState>({}); React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({}); const [rowSelection, setRowSelection] = React.useState({});
const [showData, setShowData] = React.useState("10"); const [showData, setShowData] = React.useState("10");
const [pagination, setPagination] = React.useState<PaginationState>({ const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: Number(showData), pageSize: Number(showData),
@ -85,7 +87,11 @@ const InternalTable = () => {
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const [totalPage, setTotalPage] = React.useState(1); const [totalPage, setTotalPage] = React.useState(1);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState<string>("");
const columns = useTableColumns(() => fetchData()); const userId = getCookiesDecrypt("uie");
const userLevelId = getCookiesDecrypt("ulie");
const roleId = getCookiesDecrypt("urie");
const columns = useTableColumns();
const table = useReactTable({ const table = useReactTable({
data: dataTable, data: dataTable,
columns, columns,

View File

@ -13,8 +13,6 @@ import { useTranslations } from "next-intl";
const ContestPage = () => { const ContestPage = () => {
const [userLevelId, setUserLevelId] = useState<any>(null); const [userLevelId, setUserLevelId] = useState<any>(null);
const t = useTranslations("Contest"); const t = useTranslations("Contest");
const levelNumber = Number(getCookiesDecrypt("ulne"));
useEffect(() => { useEffect(() => {
setUserLevelId(Number(getCookiesDecrypt("ulie"))); setUserLevelId(Number(getCookiesDecrypt("ulie")));
}, []); }, []);
@ -27,36 +25,18 @@ const ContestPage = () => {
<CardTitle> <CardTitle>
<div className="flex items-center"> <div className="flex items-center">
<div className="flex-1 text-xl font-medium text-default-900"> <div className="flex-1 text-xl font-medium text-default-900">
{t("table", { defaultValue: "Table" })}{" "} {t("table", { defaultValue: "Table" })} {t("contest", { defaultValue: "Contest" })}
{t("contest", { defaultValue: "Contest" })}
</div> </div>
{userLevelId !== 776 && {userLevelId !== 776 && userLevelId !== null && (
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"> <div className="flex-none">
<Link href={"/shared/contest/create"}> <Link href={"/shared/contest/create"}>
<Button color="primary" className="text-white"> <Button color="primary" className="text-white">
<UploadIcon size={18} className="mr-2" /> <UploadIcon size={18} className="mr-2" />
{t("create-contest", { {t("create-contest", { defaultValue: "Create Contest" })}
defaultValue: "Create Contest",
})}
</Button> </Button>
</Link> </Link>
</div> </div>
)} */} )}
</div> </div>
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>

View File

@ -33,7 +33,7 @@ const responseFacebook = (response: FacebookLoginResponse) => {
const clientId = const clientId =
"515231445138-5ius52rjsqucc6petfpv1d42v1lj778o.apps.googleusercontent.com"; "515231445138-5ius52rjsqucc6petfpv1d42v1lj778o.apps.googleusercontent.com";
const feedbackUrl = "https://new.netidhub.com/admin/settings/socmed"; const feedbackUrl = "https://mediahub.polri.go.id/admin/settings/socmed";
async function sendFbToken(token: string) { async function sendFbToken(token: string) {
const res = await saveFbToken(token); const res = await saveFbToken(token);

View File

@ -85,7 +85,7 @@ const LatestNews = (props: { type: string }) => {
// useEffect(() => { // useEffect(() => {
// async function fetchCategories() { // async function fetchCategories() {
// const url = "https://new.netidhub.com/api/csrf"; // const url = "https://netidhub.com/api/csrf";
// try { // try {
// const response = await fetch(url); // const response = await fetch(url);

View File

@ -82,7 +82,7 @@ const NationalNews = () => {
// useEffect(() => { // useEffect(() => {
// async function fetchCategories() { // async function fetchCategories() {
// const url = "https://new.netidhub.com/api/csrf"; // const url = "https://netidhub.com/api/csrf";
// try { // try {
// const response = await fetch(url); // const response = await fetch(url);

View File

@ -23,7 +23,7 @@ const PopularNews = () => {
useEffect(() => { useEffect(() => {
async function fetchCategories() { async function fetchCategories() {
const url = "https://new.netidhub.com/api/csrf"; const url = "https://netidhub.com/api/csrf";
try { try {
const response = await fetch(url); const response = await fetch(url);

View File

@ -75,7 +75,7 @@ const RegionalNews = () => {
// useEffect(() => { // useEffect(() => {
// async function fetchCategories() { // async function fetchCategories() {
// const url = "https://new.netidhub.com/api/csrf"; // const url = "https://netidhub.com/api/csrf";
// try { // try {
// const response = await fetch(url); // const response = await fetch(url);

View File

@ -868,7 +868,7 @@ const DetailInfo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"fb", "fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}` `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
) )
} }
> >
@ -883,7 +883,7 @@ const DetailInfo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"tw", "tw",
`https://twitter.com/share?url=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}` `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
) )
} }
> >
@ -898,7 +898,7 @@ const DetailInfo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"wa", "wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}` `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
) )
} }
> >

View File

@ -526,7 +526,7 @@ const DetailInfo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"fb", "fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}` `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
) )
} }
> >
@ -541,7 +541,7 @@ const DetailInfo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"tw", "tw",
`https://twitter.com/share?url=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}` `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
) )
} }
> >
@ -556,7 +556,7 @@ const DetailInfo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"wa", "wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}` `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
) )
} }
> >

View File

@ -666,7 +666,7 @@ const DetailVideo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"fb", "fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}` `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
) )
} }
> >
@ -681,7 +681,7 @@ const DetailVideo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"tw", "tw",
`https://twitter.com/share?url=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}` `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
) )
} }
> >
@ -696,7 +696,7 @@ const DetailVideo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"wa", "wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}` `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
) )
} }
> >

View File

@ -569,19 +569,18 @@ export default function FilterPage() {
</label> </label>
</li> </li>
))} ))}
<div className="mt-4 flex justify-center items-center gap-2 flex-wrap"> <div className="mt-4 flex gap-2 justify-center items-center">
{/* Tombol Prev */}
<button <button
onClick={() => onClick={() =>
setCategoryPage((prev) => Math.max(prev - 1, 1)) setCategoryPage((prev) => Math.max(prev - 1, 1))
} }
disabled={categoryPage === 1} disabled={categoryPage === 1}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center" className="px-3 py-1 border rounded disabled:opacity-50"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="24"
height="16" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@ -591,41 +590,20 @@ export default function FilterPage() {
</svg> </svg>
</button> </button>
{(() => { {Array.from({ length: categoryTotalPages }, (_, i) => (
const maxVisible = 4; <button
let startPage = Math.max( key={i}
1, onClick={() => setCategoryPage(i + 1)}
Math.min( className={`px-3 py-1 border rounded ${
categoryPage - Math.floor(maxVisible / 2), categoryPage === i + 1
categoryTotalPages - maxVisible + 1 ? "bg-[#bb3523] text-white"
) : ""
); }`}
const endPage = Math.min( >
categoryTotalPages, {i + 1}
startPage + maxVisible - 1 </button>
); ))}
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 <button
onClick={() => onClick={() =>
setCategoryPage((prev) => setCategoryPage((prev) =>
@ -633,12 +611,12 @@ export default function FilterPage() {
) )
} }
disabled={categoryPage === categoryTotalPages} disabled={categoryPage === categoryTotalPages}
className="px-2 py-1 border rounded disabled:opacity-50 flex items-center justify-center" className="px-3 py-1 border rounded disabled:opacity-50"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="16" width="24"
height="16" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path

View File

@ -284,35 +284,31 @@ const Galery = (props: any) => {
const copyToClip = async (url: any) => { const copyToClip = async (url: any) => {
await navigator.clipboard.writeText( await navigator.clipboard.writeText(
`https://new.netidhub.com/video/detail/${url}` `https://mediahub.polri.go.id/video/detail/${url}`
); );
setCopySuccess("Copied"); setCopySuccess("Copied");
// toast.success("Link Berhasil Di Copy"); // toast.success("Link Berhasil Di Copy");
}; };
async function shareToEmail(uploadId: any) { async function shareToEmail() {
if (!userRoleId) { if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth/login"); router.push("/auth/login");
return; } 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");
} }
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) => { const handleEmailList = (e: any) => {
@ -353,18 +349,12 @@ const Galery = (props: any) => {
Saya */} Saya */}
{pathname?.split("/")[1] == "in" ? ( {pathname?.split("/")[1] == "in" ? (
<> <>
<span className="text-black "> <span className="text-black ">{t("gallery", { defaultValue: "Gallery" })}</span>&nbsp;
{t("gallery", { defaultValue: "Gallery" })}
</span>
&nbsp;
{t("my", { defaultValue: "My" })} {t("my", { defaultValue: "My" })}
</> </>
) : ( ) : (
<> <>
<span className="text-black"> <span className="text-black">{t("my", { defaultValue: "My" })}</span>&nbsp;
{t("my", { defaultValue: "My" })}
</span>
&nbsp;
{t("gallery", { defaultValue: "Gallery" })} {t("gallery", { defaultValue: "Gallery" })}
</> </>
)} )}
@ -467,9 +457,7 @@ const Galery = (props: any) => {
fontSize={25} fontSize={25}
/> />
<p className="text-base font-semibold mb-2"> <p className="text-base font-semibold mb-2">
{t("save", { {t("save", { defaultValue: "Save" })}{" "}
defaultValue: "Save",
})}{" "}
</p> </p>
</div> </div>
<Link <Link
@ -493,25 +481,18 @@ const Galery = (props: any) => {
fontSize={20} fontSize={20}
/> />
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">
{t("share", { {t("share", { defaultValue: "Share" })}{" "}
defaultValue: "Share",
})}{" "}
</p> </p>
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">
{t("shareTo", { {t("shareTo", { defaultValue: "Share To" })}{" "}
defaultValue: "Share To",
})}{" "}
</h1> </h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">
{t("destinationEmail", { {t("destinationEmail", { defaultValue: "Destination Email" })}
defaultValue:
"Destination Email",
})}
</p> </p>
<Input <Input
value={emailShareInput} value={emailShareInput}
@ -522,22 +503,14 @@ const Galery = (props: any) => {
} }
onKeyPress={handleEmailList} onKeyPress={handleEmailList}
type="email" type="email"
placeholder={t("shareTo", { placeholder={t("shareTo", { defaultValue: "Share To" })}
defaultValue: "Share To",
})}
/> />
</div> </div>
<Button <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg" className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => onClick={() => shareToEmail()}
shareToEmail(
video.mediaUploadId
)
}
> >
{t("send", { {t("send", { defaultValue: "Send" })}
defaultValue: "Send",
})}
</Button> </Button>
</div> </div>
</PopoverContent> </PopoverContent>
@ -652,16 +625,10 @@ const Galery = (props: any) => {
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
{t("shareTo", {
defaultValue: "Share To",
})}
</h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">
{t("destinationEmail", { {t("destinationEmail", { defaultValue: "Destination Email" })}
defaultValue: "Destination Email",
})}
</p> </p>
<Input <Input
value={emailShareInput} value={emailShareInput}
@ -670,16 +637,12 @@ const Galery = (props: any) => {
} }
onKeyPress={handleEmailList} onKeyPress={handleEmailList}
type="email" type="email"
placeholder={t("pressEnter", { placeholder={t("pressEnter", { defaultValue: "Press Enter" })}
defaultValue: "Press Enter",
})}
/> />
</div> </div>
<Button <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg" className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => onClick={() => shareToEmail()}
shareToEmail(audio.mediaUploadId)
}
> >
{t("send", { defaultValue: "Send" })} {t("send", { defaultValue: "Send" })}
</Button> </Button>
@ -769,9 +732,7 @@ const Galery = (props: any) => {
fontSize={25} fontSize={25}
/> />
<p className="text-base font-semibold mb-2"> <p className="text-base font-semibold mb-2">
{t("save", { {t("save", { defaultValue: "Save" })}{" "}
defaultValue: "Save",
})}{" "}
</p> </p>
</div> </div>
<Link <Link
@ -795,25 +756,18 @@ const Galery = (props: any) => {
fontSize={20} fontSize={20}
/> />
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">
{t("share", { {t("share", { defaultValue: "Share" })}{" "}
defaultValue: "Share",
})}{" "}
</p> </p>
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">
{t("shareTo", { {t("shareTo", { defaultValue: "Share To" })}{" "}
defaultValue: "Share To",
})}{" "}
</h1> </h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">
{t("destinationEmail", { {t("destinationEmail", { defaultValue: "Destination Email" })}
defaultValue:
"Destination Email",
})}
</p> </p>
<Input <Input
value={emailShareInput} value={emailShareInput}
@ -824,22 +778,14 @@ const Galery = (props: any) => {
} }
onKeyPress={handleEmailList} onKeyPress={handleEmailList}
type="email" type="email"
placeholder={t("shareTo", { placeholder={t("shareTo", { defaultValue: "Share To" })}
defaultValue: "Share To",
})}
/> />
</div> </div>
<Button <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg" className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => onClick={() => shareToEmail()}
shareToEmail(
image.mediaUploadId
)
}
> >
{t("send", { {t("send", { defaultValue: "Send" })}
defaultValue: "Send",
})}
</Button> </Button>
</div> </div>
</PopoverContent> </PopoverContent>
@ -938,14 +884,10 @@ const Galery = (props: any) => {
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
{t("shareTo", { defaultValue: "Share To" })}
</h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">
{t("destinationEmail", { {t("destinationEmail", { defaultValue: "Destination Email" })}
defaultValue: "Destination Email",
})}
</p> </p>
<Input <Input
value={emailShareInput} value={emailShareInput}
@ -954,16 +896,12 @@ const Galery = (props: any) => {
} }
onKeyPress={handleEmailList} onKeyPress={handleEmailList}
type="email" type="email"
placeholder={t("pressEnter", { placeholder={t("pressEnter", { defaultValue: "Press Enter" })}
defaultValue: "Press Enter",
})}
/> />
</div> </div>
<Button <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg" className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => onClick={() => shareToEmail()}
shareToEmail(document.mediaUploadId)
}
> >
{t("send", { defaultValue: "Send" })} {t("send", { defaultValue: "Send" })}
</Button> </Button>

View File

@ -1,13 +1,7 @@
"use client"; "use client";
import { close, error, loading, successCallback } from "@/config/swal"; import { close, error, loading, successCallback } from "@/config/swal";
import { import { checkWishlistStatus, deleteWishlist, getInfoProfile, mediaWishlist, saveWishlist } from "@/service/landing/landing";
checkWishlistStatus,
deleteWishlist,
getInfoProfile,
mediaWishlist,
saveWishlist,
} from "@/service/landing/landing";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
@ -19,17 +13,8 @@ import withReactContent from "sweetalert2-react-content";
import { getCookiesDecrypt } from "@/lib/utils"; import { getCookiesDecrypt } from "@/lib/utils";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
DropdownMenu, import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
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 { Icon } from "@iconify/react/dist/iconify.js";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -76,23 +61,11 @@ const Galery = (props: any) => {
}, [page, category, title]); }, [page, category, title]);
async function getDataVideo() { async function getDataVideo() {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: category || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
const response = await mediaWishlist( const response = await mediaWishlist("2", isInstitute ? instituteId : "", name, filter, "9", pages, sortBy, format);
"2",
isInstitute ? instituteId : "",
name,
filter,
"9",
pages,
sortBy,
format
);
setGetTotalPage(response?.data?.data?.totalPages); setGetTotalPage(response?.data?.data?.totalPages);
setContentVideo(response?.data?.data?.content); setContentVideo(response?.data?.data?.content);
@ -121,24 +94,12 @@ const Galery = (props: any) => {
}, [page, category, title]); }, [page, category, title]);
async function getDataDocument() { async function getDataDocument() {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: category || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
const response = await mediaWishlist( const response = await mediaWishlist("3", isInstitute ? instituteId : "", name, filter, "12", pages, sortBy, format);
"3",
isInstitute ? instituteId : "",
name,
filter,
"12",
pages,
sortBy,
format
);
setGetTotalPage(response?.data?.data?.totalPages); setGetTotalPage(response?.data?.data?.totalPages);
setContentDocument(response?.data?.data?.content); setContentDocument(response?.data?.data?.content);
@ -158,24 +119,12 @@ const Galery = (props: any) => {
}, [change, refresh]); }, [change, refresh]);
async function getDataAudio() { async function getDataAudio() {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: category || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
const response = await mediaWishlist( const response = await mediaWishlist("4", isInstitute ? instituteId : "", name, filter, "6", pages, sortBy, format);
"4",
isInstitute ? instituteId : "",
name,
filter,
"6",
pages,
sortBy,
format
);
setGetTotalPage(response?.data?.data?.totalPages); setGetTotalPage(response?.data?.data?.totalPages);
setContentAudio(response?.data?.data?.content); setContentAudio(response?.data?.data?.content);
@ -191,24 +140,12 @@ const Galery = (props: any) => {
}, [page, category, title, refresh]); }, [page, category, title, refresh]);
async function getDataImage() { async function getDataImage() {
const filter = const filter = categoryFilter?.length > 0 ? categoryFilter?.sort().join(",") : category || "";
categoryFilter?.length > 0
? categoryFilter?.sort().join(",")
: category || "";
const name = title == undefined ? "" : title; const name = title == undefined ? "" : title;
const format = formatFilter == undefined ? "" : formatFilter?.join(","); const format = formatFilter == undefined ? "" : formatFilter?.join(",");
const response = await mediaWishlist( const response = await mediaWishlist("1", isInstitute ? instituteId : "", name, filter, "12", pages, sortBy, format);
"1",
isInstitute ? instituteId : "",
name,
filter,
"12",
pages,
sortBy,
format
);
setGetTotalPage(response?.data?.data?.totalPages); setGetTotalPage(response?.data?.data?.totalPages);
setContentImage(response?.data?.data?.content); setContentImage(response?.data?.data?.content);
@ -290,9 +227,7 @@ const Galery = (props: any) => {
}; };
const copyToClip = async (url: any) => { const copyToClip = async (url: any) => {
await navigator.clipboard.writeText( await navigator.clipboard.writeText(`https://mediahub.polri.go.id/video/detail/${url}`);
`https://new.netidhub.com/video/detail/${url}`
);
setCopySuccess("Copied"); setCopySuccess("Copied");
// toast.success("Link Berhasil Di Copy"); // toast.success("Link Berhasil Di Copy");
toast({ toast({
@ -300,29 +235,25 @@ const Galery = (props: any) => {
}); });
}; };
async function shareToEmail(uploadId: any) { async function shareToEmail() {
if (!userRoleId) { if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth/login"); router.push("/auth/login");
return; } 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");
} }
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) => { const handleEmailList = (e: any) => {
@ -359,9 +290,7 @@ const Galery = (props: any) => {
<div className="flex flex-col mt-4"> <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"> <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"> <h1 className="text-2xl w-fit font-bold bg-[#bb3523] px-4 py-1 rounded-lg text-center text-white">
<span className="text-black"> <span className="text-black">{t("gallery", { defaultValue: "Gallery" })} </span>
{t("gallery", { defaultValue: "Gallery" })}{" "}
</span>
{profile?.institute?.name} {profile?.institute?.name}
</h1> </h1>
<Tabs value={selectedTab} onValueChange={setSelectedTab}> <Tabs value={selectedTab} onValueChange={setSelectedTab}>
@ -372,27 +301,21 @@ const Galery = (props: any) => {
> >
{t("image", { defaultValue: "Image" })} {t("image", { defaultValue: "Image" })}
</TabsTrigger> </TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block"> <div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
|
</div>
<TabsTrigger <TabsTrigger
value="video" 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" 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" })} {t("video", { defaultValue: "Video" })}
</TabsTrigger> </TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block"> <div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
|
</div>
<TabsTrigger <TabsTrigger
value="text" 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" 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" })} {t("text", { defaultValue: "Text" })}
</TabsTrigger> </TabsTrigger>
<div className="text-[#bb3523] text-lg hidden md:inline-block"> <div className="text-[#bb3523] text-lg hidden md:inline-block">|</div>
|
</div>
<TabsTrigger <TabsTrigger
value="audio" 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" 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"
@ -408,140 +331,50 @@ const Galery = (props: any) => {
contentVideo?.length > 0 ? ( contentVideo?.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{contentVideo?.map((video: any) => ( {contentVideo?.map((video: any) => (
<Card <Card key={video?.id} className="hover:scale-105 transition-transform duration-300">
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"> <CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
<div> <div>
<div className="relative group overflow-hidden shadow-md hover:shadow-lg"> <div className="relative group overflow-hidden shadow-md hover:shadow-lg">
<div className="relative h-60 rounded-lg overflow-hidden"> <div className="relative h-60 rounded-lg overflow-hidden">
<ImageBlurry <ImageBlurry src={video?.mediaUpload?.thumbnailLink} alt={video?.mediaUpload?.title} style={{ objectFit: "cover", width: "100%", height: "100%" }} />
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"> <div className="absolute bottom-0 left-0 right-0 bg-black text-white">
<Link <Link href={`/video/detail/${video?.mediaUpload?.slug}`}>
href={`/video/detail/${video?.mediaUpload?.slug}`} <p className="text-sm p-2 lg:text-base font-semibold truncate">{video?.mediaUpload?.title}</p>
>
<p className="text-sm p-2 lg:text-base font-semibold truncate">
{video?.mediaUpload?.title}
</p>
</Link> </Link>
<p className="flex text-[10px] mr-1 mb-2 items-center justify-end self-end"> <p className="flex text-[10px] mr-1 mb-2 items-center justify-end self-end">
<Popover> <Popover>
<PopoverTrigger <PopoverTrigger className="flex cursor-pointer" asChild>
className="flex cursor-pointer"
asChild
>
<a className="flex justify-end items-center"> <a className="flex justify-end items-center">
<Icon <Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
className="text-white ml-1"
fontSize={25}
icon="tabler:dots"
/>
</a> </a>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-52"> <PopoverContent className="w-52">
<div <div onClick={() => handleSaveWishlist(video?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
onClick={() => <Icon icon="material-symbols:bookmark-outline" fontSize={25} />
handleSaveWishlist( <p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })} </p>
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> </div>
<Link <Link href={`/content-management/rewrite/create/${video?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
href={`/content-management/rewrite/create/${video?.mediaUpload?.id}`} <Icon icon="jam:write" fontSize={25} />
className="flex flex-row hover:text-red-800 gap-2" <p className="text-base font-semibold mb-2">Content Rewrite</p>
>
<Icon
icon="jam:write"
fontSize={25}
/>
<p className="text-base font-semibold mb-2">
Content Rewrite
</p>
</Link> </Link>
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg"> <div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<button className="w-full flex flex-row items-center gap-3"> <button className="w-full flex flex-row items-center gap-3">
<Icon <Icon icon="oi:share" fontSize={20} />
icon="oi:share" <p className="text-base font-semibold mb-1">{t("share", { defaultValue: "Share" })} </p>
fontSize={20}
/>
<p className="text-base font-semibold mb-1">
{t("share", {
defaultValue: "Share",
})}{" "}
</p>
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })} </h1>
{t("shareTo", {
defaultValue: "Share To",
})}{" "}
</h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
{t("destinationEmail", { <Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("shareTo", { defaultValue: "Share To" })} />
defaultValue:
"Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(
event.target.value
)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("shareTo", {
defaultValue: "Share To",
})}
/>
</div> </div>
<Button <Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
className="bg-blue-500 text-white p-2 w-fit rounded-lg" {t("send", { defaultValue: "Send" })}
onClick={() =>
shareToEmail(
video.mediaUploadId
)
}
>
{t("send", {
defaultValue: "Send",
})}
</Button> </Button>
{/* <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail()}
>
{t("send", {
defaultValue: "Send",
})}
</Button> */}
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
@ -559,64 +392,33 @@ const Galery = (props: any) => {
</div> </div>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<Image <Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
width={1920}
height={1080}
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
) )
) : selectedTab == "audio" ? ( ) : selectedTab == "audio" ? (
contentAudio?.length > 0 ? ( contentAudio?.length > 0 ? (
<div className=" grid grid-cols-1 gap-6 "> <div className=" grid grid-cols-1 gap-6 ">
{contentAudio?.map((audio: any) => ( {contentAudio?.map((audio: any) => (
<div <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">
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"> <div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16">
<svg <svg width="32" height="34" viewBox="0 0 32 34" fill="null" xmlns="http://www.w3.org/2000/svg">
width="32"
height="34"
viewBox="0 0 32 34"
fill="null"
xmlns="http://www.w3.org/2000/svg"
>
<path <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" 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" fill="white"
/> />
</svg> </svg>
</div> </div>
<Link <Link href={`/audio/detail/${audio?.mediaUpload?.slug}`} className="flex flex-col flex-1">
href={`/audio/detail/${audio?.mediaUpload?.slug}`} <div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">{audio?.mediaUpload?.title}</div>
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> </Link>
<div className="flex items-center justify-center gap-3"> <div className="flex items-center justify-center gap-3">
<div className="mt-2"> <div className="mt-2">
<img src="/assets/wave.svg" className="w-80" /> <img src="/assets/wave.svg" className="w-80" />
</div> </div>
<div className="flex flex-row items-center justify-center text-gray-500 dark:text-gray-400"> <div className="flex flex-row items-center justify-center text-gray-500 dark:text-gray-400">
<img <img src="/assets/audio-icon.png" alt="#" className="flex items-center justify-center" />
src="/assets/audio-icon.png" <div className="flex mx-2 items-center justify-center">{audio?.mediaUpload?.duration}</div>
alt="#" <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
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 <path
fill="#f00" fill="#f00"
d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z" d="M7.707 10.293a1 1 0 1 0-1.414 1.414l3 3a1 1 0 0 0 1.414 0l3-3a1 1 0 0 0-1.414-1.414L11 11.586V6h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5v5.586zM9 4a1 1 0 0 1 2 0v2H9z"
@ -625,92 +427,38 @@ const Galery = (props: any) => {
</div> </div>
</div> </div>
<Popover> <Popover>
<PopoverTrigger <PopoverTrigger className="flex justify-end gap-1 cursor-pointer" asChild>
className="flex justify-end gap-1 cursor-pointer"
asChild
>
<a className="flex justify-end items-end place-items-end"> <a className="flex justify-end items-end place-items-end">
<Icon <Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
className="text-white ml-1"
fontSize={25}
icon="tabler:dots"
/>
</a> </a>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-52"> <PopoverContent className="w-52">
<div <div onClick={() => handleSaveWishlist(audio?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
onClick={() => <Icon icon="material-symbols:bookmark-outline" fontSize={25} />
handleSaveWishlist(audio?.mediaUpload?.id) <p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })}</p>
}
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> </div>
<Link <Link href={`/content-management/rewrite/create/${audio?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
href={`/content-management/rewrite/create/${audio?.mediaUpload?.id}`}
className="flex flex-row hover:text-red-800 gap-2"
>
<Icon icon="jam:write" fontSize={25} /> <Icon icon="jam:write" fontSize={25} />
<p className="text-base font-semibold mb-2"> <p className="text-base font-semibold mb-2">Content Rewrite</p>
Content Rewrite
</p>
</Link> </Link>
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg"> <div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<button className="w-full flex items-center gap-2"> <button className="w-full flex items-center gap-2">
<Icon icon="oi:share" fontSize={20} /> <Icon icon="oi:share" fontSize={20} />
<p className="text-base font-semibold mb-2"> <p className="text-base font-semibold mb-2">{t("share", { defaultValue: "Share" })}</p>
{t("share", { defaultValue: "Share" })}
</p>
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
{t("shareTo", {
defaultValue: "Share To",
})}
</h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
{t("destinationEmail", { <Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter", { defaultValue: "Press Enter" })} />
defaultValue: "Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter", {
defaultValue: "Press Enter",
})}
/>
</div> </div>
<Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
<Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() =>
shareToEmail(audio.mediaUploadId)
}
>
{t("send", { defaultValue: "Send" })} {t("send", { defaultValue: "Send" })}
</Button> </Button>
{/* <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail()}
>
{t("send", { defaultValue: "Send" })}
</Button> */}
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
@ -722,148 +470,56 @@ const Galery = (props: any) => {
</div> </div>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<Image <Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
width={1920}
height={1080}
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
) )
) : selectedTab == "image" ? ( ) : selectedTab == "image" ? (
contentImage?.length > 0 ? ( contentImage?.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{contentImage?.map((image: any) => ( {contentImage?.map((image: any) => (
<Card <Card key={image?.id} className="hover:scale-105 transition-transform duration-300">
key={image?.id}
className="hover:scale-105 transition-transform duration-300"
>
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0"> <CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
<div> <div>
<div className="relative group overflow-hidden shadow-md hover:shadow-lg"> <div className="relative group overflow-hidden shadow-md hover:shadow-lg">
<div className="relative h-60 rounded-lg overflow-hidden"> <div className="relative h-60 rounded-lg overflow-hidden">
<ImageBlurry <ImageBlurry src={image?.mediaUpload?.thumbnailLink} alt={image?.mediaUpload?.title} style={{ objectFit: "cover", width: "100%", height: "100%" }} />
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"> <div className="absolute bottom-0 left-0 right-0 bg-black text-white">
<Link <Link href={`/video/detail/${image?.mediaUpload?.slug}`}>
href={`/video/detail/${image?.mediaUpload?.slug}`} <p className="text-sm p-2 lg:text-base font-semibold truncate">{image?.mediaUpload?.title}</p>
>
<p className="text-sm p-2 lg:text-base font-semibold truncate">
{image?.mediaUpload?.title}
</p>
</Link> </Link>
<p className="flex text-[10px] mr-1 mb-2 items-center justify-end self-end"> <p className="flex text-[10px] mr-1 mb-2 items-center justify-end self-end">
<Popover> <Popover>
<PopoverTrigger <PopoverTrigger className="flex cursor-pointer" asChild>
className="flex cursor-pointer"
asChild
>
<a className="flex justify-end items-end place-items-end"> <a className="flex justify-end items-end place-items-end">
<Icon <Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
className="text-white ml-1"
fontSize={25}
icon="tabler:dots"
/>
</a> </a>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-52"> <PopoverContent className="w-52">
<div <div onClick={() => handleSaveWishlist(image?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
onClick={() => <Icon icon="material-symbols:bookmark-outline" fontSize={25} />
handleSaveWishlist( <p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })}</p>
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> </div>
<Link <Link href={`/content-management/rewrite/create/${image?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
href={`/content-management/rewrite/create/${image?.mediaUpload?.id}`} <Icon icon="jam:write" fontSize={25} />
className="flex flex-row hover:text-red-800 gap-2" <p className="text-base font-semibold mb-2">Content Rewrite</p>
>
<Icon
icon="jam:write"
fontSize={25}
/>
<p className="text-base font-semibold mb-2">
Content Rewrite
</p>
</Link> </Link>
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg"> <div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<button className="w-full flex flex-row items-center gap-3"> <button className="w-full flex flex-row items-center gap-3">
<Icon <Icon icon="oi:share" fontSize={20} />
icon="oi:share" <p className="text-base font-semibold mb-1"> {t("share", { defaultValue: "Share" })}</p>
fontSize={20}
/>
<p className="text-base font-semibold mb-1">
{" "}
{t("share", {
defaultValue: "Share",
})}
</p>
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
{t("shareTo", {
defaultValue: "Share To",
})}
</h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
{t("destinationEmail", { <Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter", { defaultValue: "Press Enter" })} />
defaultValue:
"Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(
event.target.value
)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t(
"pressEnter",
{
defaultValue:
"Press Enter",
}
)}
/>
</div> </div>
<Button <Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
className="bg-blue-500 text-white p-2 w-fit rounded-lg" {t("send", { defaultValue: "Send" })}
onClick={() =>
shareToEmail(
image.mediaUploadId
)
}
>
{t("send", {
defaultValue: "Send",
})}
</Button> </Button>
</div> </div>
</PopoverContent> </PopoverContent>
@ -882,30 +538,15 @@ const Galery = (props: any) => {
</div> </div>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<Image <Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
width={1920}
height={1080}
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
) )
) : contentDocument.length > 0 ? ( ) : contentDocument.length > 0 ? (
<div className=" grid grid-cols-1 md:grid-cols-2 gap-6"> <div className=" grid grid-cols-1 md:grid-cols-2 gap-6">
{contentDocument?.map((document: any) => ( {contentDocument?.map((document: any) => (
<div <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">
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"> <div className="flex items-center justify-center rounded-lg w-16 h-16">
<svg <svg width="28" height="34" viewBox="0 0 28 34" fill="none" xmlns="http://www.w3.org/2000/svg">
width="28"
height="34"
viewBox="0 0 28 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path <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" 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" fill="black"
@ -914,103 +555,47 @@ const Galery = (props: any) => {
</div> </div>
<div className="flex flex-col flex-1 gap-2"> <div className="flex flex-col flex-1 gap-2">
<Link <Link href={`/document/detail/${document?.mediaUpload?.slug}`} className="font-semibold text-gray-900 dark:text-white mt-1 text-sm">
href={`/document/detail/${document?.mediaUpload?.slug}`}
className="font-semibold text-gray-900 dark:text-white mt-1 text-sm"
>
{document?.mediaUpload?.title} {document?.mediaUpload?.title}
</Link> </Link>
<div className="flex gap-2 items-center text-xs text-red-500 dark:text-red-500"> <div className="flex gap-2 items-center text-xs text-red-500 dark:text-red-500">
<svg <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512">
xmlns="http://www.w3.org/2000/svg" <path fill="#f00" d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z" />
width="1em"
height="1em"
viewBox="0 0 512 512"
>
<path
fill="#f00"
d="M224 30v256h-64l96 128l96-128h-64V30zM32 434v48h448v-48z"
/>
</svg> </svg>
Download {t("document", { defaultValue: "Document" })} Download {t("document", { defaultValue: "Document" })}
</div> </div>
</div> </div>
<Popover> <Popover>
<PopoverTrigger <PopoverTrigger className="flex justify-end gap-1 cursor-pointer" asChild>
className="flex justify-end gap-1 cursor-pointer"
asChild
>
<a className="flex justify-end items-end place-items-end"> <a className="flex justify-end items-end place-items-end">
<Icon <Icon className="text-white ml-1" fontSize={25} icon="tabler:dots" />
className="text-white ml-1"
fontSize={25}
icon="tabler:dots"
/>
</a> </a>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-52"> <PopoverContent className="w-52">
<div <div onClick={() => handleSaveWishlist(document?.mediaUpload?.id)} className="cursor-pointer flex flex-row gap-2 hover:text-red-800">
onClick={() => <Icon icon="material-symbols:bookmark-outline" fontSize={25} />
handleSaveWishlist(document?.mediaUpload?.id) <p className="text-base font-semibold mb-2">{t("save", { defaultValue: "Save" })}</p>
}
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> </div>
<Link <Link href={`/content-management/rewrite/create/${document?.mediaUpload?.id}`} className="flex flex-row hover:text-red-800 gap-2">
href={`/content-management/rewrite/create/${document?.mediaUpload?.id}`}
className="flex flex-row hover:text-red-800 gap-2"
>
<Icon icon="jam:write" fontSize={25} /> <Icon icon="jam:write" fontSize={25} />
<p className="text-base font-semibold mb-2"> <p className="text-base font-semibold mb-2">Content Rewrite</p>
Content Rewrite
</p>
</Link> </Link>
<div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg"> <div className="flex items-center gap-1 hover:text-red-800 w-full rounded-lg">
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<button className="w-full flex items-center gap-2"> <button className="w-full flex items-center gap-2">
<Icon icon="oi:share" fontSize={20} /> <Icon icon="oi:share" fontSize={20} />
<p className="text-base font-semibold mb-2"> <p className="text-base font-semibold mb-2">{t("share", { defaultValue: "Share" })}</p>
{t("share", { defaultValue: "Share" })}
</p>
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="mb-2"> <h1 className="mb-2">{t("shareTo", { defaultValue: "Share To" })}</h1>
{t("shareTo", { defaultValue: "Share To" })}
</h1>
<div className="flex flex-col mb-2"> <div className="flex flex-col mb-2">
<p className="text-base font-semibold mb-1"> <p className="text-base font-semibold mb-1">{t("destinationEmail", { defaultValue: "Destination Email" })}</p>
{t("destinationEmail", { <Input value={emailShareInput} onChange={(event) => setEmailShareInput(event.target.value)} onKeyPress={handleEmailList} type="email" placeholder={t("pressEnter", { defaultValue: "Press Enter" })} />
defaultValue: "Destination Email",
})}
</p>
<Input
value={emailShareInput}
onChange={(event) =>
setEmailShareInput(event.target.value)
}
onKeyPress={handleEmailList}
type="email"
placeholder={t("pressEnter", {
defaultValue: "Press Enter",
})}
/>
</div> </div>
<Button <Button className="bg-blue-500 text-white p-2 w-fit rounded-lg" onClick={() => shareToEmail()}>
className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() =>
shareToEmail(document?.mediaUploadId)
}
>
{t("send", { defaultValue: "Send" })} {t("send", { defaultValue: "Send" })}
</Button> </Button>
</div> </div>
@ -1024,13 +609,7 @@ const Galery = (props: any) => {
</div> </div>
) : ( ) : (
<p className="flex items-center justify-center"> <p className="flex items-center justify-center">
<Image <Image width={1920} height={1080} src="/assets/empty-data.png" alt="empty" className="h-52 w-52 my-4" />
width={1920}
height={1080}
src="/assets/empty-data.png"
alt="empty"
className="h-52 w-52 my-4"
/>
</p> </p>
)} )}
</div> </div>

View File

@ -29,7 +29,6 @@ import { sendMediaUploadToEmail } from "@/service/media-tracking/media-tracking"
import ImageBlurry from "@/components/ui/image-blurry"; import ImageBlurry from "@/components/ui/image-blurry";
import Image from "next/image"; import Image from "next/image";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { sendMediaRewriteToEmail } from "@/service/content/content";
const page = () => { const page = () => {
const [, setProfile] = useState(); const [, setProfile] = useState();
@ -134,7 +133,7 @@ const page = () => {
const copyToClip = async (url: any) => { const copyToClip = async (url: any) => {
await navigator.clipboard.writeText( await navigator.clipboard.writeText(
`https://new.netidhub.com/image/detail/${url}` `https://mediahub.polri.go.id/image/detail/${url}`
); );
setCopySuccess("Copied"); setCopySuccess("Copied");
// toast.success("Link Berhasil Di Copy"); // toast.success("Link Berhasil Di Copy");
@ -176,16 +175,18 @@ const page = () => {
} }
}; };
async function shareToEmail(uploadId: any) { async function shareToEmail() {
if (Number(userRoleId) < 1 || userRoleId == undefined) { if (Number(userRoleId) < 1 || userRoleId == undefined) {
router.push("/auth/login"); router.push("/auth/login");
} else { } else {
const data = { const data = {
id: uploadId, mediaUploadId: id?.split("-")?.[0],
email: emailShareList || [emailShareInput], email: emailShareList || [emailShareInput],
message: emailMessageInput,
url: window.location.href,
}; };
loading(); loading();
const res = await sendMediaRewriteToEmail(data.id, data.email); const res = await sendMediaUploadToEmail(data);
if (res?.error) { if (res?.error) {
error(res.message); error(res.message);
return false; return false;
@ -195,7 +196,6 @@ const page = () => {
} }
} }
const handleEmailList = (e: any) => { const handleEmailList = (e: any) => {
const arrayEmail: any = []; const arrayEmail: any = [];
for (let i = 0; i < emailShareList?.length; i += 1) { for (let i = 0; i < emailShareList?.length; i += 1) {
@ -328,7 +328,7 @@ const page = () => {
</div> </div>
<Button <Button
className="bg-blue-500 text-white p-2 w-fit rounded-lg" className="bg-blue-500 text-white p-2 w-fit rounded-lg"
onClick={() => shareToEmail(image.id)} onClick={() => shareToEmail()}
> >
{t("send", { defaultValue: "Send" })} {t("send", { defaultValue: "Send" })}
</Button> </Button>

View File

@ -43,7 +43,7 @@ export async function generateMetadata({ params }: any): Promise<Metadata> {
openGraph: { openGraph: {
title: video?.title, title: video?.title,
description: video?.description, description: video?.description,
url: `https://new.netidhub.com/in/video/detail/${slug}`, url: `https://mediahub.polri.go.id/in/video/detail/${slug}`,
type: "video.other", type: "video.other",
images: [ images: [
{ {

View File

@ -23,7 +23,7 @@ import AuthProvider from "@/providers/auth.provider";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Media Hub | POLRI", title: "Media Hub | POLRI",
description: "Media Hub Platform for POLRI", description: "Media Hub Platform for POLRI",
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'https://new.netidhub.com'), metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'https://mediahub.polri.go.id'),
openGraph: { openGraph: {
title: "Media Hub | POLRI", title: "Media Hub | POLRI",
description: "Media Hub Platform for POLRI", description: "Media Hub Platform for POLRI",

View File

@ -4,7 +4,7 @@ import { NextResponse } from "next/server";
export async function GET() { export async function GET() {
try { try {
const baseUrl = "https://new.netidhub.com/in"; const baseUrl = "https://mediahub.polri.go.id/in";
const response = await getListContent({ const response = await getListContent({
page: 1, page: 1,

View File

@ -35,7 +35,7 @@ export default function DetailContentBlast() {
const detailData = res?.data?.data; const detailData = res?.data?.data;
let updatedUrl = detailData.contentUrl; let updatedUrl = detailData.contentUrl;
const domainsToUpdate = ["new.netidhub.com", "netidhub.com"]; const domainsToUpdate = ["mediahub.polri.go.id", "netidhub.com"];
domainsToUpdate.forEach((domain) => { domainsToUpdate.forEach((domain) => {
if ( if (

View File

@ -14,7 +14,6 @@ import Swal from "sweetalert2";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { import {
getLocaleTime, getLocaleTime,
@ -25,6 +24,7 @@ import {
detailMediaSummary, detailMediaSummary,
getMediaBlastCampaignList, getMediaBlastCampaignList,
saveMediaBlastBroadcast, saveMediaBlastBroadcast,
saveMediaBlastCampaign,
} from "@/service/broadcast/broadcast"; } from "@/service/broadcast/broadcast";
import { error } from "@/config/swal"; import { error } from "@/config/swal";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
@ -44,13 +44,27 @@ import {
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
const CustomEditor = dynamic( const CustomEditor = dynamic(
() => import("@/components/editor/custom-editor"), () => {
return import("@/components/editor/custom-editor");
},
{ ssr: false } { ssr: false }
); );
const animatedComponent = makeAnimated(); const animatedComponent = makeAnimated();
const FormSchema = z.object({ 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 selected: z
.array( .array(
z.object({ z.object({
@ -59,16 +73,9 @@ const FormSchema = z.object({
value: z.string(), value: z.string(),
}) })
) )
.min(1, "Pilih minimal satu campaign"), .refine((value) => value.length > 0, {
message: "Required",
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 { interface Campaign {
@ -76,134 +83,73 @@ interface Campaign {
name: string; name: string;
} }
export default function ContentBlast() { export default function ContentBlast(props: { type: string }) {
const editor = useRef(null); const editor = useRef(null);
const id = useParams()?.id; const id = useParams()?.id;
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const { type } = props;
const [dataSelectCampaign, setDataSelectCampaign] = useState<Campaign[]>([]); const [dataSelectCampaign, setDataSelectCampaign] = useState<Campaign[]>([]);
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema), resolver: zodResolver(FormSchema),
defaultValues: { defaultValues: { selected: [], detail: "" },
selected: [],
detail: "",
messageType: [],
},
}); });
const selectedTypes = form.watch("messageType");
const onSubmit = async (data: z.infer<typeof FormSchema>) => { const onSubmit = async (data: z.infer<typeof FormSchema>) => {
MySwal.fire({ if (form.getValues("detail") == "") {
title: "Kirim Broadcast?", form.setError("detail", {
text: "Pesan akan dikirim ke semua campaign yang dipilih", type: "manual",
icon: "warning", message: "Required",
showCancelButton: true, });
cancelButtonColor: "#d33", } else {
confirmButtonColor: "#3085d6", MySwal.fire({
confirmButtonText: "Kirim", title: "Simpan Data",
}).then((result) => { text: "Apakah Anda yakin ingin menyimpan data ini?",
if (result.isConfirmed) save(data); icon: "warning",
}); showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).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(/&amp;/g, "&");
// Rapikan spasi
return html.replace(/\s+/g, " ").trim();
}
const save = async (data: z.infer<typeof FormSchema>) => { const save = async (data: z.infer<typeof FormSchema>) => {
const selectedCampaign = data.selected; const selectedCampaign = form.getValues("selected");
const mediaRes = await detailMediaSummary(String(id));
const details = mediaRes?.data?.data;
for (let i = 0; i < selectedCampaign.length; i++) { for (let i = 0; i < selectedCampaign.length; i++) {
const campaignId = selectedCampaign[i].id; 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,
};
for (let mt of data.messageType) { console.log("req =>", reqData);
let finalBody = data.detail; const response = await saveMediaBlastBroadcast(reqData);
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); 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(() => { useEffect(() => {
async function init() { async function initState() {
const response = await detailMediaSummary(String(id)); const response = await detailMediaSummary(String(id));
const details = response?.data?.data; 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 || ""; let pageUrl = details?.pageUrl || "";
if (pageUrl.includes("new.netidhub.com")) { if (pageUrl.includes("mediahub.polri.go.id")) {
pageUrl = pageUrl.replace( pageUrl = pageUrl.replace(
/(\.id)(\/|$)/, /(\.id)(\/|$)/,
(match: any, p1: any, p2: any) => { (match: any, p1: any, p2: any) => {
@ -211,43 +157,59 @@ export default function ContentBlast() {
} }
); );
} }
if (details != undefined) {
const body = `<div><p>Berita hari ini !!!</p> form.setValue("thumbnail", details.smallThumbnailLink);
<div style='margin-top:20px;border:1px solid;border-radius:10px;width:500px;background-color:#f7f7f7'> let body = `<div><p>Berita hari ini !!!</p>
<div> <div style='margin-top:20px;border:1px solid;border-radius:10px;width:500px;background-color:#f7f7f7'>
<img style='width:500px;height:auto;border-radius:10px;object-fit:cover' src='${ <div>
details.smallThumbnailLink <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> </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> <h3 style='padding:5px 20px;margin:0'>${details?.title}</h3>
<p style='padding:0 20px;margin:0;margin-bottom:10px'> <p style='padding:0 20px;margin:0;margin-bottom:10px'>
${textEllipsis(details.description, 150)} ${textEllipsis(details?.description, 150)}
</p> </p>
</div> </div>
</div>`; </div>`;
form.setValue("title", `${details?.title}`);
form.setValue("detail", body); 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 loadCampaign() { async function getCampaign() {
const response = await getMediaBlastCampaignList(); const response = await getMediaBlastCampaignList();
const campaign = response?.data?.data?.content; const campaign = response?.data?.data?.content;
handleLabelCampaign(campaign); handleLabelCampaign(campaign);
console.log(campaign);
} }
init(); initState();
loadCampaign(); getCampaign();
}, [id]); }, [id]);
function handleLabelCampaign(data: any) { function handleLabelCampaign(data: any) {
const arr = data.map((item: any) => ({ const optionArr: any = [];
id: item.id,
label: item.title, data.map((option: any) => {
value: item.title, optionArr.push({
})); id: option.id,
setDataSelectCampaign(arr); label: option.title,
value: option.title,
});
});
console.log("option arr", optionArr);
setDataSelectCampaign(optionArr);
} }
return ( return (
@ -256,127 +218,104 @@ export default function ContentBlast() {
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3 bg-white rounded-sm p-4" className="space-y-3 bg-white rounded-sm p-4"
> >
<p className="font-semibold">Broadcast</p> <p className="fonnt-semibold">Broadcast</p>
{/* SELECT CAMPAIGN */}
<FormField <FormField
control={form.control} control={form.control}
name="selected" name="selected"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Pilih Campaign</FormLabel> <FormLabel>Subject</FormLabel>
<Select <Select
className="z-50"
options={dataSelectCampaign} options={dataSelectCampaign}
closeMenuOnSelect={false} closeMenuOnSelect={false}
components={animatedComponent} components={animatedComponent}
isMulti
onChange={field.onChange} onChange={field.onChange}
isMulti
/> />
<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 /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
{/* TITLE */}
<FormField <FormField
control={form.control} control={form.control}
name="title" name="title"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Judul</FormLabel> <FormLabel>Subject</FormLabel>
<Input {...field} placeholder="Masukkan judul" /> <Input
value={field.value}
placeholder="Masukkan Judul"
onChange={field.onChange}
/>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
{/* DETAIL */}
<FormField <FormField
control={form.control} control={form.control}
name="detail" name="detail"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Isi Pesan</FormLabel> <FormLabel>Detail Perencanaan</FormLabel>
{type === "wa" ? (
<CustomEditor <Textarea value={field.value} onChange={field.onChange} />
onChange={field.onChange} ) : (
initialData={field.value} <CustomEditor
/> onChange={field.onChange}
initialData={field.value}
/>
)}
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<div className="flex flex-row gap-2 mt-4 pt-4">
{/* BUTTONS */} <Button
<div className="flex gap-2 mt-4"> size="md"
<Button variant="outline" type="button"> type="button"
variant="outline"
color="destructive"
className="text-xs"
>
Cancel Cancel
</Button> </Button>
<Button type="submit" color="primary"> <Button size="md" type="submit" color="primary" className="text-xs">
Submit Submit
</Button> </Button>
</div> </div>
{/* MODAL SENT */}
<Dialog open={openModal}> <Dialog open={openModal}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Terkirim!</DialogTitle> <DialogTitle>Terkirim !!</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="flex flex-col justify-center items-center gap-3 mb-3 text-sm">
<div className="flex flex-col items-center gap-3">
<img <img
src="/assets/img/illust-for-broadcast-sent.png" src="/assets/img/illust-for-broadcast-sent.png"
className="w-[70%]" className="w-[70%]"
/> />
Pesan telah dikirim sesuai pilihan Anda. Untuk melihat Email Terkirim silahkan cek menu Sent!
</div> </div>
<DialogFooter className="flex justify-center"> <DialogFooter className="flex justify-center">
<Button onClick={() => router.push("/admin/broadcast")}> {/* <Link
OK 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> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
@ -385,330 +324,3 @@ export default function ContentBlast() {
</Form> </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("new.netidhub.com")) {
// 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://new.netidhub.com"
// );
// 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>
// );
// }

View File

@ -311,15 +311,14 @@ export default function FormQuestionsForward() {
{` `} {` `}
mengirimkan pesan untuk{` `} mengirimkan pesan untuk{` `}
<Link <Link
// href={ href={
// detail?.feed detail?.feed
// ? detail?.feed?.permalink_url == undefined ? detail?.feed?.permalink_url == undefined
// ? detail?.feedUrl ? detail?.feedUrl
// : detail?.feed?.permalink_url : detail?.feed?.permalink_url
// : "" : ""
// } }
// target="_blank" target="_blank"
href={detail?.feedUrl}
className="font-bold" className="font-bold"
> >
{detail?.message} {detail?.message}

View File

@ -115,7 +115,7 @@ const ViewEditor = dynamic(
() => { () => {
return import("@/components/editor/view-editor"); return import("@/components/editor/view-editor");
}, },
{ ssr: false }, { ssr: false }
); );
interface Destination { interface Destination {
@ -198,12 +198,6 @@ export default function FormAudioDetail() {
satker: boolean; satker: boolean;
}> }>
>([]); >([]);
const [creatorLevelNumber, setCreatorLevelNumber] = useState<number | null>(
null,
);
const isContentFromSatker = creatorLevelNumber === 3;
const isContentFromMabesOrPolda =
creatorLevelNumber === 1 || creatorLevelNumber === 2;
useEffect(() => { useEffect(() => {
if (Number(userLevelId) === 216 && Number(roleId) === 3) { if (Number(userLevelId) === 216 && Number(roleId) === 3) {
@ -215,7 +209,7 @@ export default function FormAudioDetail() {
const handleFileUnitChange = ( const handleFileUnitChange = (
fileIndex: number, fileIndex: number,
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
setFileUnitSelections((prev) => { setFileUnitSelections((prev) => {
const newSelections = [...prev]; const newSelections = [...prev];
@ -235,7 +229,7 @@ export default function FormAudioDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[fileIndex] || new Set(), newArray[fileIndex] || new Set()
); );
if (value) { if (value) {
@ -266,12 +260,12 @@ export default function FormAudioDetail() {
(item: any) => (item: any) =>
item.levelNumber === 2 && item.levelNumber === 2 &&
item.name !== "SATKER POLRI" && item.name !== "SATKER POLRI" &&
currentFileCheckedLevels.has(Number(item.id)), currentFileCheckedLevels.has(Number(item.id))
); );
if (!hasSelectedPolda) { if (!hasSelectedPolda) {
alert( 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 return prev; // Batalkan perubahan
} }
@ -308,7 +302,7 @@ export default function FormAudioDetail() {
useEffect(() => { useEffect(() => {
if (detail?.assignedToTopLevel) { if (detail?.assignedToTopLevel) {
const outputSet = new Set( const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number), detail.assignedToTopLevel.split(",").map(Number)
); );
setUnitSelection({ setUnitSelection({
semua: outputSet.has(0), semua: outputSet.has(0),
@ -333,7 +327,7 @@ export default function FormAudioDetail() {
acc[polda.id] = false; acc[polda.id] = false;
return acc; return acc;
}, },
{}, {}
); );
setExpandedPolda(initialExpandedState); setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState); console.log("polres", initialExpandedState);
@ -368,7 +362,7 @@ export default function FormAudioDetail() {
const handleUnitChange = ( const handleUnitChange = (
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
if (key === "semua") { if (key === "semua") {
const newState = { const newState = {
@ -478,7 +472,7 @@ export default function FormAudioDetail() {
const handleCheckboxChange = (id: number) => { const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) => setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id], prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
); );
}; };
@ -500,7 +494,7 @@ export default function FormAudioDetail() {
if (scheduleId && scheduleType === "3") { if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) => const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis"), o.name.toLowerCase().includes("pers rilis")
); );
if (findCategory) { if (findCategory) {
@ -548,9 +542,6 @@ export default function FormAudioDetail() {
const details = response?.data?.data; const details = response?.data?.data;
setFiles(details?.files); setFiles(details?.files);
setDetail(details); setDetail(details);
if (details?.uploadedBy?.userLevel?.levelNumber) {
setCreatorLevelNumber(details.uploadedBy.userLevel.levelNumber);
}
setMain({ setMain({
type: details?.fileType.name, type: details?.fileType.name,
url: details?.files[0]?.url, url: details?.files[0]?.url,
@ -561,14 +552,14 @@ export default function FormAudioDetail() {
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set( const levels = new Set(
details.assignedToLevel.split(",").map(Number), details.assignedToLevel.split(",").map(Number)
); );
setCheckedLevels(levels); setCheckedLevels(levels);
} }
if (details?.publishedForObject) { if (details?.publishedForObject) {
const publisherIds = details?.publishedForObject.map( const publisherIds = details?.publishedForObject.map(
(obj: any) => obj.id, (obj: any) => obj.id
); );
setSelectedPublishers(publisherIds); setSelectedPublishers(publisherIds);
} }
@ -718,7 +709,7 @@ export default function FormAudioDetail() {
const setupPlacement = ( const setupPlacement = (
index: number, index: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
let temp = [...filePlacements]; let temp = [...filePlacements];
if (checked) { if (checked) {
@ -729,7 +720,7 @@ export default function FormAudioDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Checklist semua item di modal // Checklist semua item di modal
@ -779,7 +770,7 @@ export default function FormAudioDetail() {
// Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist // Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist
// JANGAN include satker dalam perhitungan auto "all" // JANGAN include satker dalam perhitungan auto "all"
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length === 3 && !now.includes("all")) { if (nonSatkerItems.length === 3 && !now.includes("all")) {
now.push("all"); now.push("all");
@ -794,7 +785,7 @@ export default function FormAudioDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Unchecklist semua item di modal // Unchecklist semua item di modal
@ -828,7 +819,7 @@ export default function FormAudioDetail() {
// Hapus "all" jika tidak semua item ter-checklist // Hapus "all" jika tidak semua item ter-checklist
if (now.includes("all")) { if (now.includes("all")) {
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length < 3) { if (nonSatkerItems.length < 3) {
const newData = now.filter((b) => b !== "all"); const newData = now.filter((b) => b !== "all");
@ -847,7 +838,7 @@ export default function FormAudioDetail() {
type: string, type: string,
url: string, url: string,
names: string, names: string,
format: string, format: string
) => { ) => {
console.log("Test 3 :", type, url, names, format); console.log("Test 3 :", type, url, names, format);
setMain({ setMain({
@ -882,7 +873,7 @@ export default function FormAudioDetail() {
const updateModalChecklistLevels = ( const updateModalChecklistLevels = (
fileIndex: number, fileIndex: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
if (!listDest || listDest.length === 0) return; if (!listDest || listDest.length === 0) return;
@ -915,7 +906,7 @@ export default function FormAudioDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Checklist SATKER POLRI dan semua sub-item di bawahnya // Checklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.add(Number(satkerItem.id)); currentFileLevels.add(Number(satkerItem.id));
@ -951,7 +942,7 @@ export default function FormAudioDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya // Unchecklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.delete(Number(satkerItem.id)); currentFileLevels.delete(Number(satkerItem.id));
@ -984,7 +975,7 @@ export default function FormAudioDetail() {
// Fungsi untuk mengupdate checklist levels untuk file tertentu // Fungsi untuk mengupdate checklist levels untuk file tertentu
const handleFileCheckboxChangePlacement = ( const handleFileCheckboxChangePlacement = (
fileIndex: number, fileIndex: number,
levelId: number, levelId: number
) => { ) => {
setFileCheckedLevels((prev) => { setFileCheckedLevels((prev) => {
const newArray = [...prev]; const newArray = [...prev];
@ -996,7 +987,7 @@ export default function FormAudioDetail() {
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
const poldaItem = listDest.find( const poldaItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if ( if (
poldaItem && poldaItem &&
@ -1019,7 +1010,7 @@ export default function FormAudioDetail() {
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya // Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if (satkerItem && satkerItem.name === "SATKER POLRI") { if (satkerItem && satkerItem.name === "SATKER POLRI") {
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES) // Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
@ -1050,7 +1041,7 @@ export default function FormAudioDetail() {
// Hitung total POLDA yang ada (bukan SATKER POLRI) // Hitung total POLDA yang ada (bukan SATKER POLRI)
const totalPoldaCount = listDest.filter( const totalPoldaCount = listDest.filter(
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI", (item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
).length; ).length;
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI) // Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
@ -1077,7 +1068,7 @@ export default function FormAudioDetail() {
} }
return total; return total;
}, },
0, 0
); );
// Hitung berapa banyak POLRES yang ter-checklist // Hitung berapa banyak POLRES yang ter-checklist
@ -1087,7 +1078,7 @@ export default function FormAudioDetail() {
return ( return (
total + total +
item.subDestination.filter((sub: any) => item.subDestination.filter((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
).length ).length
); );
} }
@ -1096,7 +1087,7 @@ export default function FormAudioDetail() {
// Cek apakah SATKER POLRI ter-checklist // Cek apakah SATKER POLRI ter-checklist
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
const isSatkerChecked = const isSatkerChecked =
satkerItem && currentFileLevels.has(Number(satkerItem.id)); satkerItem && currentFileLevels.has(Number(satkerItem.id));
@ -1130,7 +1121,7 @@ export default function FormAudioDetail() {
// Cek apakah semua sub-items sudah ter-checklist // Cek apakah semua sub-items sudah ter-checklist
const allSubItemsChecked = polda.subDestination?.every((sub: any) => const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
); );
if (allSubItemsChecked) { if (allSubItemsChecked) {
@ -1213,7 +1204,7 @@ export default function FormAudioDetail() {
{detail && {detail &&
!categories.find( !categories.find(
(cat) => (cat) =>
String(cat.id) === String(detail.category.id), String(cat.id) === String(detail.category.id)
) && ( ) && (
<SelectItem <SelectItem
key={String(detail.category.id)} key={String(detail.category.id)}
@ -1473,7 +1464,7 @@ export default function FormAudioDetail() {
Tingkat Distribusi: Tingkat Distribusi:
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* {[ {[
{ key: "semua", label: "Semua" }, { key: "semua", label: "Semua" },
{ key: "nasional", label: "Nasional" }, { key: "nasional", label: "Nasional" },
{ key: "wilayah", label: "Wilayah" }, { key: "wilayah", label: "Wilayah" },
@ -1481,39 +1472,6 @@ export default function FormAudioDetail() {
key: "international", key: "international",
label: "Internasional", 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) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
@ -1530,12 +1488,12 @@ export default function FormAudioDetail() {
handleFileUnitChange( handleFileUnitChange(
index, index,
item.key as keyof typeof unitSelection, item.key as keyof typeof unitSelection,
value as boolean, value as boolean
); );
setupPlacement( setupPlacement(
index, index,
item.key, item.key,
Boolean(value), Boolean(value)
); );
}} }}
/> />
@ -1551,262 +1509,251 @@ export default function FormAudioDetail() {
</div> </div>
{/* Detail Wilayah */} {/* Detail Wilayah */}
{/* {fileUnitSelections[index]?.wilayah && ( */} {fileUnitSelections[index]?.wilayah && (
{!isContentFromSatker && <div className="border-t border-gray-200 pt-2">
fileUnitSelections[index]?.wilayah && ( <p className="text-sm font-medium text-gray-700 mb-2">
<div className="border-t border-gray-200 pt-2"> Detail Wilayah:
<p className="text-sm font-medium text-gray-700 mb-2"> </p>
Detail Wilayah:
</p>
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */} {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3"> <div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[ {[
{ key: "polda", label: "POLDA" }, { key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" }, { key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" }, { key: "satker", label: "SATKER" },
].map((item, idx) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50" 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 {item.label}
id={`${item.key}-${index}`} </Label>
checked={ </div>
fileUnitSelections[index]?.[ ))}
item.key as keyof typeof unitSelection
] || false {/* Tombol Kustom sejajar dengan checkbox */}
} <div className="flex items-center justify-center p-3">
onCheckedChange={(value) => { <Dialog>
handleFileUnitChange( <DialogTrigger asChild>
index, <Button
item.key as keyof typeof unitSelection, variant="outline"
value as boolean, size="sm"
); className="gap-2"
setupPlacement(
index,
item.key,
Boolean(value),
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
> >
{item.label} <Icon
</Label> icon="material-symbols:tune"
</div> width={16}
))} height={16}
/>
{/* Tombol Kustom sejajar dengan checkbox */} {t("custom", {
<div className="flex items-center justify-center p-3"> defaultValue: "Kustom",
<Dialog> })}
<DialogTrigger asChild> </Button>
<Button </DialogTrigger>
variant="outline" <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
size="sm" <DialogHeader className="border-b border-gray-200 pb-4">
className="gap-2" <DialogTitle className="text-lg font-semibold">
> Daftar Wilayah POLDA dan POLRES
<Icon </DialogTitle>
icon="material-symbols:tune" </DialogHeader>
width={16} <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
height={16} {listDest.map((polda: any) => (
/> <div
{t("custom", { key={polda.id}
defaultValue: "Kustom", className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
})} >
</Button> {/* Header POLDA */}
</DialogTrigger> <div className="flex items-center justify-between">
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]"> <Label className="flex items-center gap-3 flex-1 cursor-pointer">
<DialogHeader className="border-b border-gray-200 pb-4"> <Checkbox
<DialogTitle className="text-lg font-semibold"> checked={
Daftar Wilayah POLDA dan fileCheckedLevels[
POLRES index
</DialogTitle> ]?.has(
</DialogHeader> Number(polda.id)
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1"> ) || false
{listDest.map((polda: any) => ( }
<div onCheckedChange={() =>
key={polda.id} handleFileCheckboxChangePlacement(
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow" index,
> Number(polda.id)
{/* Header POLDA */} )
<div className="flex items-center justify-between"> }
<Label className="flex items-center gap-3 flex-1 cursor-pointer"> />
<Checkbox <span className="font-semibold text-gray-900 text-sm">
checked={ {polda.name}
fileCheckedLevels[ </span>
index </Label>
]?.has( {polda.subDestination && (
Number(polda.id), <button
) || false onClick={(e) => {
} e.preventDefault();
onCheckedChange={() => e.stopPropagation();
handleFileCheckboxChangePlacement( toggleExpand(
index, polda.id
Number(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}
/> />
<span className="font-semibold text-gray-900 text-sm"> </button>
{polda.name} )}
</span> </div>
</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>
{/* Sub-items */} {/* Sub-items */}
{polda.subDestination && {polda.subDestination &&
expandedPolda[ expandedPolda[polda.id] && (
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="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2"> <div className="mb-2 flex justify-start">
{/* Tombol Pilih Semua untuk sub-items */} {(() => {
<div className="mb-2 flex justify-start"> const allSubItemsChecked =
{(() => { polda.subDestination?.every(
const allSubItemsChecked = (sub: any) =>
polda.subDestination?.every( fileCheckedLevels[
(sub: any) => 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>
);
})()}
</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[ fileCheckedLevels[
index index
]?.has( ]?.has(
Number( Number(
sub.id, sub.id
), )
), ) || false
); }
return ( onCheckedChange={() =>
<Button handleFileCheckboxChangePlacement(
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index, index,
polda, Number(
sub.id
)
) )
} }
> />
{allSubItemsChecked ? ( <span className="text-gray-700">
<> {sub.name}
<Icon </span>
icon="material-symbols:check-indeterminate-small" </Label>
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>
</div> ))}
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4"> </div>
<DialogClose asChild> <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<Button variant="outline"> <DialogClose asChild>
{t("cancel", { <Button variant="outline">
defaultValue: "Batal", {t("cancel", {
})} defaultValue: "Batal",
</Button> })}
</DialogClose> </Button>
<DialogClose asChild> </DialogClose>
<Button> <DialogClose asChild>
{/* {t("save", { <Button>
{/* {t("save", {
defaultValue: "Simpan", defaultValue: "Simpan",
})} */} })} */}
Simpan Simpan
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
</div> </div>
</div> </div>
)} </div>
)}
</div> </div>
</div> </div>
) : ( ) : (
@ -1885,7 +1832,7 @@ export default function FormAudioDetail() {
> >
{template} {template}
</Button> </Button>
), )
)} )}
</div> </div>
</div> </div>

View File

@ -731,7 +731,7 @@ export default function FormAudio() {
setIsStartUpload(true); setIsStartUpload(true);
setProgressList(progressInfoArr); setProgressList(progressInfoArr);
// close(); close();
// showProgress(); // showProgress();
files.map(async (item: any, index: number) => { files.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "0"); await uploadResumableFile(index, String(id), item, "0");
@ -841,8 +841,6 @@ export default function FormAudio() {
} }
if (counter == progressInfo.length) { if (counter == progressInfo.length) {
setIsStartUpload(false); setIsStartUpload(false);
close();
// hideProgress(); // hideProgress();
Cookies.remove("idCreate"); Cookies.remove("idCreate");
successSubmit("/in/contributor/content/audio"); successSubmit("/in/contributor/content/audio");
@ -1806,7 +1804,7 @@ export default function FormAudio() {
</Card> </Card>
<div className="flex flex-row justify-end gap-3"> <div className="flex flex-row justify-end gap-3">
<div className="mt-4"> <div className="mt-4">
{levelNumber !== "2" && ( {levelNumber !== "2" && levelNumber !== "3" && (
<Button type="submit" color="primary"> <Button type="submit" color="primary">
{t("submit", { defaultValue: "Submit" })} {t("submit", { defaultValue: "Submit" })}
</Button> </Button>

View File

@ -115,7 +115,7 @@ const ViewEditor = dynamic(
() => { () => {
return import("@/components/editor/view-editor"); return import("@/components/editor/view-editor");
}, },
{ ssr: false }, { ssr: false }
); );
interface Destination { interface Destination {
@ -172,12 +172,6 @@ export default function FormImageDetail() {
satker: boolean; satker: boolean;
}> }>
>([]); >([]);
const [creatorLevelNumber, setCreatorLevelNumber] = useState<number | null>(
null,
);
const isContentFromSatker = creatorLevelNumber === 3;
const isContentFromMabesOrPolda =
creatorLevelNumber === 1 || creatorLevelNumber === 2;
useEffect(() => { useEffect(() => {
if (Number(userLevelId) === 216 && Number(roleId) === 3) { if (Number(userLevelId) === 216 && Number(roleId) === 3) {
@ -206,7 +200,7 @@ export default function FormImageDetail() {
const [files, setFiles] = useState<FileType[]>([]); const [files, setFiles] = useState<FileType[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<number[]>([]); const [rejectedFiles, setRejectedFiles] = useState<number[]>([]);
const [expandedPolda, setExpandedPolda] = useState<Record<number, boolean>>( const [expandedPolda, setExpandedPolda] = useState<Record<number, boolean>>(
{}, {}
); );
// State untuk melacak apakah perubahan berasal dari checkbox utama // State untuk melacak apakah perubahan berasal dari checkbox utama
@ -237,7 +231,7 @@ export default function FormImageDetail() {
useEffect(() => { useEffect(() => {
if (detail?.assignedToTopLevel) { if (detail?.assignedToTopLevel) {
const outputSet = new Set( const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number), detail.assignedToTopLevel.split(",").map(Number)
); );
setUnitSelection({ setUnitSelection({
semua: outputSet.has(0), semua: outputSet.has(0),
@ -266,7 +260,7 @@ export default function FormImageDetail() {
acc[polda.id] = false; acc[polda.id] = false;
return acc; return acc;
}, },
{}, {}
); );
setExpandedPolda(initialExpandedState); setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState); console.log("polres", initialExpandedState);
@ -305,7 +299,7 @@ export default function FormImageDetail() {
(item: any) => (item: any) =>
item.levelNumber === 2 && item.levelNumber === 2 &&
item.name !== "SATKER POLRI" && item.name !== "SATKER POLRI" &&
checkedLevels.has(Number(item.id)), checkedLevels.has(Number(item.id))
).length; ).length;
const checkedPolresCount = listDest.reduce((total: number, item: any) => { const checkedPolresCount = listDest.reduce((total: number, item: any) => {
@ -314,7 +308,7 @@ export default function FormImageDetail() {
return ( return (
total + total +
item.subDestination.filter((sub: any) => item.subDestination.filter((sub: any) =>
checkedLevels.has(Number(sub.id)), checkedLevels.has(Number(sub.id))
).length ).length
); );
} }
@ -322,12 +316,12 @@ export default function FormImageDetail() {
}, 0); }, 0);
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
const checkedSatkerCount = satkerItem const checkedSatkerCount = satkerItem
? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) + ? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
(satkerItem.subDestination?.filter((sub: any) => (satkerItem.subDestination?.filter((sub: any) =>
checkedLevels.has(Number(sub.id)), checkedLevels.has(Number(sub.id))
).length || 0) ).length || 0)
: 0; : 0;
@ -397,7 +391,7 @@ export default function FormImageDetail() {
} else if (mainCheckboxChangeType === "satker_checked") { } else if (mainCheckboxChangeType === "satker_checked") {
// Checklist satker // Checklist satker
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
newCheckedLevels.add(Number(satkerItem.id)); newCheckedLevels.add(Number(satkerItem.id));
@ -445,7 +439,7 @@ export default function FormImageDetail() {
} else if (mainCheckboxChangeType === "satker_unchecked") { } else if (mainCheckboxChangeType === "satker_unchecked") {
// Clear satker dan semua sub-item di bawahnya dari checkedLevels // Clear satker dan semua sub-item di bawahnya dari checkedLevels
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
newCheckedLevels.delete(Number(satkerItem.id)); newCheckedLevels.delete(Number(satkerItem.id));
@ -472,7 +466,7 @@ export default function FormImageDetail() {
const handleFileUnitChange = ( const handleFileUnitChange = (
fileIndex: number, fileIndex: number,
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
setFileUnitSelections((prev) => { setFileUnitSelections((prev) => {
const newSelections = [...prev]; const newSelections = [...prev];
@ -492,7 +486,7 @@ export default function FormImageDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[fileIndex] || new Set(), newArray[fileIndex] || new Set()
); );
if (value) { if (value) {
@ -523,12 +517,12 @@ export default function FormImageDetail() {
(item: any) => (item: any) =>
item.levelNumber === 2 && item.levelNumber === 2 &&
item.name !== "SATKER POLRI" && item.name !== "SATKER POLRI" &&
currentFileCheckedLevels.has(Number(item.id)), currentFileCheckedLevels.has(Number(item.id))
); );
if (!hasSelectedPolda) { if (!hasSelectedPolda) {
alert( 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 return prev; // Batalkan perubahan
} }
@ -558,7 +552,7 @@ export default function FormImageDetail() {
// Fungsi lama untuk kompatibilitas (akan dihapus nanti) // Fungsi lama untuk kompatibilitas (akan dihapus nanti)
const handleUnitChange = ( const handleUnitChange = (
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
// Set flag bahwa perubahan berasal dari checkbox utama // Set flag bahwa perubahan berasal dari checkbox utama
setIsUpdatingFromMainCheckbox(true); setIsUpdatingFromMainCheckbox(true);
@ -584,13 +578,13 @@ export default function FormImageDetail() {
(item: any) => (item: any) =>
item.levelNumber === 2 && item.levelNumber === 2 &&
item.name !== "SATKER POLRI" && item.name !== "SATKER POLRI" &&
checkedLevels.has(Number(item.id)), checkedLevels.has(Number(item.id))
); );
if (!hasSelectedPolda) { if (!hasSelectedPolda) {
// Jika tidak ada POLDA yang dipilih, tampilkan peringatan dan batalkan // Jika tidak ada POLDA yang dipilih, tampilkan peringatan dan batalkan
alert( alert(
"Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES.", "Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."
); );
// Reset flag karena perubahan dibatalkan // Reset flag karena perubahan dibatalkan
setIsUpdatingFromMainCheckbox(false); setIsUpdatingFromMainCheckbox(false);
@ -646,14 +640,14 @@ export default function FormImageDetail() {
const handleCheckboxChange = (id: number) => { const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) => setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id], prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
); );
}; };
// Fungsi untuk mengupdate checklist levels untuk file tertentu // Fungsi untuk mengupdate checklist levels untuk file tertentu
const handleFileCheckboxChangePlacement = ( const handleFileCheckboxChangePlacement = (
fileIndex: number, fileIndex: number,
levelId: number, levelId: number
) => { ) => {
setFileCheckedLevels((prev) => { setFileCheckedLevels((prev) => {
const newArray = [...prev]; const newArray = [...prev];
@ -665,7 +659,7 @@ export default function FormImageDetail() {
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
const poldaItem = listDest.find( const poldaItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if ( if (
poldaItem && poldaItem &&
@ -688,7 +682,7 @@ export default function FormImageDetail() {
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya // Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if (satkerItem && satkerItem.name === "SATKER POLRI") { if (satkerItem && satkerItem.name === "SATKER POLRI") {
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES) // Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
@ -716,7 +710,7 @@ export default function FormImageDetail() {
// Cek apakah semua sub-items sudah ter-checklist // Cek apakah semua sub-items sudah ter-checklist
const allSubItemsChecked = polda.subDestination?.every((sub: any) => const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
); );
if (allSubItemsChecked) { if (allSubItemsChecked) {
@ -756,7 +750,7 @@ export default function FormImageDetail() {
// Hitung total POLDA yang ada (bukan SATKER POLRI) // Hitung total POLDA yang ada (bukan SATKER POLRI)
const totalPoldaCount = listDest.filter( const totalPoldaCount = listDest.filter(
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI", (item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
).length; ).length;
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI) // Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
@ -783,7 +777,7 @@ export default function FormImageDetail() {
} }
return total; return total;
}, },
0, 0
); );
// Hitung berapa banyak POLRES yang ter-checklist // Hitung berapa banyak POLRES yang ter-checklist
@ -793,7 +787,7 @@ export default function FormImageDetail() {
return ( return (
total + total +
item.subDestination.filter((sub: any) => item.subDestination.filter((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
).length ).length
); );
} }
@ -802,7 +796,7 @@ export default function FormImageDetail() {
// Cek apakah SATKER POLRI ter-checklist // Cek apakah SATKER POLRI ter-checklist
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
const isSatkerChecked = const isSatkerChecked =
satkerItem && currentFileLevels.has(Number(satkerItem.id)); satkerItem && currentFileLevels.has(Number(satkerItem.id));
@ -839,7 +833,7 @@ export default function FormImageDetail() {
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
const poldaItem = listDest.find( const poldaItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if ( if (
poldaItem && poldaItem &&
@ -862,7 +856,7 @@ export default function FormImageDetail() {
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya // Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if (satkerItem && satkerItem.name === "SATKER POLRI") { if (satkerItem && satkerItem.name === "SATKER POLRI") {
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES) // Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
@ -893,7 +887,7 @@ export default function FormImageDetail() {
if (scheduleId && scheduleType === "3") { if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) => const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis"), o.name.toLowerCase().includes("pers rilis")
); );
if (findCategory) { if (findCategory) {
@ -942,11 +936,6 @@ export default function FormImageDetail() {
console.log("detail", details); console.log("detail", details);
setFiles(details?.files); setFiles(details?.files);
setDetail(details); setDetail(details);
if (details?.uploadedBy?.userLevel?.levelNumber) {
setCreatorLevelNumber(details.uploadedBy.userLevel.levelNumber);
}
setMain({ setMain({
type: details?.fileType.name, type: details?.fileType.name,
url: details?.files[0]?.url, url: details?.files[0]?.url,
@ -957,14 +946,14 @@ export default function FormImageDetail() {
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set<number>( const levels = new Set<number>(
details.assignedToLevel.split(",").map(Number), details.assignedToLevel.split(",").map(Number)
); );
setCheckedLevels(levels); setCheckedLevels(levels);
} }
if (details?.publishedForObject) { if (details?.publishedForObject) {
const publisherIds = details?.publishedForObject?.map( const publisherIds = details?.publishedForObject?.map(
(obj: any) => obj.id, (obj: any) => obj.id
); );
setSelectedPublishers(publisherIds); setSelectedPublishers(publisherIds);
} }
@ -974,7 +963,7 @@ export default function FormImageDetail() {
const filesData = details.files || []; const filesData = details.files || [];
const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) => const fileUrls = filesData.map((file: { thumbnailFileUrl: string }) =>
file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg", file.thumbnailFileUrl ? file.thumbnailFileUrl : "default-image.jpg"
); );
setDetailThumb(fileUrls); setDetailThumb(fileUrls);
@ -1094,7 +1083,7 @@ export default function FormImageDetail() {
const setupPlacement = ( const setupPlacement = (
index: number, index: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
let temp = [...filePlacements]; let temp = [...filePlacements];
if (checked) { if (checked) {
@ -1105,7 +1094,7 @@ export default function FormImageDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Checklist semua item di modal // Checklist semua item di modal
@ -1155,7 +1144,7 @@ export default function FormImageDetail() {
// Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist // Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist
// JANGAN include satker dalam perhitungan auto "all" // JANGAN include satker dalam perhitungan auto "all"
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length === 3 && !now.includes("all")) { if (nonSatkerItems.length === 3 && !now.includes("all")) {
now.push("all"); now.push("all");
@ -1170,7 +1159,7 @@ export default function FormImageDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Unchecklist semua item di modal // Unchecklist semua item di modal
@ -1204,7 +1193,7 @@ export default function FormImageDetail() {
// Hapus "all" jika tidak semua item ter-checklist // Hapus "all" jika tidak semua item ter-checklist
if (now.includes("all")) { if (now.includes("all")) {
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length < 3) { if (nonSatkerItems.length < 3) {
const newData = now.filter((b) => b !== "all"); const newData = now.filter((b) => b !== "all");
@ -1223,7 +1212,7 @@ export default function FormImageDetail() {
const updateModalChecklistLevels = ( const updateModalChecklistLevels = (
fileIndex: number, fileIndex: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
if (!listDest || listDest.length === 0) return; if (!listDest || listDest.length === 0) return;
@ -1256,7 +1245,7 @@ export default function FormImageDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Checklist SATKER POLRI dan semua sub-item di bawahnya // Checklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.add(Number(satkerItem.id)); currentFileLevels.add(Number(satkerItem.id));
@ -1292,7 +1281,7 @@ export default function FormImageDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya // Unchecklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.delete(Number(satkerItem.id)); currentFileLevels.delete(Number(satkerItem.id));
@ -1322,7 +1311,7 @@ export default function FormImageDetail() {
type: string, type: string,
url: string, url: string,
names: string, names: string,
format: string, format: string
) => { ) => {
console.log("Test 3 :", type, url, names, format); console.log("Test 3 :", type, url, names, format);
setMain({ setMain({
@ -1422,7 +1411,7 @@ export default function FormImageDetail() {
{detail && {detail &&
!categories.find( !categories.find(
(cat) => (cat) =>
String(cat.id) === String(detail.category.id), String(cat.id) === String(detail.category.id)
) && ( ) && (
<SelectItem <SelectItem
key={String(detail.category.id)} key={String(detail.category.id)}
@ -1743,7 +1732,7 @@ export default function FormImageDetail() {
Tingkat Distribusi: Tingkat Distribusi:
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* {[ {[
{ key: "semua", label: "Semua" }, { key: "semua", label: "Semua" },
{ key: "nasional", label: "Nasional" }, { key: "nasional", label: "Nasional" },
{ key: "wilayah", label: "Wilayah" }, { key: "wilayah", label: "Wilayah" },
@ -1751,39 +1740,6 @@ export default function FormImageDetail() {
key: "international", key: "international",
label: "Internasional", 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) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
@ -1800,12 +1756,12 @@ export default function FormImageDetail() {
handleFileUnitChange( handleFileUnitChange(
index, index,
item.key as keyof typeof unitSelection, item.key as keyof typeof unitSelection,
value as boolean, value as boolean
); );
setupPlacement( setupPlacement(
index, index,
item.key, item.key,
Boolean(value), Boolean(value)
); );
}} }}
/> />
@ -1821,257 +1777,246 @@ export default function FormImageDetail() {
</div> </div>
{/* Detail Wilayah */} {/* Detail Wilayah */}
{/* {fileUnitSelections[index]?.wilayah && ( */} {fileUnitSelections[index]?.wilayah && (
{!isContentFromSatker && <div className="border-t border-gray-200 pt-2">
fileUnitSelections[index]?.wilayah && ( <p className="text-sm font-medium text-gray-700 mb-2">
<div className="border-t border-gray-200 pt-2"> Detail Wilayah:
<p className="text-sm font-medium text-gray-700 mb-2"> </p>
Detail Wilayah:
</p>
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */} {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3"> <div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[ {[
{ key: "polda", label: "POLDA" }, { key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" }, { key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" }, { key: "satker", label: "SATKER" },
].map((item, idx) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50" 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 {item.label}
id={`${item.key}-${index}`} </Label>
checked={ </div>
fileUnitSelections[index]?.[ ))}
item.key as keyof typeof unitSelection
] || false {/* Tombol Kustom sejajar dengan checkbox */}
} <div className="flex items-center justify-center p-3">
onCheckedChange={(value) => { <Dialog>
handleFileUnitChange( <DialogTrigger asChild>
index, <Button
item.key as keyof typeof unitSelection, variant="outline"
value as boolean, size="sm"
); className="gap-2"
setupPlacement(
index,
item.key,
Boolean(value),
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
> >
{item.label} <Icon
</Label> icon="material-symbols:tune"
</div> width={16}
))} height={16}
/>
{/* Tombol Kustom sejajar dengan checkbox */} {t("custom", {
<div className="flex items-center justify-center p-3"> defaultValue: "Kustom",
<Dialog> })}
<DialogTrigger asChild> </Button>
<Button </DialogTrigger>
variant="outline" <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
size="sm" <DialogHeader className="border-b border-gray-200 pb-4">
className="gap-2" <DialogTitle className="text-lg font-semibold">
> Daftar Wilayah POLDA dan POLRES
<Icon </DialogTitle>
icon="material-symbols:tune" </DialogHeader>
width={16} <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
height={16} {listDest.map((polda: any) => (
/> <div
{t("custom", { key={polda.id}
defaultValue: "Kustom", className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
})} >
</Button> {/* Header POLDA */}
</DialogTrigger> <div className="flex items-center justify-between">
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]"> <Label className="flex items-center gap-3 flex-1 cursor-pointer">
<DialogHeader className="border-b border-gray-200 pb-4"> <Checkbox
<DialogTitle className="text-lg font-semibold"> checked={
Daftar Wilayah POLDA dan fileCheckedLevels[
POLRES index
</DialogTitle> ]?.has(
</DialogHeader> Number(polda.id)
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1"> ) || false
{listDest.map((polda: any) => ( }
<div onCheckedChange={() =>
key={polda.id} handleFileCheckboxChangePlacement(
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow" index,
> Number(polda.id)
{/* Header POLDA */} )
<div className="flex items-center justify-between"> }
<Label className="flex items-center gap-3 flex-1 cursor-pointer"> />
<Checkbox <span className="font-semibold text-gray-900 text-sm">
checked={ {polda.name}
fileCheckedLevels[ </span>
index </Label>
]?.has( {polda.subDestination && (
Number(polda.id), <button
) || false onClick={(e) => {
} e.preventDefault();
onCheckedChange={() => e.stopPropagation();
handleFileCheckboxChangePlacement( toggleExpand(
index, polda.id
Number(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}
/> />
<span className="font-semibold text-gray-900 text-sm"> </button>
{polda.name} )}
</span> </div>
</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>
{/* Sub-items */} {/* Sub-items */}
{polda.subDestination && {polda.subDestination &&
expandedPolda[ expandedPolda[polda.id] && (
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="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2"> <div className="mb-2 flex justify-start">
{/* Tombol Pilih Semua untuk sub-items */} {(() => {
<div className="mb-2 flex justify-start"> const allSubItemsChecked =
{(() => { polda.subDestination?.every(
const allSubItemsChecked = (sub: any) =>
polda.subDestination?.every( fileCheckedLevels[
(sub: any) => 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>
);
})()}
</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[ fileCheckedLevels[
index index
]?.has( ]?.has(
Number( Number(
sub.id, sub.id
), )
), ) || false
); }
return ( onCheckedChange={() =>
<Button handleFileCheckboxChangePlacement(
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index, index,
polda, Number(
sub.id
)
) )
} }
> />
{allSubItemsChecked ? ( <span className="text-gray-700">
<> {sub.name}
<Icon </span>
icon="material-symbols:check-indeterminate-small" </Label>
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>
</div> ))}
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4"> </div>
<DialogClose asChild> <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<Button variant="outline"> <DialogClose asChild>
{t("cancel", { <Button variant="outline">
defaultValue: "Batal", {t("cancel", {
})} defaultValue: "Batal",
</Button> })}
</DialogClose> </Button>
<DialogClose asChild> </DialogClose>
<Button>Simpan</Button> <DialogClose asChild>
</DialogClose> <Button>Simpan</Button>
</div> </DialogClose>
</DialogContent> </div>
</Dialog> </DialogContent>
</div> </Dialog>
</div> </div>
</div> </div>
)} </div>
)}
</div> </div>
</div> </div>
) : ( ) : (
@ -2150,7 +2095,7 @@ export default function FormImageDetail() {
> >
{template} {template}
</Button> </Button>
), )
)} )}
</div> </div>
</div> </div>

View File

@ -82,7 +82,7 @@ const CustomEditor = dynamic(
() => { () => {
return import("@/components/editor/custom-editor"); return import("@/components/editor/custom-editor");
}, },
{ ssr: false }, { ssr: false }
); );
export default function FormImage() { export default function FormImage() {
@ -117,7 +117,7 @@ export default function FormImage() {
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>(""); const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>( const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null, null
); );
const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = const [selectedWritingStyle, setSelectedWritingStyle] =
@ -183,17 +183,17 @@ export default function FormImage() {
.filter( .filter(
(file) => (file) =>
["image/jpeg", "image/png", "image/jpg"].includes(file.type) && ["image/jpeg", "image/png", "image/jpg"].includes(file.type) &&
file.size <= MAX_FILE_SIZE, file.size <= MAX_FILE_SIZE
) )
.map((file) => .map((file) =>
Object.assign(file, { Object.assign(file, {
preview: URL.createObjectURL(file), preview: URL.createObjectURL(file),
}), })
); );
if (validFiles.length === 0) { if (validFiles.length === 0) {
toast.error( 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; return;
} }
@ -227,12 +227,12 @@ export default function FormImage() {
files.every( files.every(
(file: File) => (file: File) =>
["image/jpeg", "image/png", "image/jpg"].includes(file.type) && ["image/jpeg", "image/png", "image/jpg"].includes(file.type) &&
file.size <= 100 * 1024 * 1024, file.size <= 100 * 1024 * 1024
), ),
{ {
message: message:
"Hanya file .jpg, .jpeg, .png, maksimal 100MB yang diperbolehkan.", "Hanya file .jpg, .jpeg, .png, maksimal 100MB yang diperbolehkan.",
}, }
), ),
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }), categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
tags: z tags: z
@ -454,7 +454,7 @@ export default function FormImage() {
const articleData = await waitForStatusUpdate(); const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace( const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g, /<img[^>]*>/g,
"", ""
); );
const articleImagesData = articleData?.imagesUrl?.split(","); const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || ""); setArticleBody(cleanArticleBody || "");
@ -507,7 +507,7 @@ export default function FormImage() {
if (scheduleId && scheduleType === "3") { if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) => const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis"), o.name.toLowerCase().includes("pers rilis")
); );
if (findCategory) { if (findCategory) {
@ -530,7 +530,7 @@ export default function FormImage() {
setPublishedFor( setPublishedFor(
options options
.filter((opt: any) => opt.id !== "all") .filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id), .map((opt: any) => opt.id)
); );
} }
} else { } else {
@ -594,16 +594,11 @@ export default function FormImage() {
} else if (data.rewriteDescription && selectedFileType === "rewrite") { } else if (data.rewriteDescription && selectedFileType === "rewrite") {
finalDescription = data.rewriteDescription; finalDescription = data.rewriteDescription;
console.log("📤 Upload versi rewrite"); console.log("📤 Upload versi rewrite");
} else if (data.description?.trim()) { } else {
finalDescription = data.description; // fallback: gunakan versi Indonesia original
} else if (data.descriptionOri?.trim()) { finalDescription = data.descriptionOri ?? "";
finalDescription = data.descriptionOri; console.log("📤 Upload versi Indonesia (original)");
} }
// else {
// // fallback: gunakan versi Indonesia original
// finalDescription = data.descriptionOri ?? "";
// console.log("📤 Upload versi Indonesia (original)");
// }
if (!finalDescription?.trim()) { if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
@ -689,13 +684,13 @@ export default function FormImage() {
setIsStartUpload(true); setIsStartUpload(true);
setProgressList(progressInfoArr); setProgressList(progressInfoArr);
// close(); close();
files.map(async (item: any, index: number) => { files.map(async (item: any, index: number) => {
await uploadResumableFile( await uploadResumableFile(
index, index,
String(id), String(id),
item, item,
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0", fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
); );
}); });
@ -722,7 +717,7 @@ export default function FormImage() {
idx: number, idx: number,
id: string, id: string,
file: any, file: any,
duration: string, duration: string
) { ) {
console.log(idx, id, file, duration); console.log(idx, id, file, duration);
@ -758,7 +753,7 @@ export default function FormImage() {
onChunkComplete: ( onChunkComplete: (
chunkSize: any, chunkSize: any,
bytesAccepted: any, bytesAccepted: any,
bytesTotal: any, bytesTotal: any
) => { ) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100); const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen; progressInfo[idx].percentage = uploadPersen;
@ -800,7 +795,6 @@ export default function FormImage() {
} }
if (counter == progressInfo.length) { if (counter == progressInfo.length) {
setIsStartUpload(false); setIsStartUpload(false);
close();
// hideProgress(); // hideProgress();
Cookies.remove("idCreate"); Cookies.remove("idCreate");
successSubmit("/in/contributor/content/image"); successSubmit("/in/contributor/content/image");

View File

@ -1372,7 +1372,6 @@ export default function FormConvertSPIT() {
); );
} }
// {spit with new layout file placements} // {spit with new layout file placements}
// "use client"; // "use client";
// import React, { ChangeEvent, useEffect, useRef, useState } from "react"; // import React, { ChangeEvent, useEffect, useRef, useState } from "react";

View File

@ -114,7 +114,7 @@ const ViewEditor = dynamic(
() => { () => {
return import("@/components/editor/view-editor"); return import("@/components/editor/view-editor");
}, },
{ ssr: false }, { ssr: false }
); );
interface Destination { interface Destination {
@ -191,12 +191,6 @@ export default function FormTeksDetail() {
satker: boolean; satker: boolean;
}> }>
>([]); >([]);
const [creatorLevelNumber, setCreatorLevelNumber] = useState<number | null>(
null,
);
const isContentFromSatker = creatorLevelNumber === 3;
const isContentFromMabesOrPolda =
creatorLevelNumber === 1 || creatorLevelNumber === 2;
useEffect(() => { useEffect(() => {
if (Number(userLevelId) === 216 && Number(roleId) === 3) { if (Number(userLevelId) === 216 && Number(roleId) === 3) {
@ -208,7 +202,7 @@ export default function FormTeksDetail() {
const handleFileUnitChange = ( const handleFileUnitChange = (
fileIndex: number, fileIndex: number,
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
setFileUnitSelections((prev) => { setFileUnitSelections((prev) => {
const newSelections = [...prev]; const newSelections = [...prev];
@ -228,7 +222,7 @@ export default function FormTeksDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[fileIndex] || new Set(), newArray[fileIndex] || new Set()
); );
if (value) { if (value) {
@ -259,12 +253,12 @@ export default function FormTeksDetail() {
(item: any) => (item: any) =>
item.levelNumber === 2 && item.levelNumber === 2 &&
item.name !== "SATKER POLRI" && item.name !== "SATKER POLRI" &&
currentFileCheckedLevels.has(Number(item.id)), currentFileCheckedLevels.has(Number(item.id))
); );
if (!hasSelectedPolda) { if (!hasSelectedPolda) {
alert( 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 return prev; // Batalkan perubahan
} }
@ -303,7 +297,7 @@ export default function FormTeksDetail() {
useEffect(() => { useEffect(() => {
if (detail?.assignedToTopLevel) { if (detail?.assignedToTopLevel) {
const outputSet = new Set( const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number), detail.assignedToTopLevel.split(",").map(Number)
); );
setUnitSelection({ setUnitSelection({
semua: outputSet.has(0), semua: outputSet.has(0),
@ -328,7 +322,7 @@ export default function FormTeksDetail() {
acc[polda.id] = false; acc[polda.id] = false;
return acc; return acc;
}, },
{}, {}
); );
setExpandedPolda(initialExpandedState); setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState); console.log("polres", initialExpandedState);
@ -363,7 +357,7 @@ export default function FormTeksDetail() {
const handleUnitChange = ( const handleUnitChange = (
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
if (key === "semua") { if (key === "semua") {
const newState = { const newState = {
@ -442,7 +436,7 @@ export default function FormTeksDetail() {
const handleCheckboxChange = (id: number) => { const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) => setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id], prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
); );
}; };
@ -464,7 +458,7 @@ export default function FormTeksDetail() {
if (scheduleId && scheduleType === "3") { if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) => const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis"), o.name.toLowerCase().includes("pers rilis")
); );
if (findCategory) { if (findCategory) {
@ -513,9 +507,6 @@ export default function FormTeksDetail() {
console.log("detail", details); console.log("detail", details);
setFiles(details?.files); setFiles(details?.files);
setDetail(details); setDetail(details);
if (details?.uploadedBy?.userLevel?.levelNumber) {
setCreatorLevelNumber(details.uploadedBy.userLevel.levelNumber);
}
setMain({ setMain({
type: details?.fileType.name, type: details?.fileType.name,
url: details?.files[0]?.url, url: details?.files[0]?.url,
@ -526,14 +517,14 @@ export default function FormTeksDetail() {
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set( const levels = new Set(
details.assignedToLevel.split(",").map(Number), details.assignedToLevel.split(",").map(Number)
); );
setCheckedLevels(levels); setCheckedLevels(levels);
} }
if (details?.publishedForObject) { if (details?.publishedForObject) {
const publisherIds = details?.publishedForObject?.map( const publisherIds = details?.publishedForObject?.map(
(obj: any) => obj.id, (obj: any) => obj.id
); );
setSelectedPublishers(publisherIds); setSelectedPublishers(publisherIds);
} }
@ -705,7 +696,7 @@ export default function FormTeksDetail() {
type: string, type: string,
url: string, url: string,
names: string, names: string,
format: string, format: string
) => { ) => {
console.log("Test 3 :", type, url, names, format); console.log("Test 3 :", type, url, names, format);
setMain({ setMain({
@ -732,7 +723,7 @@ export default function FormTeksDetail() {
const setupPlacement = ( const setupPlacement = (
index: number, index: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
let temp = [...filePlacements]; let temp = [...filePlacements];
if (checked) { if (checked) {
@ -743,7 +734,7 @@ export default function FormTeksDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Checklist semua item di modal // Checklist semua item di modal
@ -793,7 +784,7 @@ export default function FormTeksDetail() {
// Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist // Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist
// JANGAN include satker dalam perhitungan auto "all" // JANGAN include satker dalam perhitungan auto "all"
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length === 3 && !now.includes("all")) { if (nonSatkerItems.length === 3 && !now.includes("all")) {
now.push("all"); now.push("all");
@ -808,7 +799,7 @@ export default function FormTeksDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Unchecklist semua item di modal // Unchecklist semua item di modal
@ -842,7 +833,7 @@ export default function FormTeksDetail() {
// Hapus "all" jika tidak semua item ter-checklist // Hapus "all" jika tidak semua item ter-checklist
if (now.includes("all")) { if (now.includes("all")) {
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length < 3) { if (nonSatkerItems.length < 3) {
const newData = now.filter((b) => b !== "all"); const newData = now.filter((b) => b !== "all");
@ -860,7 +851,7 @@ export default function FormTeksDetail() {
const updateModalChecklistLevels = ( const updateModalChecklistLevels = (
fileIndex: number, fileIndex: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
if (!listDest || listDest.length === 0) return; if (!listDest || listDest.length === 0) return;
@ -893,7 +884,7 @@ export default function FormTeksDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Checklist SATKER POLRI dan semua sub-item di bawahnya // Checklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.add(Number(satkerItem.id)); currentFileLevels.add(Number(satkerItem.id));
@ -929,7 +920,7 @@ export default function FormTeksDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya // Unchecklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.delete(Number(satkerItem.id)); currentFileLevels.delete(Number(satkerItem.id));
@ -962,7 +953,7 @@ export default function FormTeksDetail() {
// Fungsi untuk mengupdate checklist levels untuk file tertentu // Fungsi untuk mengupdate checklist levels untuk file tertentu
const handleFileCheckboxChangePlacement = ( const handleFileCheckboxChangePlacement = (
fileIndex: number, fileIndex: number,
levelId: number, levelId: number
) => { ) => {
setFileCheckedLevels((prev) => { setFileCheckedLevels((prev) => {
const newArray = [...prev]; const newArray = [...prev];
@ -974,7 +965,7 @@ export default function FormTeksDetail() {
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
const poldaItem = listDest.find( const poldaItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if ( if (
poldaItem && poldaItem &&
@ -997,7 +988,7 @@ export default function FormTeksDetail() {
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya // Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if (satkerItem && satkerItem.name === "SATKER POLRI") { if (satkerItem && satkerItem.name === "SATKER POLRI") {
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES) // Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
@ -1028,7 +1019,7 @@ export default function FormTeksDetail() {
// Hitung total POLDA yang ada (bukan SATKER POLRI) // Hitung total POLDA yang ada (bukan SATKER POLRI)
const totalPoldaCount = listDest.filter( const totalPoldaCount = listDest.filter(
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI", (item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
).length; ).length;
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI) // Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
@ -1055,7 +1046,7 @@ export default function FormTeksDetail() {
} }
return total; return total;
}, },
0, 0
); );
// Hitung berapa banyak POLRES yang ter-checklist // Hitung berapa banyak POLRES yang ter-checklist
@ -1065,7 +1056,7 @@ export default function FormTeksDetail() {
return ( return (
total + total +
item.subDestination.filter((sub: any) => item.subDestination.filter((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
).length ).length
); );
} }
@ -1074,7 +1065,7 @@ export default function FormTeksDetail() {
// Cek apakah SATKER POLRI ter-checklist // Cek apakah SATKER POLRI ter-checklist
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
const isSatkerChecked = const isSatkerChecked =
satkerItem && currentFileLevels.has(Number(satkerItem.id)); satkerItem && currentFileLevels.has(Number(satkerItem.id));
@ -1108,7 +1099,7 @@ export default function FormTeksDetail() {
// Cek apakah semua sub-items sudah ter-checklist // Cek apakah semua sub-items sudah ter-checklist
const allSubItemsChecked = polda.subDestination?.every((sub: any) => const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
); );
if (allSubItemsChecked) { if (allSubItemsChecked) {
@ -1191,7 +1182,7 @@ export default function FormTeksDetail() {
{detail && {detail &&
!categories.find( !categories.find(
(cat) => (cat) =>
String(cat.id) === String(detail.category.id), String(cat.id) === String(detail.category.id)
) && ( ) && (
<SelectItem <SelectItem
key={String(detail.category.id)} key={String(detail.category.id)}
@ -1474,7 +1465,7 @@ export default function FormTeksDetail() {
Tingkat Distribusi: Tingkat Distribusi:
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* {[ {[
{ key: "semua", label: "Semua" }, { key: "semua", label: "Semua" },
{ key: "nasional", label: "Nasional" }, { key: "nasional", label: "Nasional" },
{ key: "wilayah", label: "Wilayah" }, { key: "wilayah", label: "Wilayah" },
@ -1482,39 +1473,6 @@ export default function FormTeksDetail() {
key: "international", key: "international",
label: "Internasional", 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) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
@ -1531,12 +1489,12 @@ export default function FormTeksDetail() {
handleFileUnitChange( handleFileUnitChange(
index, index,
item.key as keyof typeof unitSelection, item.key as keyof typeof unitSelection,
value as boolean, value as boolean
); );
setupPlacement( setupPlacement(
index, index,
item.key, item.key,
Boolean(value), Boolean(value)
); );
}} }}
/> />
@ -1552,262 +1510,251 @@ export default function FormTeksDetail() {
</div> </div>
{/* Detail Wilayah */} {/* Detail Wilayah */}
{/* {fileUnitSelections[index]?.wilayah && ( */} {fileUnitSelections[index]?.wilayah && (
{!isContentFromSatker && <div className="border-t border-gray-200 pt-2">
fileUnitSelections[index]?.wilayah && ( <p className="text-sm font-medium text-gray-700 mb-2">
<div className="border-t border-gray-200 pt-2"> Detail Wilayah:
<p className="text-sm font-medium text-gray-700 mb-2"> </p>
Detail Wilayah:
</p>
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */} {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3"> <div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[ {[
{ key: "polda", label: "POLDA" }, { key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" }, { key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" }, { key: "satker", label: "SATKER" },
].map((item, idx) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50" 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 {item.label}
id={`${item.key}-${index}`} </Label>
checked={ </div>
fileUnitSelections[index]?.[ ))}
item.key as keyof typeof unitSelection
] || false {/* Tombol Kustom sejajar dengan checkbox */}
} <div className="flex items-center justify-center p-3">
onCheckedChange={(value) => { <Dialog>
handleFileUnitChange( <DialogTrigger asChild>
index, <Button
item.key as keyof typeof unitSelection, variant="outline"
value as boolean, size="sm"
); className="gap-2"
setupPlacement(
index,
item.key,
Boolean(value),
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
> >
{item.label} <Icon
</Label> icon="material-symbols:tune"
</div> width={16}
))} height={16}
/>
{/* Tombol Kustom sejajar dengan checkbox */} {t("custom", {
<div className="flex items-center justify-center p-3"> defaultValue: "Kustom",
<Dialog> })}
<DialogTrigger asChild> </Button>
<Button </DialogTrigger>
variant="outline" <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
size="sm" <DialogHeader className="border-b border-gray-200 pb-4">
className="gap-2" <DialogTitle className="text-lg font-semibold">
> Daftar Wilayah POLDA dan POLRES
<Icon </DialogTitle>
icon="material-symbols:tune" </DialogHeader>
width={16} <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
height={16} {listDest.map((polda: any) => (
/> <div
{t("custom", { key={polda.id}
defaultValue: "Kustom", className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
})} >
</Button> {/* Header POLDA */}
</DialogTrigger> <div className="flex items-center justify-between">
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]"> <Label className="flex items-center gap-3 flex-1 cursor-pointer">
<DialogHeader className="border-b border-gray-200 pb-4"> <Checkbox
<DialogTitle className="text-lg font-semibold"> checked={
Daftar Wilayah POLDA dan fileCheckedLevels[
POLRES index
</DialogTitle> ]?.has(
</DialogHeader> Number(polda.id)
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1"> ) || false
{listDest.map((polda: any) => ( }
<div onCheckedChange={() =>
key={polda.id} handleFileCheckboxChangePlacement(
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow" index,
> Number(polda.id)
{/* Header POLDA */} )
<div className="flex items-center justify-between"> }
<Label className="flex items-center gap-3 flex-1 cursor-pointer"> />
<Checkbox <span className="font-semibold text-gray-900 text-sm">
checked={ {polda.name}
fileCheckedLevels[ </span>
index </Label>
]?.has( {polda.subDestination && (
Number(polda.id), <button
) || false onClick={(e) => {
} e.preventDefault();
onCheckedChange={() => e.stopPropagation();
handleFileCheckboxChangePlacement( toggleExpand(
index, polda.id
Number(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}
/> />
<span className="font-semibold text-gray-900 text-sm"> </button>
{polda.name} )}
</span> </div>
</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>
{/* Sub-items */} {/* Sub-items */}
{polda.subDestination && {polda.subDestination &&
expandedPolda[ expandedPolda[polda.id] && (
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="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2"> <div className="mb-2 flex justify-start">
{/* Tombol Pilih Semua untuk sub-items */} {(() => {
<div className="mb-2 flex justify-start"> const allSubItemsChecked =
{(() => { polda.subDestination?.every(
const allSubItemsChecked = (sub: any) =>
polda.subDestination?.every( fileCheckedLevels[
(sub: any) => 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>
);
})()}
</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[ fileCheckedLevels[
index index
]?.has( ]?.has(
Number( Number(
sub.id, sub.id
), )
), ) || false
); }
return ( onCheckedChange={() =>
<Button handleFileCheckboxChangePlacement(
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index, index,
polda, Number(
sub.id
)
) )
} }
> />
{allSubItemsChecked ? ( <span className="text-gray-700">
<> {sub.name}
<Icon </span>
icon="material-symbols:check-indeterminate-small" </Label>
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>
</div> ))}
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4"> </div>
<DialogClose asChild> <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<Button variant="outline"> <DialogClose asChild>
{t("cancel", { <Button variant="outline">
defaultValue: "Batal", {t("cancel", {
})} defaultValue: "Batal",
</Button> })}
</DialogClose> </Button>
<DialogClose asChild> </DialogClose>
<Button> <DialogClose asChild>
{/* {t("save", { <Button>
{/* {t("save", {
defaultValue: "Simpan", defaultValue: "Simpan",
})} */} })} */}
Simpan Simpan
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
</div> </div>
</div> </div>
)} </div>
)}
</div> </div>
</div> </div>
) : ( ) : (
@ -1886,7 +1833,7 @@ export default function FormTeksDetail() {
> >
{template} {template}
</Button> </Button>
), )
)} )}
</div> </div>
</div> </div>

View File

@ -749,7 +749,7 @@ export default function FormTeks() {
setIsStartUpload(true); setIsStartUpload(true);
setProgressList(progressInfoArr); setProgressList(progressInfoArr);
// close(); close();
files.map(async (item: any, index: number) => { files.map(async (item: any, index: number) => {
await uploadResumableFile( await uploadResumableFile(
index, index,
@ -861,7 +861,6 @@ export default function FormTeks() {
} }
if (counter == progressInfo.length) { if (counter == progressInfo.length) {
setIsStartUpload(false); setIsStartUpload(false);
close();
// hideProgress(); // hideProgress();
Cookies.remove("idCreate"); Cookies.remove("idCreate");
successSubmit("/in/contributor/content/teks"); successSubmit("/in/contributor/content/teks");
@ -1776,7 +1775,7 @@ export default function FormTeks() {
{/* <Button type="submit" color="primary"> {/* <Button type="submit" color="primary">
{t("submit", { defaultValue: "Submit" })} {t("submit", { defaultValue: "Submit" })}
</Button> */} </Button> */}
{levelNumber !== "2" && ( {levelNumber !== "2" && levelNumber !== "3" && (
<Button type="submit" color="primary"> <Button type="submit" color="primary">
{t("submit", { defaultValue: "Submit" })} {t("submit", { defaultValue: "Submit" })}
</Button> </Button>

View File

@ -115,7 +115,7 @@ const ViewEditor = dynamic(
() => { () => {
return import("@/components/editor/view-editor"); return import("@/components/editor/view-editor");
}, },
{ ssr: false }, { ssr: false }
); );
interface Destination { interface Destination {
@ -190,12 +190,6 @@ export default function FormVideoDetail() {
satker: boolean; satker: boolean;
}> }>
>([]); >([]);
const [creatorLevelNumber, setCreatorLevelNumber] = useState<number | null>(
null,
);
const isContentFromSatker = creatorLevelNumber === 3;
const isContentFromMabesOrPolda =
creatorLevelNumber === 1 || creatorLevelNumber === 2;
useEffect(() => { useEffect(() => {
if (Number(userLevelId) === 216 && Number(roleId) === 3) { if (Number(userLevelId) === 216 && Number(roleId) === 3) {
@ -207,7 +201,7 @@ export default function FormVideoDetail() {
const handleFileUnitChange = ( const handleFileUnitChange = (
fileIndex: number, fileIndex: number,
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
setFileUnitSelections((prev) => { setFileUnitSelections((prev) => {
const newSelections = [...prev]; const newSelections = [...prev];
@ -227,7 +221,7 @@ export default function FormVideoDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[fileIndex] || new Set(), newArray[fileIndex] || new Set()
); );
if (value) { if (value) {
@ -258,12 +252,12 @@ export default function FormVideoDetail() {
(item: any) => (item: any) =>
item.levelNumber === 2 && item.levelNumber === 2 &&
item.name !== "SATKER POLRI" && item.name !== "SATKER POLRI" &&
currentFileCheckedLevels.has(Number(item.id)), currentFileCheckedLevels.has(Number(item.id))
); );
if (!hasSelectedPolda) { if (!hasSelectedPolda) {
alert( 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 return prev; // Batalkan perubahan
} }
@ -302,7 +296,7 @@ export default function FormVideoDetail() {
useEffect(() => { useEffect(() => {
if (detail?.assignedToTopLevel) { if (detail?.assignedToTopLevel) {
const outputSet = new Set( const outputSet = new Set(
detail.assignedToTopLevel.split(",").map(Number), detail.assignedToTopLevel.split(",").map(Number)
); );
setUnitSelection({ setUnitSelection({
semua: outputSet.has(0), semua: outputSet.has(0),
@ -327,7 +321,7 @@ export default function FormVideoDetail() {
acc[polda.id] = false; acc[polda.id] = false;
return acc; return acc;
}, },
{}, {}
); );
setExpandedPolda(initialExpandedState); setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState); console.log("polres", initialExpandedState);
@ -362,7 +356,7 @@ export default function FormVideoDetail() {
const handleUnitChange = ( const handleUnitChange = (
key: keyof typeof unitSelection, key: keyof typeof unitSelection,
value: boolean, value: boolean
) => { ) => {
if (key === "semua") { if (key === "semua") {
const newState = { const newState = {
@ -441,7 +435,7 @@ export default function FormVideoDetail() {
const handleCheckboxChange = (id: number) => { const handleCheckboxChange = (id: number) => {
setSelectedPublishers((prev) => setSelectedPublishers((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id], prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
); );
}; };
@ -463,7 +457,7 @@ export default function FormVideoDetail() {
if (scheduleId && scheduleType === "3") { if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) => const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis"), o.name.toLowerCase().includes("pers rilis")
); );
if (findCategory) { if (findCategory) {
@ -486,10 +480,6 @@ export default function FormVideoDetail() {
console.log("detail", details); console.log("detail", details);
setFiles(details?.files); setFiles(details?.files);
setDetail(details); setDetail(details);
if (details?.uploadedBy?.userLevel?.levelNumber) {
setCreatorLevelNumber(details.uploadedBy.userLevel.levelNumber);
}
setMain({ setMain({
type: details?.fileType.name, type: details?.fileType.name,
url: details?.files[0]?.url, url: details?.files[0]?.url,
@ -499,14 +489,14 @@ export default function FormVideoDetail() {
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set( const levels = new Set(
details.assignedToLevel.split(",").map(Number), details.assignedToLevel.split(",").map(Number)
); );
setCheckedLevels(levels); setCheckedLevels(levels);
} }
if (details?.publishedForObject) { if (details?.publishedForObject) {
const publisherIds = details?.publishedForObject?.map( const publisherIds = details?.publishedForObject?.map(
(obj: any) => obj.id, (obj: any) => obj.id
); );
setSelectedPublishers(publisherIds); setSelectedPublishers(publisherIds);
} }
@ -516,7 +506,7 @@ export default function FormVideoDetail() {
const filesData = details?.files || []; const filesData = details?.files || [];
const fileUrls = filesData.map((files: { url: string }) => const fileUrls = filesData.map((files: { url: string }) =>
files.url ? files.url : "default-image.jpg", files.url ? files.url : "default-image.jpg"
); );
setDetailVideo(fileUrls); setDetailVideo(fileUrls);
@ -694,7 +684,7 @@ export default function FormVideoDetail() {
const setupPlacement = ( const setupPlacement = (
index: number, index: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
let temp = [...filePlacements]; let temp = [...filePlacements];
if (checked) { if (checked) {
@ -705,7 +695,7 @@ export default function FormVideoDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Checklist semua item di modal // Checklist semua item di modal
@ -755,7 +745,7 @@ export default function FormVideoDetail() {
// Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist // Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist
// JANGAN include satker dalam perhitungan auto "all" // JANGAN include satker dalam perhitungan auto "all"
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length === 3 && !now.includes("all")) { if (nonSatkerItems.length === 3 && !now.includes("all")) {
now.push("all"); now.push("all");
@ -770,7 +760,7 @@ export default function FormVideoDetail() {
setFileCheckedLevels((prevLevels) => { setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels]; const newArray = [...prevLevels];
const currentFileLevels = new Set<number>( const currentFileLevels = new Set<number>(
newArray[index] || new Set(), newArray[index] || new Set()
); );
// Unchecklist semua item di modal // Unchecklist semua item di modal
@ -804,7 +794,7 @@ export default function FormVideoDetail() {
// Hapus "all" jika tidak semua item ter-checklist // Hapus "all" jika tidak semua item ter-checklist
if (now.includes("all")) { if (now.includes("all")) {
const nonSatkerItems = now.filter( const nonSatkerItems = now.filter(
(item) => item !== "satker" && item !== "all", (item) => item !== "satker" && item !== "all"
); );
if (nonSatkerItems.length < 3) { if (nonSatkerItems.length < 3) {
const newData = now.filter((b) => b !== "all"); const newData = now.filter((b) => b !== "all");
@ -822,7 +812,7 @@ export default function FormVideoDetail() {
const updateModalChecklistLevels = ( const updateModalChecklistLevels = (
fileIndex: number, fileIndex: number,
placement: string, placement: string,
checked: boolean, checked: boolean
) => { ) => {
if (!listDest || listDest.length === 0) return; if (!listDest || listDest.length === 0) return;
@ -855,7 +845,7 @@ export default function FormVideoDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Checklist SATKER POLRI dan semua sub-item di bawahnya // Checklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.add(Number(satkerItem.id)); currentFileLevels.add(Number(satkerItem.id));
@ -891,7 +881,7 @@ export default function FormVideoDetail() {
} else if (placement === "satker") { } else if (placement === "satker") {
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya // Unchecklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find( const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
if (satkerItem) { if (satkerItem) {
currentFileLevels.delete(Number(satkerItem.id)); currentFileLevels.delete(Number(satkerItem.id));
@ -920,7 +910,7 @@ export default function FormVideoDetail() {
type: string, type: string,
url: string, url: string,
names: string, names: string,
format: string, format: string
) => { ) => {
console.log("Test 3 :", type, url, names, format); console.log("Test 3 :", type, url, names, format);
setMain({ setMain({
@ -961,7 +951,7 @@ export default function FormVideoDetail() {
// Fungsi untuk mengupdate checklist levels untuk file tertentu // Fungsi untuk mengupdate checklist levels untuk file tertentu
const handleFileCheckboxChangePlacement = ( const handleFileCheckboxChangePlacement = (
fileIndex: number, fileIndex: number,
levelId: number, levelId: number
) => { ) => {
setFileCheckedLevels((prev) => { setFileCheckedLevels((prev) => {
const newArray = [...prev]; const newArray = [...prev];
@ -973,7 +963,7 @@ export default function FormVideoDetail() {
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
const poldaItem = listDest.find( const poldaItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if ( if (
poldaItem && poldaItem &&
@ -996,7 +986,7 @@ export default function FormVideoDetail() {
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya // Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => Number(item.id) === levelId, (item: any) => Number(item.id) === levelId
) as any; ) as any;
if (satkerItem && satkerItem.name === "SATKER POLRI") { if (satkerItem && satkerItem.name === "SATKER POLRI") {
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES) // Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
@ -1027,7 +1017,7 @@ export default function FormVideoDetail() {
// Hitung total POLDA yang ada (bukan SATKER POLRI) // Hitung total POLDA yang ada (bukan SATKER POLRI)
const totalPoldaCount = listDest.filter( const totalPoldaCount = listDest.filter(
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI", (item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
).length; ).length;
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI) // Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
@ -1054,7 +1044,7 @@ export default function FormVideoDetail() {
} }
return total; return total;
}, },
0, 0
); );
// Hitung berapa banyak POLRES yang ter-checklist // Hitung berapa banyak POLRES yang ter-checklist
@ -1064,7 +1054,7 @@ export default function FormVideoDetail() {
return ( return (
total + total +
item.subDestination.filter((sub: any) => item.subDestination.filter((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
).length ).length
); );
} }
@ -1073,7 +1063,7 @@ export default function FormVideoDetail() {
// Cek apakah SATKER POLRI ter-checklist // Cek apakah SATKER POLRI ter-checklist
const satkerItem = listDest.find( const satkerItem = listDest.find(
(item: any) => item.name === "SATKER POLRI", (item: any) => item.name === "SATKER POLRI"
); );
const isSatkerChecked = const isSatkerChecked =
satkerItem && currentFileLevels.has(Number(satkerItem.id)); satkerItem && currentFileLevels.has(Number(satkerItem.id));
@ -1107,7 +1097,7 @@ export default function FormVideoDetail() {
// Cek apakah semua sub-items sudah ter-checklist // Cek apakah semua sub-items sudah ter-checklist
const allSubItemsChecked = polda.subDestination?.every((sub: any) => const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
currentFileLevels.has(Number(sub.id)), currentFileLevels.has(Number(sub.id))
); );
if (allSubItemsChecked) { if (allSubItemsChecked) {
@ -1190,7 +1180,7 @@ export default function FormVideoDetail() {
{detail && {detail &&
!categories.find( !categories.find(
(cat) => (cat) =>
String(cat.id) === String(detail.category.id), String(cat.id) === String(detail.category.id)
) && ( ) && (
<SelectItem <SelectItem
key={String(detail.category.id)} key={String(detail.category.id)}
@ -1475,7 +1465,7 @@ export default function FormVideoDetail() {
Tingkat Distribusi: Tingkat Distribusi:
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* {[ {[
{ key: "semua", label: "Semua" }, { key: "semua", label: "Semua" },
{ key: "nasional", label: "Nasional" }, { key: "nasional", label: "Nasional" },
{ key: "wilayah", label: "Wilayah" }, { key: "wilayah", label: "Wilayah" },
@ -1483,39 +1473,6 @@ export default function FormVideoDetail() {
key: "international", key: "international",
label: "Internasional", 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) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
@ -1532,12 +1489,12 @@ export default function FormVideoDetail() {
handleFileUnitChange( handleFileUnitChange(
index, index,
item.key as keyof typeof unitSelection, item.key as keyof typeof unitSelection,
value as boolean, value as boolean
); );
setupPlacement( setupPlacement(
index, index,
item.key, item.key,
Boolean(value), Boolean(value)
); );
}} }}
/> />
@ -1553,262 +1510,251 @@ export default function FormVideoDetail() {
</div> </div>
{/* Detail Wilayah */} {/* Detail Wilayah */}
{/* {fileUnitSelections[index]?.wilayah && ( */} {fileUnitSelections[index]?.wilayah && (
{!isContentFromSatker && <div className="border-t border-gray-200 pt-2">
fileUnitSelections[index]?.wilayah && ( <p className="text-sm font-medium text-gray-700 mb-2">
<div className="border-t border-gray-200 pt-2"> Detail Wilayah:
<p className="text-sm font-medium text-gray-700 mb-2"> </p>
Detail Wilayah:
</p>
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */} {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3"> <div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[ {[
{ key: "polda", label: "POLDA" }, { key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" }, { key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" }, { key: "satker", label: "SATKER" },
].map((item, idx) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50" 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 {item.label}
id={`${item.key}-${index}`} </Label>
checked={ </div>
fileUnitSelections[index]?.[ ))}
item.key as keyof typeof unitSelection
] || false {/* Tombol Kustom sejajar dengan checkbox */}
} <div className="flex items-center justify-center p-3">
onCheckedChange={(value) => { <Dialog>
handleFileUnitChange( <DialogTrigger asChild>
index, <Button
item.key as keyof typeof unitSelection, variant="outline"
value as boolean, size="sm"
); className="gap-2"
setupPlacement(
index,
item.key,
Boolean(value),
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
> >
{item.label} <Icon
</Label> icon="material-symbols:tune"
</div> width={16}
))} height={16}
/>
{/* Tombol Kustom sejajar dengan checkbox */} {t("custom", {
<div className="flex items-center justify-center p-3"> defaultValue: "Kustom",
<Dialog> })}
<DialogTrigger asChild> </Button>
<Button </DialogTrigger>
variant="outline" <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
size="sm" <DialogHeader className="border-b border-gray-200 pb-4">
className="gap-2" <DialogTitle className="text-lg font-semibold">
> Daftar Wilayah POLDA dan POLRES
<Icon </DialogTitle>
icon="material-symbols:tune" </DialogHeader>
width={16} <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
height={16} {listDest.map((polda: any) => (
/> <div
{t("custom", { key={polda.id}
defaultValue: "Kustom", className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
})} >
</Button> {/* Header POLDA */}
</DialogTrigger> <div className="flex items-center justify-between">
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]"> <Label className="flex items-center gap-3 flex-1 cursor-pointer">
<DialogHeader className="border-b border-gray-200 pb-4"> <Checkbox
<DialogTitle className="text-lg font-semibold"> checked={
Daftar Wilayah POLDA dan fileCheckedLevels[
POLRES index
</DialogTitle> ]?.has(
</DialogHeader> Number(polda.id)
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1"> ) || false
{listDest.map((polda: any) => ( }
<div onCheckedChange={() =>
key={polda.id} handleFileCheckboxChangePlacement(
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow" index,
> Number(polda.id)
{/* Header POLDA */} )
<div className="flex items-center justify-between"> }
<Label className="flex items-center gap-3 flex-1 cursor-pointer"> />
<Checkbox <span className="font-semibold text-gray-900 text-sm">
checked={ {polda.name}
fileCheckedLevels[ </span>
index </Label>
]?.has( {polda.subDestination && (
Number(polda.id), <button
) || false onClick={(e) => {
} e.preventDefault();
onCheckedChange={() => e.stopPropagation();
handleFileCheckboxChangePlacement( toggleExpand(
index, polda.id
Number(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}
/> />
<span className="font-semibold text-gray-900 text-sm"> </button>
{polda.name} )}
</span> </div>
</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>
{/* Sub-items */} {/* Sub-items */}
{polda.subDestination && {polda.subDestination &&
expandedPolda[ expandedPolda[polda.id] && (
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="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2"> <div className="mb-2 flex justify-start">
{/* Tombol Pilih Semua untuk sub-items */} {(() => {
<div className="mb-2 flex justify-start"> const allSubItemsChecked =
{(() => { polda.subDestination?.every(
const allSubItemsChecked = (sub: any) =>
polda.subDestination?.every( fileCheckedLevels[
(sub: any) => 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>
);
})()}
</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[ fileCheckedLevels[
index index
]?.has( ]?.has(
Number( Number(
sub.id, sub.id
), )
), ) || false
); }
return ( onCheckedChange={() =>
<Button handleFileCheckboxChangePlacement(
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index, index,
polda, Number(
sub.id
)
) )
} }
> />
{allSubItemsChecked ? ( <span className="text-gray-700">
<> {sub.name}
<Icon </span>
icon="material-symbols:check-indeterminate-small" </Label>
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>
</div> ))}
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4"> </div>
<DialogClose asChild> <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<Button variant="outline"> <DialogClose asChild>
{t("cancel", { <Button variant="outline">
defaultValue: "Batal", {t("cancel", {
})} defaultValue: "Batal",
</Button> })}
</DialogClose> </Button>
<DialogClose asChild> </DialogClose>
<Button> <DialogClose asChild>
{/* {t("save", { <Button>
{/* {t("save", {
defaultValue: "Simpan", defaultValue: "Simpan",
})} */} })} */}
Simpan Simpan
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
</div> </div>
</div> </div>
)} </div>
)}
</div> </div>
</div> </div>
) : ( ) : (
@ -1887,7 +1833,7 @@ export default function FormVideoDetail() {
> >
{template} {template}
</Button> </Button>
), )
)} )}
</div> </div>
</div> </div>

View File

@ -59,7 +59,7 @@ const CustomEditor = dynamic(
() => { () => {
return import("@/components/editor/custom-editor"); return import("@/components/editor/custom-editor");
}, },
{ ssr: false }, { ssr: false }
); );
interface FileWithPreview extends File { interface FileWithPreview extends File {
@ -111,11 +111,11 @@ export default function FormVideo() {
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>(""); const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>( const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null, null
); );
const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [publishedForError, setPublishedForError] = useState<string | null>( const [publishedForError, setPublishedForError] = useState<string | null>(
null, null
); );
const [selectedSize, setSelectedSize] = useState(""); const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null); const [detailData, setDetailData] = useState<any>(null);
@ -184,7 +184,7 @@ export default function FormVideo() {
} }
const filesWithPreview = acceptedFiles.map((file) => const filesWithPreview = acceptedFiles.map((file) =>
Object.assign(file, { preview: URL.createObjectURL(file) }), Object.assign(file, { preview: URL.createObjectURL(file) })
); );
setFiles((prev) => { setFiles((prev) => {
@ -211,11 +211,11 @@ export default function FormVideo() {
.refine( .refine(
(files) => (files) =>
files.every((file: File) => ACCEPTED_FILE_TYPES.includes(file.type)), 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( .refine(
(files) => files.every((file: File) => file.size <= MAX_FILE_SIZE), (files) => files.every((file: File) => file.size <= MAX_FILE_SIZE),
{ message: "Ukuran file maksimal 100 MB" }, { message: "Ukuran file maksimal 100 MB" }
), ),
publishedFor: z publishedFor: z
.array(z.string()) .array(z.string())
@ -423,7 +423,7 @@ export default function FormVideo() {
const articleData = await waitForStatusUpdate(); const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace( const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g, /<img[^>]*>/g,
"", ""
); );
const articleImagesData = articleData?.imagesUrl?.split(","); const articleImagesData = articleData?.imagesUrl?.split(",");
setValue("description", cleanArticleBody || ""); setValue("description", cleanArticleBody || "");
@ -479,7 +479,7 @@ export default function FormVideo() {
if (scheduleId && scheduleType === "3") { if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) => const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis"), o.name.toLowerCase().includes("pers rilis")
); );
if (findCategory) { if (findCategory) {
@ -502,7 +502,7 @@ export default function FormVideo() {
setPublishedFor( setPublishedFor(
options options
.filter((opt: any) => opt.id !== "all") .filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id), .map((opt: any) => opt.id)
); );
} }
} else { } else {
@ -561,8 +561,8 @@ export default function FormVideo() {
translatedTitle && translatedTitle.trim() !== "" translatedTitle && translatedTitle.trim() !== ""
? translatedTitle ? translatedTitle
: isSwitchOn : isSwitchOn
? title ? title
: data.title; : data.title;
// Tentukan deskripsi final: // Tentukan deskripsi final:
// Jika ada hasil translate, kirim itu ke backend // Jika ada hasil translate, kirim itu ke backend
@ -570,10 +570,10 @@ export default function FormVideo() {
translatedContent && translatedContent.trim() !== "" translatedContent && translatedContent.trim() !== ""
? translatedContent ? translatedContent
: isSwitchOn : isSwitchOn
? data.description ? data.description
: selectedFileType === "rewrite" : selectedFileType === "rewrite"
? data.rewriteDescription ? data.rewriteDescription
: data.descriptionOri; : data.descriptionOri;
if (!finalDescription?.trim()) { if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
@ -695,7 +695,7 @@ export default function FormVideo() {
idx: number, idx: number,
id: string, id: string,
file: any, file: any,
duration: string, duration: string
) { ) {
console.log(idx, id, file, duration); console.log(idx, id, file, duration);
@ -731,7 +731,7 @@ export default function FormVideo() {
onChunkComplete: ( onChunkComplete: (
chunkSize: any, chunkSize: any,
bytesAccepted: any, bytesAccepted: any,
bytesTotal: any, bytesTotal: any
) => { ) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100); const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen; progressInfo[idx].percentage = uploadPersen;
@ -1732,7 +1732,7 @@ export default function FormVideo() {
type="button" type="button"
onClick={() => { onClick={() => {
const updatedTags = field.value.filter( const updatedTags = field.value.filter(
(_, i) => i !== index, (_, i) => i !== index
); );
field.onChange(updatedTags); field.onChange(updatedTags);
}} }}
@ -1825,7 +1825,7 @@ export default function FormVideo() {
{/* <Button type="submit" color="primary"> {/* <Button type="submit" color="primary">
{t("submit", { defaultValue: "Submit" })} {t("submit", { defaultValue: "Submit" })}
</Button> */} </Button> */}
{levelNumber !== "2" && ( {levelNumber !== "2" && levelNumber !== "3" && (
<Button type="submit" color="primary"> <Button type="submit" color="primary">
{t("submit", { defaultValue: "Submit" })} {t("submit", { defaultValue: "Submit" })}
</Button> </Button>

View File

@ -75,7 +75,6 @@ interface Detail {
youtubeUrl: string; youtubeUrl: string;
needApprovalFrom: number; needApprovalFrom: number;
uploadedById: number; uploadedById: number;
statusId?: number;
} }
export default function FormDetailLiveReport() { export default function FormDetailLiveReport() {
@ -102,11 +101,6 @@ export default function FormDetailLiveReport() {
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [uploaderLevelNumber, setUploaderLevelNumber] = useState<number | null>(
null,
);
const isScheduleFromSatker = uploaderLevelNumber === 3;
const { const {
control, control,
@ -139,11 +133,6 @@ export default function FormDetailLiveReport() {
const details = response?.data?.data; const details = response?.data?.data;
setDetail(details); setDetail(details);
if (details?.uploaderLevelNumber !== undefined) {
setUploaderLevelNumber(details.uploaderLevelNumber);
}
if (details) { if (details) {
setDate({ setDate({
from: parseISO(details.startDate), from: parseISO(details.startDate),
@ -187,71 +176,21 @@ export default function FormDetailLiveReport() {
statusId: Number(status), statusId: Number(status),
message: description, message: description,
isPublish: status === "2", isPublish: status === "2",
// placements: schedulePlacements?.filter((val) => val !== "all")?.join(","), placements: schedulePlacements?.filter((val) => val != "all")?.join(","),
placements: isScheduleFromSatker
? "satker"
: schedulePlacements?.filter((val) => val !== "all")?.join(","),
}; };
loading(); loading();
const response = await postApprovalSchedule(data); const response = await postApprovalSchedule(data);
close(); close();
setModalOpen(false); setModalOpen(false);
if (response?.error) { if (response?.error) {
error(response?.message || "Gagal menyimpan data"); error(response?.message);
return; return false;
} }
initState();
// ✅ update UI lokal (optimistic) return false;
setDetail((prev) =>
prev
? {
...prev,
statusId: Number(status),
}
: prev,
);
Swal.fire({
icon: "success",
title: "Berhasil",
text:
status === "2"
? "Jadwal berhasil disetujui"
: status === "3"
? "Jadwal dikembalikan untuk revisi"
: "Jadwal berhasil ditolak",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push("/contributor/schedule/live-report");
}
});
} }
// async function save() {
// const data = {
// scheduleId: Number(id),
// statusId: Number(status),
// message: description,
// isPublish: status === "2",
// placements: schedulePlacements?.filter((val) => val != "all")?.join(","),
// };
// loading();
// const response = await postApprovalSchedule(data);
// close();
// setModalOpen(false);
// if (response?.error) {
// error(response?.message);
// return false;
// }
// initState();
// return false;
// }
const [schedulePlacements, setSchedulePlacements] = useState<string[]>([]); const [schedulePlacements, setSchedulePlacements] = useState<string[]>([]);
const setupPlacement = (placement: string, checked: boolean) => { const setupPlacement = (placement: string, checked: boolean) => {
@ -285,15 +224,6 @@ export default function FormDetailLiveReport() {
setSchedulePlacements(temp); setSchedulePlacements(temp);
}; };
const isCreator = Number(detail?.uploadedById) === Number(userId);
const isApprover =
Number(detail?.needApprovalFrom) === Number(userLevelId) &&
Number(userLevelNumber) < 2;
const isAlreadyProcessed =
detail?.statusId === 2 || detail?.statusId === 3 || detail?.statusId === 4;
return ( return (
<div className="flex flex-col lg:flex-row gap-2"> <div className="flex flex-col lg:flex-row gap-2">
<Card className="w-full lg:w-9/12"> <Card className="w-full lg:w-9/12">
@ -354,7 +284,7 @@ export default function FormDetailLiveReport() {
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4", "w-[280px] lg:w-[250px] justify-start text-left font-normal px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground", !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon size={15} className="mr-3" /> <CalendarIcon size={15} className="mr-3" />
@ -564,38 +494,7 @@ export default function FormDetailLiveReport() {
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{(isApprover || isCreator) && !isAlreadyProcessed && ( {Number(detail?.needApprovalFrom) == 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?.needApprovalFrom) == Number(userLevelId) &&
Number(userLevelNumber) < 2 ? ( Number(userLevelNumber) < 2 ? (
Number(detail?.uploadedById) == Number(userId) ? ( Number(detail?.uploadedById) == Number(userId) ? (
"" ""
@ -628,15 +527,13 @@ export default function FormDetailLiveReport() {
) )
) : ( ) : (
"" ""
)} */} )}
</Card> </Card>
<Dialog open={modalOpen} onOpenChange={setModalOpen}> <Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent className="overflow-y-auto"> <DialogContent className="overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>{t("leave-comment", { defaultValue: "Leave Comment" })}</DialogTitle>
{t("leave-comment", { defaultValue: "Leave Comment" })}
</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="flex flex-col gap-1 text-sm"> <div className="flex flex-col gap-1 text-sm">
<p> <p>
@ -647,19 +544,18 @@ export default function FormDetailLiveReport() {
status === "2" status === "2"
? "text-primary" ? "text-primary"
: status === "3" : status === "3"
? "text-warning" ? "text-warning"
: "text-destructive" : "text-destructive"
} }
> >
{status === "2" {status === "2"
? "Disetujui" ? "Disetujui"
: status === "3" : status === "3"
? "Revisi" ? "Revisi"
: "Ditolak"} : "Ditolak"}
</span> </span>
</p> </p>
{/* {status === "2" && ( */} {status === "2" && (
{status === "2" && !isScheduleFromSatker && (
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Checkbox <Checkbox
@ -734,15 +630,9 @@ export default function FormDetailLiveReport() {
type="button" type="button"
color="primary" color="primary"
onClick={() => submit()} onClick={() => submit()}
// disabled={
// description.length < 1 ||
// (schedulePlacements.length < 1 && status === "2")
// }
disabled={ disabled={
description.length < 1 || description.length < 1 ||
(!isScheduleFromSatker && (schedulePlacements.length < 1 && status === "2")
schedulePlacements.length < 1 &&
status === "2")
} }
> >
{t("submit", { defaultValue: "Submit" })} {t("submit", { defaultValue: "Submit" })}

View File

@ -530,7 +530,7 @@ export function TambahIklanDetail() {
</p> </p>
<Image <Image
src={`https://new.netidhub.com/api/advertisements/viewer/${id}`} src={`https://netidhub.com/api/advertisements/viewer/${id}`}
alt="Thumbnail Gambar Utama" alt="Thumbnail Gambar Utama"
className=" rounded-md my-3" className=" rounded-md my-3"
width={300} width={300}

View File

@ -296,7 +296,7 @@ export function TambahIklanUpdate() {
formMedia.append("file", imageFiles[0]); formMedia.append("file", imageFiles[0]);
} else if (detail?.id) { } else if (detail?.id) {
const existingFile = await fetchExistingImageAsFile( const existingFile = await fetchExistingImageAsFile(
`https://new.netidhub.com/api/advertisements/viewer/${detail.id}`, `https://netidhub.com/api/advertisements/viewer/${detail.id}`,
"existing-image.jpg" "existing-image.jpg"
); );
formMedia.append("file", existingFile); formMedia.append("file", existingFile);
@ -631,7 +631,7 @@ export function TambahIklanUpdate() {
))} ))}
<Image <Image
src={`https://new.netidhub.com/api/advertisements/viewer/${id}`} src={`https://netidhub.com/api/advertisements/viewer/${id}`}
alt="Thumbnail Gambar Utama" alt="Thumbnail Gambar Utama"
className=" rounded-md my-3" className=" rounded-md my-3"
width={300} width={300}

View File

@ -19,7 +19,6 @@ import {
finishTaskTa, finishTaskTa,
getAcceptance, getAcceptance,
getAcceptanceAssignmentStatus, getAcceptanceAssignmentStatus,
getAcceptanceTa,
getAssignmentResponseList, getAssignmentResponseList,
getMediaUpload, getMediaUpload,
getMediaUploadTa, getMediaUploadTa,
@ -103,7 +102,6 @@ export type taskDetail = {
is_active: string; is_active: string;
isAssignmentAccepted: boolean; isAssignmentAccepted: boolean;
isDone: any; isDone: any;
isAccept?: boolean;
}; };
interface ListData { interface ListData {
@ -174,16 +172,9 @@ interface UploadResult {
creatorGroup: string; creatorGroup: string;
} }
// interface FileUploaded {
// id: number;
// url: string;
// }
interface FileUploaded { interface FileUploaded {
id: number; id: number;
url: string; url: string;
fileName: string;
fileTypeId: number;
} }
export default function FormTaskTaDetail() { export default function FormTaskTaDetail() {
@ -253,6 +244,18 @@ export default function FormTaskTaDetail() {
const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>( const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>(
new Set() new Set()
); );
const [totalPage, setTotalPage] = React.useState(1);
const [dataTable, setDataTable] = React.useState<any[]>([]);
const [totalData, setTotalData] = React.useState<number>(1);
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
const [videoFiles, setVideoFiles] = useState<FileWithPreview[]>([]);
const [textFiles, setTextFiles] = useState<FileWithPreview[]>([]);
const [audioFiles, setAudioFiles] = useState<FileWithPreview[]>([]);
const [isImageUploadFinish, setIsImageUploadFinish] = useState(false);
const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false);
const [isTextUploadFinish, setIsTextUploadFinish] = useState(false);
const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false);
const [voiceNoteLink, setVoiceNoteLink] = useState("");
const [statusAcceptance, setStatusAcceptance] = useState(); const [statusAcceptance, setStatusAcceptance] = useState();
const [modalType, setModalType] = useState<"terkirim" | "diterima" | "">(""); const [modalType, setModalType] = useState<"terkirim" | "diterima" | "">("");
const [Isloading, setLoading] = useState<boolean>(false); const [Isloading, setLoading] = useState<boolean>(false);
@ -262,8 +265,10 @@ export default function FormTaskTaDetail() {
const [isRecording, setIsRecording] = useState(false); const [isRecording, setIsRecording] = useState(false);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
const [search, setSearch] = React.useState<string>(""); const [search, setSearch] = React.useState<string>("");
const [wavesurfer, setWavesurfer] = useState<WaveSurfer>(); const [wavesurfer, setWavesurfer] = useState<WaveSurfer>();
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>( const [imageUploadedFiles, setImageUploadedFiles] = useState<FileUploaded[]>(
[] []
); );
@ -288,8 +293,6 @@ export default function FormTaskTaDetail() {
const [selectedAudio, setSelectedAudio] = useState<string | null>( const [selectedAudio, setSelectedAudio] = useState<string | null>(
audioUploadedFiles?.[0]?.url || null audioUploadedFiles?.[0]?.url || null
); );
const [acceptLoading, setAcceptLoading] = useState(false);
const [isAccepted, setIsAccepted] = useState(false);
const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [platformTypeVisible, setPlatformTypeVisible] = useState(false);
const [unitSelection, setUnitSelection] = useState({ const [unitSelection, setUnitSelection] = useState({
@ -397,31 +400,6 @@ export default function FormTaskTaDetail() {
fetchListExpert(); fetchListExpert();
}, []); }, []);
useEffect(() => {
async function fetchAcceptanceStatus() {
const res = await getAcceptanceAssignmentStatus(id);
// API: /assignment/acceptance?id=103&isAccept=true
const status = res?.data?.data?.isAccept === true;
setIsAccepted(status);
}
fetchAcceptanceStatus();
}, [id]);
const getViewerUrl = (url: string) => {
const ext = url.split(".").pop()?.toLowerCase();
if (ext === "pdf") {
return url; // langsung tampilkan PDF
}
// doc / docx → pakai Google Viewer, lebih compatible
return `https://docs.google.com/viewer?url=${encodeURIComponent(
url
)}&embedded=true`;
};
useEffect(() => { useEffect(() => {
const fetchExpertsForCompetencies = async () => { const fetchExpertsForCompetencies = async () => {
const allExperts: any[] = []; const allExperts: any[] = [];
@ -523,7 +501,7 @@ export default function FormTaskTaDetail() {
const urls = details.urls.map( const urls = details.urls.map(
(urlObj: any) => urlObj.attachmentUrl || "" (urlObj: any) => urlObj.attachmentUrl || ""
); );
setUrlInputs(urls); setUrlInputs(urls); // Save the URLs to state
} }
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
@ -765,36 +743,28 @@ export default function FormTaskTaDetail() {
} }
const handleAcceptAcceptance = async () => { const handleAcceptAcceptance = async () => {
if (acceptLoading || isAccepted) return;
setAcceptLoading(true);
loading(); loading();
console.log("Id user :", userId);
const response = await acceptAssignmentTa(id);
const res = await acceptAssignmentTa(id); if (response?.error) {
error(response?.message);
close(); return false;
setAcceptLoading(false);
if (res?.error) {
error(res?.message || "Gagal menerima tugas");
return;
} }
// ini penting!
setIsAccepted(true);
successCallback(); successCallback();
getDataAcceptance();
return false;
}; };
async function getListAcceptance(): Promise<void> { async function getListAcceptance(): Promise<void> {
const isAccept = true; const isAccept = true;
try { try {
const resSent = await getAcceptanceTa(id, !isAccept); const resSent = await getAcceptance(id, !isAccept);
setSentAcceptance(resSent?.data?.data || []); setSentAcceptance(resSent?.data?.data);
const resAccept = await getAcceptanceTa(id, isAccept); const resAccept = await getAcceptance(id, isAccept);
const acceptanceSort = resAccept?.data?.data?.sort( const acceptanceSort = resAccept?.data?.data?.sort(
(a: AcceptanceData, b: AcceptanceData) => (a: AcceptanceData, b: AcceptanceData) =>
@ -802,7 +772,7 @@ export default function FormTaskTaDetail() {
); );
console.log("Data sort:", acceptanceSort); console.log("Data sort:", acceptanceSort);
setAcceptAcceptance(acceptanceSort || []); setAcceptAcceptance(acceptanceSort);
} catch (error) { } catch (error) {
console.error("Error fetching acceptance data:", error); console.error("Error fetching acceptance data:", error);
} }
@ -981,27 +951,14 @@ export default function FormTaskTaDetail() {
const onReady = (ws: any) => { const onReady = (ws: any) => {
setWavesurfer(ws); setWavesurfer(ws);
setIsPlaying(false);
ws.on("play", () => setIsPlaying(true));
ws.on("pause", () => setIsPlaying(false));
}; };
// const onReady = (ws: any) => {
// setWavesurfer(ws);
// setIsPlaying(false);
// };
const onPlayPause = () => { const onPlayPause = () => {
wavesurfer && wavesurfer.playPause(); wavesurfer && wavesurfer.playPause();
}; };
// const handleRemoveFile = (id: number) => {}; const handleRemoveFile = (id: number) => {};
const handleRemoveFile = (file: FileUploaded) => {
console.log(file);
// contoh kalau kamu mau hapus dari state:
setVideoUploadedFiles((prev) => prev.filter((f) => f.id !== file.id));
};
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value); // Perbarui state search setSearch(e.target.value); // Perbarui state search
@ -1013,10 +970,8 @@ export default function FormTaskTaDetail() {
{detail !== undefined ? ( {detail !== undefined ? (
<div className="px-6 py-6"> <div className="px-6 py-6">
<div className="flex flex-col sm:flex-row lg:flex-row justify-between"> <div className="flex flex-col sm:flex-row lg:flex-row justify-between">
<p className="text-lg font-semibold mb-3"> <p className="text-lg font-semibold mb-3">{t("detail-task", { defaultValue: "Detail Task" })}</p>
{t("detail-task", { defaultValue: "Detail Task" })} {/* <div
</p>
<div
className="flex gap-3" className="flex gap-3"
style={ style={
detail?.createdBy?.id === Number(userId) detail?.createdBy?.id === Number(userId)
@ -1033,8 +988,7 @@ export default function FormTaskTaDetail() {
color="primary" color="primary"
onClick={() => setModalType("terkirim")} onClick={() => setModalType("terkirim")}
> >
{sentAcceptance?.length}{" "} {sentAcceptance?.length} {t("sent", { defaultValue: "Sent" })}
{t("sent", { defaultValue: "Sent" })}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
@ -1044,17 +998,14 @@ export default function FormTaskTaDetail() {
onClick={() => setModalType("diterima")} onClick={() => setModalType("diterima")}
className="ml-3" className="ml-3"
> >
{acceptAcceptance?.length}{" "} {acceptAcceptance?.length} {t("accepted", { defaultValue: "Accepted" })}
{t("accepted", { defaultValue: "Accepted" })}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{t("assignment-status-details", { {t("assignment-status-details", { defaultValue: "Assignment Status Details" })}
defaultValue: "Assignment Status Details",
})}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
@ -1063,15 +1014,13 @@ export default function FormTaskTaDetail() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
</div> </div> */}
</div> </div>
<form> <form>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
<div className="space-y-2"> <div className="space-y-2">
<Label> <Label>{t("unique-code", { defaultValue: "Unique Code" })}</Label>
{t("unique-code", { defaultValue: "Unique Code" })}
</Label>
<Controller <Controller
control={control} control={control}
name="uniqueCode" name="uniqueCode"
@ -1123,9 +1072,7 @@ export default function FormTaskTaDetail() {
</RadioGroup> </RadioGroup>
</div> */} </div> */}
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label> <Label>{t("areas-expertise", { defaultValue: "Areas Expertise" })}</Label>
{t("areas-expertise", { defaultValue: "Areas Expertise" })}
</Label>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
{userCompetencies?.map((item: any) => ( {userCompetencies?.map((item: any) => (
<div className="flex items-center gap-2" key={item.id}> <div className="flex items-center gap-2" key={item.id}>
@ -1224,9 +1171,7 @@ export default function FormTaskTaDetail() {
<div></div> <div></div>
<div className="mt-6 space-y-2"> <div className="mt-6 space-y-2">
<Label> <Label>{t("description", { defaultValue: "Description" })}</Label>
{t("description", { defaultValue: "Description" })}
</Label>
<Controller <Controller
control={control} control={control}
name="naration" name="naration"
@ -1241,15 +1186,11 @@ export default function FormTaskTaDetail() {
)} */} )} */}
</div> </div>
<div className=" mt-5 space-y-2"> <div className=" mt-5 space-y-2">
<Label htmlFor="attachment"> <Label htmlFor="attachment">{t("attachment", { defaultValue: "Attachment" })}</Label>
{t("attachment", { defaultValue: "Attachment" })}
</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
{videoUploadedFiles?.length > 0 && ( {videoUploadedFiles?.length > 0 && (
<Label> <Label>{t("audio-visual", { defaultValue: "Audio Visual" })}</Label>
{t("audio-visual", { defaultValue: "Audio Visual" })}
</Label>
)} )}
<div> <div>
{selectedVideo && ( {selectedVideo && (
@ -1293,38 +1234,6 @@ export default function FormTaskTaDetail() {
</Button> </Button>
</div> </div>
))} ))}
{/* {videoUploadedFiles?.map((file, index) => (
<div
key={index}
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div
className="flex gap-3 items-center cursor-pointer"
onClick={() =>
setSelectedVideo(
`https://new.netidhub.com/api/v2/assignment-expert/file/viewer?id=${file.id}`
)
}
>
<VideoIcon />
<div>
<div className="text-sm text-card-foreground">
{file.fileName}
</div>
</div>
</div>
<Button
size="icon"
color="destructive"
variant="outline"
className="border-none rounded-full"
onClick={() => handleRemoveFile(file)}
>
<Icon icon="tabler:x" className="h-5 w-5" />
</Button>
</div>
))} */}
</div> </div>
</div> </div>
<div> <div>
@ -1382,16 +1291,12 @@ export default function FormTaskTaDetail() {
<div> <div>
{selectedText && ( {selectedText && (
<Card className="mt-2"> <Card className="mt-2">
{/* <iframe <iframe
className="w-full h-96 rounded-md" className="w-full h-96 rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent( src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
selectedText selectedText
)}`} )}`}
title={"Document"} title={"Document"}
/> */}
<iframe
className="w-full h-96"
src={getViewerUrl(selectedText)}
/> />
</Card> </Card>
)} )}
@ -1445,7 +1350,7 @@ export default function FormTaskTaDetail() {
onPause={() => setIsPlaying(false)} onPause={() => setIsPlaying(false)}
/> />
</div> </div>
{/* <Button <Button
size="sm" size="sm"
type="button" type="button"
onClick={onPlayPause} onClick={onPlayPause}
@ -1465,22 +1370,6 @@ export default function FormTaskTaDetail() {
} }
className="h-5 w-5" className="h-5 w-5"
/> />
</Button> */}
<Button
size="sm"
type="button"
onClick={onPlayPause}
className="flex items-center gap-2 bg-primary text-white p-2 rounded"
>
{isPlaying ? "Pause" : "Play"}
<Icon
icon={
isPlaying
? "carbon:pause-outline"
: "famicons:play-sharp"
}
className="h-5 w-5"
/>
</Button> </Button>
</Card> </Card>
)} )}
@ -1544,24 +1433,6 @@ export default function FormTaskTaDetail() {
)} )}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4">
<Label>Link Url</Label>
{urlInputs.map((url: any, index: any) => (
<Link
key={index}
href={url}
target="_blank"
className="flex items-center gap-2 mt-2"
>
<input
type="url"
className="border rounded p-2 w-full cursor-pointer"
value={url}
placeholder={`Masukkan link berita ${index + 1}`}
/>
</Link>
))}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -1595,7 +1466,7 @@ export default function FormTaskTaDetail() {
</Button> </Button>
</div> */} </div> */}
<div className=""> <div className="">
{/* <Button <Button
type="button" type="button"
className="btn btn-primary lg:mx-3" className="btn btn-primary lg:mx-3"
style={ style={
@ -1610,21 +1481,6 @@ export default function FormTaskTaDetail() {
onClick={() => handleAcceptAcceptance()} onClick={() => handleAcceptAcceptance()}
> >
Terima Tugas Terima Tugas
</Button> */}
<Button
type="button"
onClick={handleAcceptAcceptance}
disabled={isAccepted || acceptLoading}
className="
btn btn-primary lg:mx-3
disabled:opacity-50 disabled:cursor-not-allowed
"
>
{isAccepted
? "Tugas Sudah Diterima"
: acceptLoading
? "Memproses..."
: "Terima Tugas"}
</Button> </Button>
</div> </div>
<div <div

View File

@ -58,8 +58,10 @@ import { getListCompetencies } from "@/service/management-user/management-user";
const taskSchema = z.object({ const taskSchema = z.object({
// uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), // uniqueCode: z.string().min(1, { message: "Judul diperlukan" }),
title: z.string().optional(), title: z.string().min(1, { message: "Judul diperlukan" }),
naration: z.string().optional(), naration: z.string().min(2, {
message: "Narasi Penugasan harus lebih dari 2 karakter.",
}),
}); });
export type taskDetail = { export type taskDetail = {
@ -583,14 +585,10 @@ export default function FormTaskTaEdit() {
assignedToRole: selectedTarget, assignedToRole: selectedTarget,
assignmentType: taskType, assignmentType: taskType,
assignmentTypeId: type, assignmentTypeId: type,
// narration: data.naration, narration: data.naration,
narration: data.naration ?? detail?.narration ?? "",
expertCompetencies: Array.from(selectedCompetencies).join(","), expertCompetencies: Array.from(selectedCompetencies).join(","),
// title: data.title, title: data.title,
title: data.title ?? detail?.title ?? "", attachmentUrl: links,
attachmentUrl: urlInputs
.map((url: any) => url.attachmentUrl || "")
.filter((url: string) => url.trim() !== ""),
}; };
const response = await createTaskTa(requestData); const response = await createTaskTa(requestData);
@ -804,7 +802,7 @@ export default function FormTaskTaEdit() {
isAudioUploadFinish && isAudioUploadFinish &&
isTextUploadFinish isTextUploadFinish
) { ) {
successSubmit("/in/contributor/task-ta"); successSubmit("/in/contributor/task");
} }
} }
@ -843,11 +841,9 @@ export default function FormTaskTaEdit() {
}; };
const handleLinkChange = (index: number, value: string) => { const handleLinkChange = (index: number, value: string) => {
setUrlInputs((prev: any) => const updatedLinks = [...links];
prev.map((url: any, idx: any) => updatedLinks[index] = value;
idx === index ? { ...url, attachmentUrl: value } : url setLinks(updatedLinks);
)
);
}; };
const handleAddLink = () => { const handleAddLink = () => {
@ -886,9 +882,7 @@ export default function FormTaskTaEdit() {
)} )}
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label> <Label>{t("areas-expertise", { defaultValue: "Areas Expertise" })}</Label>
{t("areas-expertise", { defaultValue: "Areas Expertise" })}
</Label>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
{userCompetencies?.map((item: any) => ( {userCompetencies?.map((item: any) => (
<div className="flex items-center gap-2" key={item.id}> <div className="flex items-center gap-2" key={item.id}>
@ -903,9 +897,7 @@ export default function FormTaskTaEdit() {
</div> </div>
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label> <Label>{t("choose-expert", { defaultValue: "Choose Expert" })}</Label>
{t("choose-expert", { defaultValue: "Choose Expert" })}
</Label>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
@ -986,9 +978,7 @@ export default function FormTaskTaEdit() {
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label> <Label>{t("description", { defaultValue: "Description" })}</Label>
{t("description", { defaultValue: "Description" })}
</Label>
<Controller <Controller
control={control} control={control}
name="naration" name="naration"
@ -1006,14 +996,10 @@ export default function FormTaskTaEdit() {
)} )}
</div> </div>
<div className="space-y-2.5 mt-5"> <div className="space-y-2.5 mt-5">
<Label htmlFor="attachments"> <Label htmlFor="attachments">{t("attachment", { defaultValue: "Attachment" })}</Label>
{t("attachment", { defaultValue: "Attachment" })}
</Label>
<div className="space-y-3"> <div className="space-y-3">
<div className="space-y-2"> <div className="space-y-2">
<Label> <Label>{t("audio-visual", { defaultValue: "Audio Visual" })}</Label>
{t("audio-visual", { defaultValue: "Audio Visual" })}
</Label>
<FileUploader <FileUploader
accept={{ accept={{
"mp4/*": [], "mp4/*": [],
@ -1202,9 +1188,7 @@ export default function FormTaskTaEdit() {
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
{/* Display remaining time */} {/* Display remaining time */}
<div className="mt-4 space-y-2"> <div className="mt-4 space-y-2">
<h2 className="text-lg font-bold"> <h2 className="text-lg font-bold">{t("news-links", { defaultValue: "News Links" })}</h2>
{t("news-links", { defaultValue: "News Links" })}
</h2>
{urlInputs.map((url: any, index: any) => ( {urlInputs.map((url: any, index: any) => (
<div <div
key={url.id} key={url.id}
@ -1221,13 +1205,42 @@ export default function FormTaskTaEdit() {
/> />
</div> </div>
))} ))}
<button <div className="mt-4 space-y-2">
type="button" <Label className="">{t("news-links", { defaultValue: "News Links" })}</Label>
className="mt-4 bg-green-500 text-white px-4 py-2 rounded" {links.map((link, index) => (
onClick={handleAddLink} <div
> key={index}
{t("add-links", { defaultValue: "Add Links" })} className="flex items-center gap-2 mt-2"
</button> >
<Input
type="url"
className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${index + 1}`}
value={link}
onChange={(e) =>
handleLinkChange(index, e.target.value)
}
/>
{links.length > 1 && (
<button
type="button"
className="bg-red-500 text-white px-3 py-1 rounded"
onClick={() => handleRemoveRow(index)}
>
<Trash2 className="h-4 w-4" />
</button>
)}
</div>
))}
<Button
type="button"
className="mt-2 bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleAddRow}
size="sm"
>
{t("add-links", { defaultValue: "Add Links" })}
</Button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -43,7 +43,7 @@ import { getCsrfToken } from "@/service/auth";
import { loading } from "@/lib/swal"; import { loading } from "@/lib/swal";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { cn, getCookiesDecrypt } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
@ -98,7 +98,7 @@ const CustomEditor = dynamic(
() => { () => {
return import("@/components/editor/custom-editor"); return import("@/components/editor/custom-editor");
}, },
{ ssr: false }, { ssr: false }
); );
export default function FormTaskTa() { export default function FormTaskTa() {
@ -136,7 +136,7 @@ export default function FormTaskTa() {
const [userLevels, setUserLevels] = useState<any>(); const [userLevels, setUserLevels] = useState<any>();
const [userCompetencies, setUserCompetencies] = useState<any[]>([]); const [userCompetencies, setUserCompetencies] = useState<any[]>([]);
const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>( const [selectedCompetencies, setSelectedCompetencies] = useState<Set<number>>(
new Set(), new Set()
); );
const [listExpert, setListExpert] = useState<any[]>([]); const [listExpert, setListExpert] = useState<any[]>([]);
const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set()); const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
@ -181,33 +181,6 @@ export default function FormTaskTa() {
mode: "all", mode: "all",
}); });
const [profile, setProfile] = useState<any>(null);
const userLevelId = Number(getCookiesDecrypt("ulie"));
const roleId = Number(getCookiesDecrypt("urie"));
const userId = Number(getCookiesDecrypt("uie"));
const MABES_LEVEL_ID = 216; // userLevelId Mabes Polri
const APPROVER_ROLE_ID = 3; // roleId Approver
const isMabes = userLevelId === MABES_LEVEL_ID;
const isApprover = roleId === APPROVER_ROLE_ID;
const isMabesApprover =
userLevelId === MABES_LEVEL_ID && roleId === APPROVER_ROLE_ID;
const shouldHideExpert = isMabes && isApprover;
useEffect(() => {
async function fetchUserLevel() {
try {
const res = await getUserLevelForAssignments();
setProfile(res?.data?.data);
} catch (e) {
console.error("Failed fetch user level", e);
}
}
fetchUserLevel();
}, []);
useEffect(() => { useEffect(() => {
getDataAdditional(); getDataAdditional();
}, []); }, []);
@ -245,7 +218,7 @@ export default function FormTaskTa() {
} }
const uniqueExperts = Array.from( const uniqueExperts = Array.from(
new Map(allExperts.map((e) => [e.id, e])).values(), new Map(allExperts.map((e) => [e.id, e])).values()
); );
setListExpert(uniqueExperts); setListExpert(uniqueExperts);
@ -287,216 +260,67 @@ export default function FormTaskTa() {
}); });
}; };
// const save = async (data: TaskSchema) => {
// const cleanedLinks = links
// .map((link) => link.trim())
// .filter((link) => link !== "" && link.startsWith("http"));
// const requestData: {
// id?: number;
// title: string;
// assignedToUsers: any;
// assignmentTypeId: string;
// narration: string;
// assignmentType: string;
// expertCompetencies: string;
// attachmentUrl: string[];
// } = {
// ...data,
// assignedToUsers: handleExpertChange(),
// assignmentType: taskType,
// assignmentTypeId: type,
// narration: data.narration,
// expertCompetencies: Array.from(selectedCompetencies).join(","),
// title: data.title,
// attachmentUrl: cleanedLinks,
// };
// const response = await createTaskTa(requestData);
// localStorage.setItem("TA_UPLOAD_IN_PROGRESS", "true");
// console.log("Form Data Submitted:", requestData);
// console.log("response", response);
// const id = response?.data?.data.id;
// loading();
// if (imageFiles?.length == 0) {
// setIsImageUploadFinish(true);
// }
// const allUploads: Promise<any>[] = [];
// imageFiles.forEach((item, index) => {
// allUploads.push(uploadResumableFile(index, String(id), item, "1", "0"));
// });
// videoFiles.forEach((item, index) => {
// allUploads.push(uploadResumableFile(index, String(id), item, "2", "0"));
// });
// textFiles.forEach((item, index) => {
// allUploads.push(uploadResumableFile(index, String(id), item, "3", "0"));
// });
// audioFiles.forEach((item, index) => {
// allUploads.push(uploadResumableFile(index, String(id), item, "4", "0"));
// });
// // ⬅ WAJIB
// await Promise.all(allUploads);
// localStorage.removeItem("TA_UPLOAD_IN_PROGRESS");
// // imageFiles?.map(async (item: any, index: number) => {
// // await uploadResumableFile(index, String(id), item, "1", "0");
// // });
// // if (videoFiles?.length == 0) {
// // setIsVideoUploadFinish(true);
// // }
// // videoFiles?.map(async (item: any, index: number) => {
// // await uploadResumableFile(index, String(id), item, "2", "0");
// // });
// // if (textFiles?.length == 0) {
// // setIsTextUploadFinish(true);
// // }
// // textFiles?.map(async (item: any, index: number) => {
// // await uploadResumableFile(index, String(id), item, "3", "0");
// // });
// // if (audioFiles?.length == 0) {
// // setIsAudioUploadFinish(true);
// // }
// // audioFiles.map(async (item: FileWithPreview, index: number) => {
// // await uploadResumableFile(
// // index,
// // String(id),
// // item, // Use .file to access the actual File object
// // "4",
// // "0" // Optional: Replace with actual duration if available
// // );
// // });
// };
// const save = async (data: TaskSchema) => {
// const cleanedLinks = links
// .map((link) => link.trim())
// .filter((link) => link.startsWith("http"));
// const requestData = {
// ...data,
// // assignedToUsers: handleExpertChange(),
// assignedToUsers: isMabesApprover ? "464" : handleExpertChange(),
// assignmentType: taskType,
// assignmentTypeId: type,
// expertCompetencies: Array.from(selectedCompetencies).join(","),
// attachmentUrl: cleanedLinks,
// };
// console.log("FINAL ASSIGNED TO:", {
// isMabesApprover,
// assignedToUsers: isMabesApprover
// ? String(roleId)
// : handleExpertChange(),
// });
// const response = await createTaskTa(requestData);
// const id = String(response?.data?.data.id);
// // Set block table TA
// localStorage.setItem("TA_UPLOAD_IN_PROGRESS", "true");
// loading(); // SHOW SWAL LOADING
// // Kumpulkan semua upload
// const allUploads: Promise<any>[] = [];
// imageFiles.forEach((item, idx) =>
// allUploads.push(uploadResumableFile(idx, id, item, "1", "0"))
// );
// videoFiles.forEach((item, idx) =>
// allUploads.push(uploadResumableFile(idx, id, item, "2", "0"))
// );
// textFiles.forEach((item, idx) =>
// allUploads.push(uploadResumableFile(idx, id, item, "3", "0"))
// );
// audioFiles.forEach((item, idx) =>
// allUploads.push(uploadResumableFile(idx, id, item, "4", "0"))
// );
// // Tunggu upload selesai
// await Promise.all(allUploads);
// // Hapus flag
// localStorage.removeItem("TA_UPLOAD_IN_PROGRESS");
// // Close loading + redirect
// successSubmit("/in/contributor/task-ta");
// };
const save = async (data: TaskSchema) => { const save = async (data: TaskSchema) => {
try { const requestData: {
loading(); id?: number;
title: string;
assignedToUsers: any;
assignmentTypeId: string;
narration: string;
assignmentType: string;
expertCompetencies: string;
// attachmentUrl: string[];
} = {
...data,
assignedToUsers: handleExpertChange(),
assignmentType: taskType,
assignmentTypeId: type,
narration: data.narration,
expertCompetencies: Array.from(selectedCompetencies).join(","),
title: data.title,
// attachmentUrl: links,
};
const cleanedLinks = links const response = await createTaskTa(requestData);
.map((link) => link.trim())
.filter((link) => link.startsWith("http"));
const requestData = { console.log("Form Data Submitted:", requestData);
...data, console.log("response", response);
// assignedToUsers: isMabesApprover ? "464" : handleExpertChange(),
assignedToUsers: isMabesApprover ? "464,8258" : handleExpertChange(),
assignmentType: taskType,
assignmentTypeId: type,
expertCompetencies: Array.from(selectedCompetencies).join(","),
attachmentUrl: cleanedLinks,
};
const response = await createTaskTa(requestData); const id = response?.data?.data.id;
loading();
if (!response?.data?.data?.id) { if (imageFiles?.length == 0) {
throw new Error("Gagal membuat task"); setIsImageUploadFinish(true);
}
const assignmentId = String(response.data.data.id);
const uploads: Promise<any>[] = [];
imageFiles.forEach((file, i) =>
uploads.push(uploadResumableFile(i, assignmentId, file, "1", "0")),
);
videoFiles.forEach((file, i) =>
uploads.push(uploadResumableFile(i, assignmentId, file, "2", "0")),
);
textFiles.forEach((file, i) =>
uploads.push(uploadResumableFile(i, assignmentId, file, "3", "0")),
);
audioFiles.forEach((file, i) =>
uploads.push(uploadResumableFile(i, assignmentId, file, "4", "0")),
);
await Promise.all(uploads);
successSubmit("/in/contributor/task-ta");
} catch (err: any) {
console.error("SUBMIT ERROR:", err);
Swal.fire({
icon: "error",
title: "Gagal",
text:
err?.response?.data?.message ||
err?.message ||
"Terjadi kesalahan, data tidak tersimpan",
});
} }
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0");
});
if (videoFiles?.length == 0) {
setIsVideoUploadFinish(true);
}
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0");
});
if (textFiles?.length == 0) {
setIsTextUploadFinish(true);
}
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0");
});
if (audioFiles?.length == 0) {
setIsAudioUploadFinish(true);
}
audioFiles.map(async (item: FileWithPreview, index: number) => {
await uploadResumableFile(
index,
String(id),
item, // Use .file to access the actual File object
"4",
"0" // Optional: Replace with actual duration if available
);
});
}; };
const onSubmit = (data: TaskSchema) => { const onSubmit = (data: TaskSchema) => {
@ -557,7 +381,7 @@ export default function FormTaskTa() {
// Convert Blob to File and add preview // Convert Blob to File and add preview
const fileWithPreview: FileWithPreview = Object.assign( const fileWithPreview: FileWithPreview = Object.assign(
new File([blob], "voiceNote.webm", { type: "audio/webm" }), new File([blob], "voiceNote.webm", { type: "audio/webm" }),
{ preview: url }, { preview: url }
); );
// Add to state // Add to state
@ -569,162 +393,75 @@ export default function FormTaskTa() {
setAudioFiles((prev) => prev.filter((_, idx) => idx !== index)); setAudioFiles((prev) => prev.filter((_, idx) => idx !== index));
}; };
// async function uploadResumableFile( async function uploadResumableFile(
// idx: number,
// id: string,
// file: any,
// fileTypeId: string,
// duration: string
// ) {
// console.log("Tus Upload : ", idx, id, file, fileTypeId, duration);
// const resCsrf = await getCsrfToken();
// const csrfToken = resCsrf?.data?.token;
// console.log("CSRF TOKEN : ", csrfToken);
// const headers = {
// "X-XSRF-TOKEN": csrfToken,
// };
// const upload = new Upload(file, {
// endpoint: `${process.env.NEXT_PUBLIC_API}/assignment-expert/file/upload`,
// headers: headers,
// retryDelays: [0, 3000, 6000, 12_000, 24_000],
// chunkSize: 20_000,
// metadata: {
// assignmentId: id,
// filename: file.name,
// contentType: file.type,
// fileTypeId: fileTypeId,
// duration,
// },
// onBeforeRequest: function (req) {
// var xhr = req.getUnderlyingObject();
// xhr.withCredentials = true;
// },
// onError: async (e: any) => {
// console.log("Error upload :", e);
// error(e);
// },
// onChunkComplete: (
// chunkSize: any,
// bytesAccepted: any,
// bytesTotal: any
// ) => {
// // const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// // progressInfo[idx].percentage = uploadPersen;
// // counterUpdateProgress++;
// // console.log(counterUpdateProgress);
// // setProgressList(progressInfo);
// // setCounterProgress(counterUpdateProgress);
// },
// onSuccess: async () => {
// // uploadPersen = 100;
// // progressInfo[idx].percentage = 100;
// // counterUpdateProgress++;
// // setCounterProgress(counterUpdateProgress);
// successTodo();
// if (fileTypeId == "1") {
// setIsImageUploadFinish(true);
// } else if (fileTypeId == "2") {
// setIsVideoUploadFinish(true);
// }
// if (fileTypeId == "3") {
// setIsTextUploadFinish(true);
// }
// if (fileTypeId == "4") {
// setIsAudioUploadFinish(true);
// }
// },
// });
// upload.start();
// }
// function uploadResumableFile(
// idx: number,
// id: string,
// file: any,
// fileTypeId: string,
// duration: string
// ) {
// return new Promise(async (resolve, reject) => {
// const resCsrf = await getCsrfToken();
// const csrfToken = resCsrf?.data?.token;
// const upload = new Upload(file, {
// endpoint: `${process.env.NEXT_PUBLIC_API}/assignment-expert/file/upload`,
// headers: { "X-XSRF-TOKEN": csrfToken },
// retryDelays: [0, 3000, 6000, 12000],
// chunkSize: 20000,
// metadata: {
// assignmentId: id,
// filename: file.name,
// contentType: file.type,
// fileTypeId,
// duration,
// },
// onBeforeRequest(req) {
// req.getUnderlyingObject().withCredentials = true;
// },
// onError(err) {
// console.error("Upload error:", err);
// reject(err);
// },
// onSuccess() {
// console.log("Upload selesai:", file.name);
// resolve(true);
// },
// });
// upload.start();
// });
// }
function uploadResumableFile(
idx: number, idx: number,
id: string, id: string,
file: File, file: any,
fileTypeId: string, fileTypeId: string,
duration: string, duration: string
) { ) {
return new Promise(async (resolve, reject) => { console.log("Tus Upload : ", idx, id, file, fileTypeId, duration);
try {
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token;
const upload = new Upload(file, { const resCsrf = await getCsrfToken();
endpoint: `${process.env.NEXT_PUBLIC_API}/assignment-expert/file/upload`, const csrfToken = resCsrf?.data?.token;
headers: { "X-XSRF-TOKEN": csrfToken }, console.log("CSRF TOKEN : ", csrfToken);
retryDelays: [0, 3000, 6000], const headers = {
chunkSize: 20000, "X-XSRF-TOKEN": csrfToken,
metadata: { };
assignmentId: id,
filename: file.name,
contentType: file.type,
fileTypeId,
duration,
},
onBeforeRequest(req) { const upload = new Upload(file, {
req.getUnderlyingObject().withCredentials = true; endpoint: `${process.env.NEXT_PUBLIC_API}/assignment-expert/file/upload`,
}, headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
onError(error) { chunkSize: 20_000,
reject(error); metadata: {
}, assignmentId: id,
filename: file.name,
onSuccess() { contentType: file.type,
resolve(true); fileTypeId: fileTypeId,
}, duration,
}); },
onBeforeRequest: function (req) {
upload.start(); var xhr = req.getUnderlyingObject();
} catch (err) { xhr.withCredentials = true;
reject(err); },
} onError: async (e: any) => {
console.log("Error upload :", e);
error(e);
},
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
) => {
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// progressInfo[idx].percentage = uploadPersen;
// counterUpdateProgress++;
// console.log(counterUpdateProgress);
// setProgressList(progressInfo);
// setCounterProgress(counterUpdateProgress);
},
onSuccess: async () => {
// uploadPersen = 100;
// progressInfo[idx].percentage = 100;
// counterUpdateProgress++;
// setCounterProgress(counterUpdateProgress);
successTodo();
if (fileTypeId == "1") {
setIsImageUploadFinish(true);
} else if (fileTypeId == "2") {
setIsVideoUploadFinish(true);
}
if (fileTypeId == "3") {
setIsTextUploadFinish(true);
}
if (fileTypeId == "4") {
setIsAudioUploadFinish(true);
}
},
}); });
upload.start();
} }
useEffect(() => { useEffect(() => {
@ -777,9 +514,7 @@ export default function FormTaskTa() {
return ( return (
<Card> <Card>
<div className="px-6 py-6"> <div className="px-6 py-6">
<p className="text-lg font-semibold mb-3"> <p className="text-lg font-semibold mb-3">{t("form-task", { defaultValue: "Form Task" })}</p>
{t("form-task", { defaultValue: "Form Task" })}
</p>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
@ -805,36 +540,17 @@ export default function FormTaskTa() {
</div> </div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label> <Label>{t("assigment-type", { defaultValue: "Assigment Type" })} </Label>
{t("assigment-type", { defaultValue: "Assigment Type" })}{" "}
</Label>
<RadioGroup <RadioGroup
value={taskType} value={taskType}
onValueChange={(value) => setTaskType(String(value))} onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3" className="flex flex-wrap gap-3"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="atensi-khusus" id="khusus" />
<Label htmlFor="khusus">Atensi Khusus</Label>
</div>
{!isMabes && (
<div className="flex items-center gap-2">
<RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor="harian">Tugas Harian</Label>
</div>
)}
</RadioGroup>
{/* <RadioGroup
value={taskType}
onValueChange={(value) => setTaskType(String(value))}
className="flex flex-wrap gap-3"
> >
<RadioGroupItem value="atensi-khusus" id="khusus" /> <RadioGroupItem value="atensi-khusus" id="khusus" />
<Label htmlFor="atensi-khusus">Atensi Khusus</Label> <Label htmlFor="atensi-khusus">Atensi Khusus</Label>
<RadioGroupItem value="tugas-harian" id="harian" /> {/* <RadioGroupItem value="tugas-harian" id="harian" />
<Label htmlFor="tugas-harian">Tugas Harian</Label> <Label htmlFor="tugas-harian">Tugas Harian</Label> */}
</RadioGroup> */} </RadioGroup>
</div> </div>
<div className="flex flex-col space-y-2 mt-5"> <div className="flex flex-col space-y-2 mt-5">
<Label className="mr-3 mb-1">Tanggal</Label> <Label className="mr-3 mb-1">Tanggal</Label>
@ -846,7 +562,7 @@ export default function FormTaskTa() {
variant={"outline"} variant={"outline"}
className={cn( className={cn(
"w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300 px-0 md:px-0 lg:px-4", "w-[280px] lg:w-[250px] justify-start text-left font-normal border border-slate-300 px-0 md:px-0 lg:px-4",
!date && "text-muted-foreground", !date && "text-muted-foreground"
)} )}
> >
<CalendarIcon size={15} className="mr-3" /> <CalendarIcon size={15} className="mr-3" />
@ -876,109 +592,97 @@ export default function FormTaskTa() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
{!isMabesApprover && ( <div className="mt-5 space-y-2">
<div className="mt-5 space-y-2"> <Label>{t("areas-expertise", { defaultValue: "Areas Expertise" })}</Label>
<Label> <div className="flex flex-wrap gap-4">
{t("areas-expertise", { defaultValue: "Areas Expertise" })} {userCompetencies?.map((item: any) => (
</Label> <div className="flex items-center gap-2" key={item.id}>
<div className="flex flex-wrap gap-4"> <Checkbox
{userCompetencies?.map((item: any) => ( id={`comp-${item.id}`}
<div className="flex items-center gap-2" key={item.id}> checked={selectedCompetencies.has(item.id)}
<Checkbox onCheckedChange={() => handleCompetencyChange(item.id)}
id={`comp-${item.id}`} />
checked={selectedCompetencies.has(item.id)} <Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
onCheckedChange={() => handleCompetencyChange(item.id)}
/>
<Label htmlFor={`comp-${item.id}`}>{item.name}</Label>
</div>
))}
</div>
</div>
)}
{!isMabesApprover && (
<div className="mt-5 space-y-2">
<Label>
{t("choose-expert", { defaultValue: "Choose Expert" })}
</Label>
<div className="flex flex-wrap gap-4">
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[{"Pilih Tenaga Ahli"}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>Daftar Tenaga Ahli</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listExpert?.map((expert: any) => (
<div key={expert.id} className="border p-2">
<Label className="flex items-center">
<Checkbox
checked={checkedLevels.has(expert.id)}
onCheckedChange={() =>
handleCheckboxChange(expert.id)
}
className="mr-3"
/>
<div className="flex flex-col gap-2">
<div className="font-bold">
{expert.fullname}
</div>
<div className="italic">
({expert.username})
</div>
</div>
</Label>
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
{checkedLevels.size > 0 && (
<div className="mt-3">
<Label className="text-sm text-gray-600 mb-2 block">
Tenaga Ahli Terpilih ({checkedLevels.size})
</Label>
<div className="flex flex-wrap gap-2">
{Array.from(checkedLevels).map((expertId) => {
const expert = listExpert?.find(
(exp: any) => exp.id === expertId,
);
return expert ? (
<div
key={expert.id}
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
>
<span>{expert.fullname}</span>
<button
type="button"
onClick={() => handleCheckboxChange(expert.id)}
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
title="Remove expert"
>
<svg
className="w-3 h-3"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
) : null;
})}
</div>
</div> </div>
)} ))}
</div> </div>
)} </div>
<div className="mt-5 space-y-2">
<Label>{t("choose-expert", { defaultValue: "Choose Expert" })}</Label>
<div className="flex flex-wrap gap-4">
<Dialog>
<DialogTrigger asChild>
<Button variant="soft" size="sm" color="primary">
[{"Pilih Tenaga Ahli"}]
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px]">
<DialogHeader>
<DialogTitle>Daftar Tenaga Ahli</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-2 gap-2 max-h-[400px] overflow-y-auto">
{listExpert?.map((expert: any) => (
<div key={expert.id} className="border p-2">
<Label className="flex items-center">
<Checkbox
checked={checkedLevels.has(expert.id)}
onCheckedChange={() =>
handleCheckboxChange(expert.id)
}
className="mr-3"
/>
<div className="flex flex-col gap-2">
<div className="font-bold">{expert.fullname}</div>
<div className="italic">({expert.username})</div>
</div>
</Label>
</div>
))}
</div>
</DialogContent>
</Dialog>
</div>
{checkedLevels.size > 0 && (
<div className="mt-3">
<Label className="text-sm text-gray-600 mb-2 block">
Tenaga Ahli Terpilih ({checkedLevels.size})
</Label>
<div className="flex flex-wrap gap-2">
{Array.from(checkedLevels).map((expertId) => {
const expert = listExpert?.find(
(exp: any) => exp.id === expertId
);
return expert ? (
<div
key={expert.id}
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-sm font-medium px-3 py-1.5 rounded-full border border-blue-200"
>
<span>{expert.fullname}</span>
<button
type="button"
onClick={() => handleCheckboxChange(expert.id)}
className="ml-1 text-blue-600 hover:text-blue-800 hover:bg-blue-200 rounded-full p-0.5 transition-colors"
title="Remove expert"
>
<svg
className="w-3 h-3"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
) : null;
})}
</div>
</div>
)}
</div>
<div className="mt-5 space-y-2"> <div className="mt-5 space-y-2">
<Label>{t("description", { defaultValue: "Description" })}</Label> <Label>{t("description", { defaultValue: "Description" })}</Label>
<Controller <Controller
@ -995,23 +699,11 @@ export default function FormTaskTa() {
)} )}
</div> </div>
<div className="space-y-2.5 mt-5"> <div className="space-y-2.5 mt-5">
<Label htmlFor="attachments"> <Label htmlFor="attachments">{t("attachment", { defaultValue: "Attachment" })}</Label>
{t("attachment", { defaultValue: "Attachment" })}
</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label> <Label>{t("audio-visual", { defaultValue: "Audio Visual" })}</Label>
{t("audio-visual", { defaultValue: "Audio Visual" })}
</Label>
<FileUploader <FileUploader
accept={{
"video/*": [],
}}
maxSize={100}
label="Upload file dengan format video."
onDrop={(files) => setVideoFiles(files)}
/>
{/* <FileUploader
accept={{ accept={{
"mp4/*": [], "mp4/*": [],
"mov/*": [], "mov/*": [],
@ -1019,7 +711,7 @@ export default function FormTaskTa() {
maxSize={100} maxSize={100}
label="Upload file dengan format .mp4 atau .mov." label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setVideoFiles(files)} onDrop={(files) => setVideoFiles(files)}
/> */} />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>{t("image", { defaultValue: "Image" })}</Label> <Label>{t("image", { defaultValue: "Image" })}</Label>
@ -1035,21 +727,13 @@ export default function FormTaskTa() {
<div className="space-y-2"> <div className="space-y-2">
<Label>{t("text", { defaultValue: "Text" })}</Label> <Label>{t("text", { defaultValue: "Text" })}</Label>
<FileUploader <FileUploader
accept={{
"application/pdf": [],
}}
maxSize={100}
label="Upload file PDF."
onDrop={(files) => setTextFiles(files)}
/>
{/* <FileUploader
accept={{ accept={{
"pdf/*": [], "pdf/*": [],
}} }}
maxSize={100} maxSize={100}
label="Upload file dengan format .pdf." label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)} onDrop={(files) => setTextFiles(files)}
/> */} />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>{t("audio", { defaultValue: "Audio" })}</Label> <Label>{t("audio", { defaultValue: "Audio" })}</Label>
@ -1060,20 +744,9 @@ export default function FormTaskTa() {
echoCancellation: true, echoCancellation: true,
}} }}
downloadOnSavePress={true} downloadOnSavePress={true}
downloadFileExtension="mp3" downloadFileExtension="webm"
/> />
<FileUploader <FileUploader
accept={{
"audio/*": [],
}}
maxSize={100}
label="Upload file audio (mp3, wav, webm)."
onDrop={(files) =>
setAudioFiles((prev) => [...prev, ...files])
}
className="mt-2"
/>
{/* <FileUploader
accept={{ accept={{
"mp3/*": [], "mp3/*": [],
"wav/*": [], "wav/*": [],
@ -1084,7 +757,7 @@ export default function FormTaskTa() {
setAudioFiles((prev) => [...prev, ...files]) setAudioFiles((prev) => [...prev, ...files])
} }
className="mt-2" className="mt-2"
/> */} />
</div> </div>
{audioFiles?.map((audio: any, idx: any) => ( {audioFiles?.map((audio: any, idx: any) => (
<div <div
@ -1102,19 +775,15 @@ export default function FormTaskTa() {
</Button> </Button>
</div> </div>
))} ))}
{isRecording && <p>Recording... {timer} seconds remaining</p>}{" "} {/* {isRecording && <p>Recording... {timer} seconds remaining</p>}{" "}
<div className="mt-4 space-y-2"> <div className="mt-4 space-y-2">
<Label className=""> <Label className="">{t("news-links", { defaultValue: "News Links" })}</Label>
{t("news-links", { defaultValue: "News Links" })}
</Label>
{links.map((link, index) => ( {links.map((link, index) => (
<div key={index} className="flex items-center gap-2 mt-2"> <div key={index} className="flex items-center gap-2 mt-2">
<Input <Input
type="url" type="url"
className="border rounded p-2 w-full" className="border rounded p-2 w-full"
placeholder={`Masukkan link berita ${ placeholder={`Masukkan link berita ${index + 1}`}
index + 1
} | Contoh: https://www.detik.com`}
value={link} value={link}
onChange={(e) => onChange={(e) =>
handleLinkChange(index, e.target.value) handleLinkChange(index, e.target.value)
@ -1139,7 +808,7 @@ export default function FormTaskTa() {
> >
{t("add-links", { defaultValue: "Add Links" })} {t("add-links", { defaultValue: "Add Links" })}
</Button> </Button>
</div> </div> */}
</div> </div>
</div> </div>
</div> </div>

View File

@ -87,7 +87,9 @@ const CustomEditor = dynamic(
export default function FormTaskTaNew() { export default function FormTaskTaNew() {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const router = useRouter(); const router = useRouter();
const editor = useRef(null);
type ImageSchema = z.infer<typeof imageSchema>; type ImageSchema = z.infer<typeof imageSchema>;
const t = useTranslations("Form"); const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]); const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId"); const taskId = Cookies.get("taskId");
@ -95,14 +97,33 @@ export default function FormTaskTaNew() {
const scheduleType = Cookies.get("scheduleType"); const scheduleType = Cookies.get("scheduleType");
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const { id } = useParams() as { id: string }; const { id } = useParams() as { id: string };
console.log(id);
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>(); const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]); const [tags, setTags] = useState<any[]>([]);
const [thumbnail, setThumbnail] = useState<File | null>(null); const [thumbnail, setThumbnail] = useState<File | null>(null);
const [preview, setPreview] = useState<string | null>(null); const [preview, setPreview] = useState<string | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState("");
const [selectedSEO, setSelectedSEO] = useState<string>("");
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [selectedAdvConfig, setSelectedAdvConfig] = useState<string>("");
const [editingArticleId, setEditingArticleId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoadingData, setIsLoadingData] = useState<boolean>(false); const [isLoadingData, setIsLoadingData] = useState<boolean>(false);
const [articleIds, setArticleIds] = useState<string[]>([]);
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>(""); const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedWritingStyle, setSelectedWritingStyle] = useState("");
const [selectedSize, setSelectedSize] = useState("");
const [detailData, setDetailData] = useState<any>(null);
const [articleImages, setArticleImages] = useState<string[]>([]);
const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false); const [isSwitchOn, setIsSwitchOn] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]); const [imageFiles, setImageFiles] = useState<FileWithPreview[]>([]);
@ -118,7 +139,26 @@ export default function FormTaskTaNew() {
const [links, setLinks] = useState<string[]>([""]); const [links, setLinks] = useState<string[]>([""]);
const [timer, setTimer] = useState<number>(120); const [timer, setTimer] = useState<number>(120);
const [fileTypeId, setFileTypeId] = useState<string>(""); const [fileTypeId, setFileTypeId] = useState<string>("");
const [content, setContent] = useState("");
const [selectedTarget, setSelectedTarget] = useState("");
const [unitSelection, setUnitSelection] = useState({
allUnit: false,
mabes: false,
polda: false,
polres: false,
});
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 [files, setFiles] = useState<FileWithPreview[]>([]); const [files, setFiles] = useState<FileWithPreview[]>([]);
const [filesTemp, setFilesTemp] = useState<File[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]); const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [detail, setDetail] = useState<taskDetail>(); const [detail, setDetail] = useState<taskDetail>();
const [refresh] = useState(false); const [refresh] = useState(false);
@ -138,7 +178,7 @@ export default function FormTaskTaNew() {
}); });
const imageSchema = z.object({ const imageSchema = z.object({
title: z.string().optional(), title: z.string().min(1, { message: "Judul diperlukan" }),
description: z description: z
.string() .string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }) .min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." })
@ -296,109 +336,17 @@ export default function FormTaskTaNew() {
initState(); initState();
}, [id, refresh]); }, [id, refresh]);
// const save = async (data: ImageSchema) => {
// loading();
// const finalTags = tags.join(", ");
// const finalTitle = data.title || detail?.title || "";
// const finalDescription = articleBody || data.description;
// if (!finalDescription.trim()) {
// MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
// return;
// }
// let requestData: {
// assignmentExpertId: any;
// 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; // ✅ Tambahkan properti ini
// } = {
// ...data,
// assignmentExpertId: detail?.id || null,
// title: finalTitle,
// description: finalDescription,
// htmlDescription: finalDescription,
// fileTypeId: fileTypeId,
// categoryId: "235",
// subCategoryId: "171",
// uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
// statusId: "1",
// publishedFor: "7",
// creatorName: "Penugasan-Ta",
// tags: "penugasan-Ta",
// isYoutube: false,
// isInternationalMedia: false,
// };
// let id = Cookies.get("idCreate");
// if (scheduleId !== undefined) {
// requestData.attachFromScheduleId = Number(scheduleId); // ✅ Tambahkan nilai ini
// }
// 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;
// // Upload Thumbnail
// loading();
// if (imageFiles?.length == 0) {
// setIsImageUploadFinish(true);
// }
// imageFiles?.map(async (item: any, index: number) => {
// await uploadResumableFile(index, String(id), item, "1", "0");
// });
// if (videoFiles?.length == 0) {
// setIsVideoUploadFinish(true);
// }
// videoFiles?.map(async (item: any, index: number) => {
// await uploadResumableFile(index, String(id), item, "2", "0");
// });
// if (textFiles?.length == 0) {
// setIsTextUploadFinish(true);
// }
// textFiles?.map(async (item: any, index: number) => {
// await uploadResumableFile(index, String(id), item, "3", "0");
// });
// if (audioFiles?.length == 0) {
// setIsAudioUploadFinish(true);
// }
// audioFiles.map(async (item: FileWithPreview, index: number) => {
// await uploadResumableFile(
// index,
// String(id),
// item, // Use .file to access the actual File object
// "4",
// "0" // Optional: Replace with actual duration if available
// );
// });
// }
// };
const save = async (data: ImageSchema) => { const save = async (data: ImageSchema) => {
loading(); loading();
const finalTags = tags.join(", "); const finalTags = tags.join(", ");
const finalTitle = data.title || detail?.title || ""; const finalTitle = isSwitchOn ? title : data.title;
const finalDescription = articleBody || data.description; const finalDescription = articleBody || data.description;
if (!finalDescription.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return;
}
let requestData: { let requestData: {
assignmentExpertId: any; assignmentExpertId: any;
title: string; title: string;
@ -414,53 +362,74 @@ export default function FormTaskTaNew() {
tags: string; tags: string;
isYoutube: boolean; isYoutube: boolean;
isInternationalMedia: boolean; isInternationalMedia: boolean;
attachFromScheduleId?: number; attachFromScheduleId?: number; // ✅ Tambahkan properti ini
} = { } = {
...data,
assignmentExpertId: detail?.id || null, assignmentExpertId: detail?.id || null,
title: finalTitle, title: finalTitle,
description: finalDescription, description: finalDescription,
htmlDescription: finalDescription, htmlDescription: finalDescription,
fileTypeId: fileTypeId, fileTypeId: fileTypeId,
categoryId: "171", categoryId: "235",
subCategoryId: "171", subCategoryId: selectedCategory,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1", statusId: "1",
publishedFor: "7", publishedFor: publishedFor.join(","),
creatorName: "Penugasan-Ta", creatorName: "Penugasan-Ta",
tags: "penugasan-Ta", tags: "penugasan-Ta",
isYoutube: false, isYoutube: false,
isInternationalMedia: false, isInternationalMedia: false,
}; };
let id = Cookies.get("idCreate");
if (scheduleId !== undefined) { if (scheduleId !== undefined) {
requestData.attachFromScheduleId = Number(scheduleId); requestData.attachFromScheduleId = Number(scheduleId); // ✅ Tambahkan nilai ini
} }
// SELALU create baru if (id == undefined) {
const response = await createMedia(requestData); const response = await createMedia(requestData);
const id = String(response?.data?.data); console.log("Form Data Submitted:", requestData);
const allUploads: Promise<any>[] = []; Cookies.set("idCreate", response?.data?.data, { expires: 1 });
id = response?.data?.data;
imageFiles.forEach((file, idx) => // Upload Thumbnail
allUploads.push(uploadResumableFile(idx, id, file, "1", "0")) loading();
); if (imageFiles?.length == 0) {
setIsImageUploadFinish(true);
}
imageFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "1", "0");
});
videoFiles.forEach((file, idx) => if (videoFiles?.length == 0) {
allUploads.push(uploadResumableFile(idx, id, file, "2", "0")) setIsVideoUploadFinish(true);
); }
videoFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "2", "0");
});
textFiles.forEach((file, idx) => if (textFiles?.length == 0) {
allUploads.push(uploadResumableFile(idx, id, file, "3", "0")) setIsTextUploadFinish(true);
); }
textFiles?.map(async (item: any, index: number) => {
await uploadResumableFile(index, String(id), item, "3", "0");
});
audioFiles.forEach((file, idx) => if (audioFiles?.length == 0) {
allUploads.push(uploadResumableFile(idx, id, file, "4", "0")) setIsAudioUploadFinish(true);
); }
audioFiles.map(async (item: FileWithPreview, index: number) => {
await Promise.all(allUploads); await uploadResumableFile(
index,
successSubmit("/in/contributor/task-ta"); String(id),
item, // Use .file to access the actual File object
"4",
"0" // Optional: Replace with actual duration if available
);
});
}
}; };
const onSubmit = (data: ImageSchema) => { const onSubmit = (data: ImageSchema) => {
@ -479,117 +448,76 @@ export default function FormTaskTaNew() {
}); });
}; };
// async function uploadResumableFile( async function uploadResumableFile(
// idx: number,
// id: string,
// file: any,
// fileTypeId: string,
// duration: string
// ) {
// console.log(idx, id, file, fileTypeId, duration);
// const resCsrf = await getCsrfToken();
// const csrfToken = resCsrf?.data?.token;
// const headers = {
// "X-XSRF-TOKEN": csrfToken,
// };
// const upload = new Upload(file, {
// endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
// headers: headers,
// retryDelays: [0, 3000, 6000, 12_000, 24_000],
// chunkSize: 20_000,
// metadata: {
// mediaid: id,
// filename: file.name,
// contentType: file.type,
// fileTypeId: fileTypeId,
// duration,
// isWatermark: "true",
// },
// onBeforeRequest: function (req) {
// var xhr = req.getUnderlyingObject();
// xhr.withCredentials = true;
// },
// onError: async (e: any) => {
// console.log("Error upload :", e);
// error(e);
// },
// onChunkComplete: (
// chunkSize: any,
// bytesAccepted: any,
// bytesTotal: any
// ) => {
// // const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// // progressInfo[idx].percentage = uploadPersen;
// // counterUpdateProgress++;
// // console.log(counterUpdateProgress);
// // setProgressList(progressInfo);
// // setCounterProgress(counterUpdateProgress);
// },
// onSuccess: async () => {
// // uploadPersen = 100;
// // progressInfo[idx].percentage = 100;
// // counterUpdateProgress++;
// // setCounterProgress(counterUpdateProgress);
// successTodo();
// if (fileTypeId == "1") {
// setIsImageUploadFinish(true);
// } else if (fileTypeId == "2") {
// setIsVideoUploadFinish(true);
// }
// if (fileTypeId == "3") {
// setIsTextUploadFinish(true);
// }
// if (fileTypeId == "4") {
// setIsAudioUploadFinish(true);
// }
// },
// });
// upload.start();
// }
function uploadResumableFile(
idx: number, idx: number,
id: string, id: string,
file: any, file: any,
fileTypeId: string, fileTypeId: string,
duration: string duration: string
) { ) {
return new Promise(async (resolve, reject) => { console.log(idx, id, file, fileTypeId, duration);
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token;
const upload = new Upload(file, { const resCsrf = await getCsrfToken();
endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`, const csrfToken = resCsrf?.data?.token;
headers: { "X-XSRF-TOKEN": csrfToken }, console.log("CSRF TOKEN : ", csrfToken);
retryDelays: [0, 3000, 6000, 12_000, 24_000], const headers = {
chunkSize: 20_000, "X-XSRF-TOKEN": csrfToken,
metadata: { };
mediaid: id,
filename: file.name,
contentType: file.type,
fileTypeId,
duration,
isWatermark: "true",
},
onBeforeRequest: function (req) {
req.getUnderlyingObject().withCredentials = true;
},
onError: (e) => { const upload = new Upload(file, {
console.log("Error upload:", e); endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
reject(e); headers: headers,
}, retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
onSuccess: () => { metadata: {
resolve(true); mediaid: id,
}, filename: file.name,
}); contentType: file.type,
fileTypeId: fileTypeId,
upload.start(); duration,
isWatermark: "true",
},
onBeforeRequest: function (req) {
var xhr = req.getUnderlyingObject();
xhr.withCredentials = true;
},
onError: async (e: any) => {
console.log("Error upload :", e);
error(e);
},
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
) => {
// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
// progressInfo[idx].percentage = uploadPersen;
// counterUpdateProgress++;
// console.log(counterUpdateProgress);
// setProgressList(progressInfo);
// setCounterProgress(counterUpdateProgress);
},
onSuccess: async () => {
// uploadPersen = 100;
// progressInfo[idx].percentage = 100;
// counterUpdateProgress++;
// setCounterProgress(counterUpdateProgress);
successTodo();
if (fileTypeId == "1") {
setIsImageUploadFinish(true);
} else if (fileTypeId == "2") {
setIsVideoUploadFinish(true);
}
if (fileTypeId == "3") {
setIsTextUploadFinish(true);
}
if (fileTypeId == "4") {
setIsAudioUploadFinish(true);
}
},
}); });
upload.start();
} }
useEffect(() => { useEffect(() => {
@ -734,7 +662,8 @@ export default function FormTaskTaNew() {
size="md" size="md"
type="text" type="text"
defaultValue={detail?.title} defaultValue={detail?.title}
{...field} onChange={field.onChange}
placeholder="Enter Title"
/> />
)} )}
/> />
@ -747,11 +676,7 @@ export default function FormTaskTaNew() {
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center gap-3"> <div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center gap-3">
<div className=" space-y-2 w-3/12"> <div className=" space-y-2 w-3/12">
<Label> <Label>{t("assigment-type", { defaultValue: "Assigment Type" })}</Label>
{t("assigment-type", {
defaultValue: "Assigment Type",
})}
</Label>
<Select onValueChange={(val) => setFileTypeId(val)}> <Select onValueChange={(val) => setFileTypeId(val)}>
<SelectTrigger size="md"> <SelectTrigger size="md">
<SelectValue placeholder="Choose" /> <SelectValue placeholder="Choose" />
@ -792,9 +717,7 @@ export default function FormTaskTaNew() {
</div> </div>
<div className="py-3 space-y-2"> <div className="py-3 space-y-2">
<Label> <Label>{t("description", { defaultValue: "Description" })}</Label>
{t("description", { defaultValue: "Description" })}
</Label>
<Controller <Controller
control={control} control={control}
name="description" name="description"
@ -820,21 +743,13 @@ export default function FormTaskTaNew() {
)} )}
</div> </div>
<div className="space-y-2.5 mt-5"> <div className="space-y-2.5 mt-5">
<Label htmlFor="attachments"> <Label htmlFor="attachments">{t("attachment", { defaultValue: "Attachment" })}</Label>
{t("attachment", { defaultValue: "Attachment" })}
</Label>
<div className="space-y-3"> <div className="space-y-3">
{fileTypeId === "2" && ( {fileTypeId === "2" && (
<div> <div>
<Label> <Label>{t("audio-visual", { defaultValue: "Audio Visual" })}</Label>
{t("audio-visual", {
defaultValue: "Audio Visual",
})}
</Label>
<FileUploader <FileUploader
accept={{ accept={{ "mp4/*": [], "mov/*": [] }}
"video/*": [], // menerima mp4, mov, mkv, avi, dll (lebih aman)
}}
maxSize={100} maxSize={100}
label="Upload file dengan format .mp4 atau .mov." label="Upload file dengan format .mp4 atau .mov."
onDrop={(files) => setVideoFiles(files)} onDrop={(files) => setVideoFiles(files)}
@ -858,9 +773,7 @@ export default function FormTaskTaNew() {
<div className="space-y-2"> <div className="space-y-2">
<Label>{t("text", { defaultValue: "Text" })}</Label> <Label>{t("text", { defaultValue: "Text" })}</Label>
<FileUploader <FileUploader
accept={{ accept={{ "pdf/*": [] }}
"application/pdf": [], // lebih presisi
}}
maxSize={100} maxSize={100}
label="Upload file dengan format .pdf." label="Upload file dengan format .pdf."
onDrop={(files) => setTextFiles(files)} onDrop={(files) => setTextFiles(files)}
@ -881,9 +794,7 @@ export default function FormTaskTaNew() {
downloadFileExtension="webm" downloadFileExtension="webm"
/> />
<FileUploader <FileUploader
accept={{ accept={{ "mp3/*": [], "wav/*": [] }}
"audio/*": [], // menerima mp3, wav, webm (untuk hasil rekaman)
}}
maxSize={100} maxSize={100}
label="Upload file dengan format .mp3 atau .wav." label="Upload file dengan format .mp3 atau .wav."
onDrop={(files) => onDrop={(files) =>
@ -900,9 +811,7 @@ export default function FormTaskTaNew() {
key={idx} key={idx}
className="flex flex-row justify-between items-center" className="flex flex-row justify-between items-center"
> >
<p> <p>{t("voice-note", { defaultValue: "Voice Note" })}</p>
{t("voice-note", { defaultValue: "Voice Note" })}
</p>
<Button <Button
type="button" type="button"
onClick={() => handleDeleteAudio(idx)} onClick={() => handleDeleteAudio(idx)}
@ -928,7 +837,7 @@ export default function FormTaskTaNew() {
</Button> </Button>
</div> </div>
<div className="mt-4"> <div className="mt-4">
<Link href={"/contributor/task-ta"}> <Link href={"/contributor/content/image"}>
<Button type="submit" color="primary" variant="outline"> <Button type="submit" color="primary" variant="outline">
{t("cancel", { defaultValue: "Cancel" })} {t("cancel", { defaultValue: "Cancel" })}
</Button> </Button>

View File

@ -811,27 +811,12 @@ export default function FormTaskDetail() {
// getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel // getColumn("judul")?.setFilterValue(e.target.value); // Set filter tabel
}; };
const getViewerUrl = (url: string) => {
const ext = url.split(".").pop()?.toLowerCase();
if (ext === "pdf") {
return url; // langsung tampilkan PDF
}
// doc / docx → pakai Google Viewer, lebih compatible
return `https://docs.google.com/viewer?url=${encodeURIComponent(
url
)}&embedded=true`;
};
return ( return (
<Card> <Card>
{detail !== undefined ? ( {detail !== undefined ? (
<div className="px-6 py-6"> <div className="px-6 py-6">
<div className="flex flex-col sm:flex-row lg:flex-row justify-between"> <div className="flex flex-col sm:flex-row lg:flex-row justify-between">
<p className="text-lg font-semibold mb-3"> <p className="text-lg font-semibold mb-3">{t("detail-task", { defaultValue: "Detail Task" })}</p>
{t("detail-task", { defaultValue: "Detail Task" })}
</p>
<div <div
className="flex gap-3" className="flex gap-3"
style={ style={
@ -849,8 +834,7 @@ export default function FormTaskDetail() {
color="primary" color="primary"
onClick={() => setModalType("terkirim")} onClick={() => setModalType("terkirim")}
> >
{sentAcceptance?.length}{" "} {sentAcceptance?.length} {t("sent", { defaultValue: "Sent" })}
{t("sent", { defaultValue: "Sent" })}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
@ -860,17 +844,14 @@ export default function FormTaskDetail() {
onClick={() => setModalType("diterima")} onClick={() => setModalType("diterima")}
className="ml-3" className="ml-3"
> >
{acceptAcceptance?.length}{" "} {acceptAcceptance?.length} {t("accepted", { defaultValue: "Accepted" })}
{t("accepted", { defaultValue: "Accepted" })}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[500px] lg:max-w-[1500px] overflow-y-auto max-h-[500px]">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{t("assignment-status-details", { {t("assignment-status-details", { defaultValue: "Assignment Status Details" })}
defaultValue: "Assignment Status Details",
})}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
@ -885,9 +866,7 @@ export default function FormTaskDetail() {
<form> <form>
<div className="gap-5 mb-5"> <div className="gap-5 mb-5">
<div className="space-y-2"> <div className="space-y-2">
<Label> <Label>{t("unique-code", { defaultValue: "Unique Code" })}</Label>
{t("unique-code", { defaultValue: "Unique Code" })}
</Label>
<Controller <Controller
control={control} control={control}
name="uniqueCode" name="uniqueCode"
@ -924,11 +903,7 @@ export default function FormTaskDetail() {
</div> </div>
<div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center"> <div className="flex flex-col sm:flex-row lg:flex-row sm:items-center lg:items-center">
<div className="mt-6 space-y-2"> <div className="mt-6 space-y-2">
<Label> <Label>{t("assignment-selection", { defaultValue: "Assignment Selection" })}</Label>
{t("assignment-selection", {
defaultValue: "Assignment Selection",
})}
</Label>
<Select <Select
onValueChange={setSelectedTarget} onValueChange={setSelectedTarget}
value={detail.assignedToRole} value={detail.assignedToRole}
@ -1065,9 +1040,7 @@ export default function FormTaskDetail() {
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6 space-y-2"> <div className="mt-6 space-y-2">
<Label> <Label>{t("assigment-type", { defaultValue: "Assigment Type" })} </Label>
{t("assigment-type", { defaultValue: "Assigment Type" })}{" "}
</Label>
<RadioGroup <RadioGroup
value={detail.taskType.toString()} value={detail.taskType.toString()}
onValueChange={(value) => setTaskType(String(value))} onValueChange={(value) => setTaskType(String(value))}
@ -1081,9 +1054,7 @@ export default function FormTaskDetail() {
</div> </div>
{/* RadioGroup Assignment Category */} {/* RadioGroup Assignment Category */}
<div className="mt-6 space-y-2"> <div className="mt-6 space-y-2">
<Label> <Label>{t("type-of-task", { defaultValue: "Type Of Task" })}</Label>
{t("type-of-task", { defaultValue: "Type Of Task" })}
</Label>
<RadioGroup <RadioGroup
value={detail.assignmentType.id.toString()} value={detail.assignmentType.id.toString()}
onValueChange={(value) => setType(value)} onValueChange={(value) => setType(value)}
@ -1104,9 +1075,7 @@ export default function FormTaskDetail() {
</RadioGroup> </RadioGroup>
</div> </div>
<div className="mt-6 space-y-2"> <div className="mt-6 space-y-2">
<Label> <Label>{t("output-task", { defaultValue: "Output Task" })}</Label>
{t("output-task", { defaultValue: "Output Task" })}
</Label>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{Object.keys(taskOutput).map((key) => ( {Object.keys(taskOutput).map((key) => (
<div className="flex items-center gap-2" key={key}> <div className="flex items-center gap-2" key={key}>
@ -1127,9 +1096,7 @@ export default function FormTaskDetail() {
</div> </div>
<div className="mt-6 space-y-2"> <div className="mt-6 space-y-2">
<Label> <Label>{t("description", { defaultValue: "Description" })}</Label>
{t("description", { defaultValue: "Description" })}
</Label>
<Controller <Controller
control={control} control={control}
name="naration" name="naration"
@ -1144,15 +1111,11 @@ export default function FormTaskDetail() {
)} */} )} */}
</div> </div>
<div className=" mt-5 space-y-2"> <div className=" mt-5 space-y-2">
<Label htmlFor="attachment"> <Label htmlFor="attachment">{t("attachment", { defaultValue: "Attachment" })}</Label>
{t("attachment", { defaultValue: "Attachment" })}
</Label>
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
{videoUploadedFiles?.length > 0 && ( {videoUploadedFiles?.length > 0 && (
<Label> <Label>{t("audio-visual", { defaultValue: "Audio Visual" })}</Label>
{t("audio-visual", { defaultValue: "Audio Visual" })}
</Label>
)} )}
<div> <div>
{selectedVideo && ( {selectedVideo && (
@ -1253,16 +1216,12 @@ export default function FormTaskDetail() {
<div> <div>
{selectedText && ( {selectedText && (
<Card className="mt-2"> <Card className="mt-2">
{/* <iframe <iframe
className="w-full h-96 rounded-md" className="w-full h-96 rounded-md"
src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent( src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
selectedText selectedText
)}`} )}`}
title={"Document"} title={"Document"}
/> */}
<iframe
className="w-full h-96"
src={getViewerUrl(selectedText)}
/> />
</Card> </Card>
)} )}

View File

@ -338,48 +338,22 @@ export default function FormTask() {
.map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping]) .map((key) => fileTypeMapping[key as keyof typeof fileTypeMapping])
.join(","); .join(",");
let roleTargets = selectedTarget
? selectedTarget.split(",").map((x) => Number(x))
: [];
if (!roleTargets.includes(11)) {
roleTargets.push(11);
}
const finalAssignedToRole = roleTargets.join(",");
const requestData = { const requestData = {
...data, ...data,
assignedToRole: finalAssignedToRole,
assignedToLevel: handlePoldaPolresChange(), assignedToLevel: handlePoldaPolresChange(),
assignmentPurpose: assignmentPurposeString, assignmentPurpose: assignmentPurposeString,
assignedToRole: selectedTarget,
taskType, taskType,
broadcastType, broadcastType,
assignmentMainTypeId: mainType, assignmentMainTypeId: mainType,
fileTypeOutput: selectedOutputs,
assignmentTypeId: type, assignmentTypeId: type,
fileTypeOutput: selectedOutputs,
narration: data.naration, narration: data.naration,
platformType: "",
title: data.title, title: data.title,
attachmentUrl: links, attachmentUrl: links,
platformType: "",
}; };
// const requestData = {
// ...data,
// assignedToLevel: handlePoldaPolresChange(),
// assignmentPurpose: assignmentPurposeString,
// assignedToRole: selectedTarget,
// taskType,
// broadcastType,
// assignmentMainTypeId: mainType,
// assignmentTypeId: type,
// fileTypeOutput: selectedOutputs,
// narration: data.naration,
// platformType: "",
// title: data.title,
// attachmentUrl: links,
// };
const response = await createTask(requestData); const response = await createTask(requestData);
const id = response?.data?.data.id; const id = response?.data?.data.id;

View File

@ -80,8 +80,7 @@
// }; // };
// export default AdvertisementPlacements; // export default AdvertisementPlacements;
"use client"; import { listDataAdvertisements } from "@/service/broadcast/broadcast";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import * as React from "react"; import * as React from "react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
@ -94,40 +93,35 @@ interface Advertisement {
[key: string]: any; [key: string]: any;
} }
interface AdvertisementPlacementsProps { const AdvertisementPlacements = (props: {
placement: string; placement: string;
data?: Advertisement[]; // ⬅️ PENTING: optional data: Advertisement[];
loading: boolean; loading: boolean;
} }) => {
const [ads, setAds] = useState<Advertisement[] | undefined[]>([]);
const AdvertisementPlacements = ({
placement,
data = [], // ⬅️ DEFAULT VALUE (KUNCI UTAMA)
loading,
}: AdvertisementPlacementsProps) => {
const [ads, setAds] = useState<(Advertisement | undefined)[]>([]);
useEffect(() => { useEffect(() => {
if (loading || data.length === 0) return; if (!props.loading && props.data.length > 0) {
const filtered = props.data.filter((a) =>
a.placements.includes(props.placement)
);
const filtered = data.filter((a) => a.placements?.includes(placement)); let temps: Advertisement[] | undefined[] = [];
temps[0] = filtered.find((b) => b.placements.includes("top"));
const temps: (Advertisement | undefined)[] = []; temps[1] = filtered.find((b) => b.placements.includes("bottom"));
temps[0] = filtered.find((b) => b.placements?.includes("top")); setAds(temps);
temps[1] = filtered.find((b) => b.placements?.includes("bottom")); }
}, [props.data, props.loading, props.placement]);
setAds(temps);
}, [data, loading, placement]);
return ( return (
<div <div
className={`sticky top-0 space-y-4 ${ className={`sticky top-0 space-y-4 ${
placement === "left" ? "ml-14" : "mr-14" props.placement === "left" ? "ml-14" : "mr-14"
}`} }`}
> >
{loading && <p className="text-sm text-gray-500">Loading...</p>} {props.loading && <p className="text-sm text-gray-500">Loading...</p>}
{ads.map( {ads?.map(
(ad) => (ad) =>
ad && ( ad && (
<Dialog key={ad.id}> <Dialog key={ad.id}>
@ -138,7 +132,6 @@ const AdvertisementPlacements = ({
className="w-full cursor-pointer rounded-lg shadow-md hover:opacity-90 transition" className="w-full cursor-pointer rounded-lg shadow-md hover:opacity-90 transition"
/> />
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-w-4xl p-0 bg-transparent border-0 shadow-none"> <DialogContent className="max-w-4xl p-0 bg-transparent border-0 shadow-none">
<img <img
src={ad.contentFileUrl || ad.imageUrl} src={ad.contentFileUrl || ad.imageUrl}

View File

@ -1,10 +1,22 @@
import { getPublicCategoryDataNew } from "@/service/landing/landing"; import {
getCategoryData,
getPublicCategoryData,
getPublicCategoryDataNew,
} from "@/service/landing/landing";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Reveal } from "./Reveal"; import { Reveal } from "./Reveal";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { Link, useRouter } from "@/i18n/routing"; import Image from "next/image";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "../ui/carousel";
import { useRouter } from "@/i18n/routing";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import ImageBlurry from "../ui/image-blurry"; import ImageBlurry from "../ui/image-blurry";
@ -94,10 +106,9 @@ const ContentCategory = (props: { group?: string; type: string }) => {
(category: any) => ( (category: any) => (
<div key={category?.id}> <div key={category?.id}>
<div <div
// href={prefixPath + `/all/filter?category=${category?.id}`}
onClick={() => onClick={() =>
router.push( router.push(
`${prefixPath + `/all/filter?category=${category?.id}`}` `${prefixPath}all/filter?category=${category?.id}`
) )
} }
className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg block" className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg block"

View File

@ -288,18 +288,9 @@ export default function FilterAudioComponent(props: {
{/* Caption */} {/* Caption */}
<div className="p-4"> <div className="p-4">
<div className="flex flex-row justify-between"> <p className="text-[12px] font-bold text-[#bb3523] uppercase mb-1">
<p className="text-[12px] font-bold text-[#bb3523] uppercase mb-1"> {audio?.categoryName?.toUpperCase() ?? "GIAT PIMPINAN"}
{audio?.categoryName?.toUpperCase() ?? "GIAT PIMPINAN"} </p>
</p>
<p className="flex flex-row items-center text-[10px] gap-2">
{formatDateToIndonesian(new Date(audio?.createdAt))}{" "}
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{audio?.clickCount}{" "}
</p>
</div>
<p className="text-xl font-semibold text-black dark:text-white line-clamp-4"> <p className="text-xl font-semibold text-black dark:text-white line-clamp-4">
{audio?.title} {audio?.title}

View File

@ -259,18 +259,9 @@ export default function FilterDocumentComponent(props: {
{/* Konten bawah */} {/* Konten bawah */}
<div className="p-4 flex flex-col gap-2"> <div className="p-4 flex flex-col gap-2">
<div className="flex flex-row justify-between"> {/* Kategori merah */}
{/* Kategori merah */} <div className="text-[12px] font-bold text-red-600 uppercase">
<div className="text-[12px] font-bold text-red-600 uppercase"> {text?.categoryName?.toUpperCase() ?? "Text"}
{text?.categoryName?.toUpperCase() ?? "Text"}
</div>
<p className="flex flex-row items-center text-[10px] gap-2">
{formatDateToIndonesian(new Date(text?.createdAt))}{" "}
{text?.timezone ? text?.timezone : "WIB"} |{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{text?.clickCount}{" "}
</p>
</div> </div>
{/* Judul */} {/* Judul */}

View File

@ -6,7 +6,6 @@ import {
CarouselNext, CarouselNext,
CarouselPrevious, CarouselPrevious,
} from "@/components/ui/carousel"; } from "@/components/ui/carousel";
import { Icon } from "@/components/ui/icon";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import { Link, usePathname, useRouter } from "@/i18n/routing"; import { Link, usePathname, useRouter } from "@/i18n/routing";
import { listData, listDataRegional } from "@/service/landing/landing"; import { listData, listDataRegional } from "@/service/landing/landing";
@ -260,18 +259,9 @@ export default function FilterImageComponent(props: {
{/* Caption section */} {/* Caption section */}
<div className="p-4 h-full flex flex-col justify-between"> <div className="p-4 h-full flex flex-col justify-between">
<div className="flex flex-col gap-1 flex-grow"> <div className="flex flex-col gap-1 flex-grow">
<div className="flex flex-row justify-between"> <p className="text-[10px] font-bold text-[#bb3523] uppercase">
<p className="text-[10px] font-bold text-[#bb3523] uppercase"> {image?.categoryName?.toUpperCase() ?? "Giat Pimpinan"}
{image?.categoryName?.toUpperCase() ?? "Giat Pimpinan"} </p>
</p>
<p className="flex flex-row items-center text-[10px] gap-2">
{formatDateToIndonesian(new Date(image?.createdAt))}{" "}
{image?.timezone ? image?.timezone : "WIB"}{" "}|{" "}
<Icon icon="formkit:eye" width="15" height="15" />{" "}
{image?.clickCount}{" "}
</p>
</div>
<p <p
className=" className="
text-sm lg:text-base font-semibold text-black dark:text-white text-sm lg:text-base font-semibold text-black dark:text-white

View File

@ -234,18 +234,9 @@ export default function FilterVideoComponent(props: {
{/* Caption section */} {/* Caption section */}
<div className="p-4 h-full flex flex-col justify-between"> <div className="p-4 h-full flex flex-col justify-between">
<div className="flex flex-col gap-1 flex-grow"> <div className="flex flex-col gap-1 flex-grow">
<div className="flex flex-row justify-between"> <p className="text-[10px] font-bold text-[#bb3523] uppercase">
<p className="text-[10px] font-bold text-[#bb3523] uppercase"> {video?.categoryName?.toUpperCase() ?? "Giat Pimpinan"}
{video?.categoryName?.toUpperCase() ?? "Giat Pimpinan"} </p>
</p>
<p className="flex flex-row items-center text-[10px] gap-2">
{formatDateToIndonesian(new Date(video?.createdAt))}{" "}
{video?.timezone ? 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-5"> <p className="text-sm lg:text-base font-semibold text-black dark:text-white line-clamp-5">
{video?.title} {video?.title}
</p> </p>

View File

@ -63,7 +63,7 @@ type HeroModalProps = {
// useEffect(() => { // useEffect(() => {
// async function fetchCategories() { // async function fetchCategories() {
// const url = "https://new.netidhub.com/api/csrf"; // const url = "https://netidhub.com/api/csrf";
// try { // try {
// const response = await fetch(url); // const response = await fetch(url);

View File

@ -50,7 +50,7 @@ const HeroModal = ({ onClose }: { onClose: () => void }) => {
useEffect(() => { useEffect(() => {
async function fetchCategories() { async function fetchCategories() {
const url = "https://new.netidhub.com/api/csrf"; const url = "https://netidhub.com/api/csrf";
try { try {
const response = await fetch(url); const response = await fetch(url);
@ -268,7 +268,7 @@ const Hero = (props: { group?: string }) => {
useEffect(() => { useEffect(() => {
async function fetchCategories() { async function fetchCategories() {
const url = "https://new.netidhub.com/api/csrf"; const url = "https://netidhub.com/api/csrf";
try { try {
const response = await fetch(url); const response = await fetch(url);

View File

@ -209,7 +209,7 @@ const Navbar = () => {
<div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50"> <div className="bg-[#f7f7f7] dark:bg-black shadow-md sticky top-0 z-50">
<div className="flex items-center justify-between px-4 lg:px-16 py-2 gap-3"> <div className="flex items-center justify-between px-4 lg:px-16 py-2 gap-3">
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<Link href="/" className="flex items-center"> <Link href={prefixPath} className="flex items-center">
<Image <Image
priority={true} priority={true}
src="/assets/mediahub-logo.gif" src="/assets/mediahub-logo.gif"

View File

@ -83,7 +83,7 @@ const NewContent = (props: { group: string; type: string }) => {
? "1" ? "1"
: selectedTab == "video" : selectedTab == "video"
? "2" ? "2"
: selectedTab == "document" : selectedTab == "text"
? "3" ? "3"
: selectedTab == "audio" : selectedTab == "audio"
? "4" ? "4"

View File

@ -36,7 +36,7 @@ const ScrollableContentPolda = () => {
useEffect(() => { useEffect(() => {
async function fetchCategories() { async function fetchCategories() {
const url = "https://new.netidhub.com/api/csrf"; const url = "https://netidhub.com/api/csrf";
try { try {
const response = await fetch(url); const response = await fetch(url);

View File

@ -35,7 +35,7 @@ const ScrollableContentSatker = () => {
: ""; : "";
useEffect(() => { useEffect(() => {
async function fetchCategories() { async function fetchCategories() {
const url = "https://new.netidhub.com/api/csrf"; const url = "https://netidhub.com/api/csrf";
try { try {
const response = await fetch(url); const response = await fetch(url);

View File

@ -39,7 +39,7 @@ const ScrollableContent = () => {
useEffect(() => { useEffect(() => {
async function fetchCategories() { async function fetchCategories() {
const url = "https://new.netidhub.com/api/csrf"; const url = "https://netidhub.com/api/csrf";
try { try {
const response = await fetch(url); const response = await fetch(url);

View File

@ -247,16 +247,7 @@ const DetailAudio = () => {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = const contentType =
xhr.getResponseHeader("content-type") || "application/octet-stream"; xhr.getResponseHeader("content-type") || "application/octet-stream";
let extension = contentType.split("/")[1]; const extension = contentType.split("/")[1];
if (selectedSize === "MP3") {
extension = "mp3";
}
if (selectedSize === "WAV") {
extension = "wav";
}
const filename = `${name}.${extension}`; const filename = `${name}.${extension}`;
const blob = new Blob([xhr.response], { const blob = new Blob([xhr.response], {
@ -892,7 +883,7 @@ const DetailAudio = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"fb", "fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}` `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
) )
} }
> >
@ -907,7 +898,7 @@ const DetailAudio = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"tw", "tw",
`https://twitter.com/share?url=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}` `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
) )
} }
> >
@ -922,7 +913,7 @@ const DetailAudio = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"wa", "wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}` `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
) )
} }
> >

View File

@ -247,19 +247,7 @@ const DetailDocument = () => {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
const contentType = const contentType =
xhr.getResponseHeader("content-type") || "application/octet-stream"; xhr.getResponseHeader("content-type") || "application/octet-stream";
let extension = contentType.split("/")[1]; const extension = contentType.split("/")[1];
if (selectedSize === "DOC") {
extension = "doc";
}
if (selectedSize === "PPT") {
extension = "ppt";
}
if (selectedSize === "PDF") {
extension = "pdf";
}
const filename = `${name}.${extension}`; const filename = `${name}.${extension}`;
const blob = new Blob([xhr.response], { const blob = new Blob([xhr.response], {
@ -742,7 +730,7 @@ const DetailDocument = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"fb", "fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}` `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
) )
} }
> >
@ -757,7 +745,7 @@ const DetailDocument = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"tw", "tw",
`https://twitter.com/share?url=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}` `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
) )
} }
> >
@ -772,7 +760,7 @@ const DetailDocument = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"wa", "wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}` `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
) )
} }
> >

View File

@ -891,7 +891,7 @@ const DetailImage = (data: any) => {
onClick={() => onClick={() =>
handleShare( handleShare(
"fb", "fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}` `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
) )
} }
> >
@ -906,7 +906,7 @@ const DetailImage = (data: any) => {
onClick={() => onClick={() =>
handleShare( handleShare(
"tw", "tw",
`https://twitter.com/share?url=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}` `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
) )
} }
> >
@ -921,7 +921,7 @@ const DetailImage = (data: any) => {
onClick={() => onClick={() =>
handleShare( handleShare(
"wa", "wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}` `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
) )
} }
> >

View File

@ -711,7 +711,7 @@ const DetailVideo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"fb", "fb",
`https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}` `https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&quote=${content?.title}`
) )
} }
> >
@ -726,7 +726,7 @@ const DetailVideo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"tw", "tw",
`https://twitter.com/share?url=https%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}` `https://twitter.com/share?url=https%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}&text=${content?.title}`
) )
} }
> >
@ -741,7 +741,7 @@ const DetailVideo = () => {
onClick={() => onClick={() =>
handleShare( handleShare(
"wa", "wa",
`text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fnew.netidhub.com%2F${typeString}%2Fdetail%2F${content?.id}` `text=${content?.title}%0D%0A%0D%0Ahttps%3A%2F%2Fmediahub.polri.go.id%2F${typeString}%2Fdetail%2F${content?.id}`
) )
} }
> >

View File

@ -31,7 +31,7 @@ const DashCodeFooter = () => {
> >
<div className="h-[50px] w-[50px] rounded-full relative left-[0px] top-[0px] custom-dropshadow"> <div className="h-[50px] w-[50px] rounded-full relative left-[0px] top-[0px] custom-dropshadow">
<Image <Image
src={"https://new.netidhub.com/assets/img/user-avatar.png"} src={"https://netidhub.com/assets/img/user-avatar.png"}
alt={"Image"} alt={"Image"}
width={50} width={50}
height={50} height={50}

View File

@ -1,111 +1,64 @@
"use client"; 'use client';
import React from "react"; import React from 'react'
import { Link, usePathname, useRouter } from "@/components/navigation"; import { Link, usePathname, useRouter } from "@/components/navigation";
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbEllipsis,
BreadcrumbLink, BreadcrumbItem,
BreadcrumbList, BreadcrumbLink,
BreadcrumbSeparator, BreadcrumbList,
} from "@/components/ui/breadcrumb"; BreadcrumbPage,
import { Icon } from "@/components/ui/icon"; BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Icon } from "@/components/ui/icon"
import { ReactNode } from "react"; import { ReactNode } from "react";
const SiteBreadcrumb = ({ children }: { children?: ReactNode }) => { const SiteBreadcrumb = ({ children }: { children?: ReactNode }) => {
const router = useRouter(); const location = usePathname();
const locations = location.split('/').filter(path => path)
return (
<div className="flex justify-between gap-3 items-center mb-6">
<div className="flex-1">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<Link href="/dashboard">
<Icon icon="heroicons:home" className="h-5 w-5" />
</Link>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem> return (
<button <div className="flex justify-between gap-3 items-center mb-6">
onClick={() => router.back()} <div className="flex-1">
className="capitalize flex items-center gap-1" <Breadcrumb>
> <BreadcrumbList>
Back
<BreadcrumbSeparator />
</button>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
<div className="flex-none flex gap-2">{children}</div> <BreadcrumbItem
</div> >
); <Link href="/dashboard">
<Icon icon="heroicons:home" className=" h-5 w-5" />
</Link>
</BreadcrumbItem>
<BreadcrumbSeparator />
{
locations.map((link, index) => {
let href = `/${locations.slice(0, index + 1).join('/')}`
let itemLink = link
const isLast = index === locations.length - 1;
return (
<React.Fragment key={index}>
<BreadcrumbItem className=' capitalize' >
{isLast ? (
itemLink?.replaceAll("-", " ")
) : (
<Link href={href} >{itemLink?.replaceAll("-", "")}</Link>
)}
</BreadcrumbItem>
{locations.length !== index + 1 && <BreadcrumbSeparator />}
</React.Fragment>
)
})
}
</BreadcrumbList>
</Breadcrumb>
</div>
<div className=" flex-none flex gap-2">{children}</div>
</div>
);
}; };
export default SiteBreadcrumb; export default SiteBreadcrumb;
// 'use client';
// import React from 'react'
// import { Link, usePathname, useRouter } from "@/components/navigation";
// import {
// Breadcrumb,
// BreadcrumbEllipsis,
// BreadcrumbItem,
// BreadcrumbLink,
// BreadcrumbList,
// BreadcrumbPage,
// BreadcrumbSeparator,
// } from "@/components/ui/breadcrumb"
// import { Icon } from "@/components/ui/icon"
// import { ReactNode } from "react";
// const SiteBreadcrumb = ({ children }: { children?: ReactNode }) => {
// const location = usePathname();
// const locations = location.split('/').filter(path => path)
// return (
// <div className="flex justify-between gap-3 items-center mb-6">
// <div className="flex-1">
// <Breadcrumb>
// <BreadcrumbList>
// <BreadcrumbItem
// >
// <Link href="/dashboard">
// <Icon icon="heroicons:home" className=" h-5 w-5" />
// </Link>
// </BreadcrumbItem>
// <BreadcrumbSeparator />
// {
// locations.map((link, index) => {
// let href = `/${locations.slice(0, index + 1).join('/')}`
// let itemLink = link
// const isLast = index === locations.length - 1;
// return (
// <React.Fragment key={index}>
// <BreadcrumbItem className=' capitalize' >
// {isLast ? (
// itemLink?.replaceAll("-", " ")
// ) : (
// <Link href={href} >{itemLink?.replaceAll("-", "")}</Link>
// )}
// </BreadcrumbItem>
// {locations.length !== index + 1 && <BreadcrumbSeparator />}
// </React.Fragment>
// )
// })
// }
// </BreadcrumbList>
// </Breadcrumb>
// </div>
// <div className=" flex-none flex gap-2">{children}</div>
// </div>
// );
// };
// export default SiteBreadcrumb;

View File

@ -133,35 +133,35 @@ const columns: ColumnDef<any>[] = [
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <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"> <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
<a>Aktivasi</a> <a>Aktivasi</a>
</DropdownMenuItem> */} </DropdownMenuItem>
<Link <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
href={`/admin/management-user/external/detail/${row.original.id}`} <Link
> href={`/admin/management-user/external/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 Detail
</DropdownMenuItem> </Link>
</Link> </DropdownMenuItem>
<Link <DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
href={`/admin/management-user/external/edit/${row.original.id}`} <Link
> href={`/admin/management-user/external/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 Edit
</DropdownMenuItem> </Link>
</Link> </DropdownMenuItem>
<a <DropdownMenuItem
onClick={() => { color="red"
handleDelete(row.original.id); className="p-2 border-b text-red-500 group focus:bg-red-500 focus:text-white rounded-none"
}}
> >
<DropdownMenuItem <a
color="red" onClick={() => {
className="p-2 border-b text-red-500 group focus:bg-red-500 focus:text-white rounded-none cursor-pointer" handleDelete(row.original.id);
}}
> >
Hapus Hapus
</DropdownMenuItem> </a>
</a> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );

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