feat: update user levels and user management
This commit is contained in:
parent
c736d39b27
commit
b91b7f93cd
|
|
@ -1,773 +1,26 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import { useRouter } from "@/i18n/routing";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import UserForm from "@/components/form/user/user-form";
|
||||||
import { Check, ChevronsUpDown, Eye, EyeOff } from "lucide-react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
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 { 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 { AdministrationLevelList, getListCompetencies, getListEducation, getListSchools, saveUserInternal } from "@/service/service/management-user/management-user";
|
|
||||||
|
|
||||||
const PasswordChecklist = dynamic(() => import("react-password-checklist"), {
|
export default function CreateUserPage() {
|
||||||
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 CreateUserForm() {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
const levelName = getCookiesDecrypt("ulnae");
|
|
||||||
const [roleList, setRoleList] = useState<RoleData[]>([]);
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
||||||
const [userEducations, setUserEducations] = useState<any>();
|
|
||||||
const [userSchools, setUserSchools] = useState<any>();
|
|
||||||
const [userCompetencies, setUserCompetencies] = useState<any>();
|
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof FormSchema>>({
|
const handleSuccess = () => {
|
||||||
resolver: zodResolver(FormSchema),
|
router.push("/admin/management-user");
|
||||||
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(() => {
|
|
||||||
initFetch();
|
|
||||||
getDataAdditional();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const initFetch = async () => {
|
|
||||||
const response = await AdministrationLevelList();
|
|
||||||
const res = response?.data?.data;
|
|
||||||
var levelsArr: RoleData[] = [];
|
|
||||||
res.forEach((levels: RoleData) => {
|
|
||||||
levelsArr.push({
|
|
||||||
id: levels.id,
|
|
||||||
label: levels.name,
|
|
||||||
name: levels.name,
|
|
||||||
value: String(levels.id),
|
|
||||||
levelNumber: levels.levelNumber,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setRoleList(levelsArr);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getDataAdditional() {
|
const handleCancel = () => {
|
||||||
const resEducations = await getListEducation();
|
router.push("/admin/management-user");
|
||||||
setUserEducations(resEducations?.data?.data);
|
};
|
||||||
const resSchools = await getListSchools();
|
|
||||||
setUserSchools(resSchools?.data?.data);
|
|
||||||
const resCompetencies = await getListCompetencies();
|
|
||||||
setUserCompetencies(resCompetencies?.data?.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const roles =
|
|
||||||
levelName == "MABES POLRI"
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
id: "ADM-ID",
|
|
||||||
name: "Admin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "EXE-ID",
|
|
||||||
name: "Eksekutif",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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>) {
|
|
||||||
let req: any = {
|
|
||||||
firstName: data.fullname,
|
|
||||||
username: data.username,
|
|
||||||
roleId: data.role,
|
|
||||||
userLevelId: Number(data.level),
|
|
||||||
memberIdentity: data.nrp,
|
|
||||||
address: data.address,
|
|
||||||
email: data.email,
|
|
||||||
phoneNumber: data.phoneNumber,
|
|
||||||
password: data.password,
|
|
||||||
passwordConf: data.confirmPassword,
|
|
||||||
isDefault: false,
|
|
||||||
isAdmin: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.role == "OPT-ID") {
|
|
||||||
req.handledSocialMedia = data?.sns ? data.sns.join(",") : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.role == "KUR-ID") {
|
|
||||||
req.userEducationId = Number(data.education);
|
|
||||||
req.userSchoolsId = Number(data.school);
|
|
||||||
req.userCompetencyId = Number(data.competency);
|
|
||||||
}
|
|
||||||
|
|
||||||
loading();
|
|
||||||
const response = await saveUserInternal(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>
|
<div className="container mx-auto py-6">
|
||||||
<SiteBreadcrumb />
|
<UserForm
|
||||||
<Form {...form}>
|
mode="create"
|
||||||
<form
|
onSuccess={handleSuccess}
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onCancel={handleCancel}
|
||||||
className="space-y-6 bg-white dark:bg-black p-10 w-full"
|
/>
|
||||||
>
|
|
||||||
<p className="text-xl">Data Pengelola Media Hub</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}
|
|
||||||
defaultValue={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>
|
|
||||||
<div className="relative w-1/2">
|
|
||||||
<Input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="Masukkan kata sandi"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="confirmPassword"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Konfirmasi Kata Sandi</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<div className="relative w-1/2">
|
|
||||||
<Input
|
|
||||||
type={showConfirmPassword ? "text" : "password"}
|
|
||||||
placeholder="Masukkan kata sandi"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
setShowConfirmPassword(!showConfirmPassword)
|
|
||||||
}
|
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500"
|
|
||||||
>
|
|
||||||
{showConfirmPassword ? (
|
|
||||||
<EyeOff size={18} />
|
|
||||||
) : (
|
|
||||||
<Eye size={18} />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,778 +1,45 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import SiteBreadcrumb from "@/components/site-breadcrumb";
|
import { useRouter, useParams } from "@/i18n/routing";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import UserForm from "@/components/form/user/user-form";
|
||||||
import { Check, ChevronsUpDown } from "lucide-react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { cn, getCookiesDecrypt } from "@/lib/utils";
|
export default function EditUserPage() {
|
||||||
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 { 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 { AdministrationLevelList, getListCompetencies, getListEducation, getListSchools, getUserById, saveUserInternal } from "@/service/service/management-user/management-user";
|
|
||||||
|
|
||||||
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 = useParams();
|
const params = useParams();
|
||||||
const id = params?.id;
|
const userId = params?.id ? Number(params.id) : undefined;
|
||||||
const MySwal = withReactContent(Swal);
|
|
||||||
const levelName = getCookiesDecrypt("ulnae");
|
|
||||||
const [roleList, setRoleList] = useState<RoleData[]>([]);
|
|
||||||
|
|
||||||
const [userEducations, setUserEducations] = useState<any>();
|
const handleSuccess = () => {
|
||||||
const [userSchools, setUserSchools] = useState<any>();
|
router.push("/admin/management-user");
|
||||||
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();
|
|
||||||
const response = await getUserById(String(id));
|
|
||||||
const res = response?.data?.data;
|
|
||||||
close();
|
|
||||||
console.log("res", res);
|
|
||||||
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();
|
|
||||||
console.log("sadad", res?.role?.code);
|
|
||||||
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 AdministrationLevelList();
|
|
||||||
const res = response?.data?.data;
|
|
||||||
var levelsArr: RoleData[] = [];
|
|
||||||
res.forEach((levels: RoleData) => {
|
|
||||||
levelsArr.push({
|
|
||||||
id: levels.id,
|
|
||||||
label: levels.name,
|
|
||||||
name: levels.name,
|
|
||||||
value: String(levels.id),
|
|
||||||
levelNumber: levels.levelNumber,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
setRoleList(levelsArr);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getDataAdditional() {
|
const handleCancel = () => {
|
||||||
const resEducations = await getListEducation();
|
router.push("/admin/management-user");
|
||||||
setUserEducations(resEducations?.data?.data);
|
};
|
||||||
const resSchools = await getListSchools();
|
|
||||||
setUserSchools(resSchools?.data?.data);
|
|
||||||
const resCompetencies = await getListCompetencies();
|
|
||||||
setUserCompetencies(resCompetencies?.data?.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const roles =
|
if (!userId) {
|
||||||
levelName == "MABES POLRI"
|
return (
|
||||||
? [
|
<div className="container mx-auto py-6">
|
||||||
{
|
<div className="text-center">
|
||||||
id: "ADM-ID",
|
<p className="text-red-500">User ID tidak valid</p>
|
||||||
name: "Admin",
|
<button
|
||||||
},
|
onClick={() => router.push("/admin/management-user")}
|
||||||
{
|
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
id: "APP-ID",
|
>
|
||||||
name: "Approver",
|
Kembali ke Management User
|
||||||
},
|
</button>
|
||||||
{
|
</div>
|
||||||
id: "CON-ID",
|
</div>
|
||||||
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>) {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.role == "OPT-ID") {
|
|
||||||
req.handledSocialMedia = data?.sns ? data.sns.join(",") : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.role == "KUR-ID") {
|
|
||||||
req.userEducationId = Number(data.education);
|
|
||||||
req.userSchoolsId = Number(data.school);
|
|
||||||
req.userCompetencyId = Number(data.competency);
|
|
||||||
}
|
|
||||||
|
|
||||||
loading();
|
|
||||||
const response = await saveUserInternal(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>
|
<div className="container mx-auto py-6">
|
||||||
<SiteBreadcrumb />
|
<UserForm
|
||||||
<Form {...form}>
|
id={userId}
|
||||||
<form
|
mode="edit"
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSuccess={handleSuccess}
|
||||||
className="space-y-6 bg-white p-10 w-full"
|
onCancel={handleCancel}
|
||||||
>
|
/>
|
||||||
<p className="text-xl">Data Pengelola Media Hub</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +59,7 @@ function TenantSettingsContent() {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const [comprehensiveWorkflowRes, userLevelsRes] = await Promise.all([
|
const [comprehensiveWorkflowRes, userLevelsRes] = await Promise.all([
|
||||||
getApprovalWorkflowComprehensiveDetails(4), // Using workflow ID 4 as per API example
|
getApprovalWorkflowComprehensiveDetails(), // Using workflow ID 4 as per API example
|
||||||
getUserLevels(),
|
getUserLevels(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ import withReactContent from "sweetalert2-react-content";
|
||||||
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
|
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
|
||||||
import { close } from "@/config/swal";
|
import { close } from "@/config/swal";
|
||||||
import { getUserLevelDetail, updateUserLevel } from "@/service/tenant";
|
import { getUserLevelDetail, updateUserLevel } from "@/service/tenant";
|
||||||
|
import { UserLevelsCreateRequest, getUserLevels, getProvinces } from "@/service/approval-workflows";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
|
||||||
const tenantSchema = z.object({
|
const tenantSchema = z.object({
|
||||||
aliasName: z.string().trim().min(1, { message: "Alias Name wajib diisi" }),
|
aliasName: z.string().trim().min(1, { message: "Alias Name wajib diisi" }),
|
||||||
|
|
@ -22,23 +25,18 @@ const tenantSchema = z.object({
|
||||||
})
|
})
|
||||||
.min(1, { message: "Level Number minimal 1" }),
|
.min(1, { message: "Level Number minimal 1" }),
|
||||||
name: z.string().trim().min(1, { message: "Name wajib diisi" }),
|
name: z.string().trim().min(1, { message: "Name wajib diisi" }),
|
||||||
|
parentLevelId: z.number().optional(),
|
||||||
|
provinceId: z.number().optional(),
|
||||||
|
group: z.string().optional(),
|
||||||
|
isApprovalActive: z.boolean().default(true),
|
||||||
|
isActive: z.boolean().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
type TenantSchema = z.infer<typeof tenantSchema>;
|
type TenantSchema = z.infer<typeof tenantSchema>;
|
||||||
|
|
||||||
interface TenantUpdateFormProps {
|
interface TenantUpdateFormProps {
|
||||||
id: number;
|
id: number;
|
||||||
/** ✅ Tambahkan properti ini */
|
initialData?: UserLevelsCreateRequest;
|
||||||
initialData?: {
|
|
||||||
name: string;
|
|
||||||
aliasName: string;
|
|
||||||
levelNumber: number;
|
|
||||||
parentLevelId?: number;
|
|
||||||
provinceId?: number;
|
|
||||||
group: string;
|
|
||||||
isApprovalActive: boolean;
|
|
||||||
isActive: boolean;
|
|
||||||
};
|
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +48,8 @@ export default function TenantUpdateForm({
|
||||||
}: TenantUpdateFormProps) {
|
}: TenantUpdateFormProps) {
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const [loadingData, setLoadingData] = useState(false);
|
const [loadingData, setLoadingData] = useState(false);
|
||||||
|
const [userLevels, setUserLevels] = useState<{id: number; name: string}[]>([]);
|
||||||
|
const [provinces, setProvinces] = useState<{id: number; name: string}[]>([]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|
@ -62,42 +62,67 @@ export default function TenantUpdateForm({
|
||||||
aliasName: "",
|
aliasName: "",
|
||||||
levelNumber: 1,
|
levelNumber: 1,
|
||||||
name: "",
|
name: "",
|
||||||
|
parentLevelId: undefined,
|
||||||
|
provinceId: undefined,
|
||||||
|
group: "",
|
||||||
|
isApprovalActive: true,
|
||||||
|
isActive: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getDetail() {
|
async function loadData() {
|
||||||
setLoadingData(true);
|
setLoadingData(true);
|
||||||
try {
|
try {
|
||||||
const response = await getUserLevelDetail(id);
|
const [detailResponse, userLevelsResponse, provincesResponse] = await Promise.all([
|
||||||
|
getUserLevelDetail(id),
|
||||||
if (!response.error && response.data) {
|
getUserLevels(),
|
||||||
const detail = response.data;
|
getProvinces(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!detailResponse.error && detailResponse.data) {
|
||||||
|
const detail = detailResponse.data;
|
||||||
setValue("aliasName", detail.aliasName ?? "");
|
setValue("aliasName", detail.aliasName ?? "");
|
||||||
setValue("levelNumber", detail.levelNumber ?? 1);
|
setValue("levelNumber", detail.levelNumber ?? 1);
|
||||||
setValue("name", detail.name ?? "");
|
setValue("name", detail.name ?? "");
|
||||||
|
setValue("parentLevelId", detail.parentLevelId);
|
||||||
|
setValue("provinceId", detail.provinceId);
|
||||||
|
setValue("group", detail.group ?? "");
|
||||||
|
setValue("isApprovalActive", detail.isApprovalActive ?? true);
|
||||||
|
setValue("isActive", detail.isActive ?? true);
|
||||||
} else {
|
} else {
|
||||||
console.error("Gagal mengambil detail:", response.message);
|
console.error("Gagal mengambil detail:", detailResponse.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userLevelsResponse?.error) {
|
||||||
|
setUserLevels(userLevelsResponse?.data?.data || []);
|
||||||
|
}
|
||||||
|
if (!provincesResponse?.error) {
|
||||||
|
setProvinces(provincesResponse?.data?.data || []);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error fetching detail:", err);
|
console.error("Error loading data:", err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingData(false);
|
setLoadingData(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) getDetail();
|
if (id) loadData();
|
||||||
}, [id, setValue]);
|
}, [id, setValue]);
|
||||||
|
|
||||||
const onSubmit = async (data: TenantSchema) => {
|
const onSubmit = async (data: TenantSchema) => {
|
||||||
try {
|
try {
|
||||||
loading();
|
loading();
|
||||||
|
|
||||||
const payload = {
|
const payload: UserLevelsCreateRequest = {
|
||||||
aliasName: data.aliasName,
|
aliasName: data.aliasName,
|
||||||
levelNumber: data.levelNumber,
|
levelNumber: data.levelNumber,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
parentLevelId: data.parentLevelId,
|
||||||
|
provinceId: data.provinceId,
|
||||||
|
group: data.group,
|
||||||
|
isApprovalActive: data.isApprovalActive,
|
||||||
|
isActive: data.isActive,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Payload dikirim ke API:", payload);
|
console.log("Payload dikirim ke API:", payload);
|
||||||
|
|
@ -123,6 +148,19 @@ export default function TenantUpdateForm({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (loadingData) {
|
||||||
|
return (
|
||||||
|
<Card className="p-6 w-full lg:w-2/3">
|
||||||
|
<div className="flex items-center justify-center py-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4"></div>
|
||||||
|
<p>Loading data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Card className="p-6 w-full lg:w-2/3">
|
<Card className="p-6 w-full lg:w-2/3">
|
||||||
|
|
@ -182,6 +220,121 @@ export default function TenantUpdateForm({
|
||||||
<p className="text-red-500 text-sm">{errors.name.message}</p>
|
<p className="text-red-500 text-sm">{errors.name.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Parent Level */}
|
||||||
|
<div>
|
||||||
|
<Label>Parent Level</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="parentLevelId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select onValueChange={(value) => field.onChange(value && value !== "no-data" ? Number(value) : undefined)} value={field.value?.toString() || ""}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih parent level" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{userLevels.length > 0 ? (
|
||||||
|
userLevels.map((level) => (
|
||||||
|
<SelectItem key={level.id} value={level.id.toString()}>
|
||||||
|
{level.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="no-data" disabled>
|
||||||
|
Tidak ada data tersedia
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.parentLevelId && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.parentLevelId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Province */}
|
||||||
|
<div>
|
||||||
|
<Label>Provinsi</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provinceId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select onValueChange={(value) => field.onChange(value && value !== "no-data" ? Number(value) : undefined)} value={field.value?.toString() || ""}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih provinsi" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{provinces.length > 0 ? (
|
||||||
|
provinces.map((province) => (
|
||||||
|
<SelectItem key={province.id} value={province.id.toString()}>
|
||||||
|
{province.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="no-data" disabled>
|
||||||
|
Tidak ada data tersedia
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.provinceId && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.provinceId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Group */}
|
||||||
|
<div>
|
||||||
|
<Label>Group</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="group"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} placeholder="Masukkan group" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.group && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.group.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Approval Active */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isApprovalActive"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Label>Approval Active</Label>
|
||||||
|
{errors.isApprovalActive && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.isApprovalActive.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isActive"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Label>Active</Label>
|
||||||
|
{errors.isActive && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.isActive.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,350 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
|
||||||
|
import { close } from "@/config/swal";
|
||||||
|
import {
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
getUserDetail,
|
||||||
|
AdministrationLevelList,
|
||||||
|
CreateUserRequest,
|
||||||
|
UpdateUserRequest
|
||||||
|
} from "@/service/service/management-user/management-user";
|
||||||
|
|
||||||
|
const userSchema = z.object({
|
||||||
|
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
|
||||||
|
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
|
||||||
|
dateOfBirth: z.string().trim().min(1, { message: "Date of Birth wajib diisi" }),
|
||||||
|
email: z.string().email({ message: "Email tidak valid" }),
|
||||||
|
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
|
||||||
|
password: z.string().min(6, { message: "Password minimal 6 karakter" }).optional(),
|
||||||
|
phoneNumber: z.string().trim().min(1, { message: "Phone Number wajib diisi" }),
|
||||||
|
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
|
||||||
|
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
type UserSchema = z.infer<typeof userSchema>;
|
||||||
|
|
||||||
|
interface UserFormProps {
|
||||||
|
id?: number;
|
||||||
|
initialData?: CreateUserRequest;
|
||||||
|
onSuccess?: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
mode?: "create" | "edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserForm({
|
||||||
|
id,
|
||||||
|
onSuccess,
|
||||||
|
onCancel,
|
||||||
|
mode = "create",
|
||||||
|
}: UserFormProps) {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const [loadingData, setLoadingData] = useState(false);
|
||||||
|
const [userLevels, setUserLevels] = useState<{id: number; name: string}[]>([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<UserSchema>({
|
||||||
|
resolver: zodResolver(userSchema),
|
||||||
|
defaultValues: {
|
||||||
|
address: "",
|
||||||
|
clientId: "",
|
||||||
|
dateOfBirth: "",
|
||||||
|
email: "",
|
||||||
|
fullname: "",
|
||||||
|
password: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
userLevelId: 0,
|
||||||
|
username: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadData() {
|
||||||
|
setLoadingData(true);
|
||||||
|
try {
|
||||||
|
const [userLevelsResponse] = await Promise.all([
|
||||||
|
AdministrationLevelList(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!userLevelsResponse?.error) {
|
||||||
|
setUserLevels(userLevelsResponse?.data?.data || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load user detail if in edit mode
|
||||||
|
if (mode === "edit" && id) {
|
||||||
|
const userResponse = await getUserDetail(id);
|
||||||
|
if (!userResponse.error && userResponse.data) {
|
||||||
|
const user = userResponse.data;
|
||||||
|
setValue("address", user.address || "");
|
||||||
|
setValue("clientId", user.clientId || "");
|
||||||
|
setValue("dateOfBirth", user.dateOfBirth || "");
|
||||||
|
setValue("email", user.email || "");
|
||||||
|
setValue("fullname", user.fullname || "");
|
||||||
|
setValue("phoneNumber", user.phoneNumber || "");
|
||||||
|
setValue("userLevelId", user.userLevelId || 0);
|
||||||
|
setValue("username", user.username || "");
|
||||||
|
// Don't set password for edit mode
|
||||||
|
} else {
|
||||||
|
console.error("Gagal mengambil detail user:", userResponse.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error loading data:", err);
|
||||||
|
} finally {
|
||||||
|
setLoadingData(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
}, [id, setValue, mode]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: UserSchema) => {
|
||||||
|
try {
|
||||||
|
loading();
|
||||||
|
|
||||||
|
const payload: CreateUserRequest = {
|
||||||
|
address: data.address,
|
||||||
|
clientId: data.clientId,
|
||||||
|
dateOfBirth: data.dateOfBirth,
|
||||||
|
email: data.email,
|
||||||
|
fullname: data.fullname,
|
||||||
|
password: data.password || "",
|
||||||
|
phoneNumber: data.phoneNumber,
|
||||||
|
userLevelId: data.userLevelId,
|
||||||
|
userRoleId: 3, // Hardcoded as per requirement
|
||||||
|
username: data.username,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Payload dikirim ke API:", payload);
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (mode === "edit" && id) {
|
||||||
|
response = await updateUser(id, payload);
|
||||||
|
} else {
|
||||||
|
response = await createUser(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
errorAutoClose(response.message || `Gagal ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
successAutoClose(`Data berhasil ${mode === "edit" ? "diperbarui" : "disimpan"}.`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (onSuccess) onSuccess();
|
||||||
|
}, 3000);
|
||||||
|
} catch (err) {
|
||||||
|
close();
|
||||||
|
errorAutoClose(`Terjadi kesalahan saat ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`);
|
||||||
|
console.error("User operation error:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loadingData) {
|
||||||
|
return (
|
||||||
|
<Card className="p-6 w-full lg:w-2/3">
|
||||||
|
<div className="flex items-center justify-center py-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4"></div>
|
||||||
|
<p>Loading data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Card className="p-6 w-full lg:w-2/3">
|
||||||
|
<h2 className="text-lg font-semibold mb-4">
|
||||||
|
{mode === "edit" ? "Edit User" : "Add New User"}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Full Name */}
|
||||||
|
<div>
|
||||||
|
<Label>Full Name</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="fullname"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} placeholder="Masukkan full name" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.fullname && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.fullname.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Username */}
|
||||||
|
<div>
|
||||||
|
<Label>Username</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} placeholder="Masukkan username" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.username && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.username.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Email */}
|
||||||
|
<div>
|
||||||
|
<Label>Email</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} type="email" placeholder="Masukkan email" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.email.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password - Only show for create mode */}
|
||||||
|
{mode === "create" && (
|
||||||
|
<div>
|
||||||
|
<Label>Password</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} type="password" placeholder="Masukkan password" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.password && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.password.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Phone Number */}
|
||||||
|
<div>
|
||||||
|
<Label>Phone Number</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="phoneNumber"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} placeholder="Masukkan phone number" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.phoneNumber && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.phoneNumber.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Address */}
|
||||||
|
<div>
|
||||||
|
<Label>Address</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="address"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} placeholder="Masukkan address" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.address && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.address.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Date of Birth */}
|
||||||
|
<div>
|
||||||
|
<Label>Date of Birth</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="dateOfBirth"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} type="date" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.dateOfBirth && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.dateOfBirth.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Client ID */}
|
||||||
|
<div>
|
||||||
|
<Label>Client ID</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="clientId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input {...field} placeholder="Masukkan client ID" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.clientId && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.clientId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User Level */}
|
||||||
|
<div>
|
||||||
|
<Label>User Level</Label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="userLevelId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select onValueChange={(value) => field.onChange(Number(value))} value={field.value?.toString() || ""}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih user level" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{userLevels.length > 0 ? (
|
||||||
|
userLevels.map((level) => (
|
||||||
|
<SelectItem key={level.id} value={level.id.toString()}>
|
||||||
|
{level.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="no-data" disabled>
|
||||||
|
Tidak ada data tersedia
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{errors.userLevelId && (
|
||||||
|
<p className="text-red-500 text-sm">{errors.userLevelId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="mt-6 flex justify-end gap-3">
|
||||||
|
<Button type="submit">
|
||||||
|
{mode === "edit" ? "Update" : "Create"}
|
||||||
|
</Button>
|
||||||
|
<Button type="button" variant="outline" onClick={() => onCancel?.()}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -14,8 +14,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { formatDateToIndonesian } from "@/utils/globals";
|
import { formatDateToIndonesian } from "@/utils/globals";
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { stringify } from "querystring";
|
import { deleteUserById } from "@/service/service/management-user/management-user";
|
||||||
import { deleteUser } from "@/service/service/management-user/management-user";
|
|
||||||
|
|
||||||
const columns: ColumnDef<any>[] = [
|
const columns: ColumnDef<any>[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -91,18 +90,26 @@ const columns: ColumnDef<any>[] = [
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const doDelete = async (id: number) => {
|
const doDelete = async (id: number) => {
|
||||||
const response = await deleteUser(id);
|
try {
|
||||||
if (response?.error) {
|
const response = await deleteUserById(id);
|
||||||
|
if (response?.error) {
|
||||||
|
toast({
|
||||||
|
title: response?.message || "Gagal menghapus user",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
toast({
|
toast({
|
||||||
title: stringify(response?.message),
|
title: "User berhasil dihapus",
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
router.push("?dataChange=true");
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: "Terjadi kesalahan saat menghapus user",
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
toast({
|
|
||||||
title: "Success delete",
|
|
||||||
});
|
|
||||||
|
|
||||||
router.push("?dataChange=true");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: number) => {
|
const handleDelete = (id: number) => {
|
||||||
|
|
|
||||||
|
|
@ -287,8 +287,7 @@ export async function deleteApprovalWorkflow(id: number) {
|
||||||
return httpDeleteInterceptor(url);
|
return httpDeleteInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getApprovalWorkflowComprehensiveDetails(workflowId: number) {
|
export async function getApprovalWorkflowComprehensiveDetails() {
|
||||||
const url = `approval-workflows/comprehensive-details`;
|
const url = `approval-workflows/comprehensive-details`;
|
||||||
const data = { workflowId };
|
return httpGetInterceptor(url);
|
||||||
return httpPostInterceptor(url, data);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {
|
||||||
httpDeleteInterceptor,
|
httpDeleteInterceptor,
|
||||||
httpGetInterceptor,
|
httpGetInterceptor,
|
||||||
httpPostInterceptor,
|
httpPostInterceptor,
|
||||||
|
httpPutInterceptor,
|
||||||
} from "../http-config/http-interceptor-service";
|
} from "../http-config/http-interceptor-service";
|
||||||
|
|
||||||
export async function AdministrationUserList(
|
export async function AdministrationUserList(
|
||||||
|
|
@ -80,3 +81,41 @@ export async function getOperatorUser(typeId: any) {
|
||||||
const url = `users/search-operator-user?code=opt-id&typeId=${typeId}`;
|
const url = `users/search-operator-user?code=opt-id&typeId=${typeId}`;
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User CRUD Operations
|
||||||
|
export interface CreateUserRequest {
|
||||||
|
address: string;
|
||||||
|
clientId: string;
|
||||||
|
dateOfBirth: string;
|
||||||
|
email: string;
|
||||||
|
fullname: string;
|
||||||
|
password: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
userLevelId: number;
|
||||||
|
userRoleId: number;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateUserRequest extends Partial<CreateUserRequest> {
|
||||||
|
id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createUser(data: CreateUserRequest) {
|
||||||
|
const url = "users";
|
||||||
|
return httpPostInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUser(id: number, data: UpdateUserRequest) {
|
||||||
|
const url = `users/${id}`;
|
||||||
|
return httpPutInterceptor(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUserById(id: number) {
|
||||||
|
const url = `users/${id}`;
|
||||||
|
return httpDeleteInterceptor(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserDetail(id: number) {
|
||||||
|
const url = `users/${id}`;
|
||||||
|
return httpGetInterceptor(url);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue