fix: tab banner and tag
This commit is contained in:
parent
74a2ab2872
commit
05301cf086
|
|
@ -1,48 +1,841 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "@/i18n/routing";
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
import UserForm from "@/components/form/user/user-form";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export default function EditUserPage() {
|
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import {
|
||||||
|
AdministrationLevelList,
|
||||||
|
getListCompetencies,
|
||||||
|
getListEducation,
|
||||||
|
getListSchools,
|
||||||
|
getUserById,
|
||||||
|
updateUserInternal,
|
||||||
|
} from "@/service/management-user/management-user";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import { getUserLevels } from "@/service/approval-workflows";
|
||||||
|
|
||||||
|
const PasswordChecklist = dynamic(() => import("react-password-checklist"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sns = [
|
||||||
|
{
|
||||||
|
key: 1,
|
||||||
|
id: "comment",
|
||||||
|
typeId: 1,
|
||||||
|
name: "Komentar Konten",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 2,
|
||||||
|
id: "fb",
|
||||||
|
typeId: 2,
|
||||||
|
name: "Facebook",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 3,
|
||||||
|
id: "ig",
|
||||||
|
typeId: 3,
|
||||||
|
name: "Instagram",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 4,
|
||||||
|
id: "twt",
|
||||||
|
typeId: 4,
|
||||||
|
name: "Twitter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 5,
|
||||||
|
id: "yt",
|
||||||
|
typeId: 5,
|
||||||
|
name: "Youtube",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 6,
|
||||||
|
id: "emergency",
|
||||||
|
typeId: 6,
|
||||||
|
name: "Emergency Issue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 7,
|
||||||
|
id: "email",
|
||||||
|
typeId: 7,
|
||||||
|
name: "Email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 8,
|
||||||
|
id: "inbox",
|
||||||
|
typeId: 8,
|
||||||
|
name: "Pesan Masuk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 9,
|
||||||
|
id: "whatsapp",
|
||||||
|
typeId: 9,
|
||||||
|
name: "Whatssapp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 10,
|
||||||
|
id: "tiktok",
|
||||||
|
typeId: 10,
|
||||||
|
name: "Tiktok",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface RoleData {
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
levelNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
level: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
fullname: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
username: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
role: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
// nrp: z.string({
|
||||||
|
// required_error: "Required",
|
||||||
|
// }),
|
||||||
|
address: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
email: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
phoneNumber: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
password: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
confirmPassword: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
isValidPassword: z.boolean().refine((val) => val === true, {
|
||||||
|
message: "Check Password",
|
||||||
|
}),
|
||||||
|
sns: z.array(z.string()).optional(),
|
||||||
|
education: z.string().optional(),
|
||||||
|
school: z.string().optional(),
|
||||||
|
competency: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function EditUserForm() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useSearchParams();
|
const params = useParams();
|
||||||
// const userId = params?.id ? Number(params.id) : undefined;
|
const id = params?.id;
|
||||||
const userIdParam = params.get("id");
|
const MySwal = withReactContent(Swal);
|
||||||
const userId = userIdParam ? Number(userIdParam) : undefined;
|
const levelName = getCookiesDecrypt("ulnae");
|
||||||
|
const [roleList, setRoleList] = useState<RoleData[]>([]);
|
||||||
|
|
||||||
const handleSuccess = () => {
|
const [userEducations, setUserEducations] = useState<any>();
|
||||||
router.push("/admin/management-user");
|
const [userSchools, setUserSchools] = useState<any>();
|
||||||
|
const [userCompetencies, setUserCompetencies] = useState<any>();
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
defaultValues: {
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
sns: [],
|
||||||
|
education: "1",
|
||||||
|
school: "4",
|
||||||
|
competency: "2",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const passwordVal = form.watch("password");
|
||||||
|
const confPasswordVal = form.watch("confirmPassword");
|
||||||
|
const selectedRole = form.watch("role");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDataAdditional();
|
||||||
|
initData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const initData = async () => {
|
||||||
|
loading();
|
||||||
|
|
||||||
|
// 1️⃣ Ambil level dulu
|
||||||
|
const levels = await initFetch();
|
||||||
|
console.log("LEVEL READY:", levels);
|
||||||
|
|
||||||
|
// 2️⃣ Ambil user
|
||||||
|
const response = await getUserById(String(id));
|
||||||
|
close();
|
||||||
|
|
||||||
|
const list = response?.data?.data;
|
||||||
|
const user = list?.find((item: any) => item.id === Number(id));
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
error("User tidak ditemukan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("RESET WITH USER:", user);
|
||||||
|
|
||||||
|
// 3️⃣ Reset SETELAH level ADA
|
||||||
|
form.reset({
|
||||||
|
fullname: user.fullname ?? "",
|
||||||
|
username: user.username ?? "",
|
||||||
|
email: user.email ?? "",
|
||||||
|
address: user.address ?? "",
|
||||||
|
phoneNumber: user.phoneNumber ?? "",
|
||||||
|
// nrp: user.identityNumber ?? "",
|
||||||
|
level: String(user.userLevelId),
|
||||||
|
role: String(user.userRoleId ?? ""),
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
sns: [],
|
||||||
|
education: "",
|
||||||
|
school: "",
|
||||||
|
competency: "",
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
// const initData = async () => {
|
||||||
router.push("/admin/management-user");
|
// console.log("PARAM ID:", id);
|
||||||
|
|
||||||
|
// loading();
|
||||||
|
// const response = await getUserById(String(id));
|
||||||
|
// const res = response?.data?.data;
|
||||||
|
// close();
|
||||||
|
// console.log("FULL RESPONSE:", response);
|
||||||
|
// console.log("RESPONSE.DATA:", response?.data);
|
||||||
|
// console.log("RESPONSE.DATA.DATA:", response?.data?.data);
|
||||||
|
// if (Number(res.roleId) > 4) {
|
||||||
|
// form.setValue("fullname", res?.fullname);
|
||||||
|
// form.setValue("username", res?.username);
|
||||||
|
// form.setValue("phoneNumber", res?.phoneNumber);
|
||||||
|
// form.setValue("nrp", res?.memberIdentity);
|
||||||
|
// form.setValue("address", res?.address);
|
||||||
|
// form.setValue("email", res?.email);
|
||||||
|
// form.setValue("role", res?.role?.code);
|
||||||
|
// form.setValue("level", String(res?.userLevelId));
|
||||||
|
// } else {
|
||||||
|
// initFetch();
|
||||||
|
// form.setValue("fullname", res?.fullname);
|
||||||
|
// form.setValue("username", res?.username);
|
||||||
|
// form.setValue("phoneNumber", res?.phoneNumber);
|
||||||
|
// form.setValue("nrp", res?.memberIdentity);
|
||||||
|
// form.setValue("address", res?.address);
|
||||||
|
// form.setValue("email", res?.email);
|
||||||
|
// form.setValue("role", res?.role?.code);
|
||||||
|
// form.setValue("level", String(res?.userLevelId));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const initFetch = async () => {
|
||||||
|
const response = await getUserLevels();
|
||||||
|
const res = response?.data?.data ?? [];
|
||||||
|
|
||||||
|
const levelsArr: RoleData[] = res.map((levels: any) => ({
|
||||||
|
id: levels.id,
|
||||||
|
label: levels.aliasName ?? levels.name,
|
||||||
|
name: levels.name,
|
||||||
|
value: String(levels.id),
|
||||||
|
levelNumber: levels.levelNumber,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setRoleList(levelsArr);
|
||||||
|
return levelsArr; // ⬅️ PENTING
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!userId) {
|
async function getDataAdditional() {
|
||||||
return (
|
const resEducations = await getListEducation();
|
||||||
<div className="container mx-auto py-6">
|
setUserEducations(resEducations?.data?.data);
|
||||||
<div className="text-center">
|
const resSchools = await getListSchools();
|
||||||
<p className="text-red-500">User ID tidak valid</p>
|
setUserSchools(resSchools?.data?.data);
|
||||||
<button
|
const resCompetencies = await getListCompetencies();
|
||||||
onClick={() => router.push("/admin/management-user")}
|
setUserCompetencies(resCompetencies?.data?.data);
|
||||||
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
}
|
||||||
>
|
|
||||||
Kembali ke Management User
|
const rawRoleId = getCookiesDecrypt("urie");
|
||||||
</button>
|
const userRoleId = rawRoleId ? Number(rawRoleId) : undefined;
|
||||||
</div>
|
|
||||||
</div>
|
if (!userRoleId || Number.isNaN(userRoleId)) {
|
||||||
);
|
error("Role user tidak valid, silakan login ulang");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles =
|
||||||
|
levelName == "MABES POLRI"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: "ADM-ID",
|
||||||
|
name: "Admin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "APP-ID",
|
||||||
|
name: "Approver",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "CON-ID",
|
||||||
|
name: "Kontributor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "SPV-ID",
|
||||||
|
name: "Supervisor Feedback Center",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "OPT-ID",
|
||||||
|
name: "Operator Feedback Center",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "KKUR-ID",
|
||||||
|
name: "Koor Kurator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "KUR-ID",
|
||||||
|
name: "Kurator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
id: "APP-ID",
|
||||||
|
name: "Approver",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "CON-ID",
|
||||||
|
name: "Kontributor",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async function save(data: z.infer<typeof FormSchema>) {
|
||||||
|
const req = {
|
||||||
|
fullname: data.fullname,
|
||||||
|
username: data.username,
|
||||||
|
email: data.email,
|
||||||
|
address: data.address,
|
||||||
|
phoneNumber: data.phoneNumber,
|
||||||
|
userLevelId: Number(data.level),
|
||||||
|
userRoleId: userRoleId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// let req: any = {
|
||||||
|
// id: Number(id),
|
||||||
|
// firstName: data.fullname,
|
||||||
|
// username: data.username,
|
||||||
|
// roleId: data.role,
|
||||||
|
// userLevelId: Number(data.level),
|
||||||
|
// memberIdentity: data.nrp,
|
||||||
|
// address: data.address,
|
||||||
|
// email: data.email,
|
||||||
|
// password: data.password,
|
||||||
|
// passwordConf: data.confirmPassword,
|
||||||
|
// isDefault: false,
|
||||||
|
// isAdmin: true,
|
||||||
|
// };
|
||||||
|
|
||||||
|
console.log("USER ROLE ID FROM COOKIE:", rawRoleId, userRoleId);
|
||||||
|
console.log("REQUEST UPDATE:", req);
|
||||||
|
|
||||||
|
loading();
|
||||||
|
const response = await updateUserInternal(Number(id), req);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
error(response.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Sukses",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Oke",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
router.push("/admin/management-user");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Simpan Data?",
|
||||||
|
text: "",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Simpan",
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
save(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto py-6">
|
<div>
|
||||||
<UserForm
|
<SiteBreadcrumb />
|
||||||
id={userId}
|
<Form {...form}>
|
||||||
mode="edit"
|
<form
|
||||||
onSuccess={handleSuccess}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
onCancel={handleCancel}
|
className="space-y-6 bg-white p-10 w-full"
|
||||||
/>
|
>
|
||||||
|
<p className="text-xl">Data User</p>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="level"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>Pilih Level</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className={cn(
|
||||||
|
"w-[400px] justify-between",
|
||||||
|
!field.value && "text-muted-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value
|
||||||
|
? roleList.find((role) => role.value === field.value)
|
||||||
|
?.label
|
||||||
|
: "Pilih level"}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[400px] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No role found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{roleList.map((role) => (
|
||||||
|
<CommandItem
|
||||||
|
value={role.label}
|
||||||
|
key={role.value}
|
||||||
|
onSelect={() => {
|
||||||
|
form.setValue("level", role.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{role.label}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto",
|
||||||
|
role.value === field.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="fullname"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nama Lengkap</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Masukkan nama lengkap"
|
||||||
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Masukkan username"
|
||||||
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* <FormField
|
||||||
|
control={form.control}
|
||||||
|
name="role"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-3">
|
||||||
|
<FormLabel>Role</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroup
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value}
|
||||||
|
className="flex flex-wrap gap-3 w-1/2"
|
||||||
|
>
|
||||||
|
{roles.map((role) => (
|
||||||
|
<FormItem
|
||||||
|
key={role.id}
|
||||||
|
className="flex items-center space-x-3 space-y-0"
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<RadioGroupItem value={role.id} />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">
|
||||||
|
{role.name}
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/> */}
|
||||||
|
{/* {selectedRole === "OPT-ID" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="sns"
|
||||||
|
render={() => (
|
||||||
|
<FormItem>
|
||||||
|
<div className="mb-4">
|
||||||
|
<FormLabel>Social Media Yang Ditangani</FormLabel>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-5 gap-2 w-1/2">
|
||||||
|
{sns.map((item) => (
|
||||||
|
<FormField
|
||||||
|
key={item.id}
|
||||||
|
control={form.control}
|
||||||
|
name="sns"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem
|
||||||
|
key={item.typeId}
|
||||||
|
className="flex flex-row items-start space-x-3 space-y-0"
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value?.includes(
|
||||||
|
String(item.typeId),
|
||||||
|
)}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
return checked
|
||||||
|
? field.onChange([
|
||||||
|
...(field.value || []),
|
||||||
|
String(item.typeId),
|
||||||
|
])
|
||||||
|
: field.onChange(
|
||||||
|
(field.value || []).filter(
|
||||||
|
(value) =>
|
||||||
|
value !== String(item.typeId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel className="font-normal">
|
||||||
|
{item.name}
|
||||||
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedRole === "KUR-ID" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="education"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Pendidikan Terakhir</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{userEducations?.map((edu: any) => (
|
||||||
|
<SelectItem key={edu.id} value={String(edu.id)}>
|
||||||
|
{edu.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="school"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Universitas / Perguruan Tinggi</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{userSchools?.map((edu: any) => (
|
||||||
|
<SelectItem key={edu.id} value={String(edu.id)}>
|
||||||
|
{edu.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="competency"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Kompetensi</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{userCompetencies?.map((edu: any) => (
|
||||||
|
<SelectItem key={edu.id} value={String(edu.id)}>
|
||||||
|
{edu.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="nrp"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nomor Regitrasi Polri {`(NRP)`}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan NRP"
|
||||||
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="address"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Alamat</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Masukkan alamat"
|
||||||
|
className="resize-none w-1/2"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
placeholder="Masukkan email"
|
||||||
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="phoneNumber"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>No. Handphone</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="Masukkan nomor handphone"
|
||||||
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Password</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Masukkan kata sandi"
|
||||||
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="confirmPassword"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Konfirmasi Kata Sandi</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Masukkan kata sandi"
|
||||||
|
{...field}
|
||||||
|
className="w-1/2"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<PasswordChecklist
|
||||||
|
rules={["minLength", "specialChar", "number", "capital", "match"]}
|
||||||
|
minLength={8}
|
||||||
|
value={passwordVal || ""}
|
||||||
|
valueAgain={confPasswordVal || ""}
|
||||||
|
onChange={(isValid) => {
|
||||||
|
form.setValue("isValidPassword", isValid);
|
||||||
|
}}
|
||||||
|
className="text-sm"
|
||||||
|
messages={{
|
||||||
|
minLength: "Password harus lebih dari 8 karakter",
|
||||||
|
specialChar: "Password harus memiliki spesial karakter",
|
||||||
|
number: "Password harus memiliki angka",
|
||||||
|
capital: "Password harus memiliki huruf kapital",
|
||||||
|
match: "Password sama",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Link href="/admin/management-user">
|
||||||
|
<Button type="button" color="primary" variant="outline">
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Button type="submit" color="primary" className="mx-3">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
||||||
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuItem,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
formatDateToIndonesian,
|
||||||
|
getOnlyDate,
|
||||||
|
htmlToString,
|
||||||
|
} from "@/utils/globals";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible";
|
||||||
|
import StatusToogle from "./status-toogle";
|
||||||
|
import StaticToogle from "./static-toogle";
|
||||||
|
|
||||||
|
const columns: ColumnDef<any>[] = [
|
||||||
|
// {
|
||||||
|
// accessorKey: "no",
|
||||||
|
// header: "No",
|
||||||
|
// cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
|
// },
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "title",
|
||||||
|
header: "Judul",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "categoryName",
|
||||||
|
header: "Kategori",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "createdAt",
|
||||||
|
header: "Tanggal Unggah",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "statusName",
|
||||||
|
header: "Status Banner",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<StatusToogle id={row.original.id} initChecked={row.original.isBanner} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
accessorKey: "action",
|
||||||
|
header: "Actions",
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
|
<Link
|
||||||
|
href={`/contributor/content/image/detail/${row.original.id}`}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default columns;
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
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 { useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import columns from "./banner-column";
|
||||||
|
import { listBanner } from "@/service/service/settings/settings";
|
||||||
|
|
||||||
|
const BannerListTable = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [showData, setShowData] = React.useState("10");
|
||||||
|
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: Number(showData),
|
||||||
|
});
|
||||||
|
const [getData, setGetData] = React.useState<any>([]);
|
||||||
|
const dataChange = searchParams?.get("dataChange");
|
||||||
|
|
||||||
|
const [page, setPage] = React.useState(1);
|
||||||
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
|
const table = useReactTable({
|
||||||
|
data: getData,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const pageFromUrl = searchParams?.get("page");
|
||||||
|
if (pageFromUrl) {
|
||||||
|
setPage(Number(pageFromUrl));
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (dataChange) {
|
||||||
|
router.push("/admin/settings/banner");
|
||||||
|
}
|
||||||
|
getListBanner();
|
||||||
|
}, [dataChange]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
getListBanner();
|
||||||
|
// getListStaticBanner();
|
||||||
|
}, [page, showData]);
|
||||||
|
|
||||||
|
async function getListBanner() {
|
||||||
|
loading();
|
||||||
|
let temp: any;
|
||||||
|
|
||||||
|
const response = await listBanner();
|
||||||
|
const data = response?.data?.data?.content;
|
||||||
|
console.log("banner", data);
|
||||||
|
setGetData(data);
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table className="overflow-hidden mt-10">
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
className="h-[75px]"
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BannerListTable;
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuItem,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
formatDateToIndonesian,
|
||||||
|
htmlToString,
|
||||||
|
} from "@/utils/globals";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { setBanner } from "@/service/service/settings/settings";
|
||||||
|
|
||||||
|
const columns: ColumnDef<any>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "no",
|
||||||
|
header: "No",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "title",
|
||||||
|
header: "Judul",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("title")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "categoryName",
|
||||||
|
header: "Kategori",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("categoryName")}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "createdAt",
|
||||||
|
header: "Tanggal Unggah",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span>{formatDateToIndonesian(row.getValue("createdAt"))}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "statusName",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("statusName")}</span>,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
accessorKey: "action",
|
||||||
|
header: "Actions",
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const handleBanner = async (id: number) => {
|
||||||
|
const response = setBanner(id, true);
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="bg-transparent ring-offset-transparent hover:bg-transparent hover:ring-0 hover:ring-transparent"
|
||||||
|
>
|
||||||
|
<MoreVertical className="h-4 w-4 text-default-800" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="p-0" align="end">
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
|
<Link
|
||||||
|
href={`/contributor/content/image/detail/${row.original.id}`}
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="p-2 border-b text-default-700 group focus:bg-default focus:text-primary-foreground rounded-none">
|
||||||
|
<a onClick={() => handleBanner(row.original.id)}>
|
||||||
|
Jadikan Banner
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default columns;
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
import { setStaticBanner } from "@/service/service/settings/settings";
|
||||||
|
|
||||||
|
export default function StaticToogle(props: {
|
||||||
|
id: number;
|
||||||
|
initChecked: boolean;
|
||||||
|
}) {
|
||||||
|
const { id, initChecked } = props;
|
||||||
|
const { toast } = useToast();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const changeStaticBanner = async (id: number) => {
|
||||||
|
const response = await setStaticBanner(id);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: response?.message,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Success ",
|
||||||
|
});
|
||||||
|
router.push("/admin/settings/banner?dataChange=true");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
id="static-toogle"
|
||||||
|
checked={initChecked}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
changeStaticBanner(id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
import { setBanner } from "@/service/service/settings/settings";
|
||||||
|
|
||||||
|
export default function StatusToogle(props: {
|
||||||
|
id: number;
|
||||||
|
initChecked: boolean;
|
||||||
|
}) {
|
||||||
|
const { id, initChecked } = props;
|
||||||
|
const { toast } = useToast();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const disableBanner = async () => {
|
||||||
|
const response = await setBanner(id, false);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: response?.message,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Success ",
|
||||||
|
});
|
||||||
|
router.push("/admin/settings/banner?dataChange=true");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
id="status-toogle"
|
||||||
|
checked={initChecked}
|
||||||
|
onCheckedChange={() => disableBanner()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,432 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link } from "@/i18n/routing";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import CustomPagination from "@/components/table/custom-pagination";
|
||||||
|
import { listDataMedia } from "@/service/service/broadcast/broadcast";
|
||||||
|
import { setBanner } from "@/service/service/settings/settings";
|
||||||
|
|
||||||
|
const ContentListBanner = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [showData, setShowData] = React.useState("9");
|
||||||
|
const [categories, setCategories] = React.useState<any>();
|
||||||
|
const [data, setData] = React.useState<any[]>([]);
|
||||||
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: Number(showData),
|
||||||
|
});
|
||||||
|
const [categoryFilter, setCategoryFilter] = React.useState<number[]>([]);
|
||||||
|
const [statusFilter, setStatusFilter] = React.useState<number[]>([]);
|
||||||
|
const [selectedItems, setSelectedItems] = React.useState<number[]>([]);
|
||||||
|
const [page, setPage] = React.useState(1);
|
||||||
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
|
const [searchQuery, setSearchQuery] = React.useState("");
|
||||||
|
const [bannerCount, setBannerCount] = React.useState<number>(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchBannerCount();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function fetchBannerCount() {
|
||||||
|
try {
|
||||||
|
const res = await listDataMedia(0, "100", "", "", "");
|
||||||
|
const banners = res?.data?.data?.content?.filter(
|
||||||
|
(item: any) => item.isBanner
|
||||||
|
);
|
||||||
|
setBannerCount(banners?.length || 0);
|
||||||
|
|
||||||
|
setBannerCount(data?.length || 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching banner count:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyUp = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
let typingTimer: NodeJS.Timeout;
|
||||||
|
const doneTypingInterval = 1500;
|
||||||
|
|
||||||
|
const handleTyping = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
typingTimer = setTimeout(() => {
|
||||||
|
setPage(1);
|
||||||
|
fetchData();
|
||||||
|
}, doneTypingInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [categoryFilter, statusFilter]);
|
||||||
|
|
||||||
|
async function doneTyping() {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const pageFromUrl = searchParams?.get("page");
|
||||||
|
if (pageFromUrl) {
|
||||||
|
setPage(Number(pageFromUrl));
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
// setPagination({
|
||||||
|
// pageIndex: 0,
|
||||||
|
// pageSize: Number(showData),
|
||||||
|
// });
|
||||||
|
}, [page, showData]);
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
loading();
|
||||||
|
const res = await listDataMedia(
|
||||||
|
page - 1,
|
||||||
|
showData,
|
||||||
|
searchQuery,
|
||||||
|
categoryFilter?.sort().join(","),
|
||||||
|
statusFilter?.sort().join(",")
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = res?.data?.data;
|
||||||
|
const contentData = data?.content;
|
||||||
|
contentData.forEach((item: any, index: number) => {
|
||||||
|
item.no = (page - 1) * Number(showData) + index + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("contentData : ", data);
|
||||||
|
|
||||||
|
setData(contentData);
|
||||||
|
setTotalData(data?.totalElements);
|
||||||
|
setTotalPage(data?.totalPages);
|
||||||
|
close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching tasks:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
getCategories();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listEnableCategory("");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (type: string, id: number, checked: boolean) => {
|
||||||
|
if (type === "category") {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...categoryFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = categoryFilter.filter((a) => a !== id);
|
||||||
|
setCategoryFilter(temp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (checked) {
|
||||||
|
const temp: number[] = [...statusFilter];
|
||||||
|
temp.push(id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
} else {
|
||||||
|
const temp = statusFilter.filter((a) => a !== id);
|
||||||
|
setStatusFilter(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (id: number) => {
|
||||||
|
setSelectedItems((prev) =>
|
||||||
|
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAll = () => {
|
||||||
|
if (selectedItems.length === data.length) {
|
||||||
|
setSelectedItems([]);
|
||||||
|
} else {
|
||||||
|
setSelectedItems(data.map((item: any) => Number(item.id)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const handleBanner = async (ids: number[]) => {
|
||||||
|
try {
|
||||||
|
// const res = await Promise.all(ids.map((id) => setBanner(id, true)));
|
||||||
|
|
||||||
|
for (const element of ids) {
|
||||||
|
loading();
|
||||||
|
const res = await setBanner(element, true);
|
||||||
|
close();
|
||||||
|
if (res?.error) {
|
||||||
|
toast({
|
||||||
|
title: "Gagal",
|
||||||
|
description:
|
||||||
|
"Banner sudah melebihi batas maksimum (4 konten). Silahkan di disable banner Lainnya.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "Sukses",
|
||||||
|
description: `item berhasil dijadikan banner.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
title: "Gagal",
|
||||||
|
description: "Terjadi kesalahan saat menjadikan banner.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex justify-between ">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchQuery(e.target.value);
|
||||||
|
handleTyping();
|
||||||
|
}}
|
||||||
|
className="max-w-[300px]"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* <div className="flex flex-row gap-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button size="md" variant="outline">
|
||||||
|
1 - {showData} Data
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="w-56 text-sm">
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
value={showData}
|
||||||
|
onValueChange={setShowData}
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem value="10">
|
||||||
|
1 - 10 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="20">
|
||||||
|
1 - 20 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="25">
|
||||||
|
1 - 25 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
<DropdownMenuRadioItem value="50">
|
||||||
|
1 - 50 Data
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button size="md" variant="outline">
|
||||||
|
Filter
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-80 ">
|
||||||
|
<div className="flex flex-col gap-2 px-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<p>Filter</p>
|
||||||
|
<a
|
||||||
|
onClick={() => fetchData()}
|
||||||
|
className="cursor-pointer text-primary"
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1 overflow-auto max-h-[300px] text-xs custom-scrollbar-table">
|
||||||
|
<p>Kategory</p>
|
||||||
|
{categories?.map((category: any) => (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={category.id}
|
||||||
|
checked={categoryFilter.includes(category.id)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("category", category.id, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={category.id}
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<p className="mt-3">Status</p>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(1)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 1, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Menunggu Review
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(2)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 2, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Diterima
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(3)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 3, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Minta Update{" "}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="accepted"
|
||||||
|
checked={statusFilter.includes(4)}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleChange("status", 4, Boolean(e))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="accepted"
|
||||||
|
className="text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
Ditolak
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{/* Header select all + action */}
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedItems.length === data.length}
|
||||||
|
onCheckedChange={handleSelectAll}
|
||||||
|
/>
|
||||||
|
<span className="text-black dark:text-white">Pilih Semua</span>
|
||||||
|
</div>
|
||||||
|
{selectedItems.length > 0 && (
|
||||||
|
<Button color="primary" onClick={() => handleBanner(selectedItems)}>
|
||||||
|
Jadikan Banner
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Grid Cards */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
|
{data?.map((item: any) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="relative rounded-lg shadow-md overflow-hidden border border-gray-200"
|
||||||
|
>
|
||||||
|
<div className="absolute top-2 left-2 z-10">
|
||||||
|
<Checkbox
|
||||||
|
checked={selectedItems.includes(item.id)}
|
||||||
|
onCheckedChange={() => handleSelect(item.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src={item.smallThumbnailLink || "/placeholder.jpg"}
|
||||||
|
alt={item.title}
|
||||||
|
className="w-full h-48 object-cover"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
href={`/contributor/content/image/detail/${item?.id}`}
|
||||||
|
className="p-3"
|
||||||
|
>
|
||||||
|
<h4 className="font-semibold text-sm">{item.title}</h4>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-3">
|
||||||
|
{data && data?.length > 0 ? (
|
||||||
|
<CustomPagination
|
||||||
|
totalPage={totalPage}
|
||||||
|
onPageChange={(data) => setPage(data)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<p>No Data</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContentListBanner;
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
"use client";
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import ContentListTable from "./component/table";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import BannerListTable from "./component/banner-table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import ContentListBanner from "./component/table";
|
||||||
|
|
||||||
|
export default function AdminBanner() {
|
||||||
|
const [selectedTab, setSelectedTab] = useState("content");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<div className="w-full overflow-x-auto bg-white dark:bg-black p-4 rounded-sm space-y-3">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
{selectedTab === "content" ? "List Media" : " List Banner"}
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-1 border-2 rounded-md w-fit mb-5">
|
||||||
|
<Button
|
||||||
|
rounded="md"
|
||||||
|
onClick={() => setSelectedTab("content")}
|
||||||
|
className={` hover:text-white
|
||||||
|
${
|
||||||
|
selectedTab === "content"
|
||||||
|
? "bg-black text-white "
|
||||||
|
: "bg-white text-black "
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Konten
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
rounded="md"
|
||||||
|
onClick={() => setSelectedTab("banner")}
|
||||||
|
className={` hover:text-white
|
||||||
|
${
|
||||||
|
selectedTab === "banner"
|
||||||
|
? "bg-black text-white "
|
||||||
|
: "bg-white text-black "
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Banner
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedTab === "content" ? (
|
||||||
|
<ContentListBanner />
|
||||||
|
) : (
|
||||||
|
<BannerListTable />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import {
|
||||||
|
Menubar,
|
||||||
|
MenubarContent,
|
||||||
|
MenubarMenu,
|
||||||
|
MenubarTrigger,
|
||||||
|
} from "@/components/ui/menubar";
|
||||||
|
import EditTagModal from "./edit";
|
||||||
|
|
||||||
|
const columns: ColumnDef<any>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "no",
|
||||||
|
header: "No",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
accessorKey: "tagName",
|
||||||
|
header: "Nama Tag",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="normal-case">{row.getValue("tagName")}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "categoryName",
|
||||||
|
header: "Kategori",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="normal-case">{row.getValue("categoryName")}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "contentCount",
|
||||||
|
header: "Jumlah Content",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="normal-case">
|
||||||
|
{row.getValue("contentCount") ? row.getValue("contentCount") : "-"}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
accessorKey: "action",
|
||||||
|
header: "Actions",
|
||||||
|
enableHiding: false,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const tagDelete = async (id: string) => {
|
||||||
|
// const response = await deleteDataFAQ(id);
|
||||||
|
// console.log(response);
|
||||||
|
// if (response?.error) {
|
||||||
|
// error(response.message);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
toast({
|
||||||
|
title: "Sukses",
|
||||||
|
description: "Berhasil Delete",
|
||||||
|
});
|
||||||
|
router.push("/admin/settings/tag?dataChange=true");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Menubar className="border-none">
|
||||||
|
<MenubarMenu>
|
||||||
|
<MenubarTrigger>
|
||||||
|
<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>
|
||||||
|
</MenubarTrigger>
|
||||||
|
<MenubarContent className="flex flex-col gap-2 justify-center items-start p-4">
|
||||||
|
<EditTagModal
|
||||||
|
id={row.original.id}
|
||||||
|
isDetail={true}
|
||||||
|
data={row.original}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EditTagModal
|
||||||
|
id={row.original.id}
|
||||||
|
isDetail={false}
|
||||||
|
data={row.original}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a
|
||||||
|
onClick={() => tagDelete(row.original.id)}
|
||||||
|
className="hover:underline cursor-pointer hover:text-destructive"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</MenubarContent>
|
||||||
|
</MenubarMenu>
|
||||||
|
</Menubar>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default columns;
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { getCategoriesAll } from "@/service/service/settings/settings";
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
name: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
category: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function CreateTagModal() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const t = useTranslations("Menu");
|
||||||
|
const [categoryList, setCategoryList] = useState<
|
||||||
|
{ id: number; label: string; value: string }[]
|
||||||
|
>([]);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||||
|
const request = {
|
||||||
|
tagName: data.name,
|
||||||
|
categoryId: Number(data.category),
|
||||||
|
isActive: true,
|
||||||
|
};
|
||||||
|
console.log("reqqq", request);
|
||||||
|
// const response = await postDataFeedback(request);
|
||||||
|
// close();
|
||||||
|
// if (response?.error) {
|
||||||
|
// toast({ title: stringify(response.message), variant: "destructive" });
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
toast({
|
||||||
|
title: "Succes",
|
||||||
|
description: "Tag berhasil dibuat",
|
||||||
|
});
|
||||||
|
router.push("/admin/settings/tag?dataChange=true");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getCategoryParent();
|
||||||
|
}, []);
|
||||||
|
async function getCategoryParent() {
|
||||||
|
const response = await getCategoriesAll();
|
||||||
|
const res = response?.data?.data.content;
|
||||||
|
console.log("res", res);
|
||||||
|
var levelsArr: { id: number; label: string; value: string }[] = [];
|
||||||
|
res.forEach((levels: { id: number; name: string }) => {
|
||||||
|
levelsArr.push({
|
||||||
|
id: levels.id,
|
||||||
|
label: levels.name,
|
||||||
|
value: String(levels.id),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setCategoryList(levelsArr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button color="primary" size="md">
|
||||||
|
{t("add-tags", { defaultValue: "Add Tags" })}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent size="md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle> {t("add-tags", { defaultValue: "Add Tags" })}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="space-y-3 bg-white rounded-sm"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="category"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>Pilih Category</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className={cn(
|
||||||
|
"w-[400px] justify-between",
|
||||||
|
!field.value && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value
|
||||||
|
? categoryList.find(
|
||||||
|
(categ) => categ.value === field.value
|
||||||
|
)?.label
|
||||||
|
: "Pilih level"}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[400px] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No role found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{categoryList.map((role) => (
|
||||||
|
<CommandItem
|
||||||
|
value={role.label}
|
||||||
|
key={role.value}
|
||||||
|
onSelect={() => {
|
||||||
|
form.setValue("category", role.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{role.label}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto",
|
||||||
|
role.value === field.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nama Tag</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Masukkan nama tag" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
Tambah Tag
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,263 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
|
import { close, error, loading } from "@/config/swal";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
import { stringify } from "querystring";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { getCategoriesAll, postDataFeedback } from "@/service/service/settings/settings";
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
name: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
category: z.string({
|
||||||
|
required_error: "Required",
|
||||||
|
}),
|
||||||
|
|
||||||
|
// publishTo: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||||
|
// message: "Required",
|
||||||
|
// }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const publishToList = [
|
||||||
|
{
|
||||||
|
id: "mabes",
|
||||||
|
name: "Nasional",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "polda",
|
||||||
|
name: "Polda",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "satker",
|
||||||
|
name: "Satker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "internasional",
|
||||||
|
name: "Internasional",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function EditTagModal(props: {
|
||||||
|
id: string;
|
||||||
|
isDetail: boolean;
|
||||||
|
data: {
|
||||||
|
id: number;
|
||||||
|
tagName: string;
|
||||||
|
categoryId: number;
|
||||||
|
subCategoryId: number;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const { id, isDetail, data } = props;
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [categoryList, setCategoryList] = useState<
|
||||||
|
{ id: number; label: string; value: string }[]
|
||||||
|
>([]);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof FormSchema>>({
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initState();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
const initState = async () => {
|
||||||
|
form.setValue("name", data.tagName);
|
||||||
|
form.setValue("category", String(data.categoryId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
|
||||||
|
const request = {
|
||||||
|
id: Number(id),
|
||||||
|
tagName: data.name,
|
||||||
|
categoryId: Number(data.category),
|
||||||
|
isActive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await postDataFeedback(request);
|
||||||
|
close();
|
||||||
|
if (response?.error) {
|
||||||
|
toast({ title: stringify(response.message), variant: "destructive" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: "Succes",
|
||||||
|
description: "Tag berhasil diubah",
|
||||||
|
});
|
||||||
|
router.push("/admin/settings/tag?dataChange=true");
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getCategoryParent();
|
||||||
|
}, []);
|
||||||
|
async function getCategoryParent() {
|
||||||
|
const response = await getCategoriesAll();
|
||||||
|
const res = response?.data?.data.content;
|
||||||
|
console.log("res", res);
|
||||||
|
var levelsArr: { id: number; label: string; value: string }[] = [];
|
||||||
|
res.forEach((levels: { id: number; name: string }) => {
|
||||||
|
levelsArr.push({
|
||||||
|
id: levels.id,
|
||||||
|
label: levels.name,
|
||||||
|
value: String(levels.id),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setCategoryList(levelsArr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<a className="hover:underline cursor-pointer">
|
||||||
|
{isDetail ? "Detail" : "Edit"}
|
||||||
|
</a>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent size="md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{isDetail ? "Detail" : "Edit"} Tag</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="space-y-3 bg-white rounded-sm"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="category"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>Pilih Category</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild disabled={isDetail}>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
className={cn(
|
||||||
|
"w-[400px] justify-between",
|
||||||
|
!field.value && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value
|
||||||
|
? categoryList.find(
|
||||||
|
(categ) => categ.value === field.value
|
||||||
|
)?.label
|
||||||
|
: "Pilih level"}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[400px] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No role found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{categoryList.map((role) => (
|
||||||
|
<CommandItem
|
||||||
|
value={role.label}
|
||||||
|
key={role.value}
|
||||||
|
onSelect={() => {
|
||||||
|
form.setValue("category", role.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{role.label}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto",
|
||||||
|
role.value === field.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nama Tag</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
readOnly={isDetail}
|
||||||
|
placeholder="Masukkan nama tag"
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isDetail && (
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
Edit Tag
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import TablePagination from "@/components/table/table-pagination";
|
||||||
|
import columns from "./column";
|
||||||
|
|
||||||
|
import { listEnableCategory } from "@/service/content/content";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
|
|
||||||
|
import CreateFAQModal from "./create";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { getTags } from "@/service/content";
|
||||||
|
|
||||||
|
const AdminTagTable = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const t = useTranslations("Menu");
|
||||||
|
const dataChange = searchParams?.get("dataChange");
|
||||||
|
const [openModal, setOpenModal] = React.useState(false);
|
||||||
|
const [dataTable, setDataTable] = React.useState<any[]>([]);
|
||||||
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [page, setPage] = React.useState(1);
|
||||||
|
const [totalPage, setTotalPage] = React.useState(1);
|
||||||
|
const table = useReactTable({
|
||||||
|
data: dataTable,
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (dataChange) {
|
||||||
|
router.push("/admin/settings/tag");
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [dataChange]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const pageFromUrl = searchParams?.get("page");
|
||||||
|
if (pageFromUrl) {
|
||||||
|
setPage(Number(pageFromUrl));
|
||||||
|
}
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
loading();
|
||||||
|
const response = await getTags();
|
||||||
|
const data = response?.data?.data;
|
||||||
|
data.forEach((item: any, index: number) => {
|
||||||
|
item.no = (page - 1) * 10 + index + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
setDataTable(data);
|
||||||
|
setTotalData(data?.length);
|
||||||
|
setTotalPage(1);
|
||||||
|
close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching tasks:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full overflow-x-auto bg-white p-4 rounded-sm space-y-3">
|
||||||
|
<div className="flex justify-between mb-10 items-center">
|
||||||
|
<p className="text-xl font-medium text-default-900">{t("tags", { defaultValue: "Tags" })}</p>
|
||||||
|
<CreateFAQModal />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table className="overflow-hidden">
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id} className="bg-default-200">
|
||||||
|
{headerGroup.headers.map((header) => (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
className="h-[75px]"
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
{/* <TablePagination
|
||||||
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminTagTable;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
||||||
|
import AdminTagTable from "./component/table";
|
||||||
|
|
||||||
|
export default function TagCategory() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteBreadcrumb />
|
||||||
|
<AdminTagTable />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { formatDateToIndonesian } from "@/utils/globals";
|
import { formatDateToIndonesian } from "@/utils/globals";
|
||||||
import { getArticleCategoryDetail } from "@/service/categories/categories";
|
import { getArticleCategoryDetail } from "@/service/categories/categories";
|
||||||
|
import { getUserInfo } from "@/service/user";
|
||||||
|
|
||||||
type CategoryDetail = {
|
type CategoryDetail = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -27,16 +28,29 @@ type CategoryDetail = {
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CurrentUser = {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
fullname?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export default function CategoriesDetailForm() {
|
export default function CategoriesDetailForm() {
|
||||||
const { id } = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [detail, setDetail] = useState<CategoryDetail | null>(null);
|
const [detail, setDetail] = useState<CategoryDetail | null>(null);
|
||||||
|
const [currentUser, setCurrentUser] = useState<CurrentUser | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getUserInfo().then((res) => setCurrentUser(res.data.data));
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function init() {
|
async function init() {
|
||||||
if (id) {
|
if (id) {
|
||||||
try {
|
try {
|
||||||
const res = await getArticleCategoryDetail(Number(id));
|
const res = await getArticleCategoryDetail(Number(id));
|
||||||
|
const user = await getUserInfo();
|
||||||
|
|
||||||
setDetail(res?.data?.data);
|
setDetail(res?.data?.data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error fetching category detail:", err);
|
console.error("Error fetching category detail:", err);
|
||||||
|
|
@ -50,7 +64,7 @@ export default function CategoriesDetailForm() {
|
||||||
<form>
|
<form>
|
||||||
{detail ? (
|
{detail ? (
|
||||||
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg ">
|
<div className="flex flex-col lg:flex-row gap-10 border rounded-lg ">
|
||||||
{/* MAIN FORM */}
|
{/* MAIN FORM */}
|
||||||
<Card className="w-full lg:w-8/12 px-6 py-6 m-2">
|
<Card className="w-full lg:w-8/12 px-6 py-6 m-2">
|
||||||
<p className="text-lg font-semibold mb-3">Form Category Detail</p>
|
<p className="text-lg font-semibold mb-3">Form Category Detail</p>
|
||||||
|
|
||||||
|
|
@ -77,7 +91,7 @@ export default function CategoriesDetailForm() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Thumbnail */}
|
{/* Thumbnail */}
|
||||||
<div className="space-y-2 py-3">
|
{/* <div className="space-y-2 py-3">
|
||||||
<Label>Thumbnail</Label>
|
<Label>Thumbnail</Label>
|
||||||
<Card className="mt-2 w-fit p-2 border">
|
<Card className="mt-2 w-fit p-2 border">
|
||||||
<img
|
<img
|
||||||
|
|
@ -86,7 +100,7 @@ export default function CategoriesDetailForm() {
|
||||||
className="h-[200px] rounded"
|
className="h-[200px] rounded"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
{/* <div className="space-y-2 py-3 ">
|
{/* <div className="space-y-2 py-3 ">
|
||||||
|
|
@ -111,19 +125,27 @@ export default function CategoriesDetailForm() {
|
||||||
{/* Creator */}
|
{/* Creator */}
|
||||||
<div>
|
<div>
|
||||||
<Label>Created By</Label>
|
<Label>Created By</Label>
|
||||||
<Input
|
{/* <Input
|
||||||
type="text"
|
type="text"
|
||||||
value={detail.createdByName || detail.createdById}
|
value={detail.createdByName || detail.createdById}
|
||||||
readOnly
|
readOnly
|
||||||
|
/> */}
|
||||||
|
<Input
|
||||||
|
readOnly
|
||||||
|
value={
|
||||||
|
currentUser?.id === detail.createdById
|
||||||
|
? currentUser.username
|
||||||
|
: `User ID ${detail.createdById}`
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div>
|
<div>
|
||||||
<Label>Status</Label>
|
<Label>Status</Label>
|
||||||
<p className="text-sm text-slate-600">
|
<p className="text-sm text-green-500">
|
||||||
{/* {detail.isPublish ? "Published" : "Draft"} |{" "} */}
|
{/* {detail.isPublish ? "Published" : "Draft"} |{" "} */}
|
||||||
{detail.isActive ? "Active" : "Inactive"}
|
{detail.isActive ? "* Active" : "- Inactive"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
httpPutInterceptor,
|
httpPutInterceptor,
|
||||||
} from "../http-config/http-interceptor-service";
|
} from "../http-config/http-interceptor-service";
|
||||||
|
|
||||||
|
|
||||||
// User CRUD Operations
|
// User CRUD Operations
|
||||||
export interface CreateUserRequest {
|
export interface CreateUserRequest {
|
||||||
address: string;
|
address: string;
|
||||||
|
|
@ -35,7 +34,7 @@ export async function AdministrationUserList(
|
||||||
name = "",
|
name = "",
|
||||||
size: string,
|
size: string,
|
||||||
featureId: string,
|
featureId: string,
|
||||||
role = ""
|
role = "",
|
||||||
) {
|
) {
|
||||||
const url = `users/pagination/internal?enablePage=1&size=${size}&page=${page}&levelId=${id}&name=${name}&featureId=${featureId}&roleFilter=${role}`;
|
const url = `users/pagination/internal?enablePage=1&size=${size}&page=${page}&levelId=${id}&name=${name}&featureId=${featureId}&roleFilter=${role}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
|
|
@ -89,9 +88,9 @@ export async function getListExperiences() {
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveUserInternal(data: any) {
|
export async function updateUserInternal(id: number, data: any) {
|
||||||
const url = "users/save";
|
const url = `users/${id}`;
|
||||||
return httpPostInterceptor(url, data);
|
return httpPutInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkRolePlacementsAvailability(data: any) {
|
export async function checkRolePlacementsAvailability(data: any) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue