feat : update tenaga-ahli

This commit is contained in:
hanif salafi 2025-03-03 09:46:53 +07:00
parent 146bd8d782
commit 841d63b2cc
10 changed files with 538 additions and 73 deletions

View File

@ -23,27 +23,30 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useEffect, useState } from "react";
import { AdministrationLevelList, getListCompetencies, getListExperiences, saveUserInternal, saveUserRolePlacements } from "@/service/management-user/management-user";
import { loading } from "@/config/swal";
const FormSchema = z.object({
name: z.string({
required_error: "Required",
}),
username: z.string({
required_error: "Required",
}),
password: z.string({
required_error: "Required",
}),
phoneNumber: z.string({
required_error: "Required",
}),
email: z.string({
required_error: "Required",
}),
position: z.string({
required_error: "Required",
}),
region: z.string({
required_error: "Required",
}),
skills: z.string({
required_error: "Required",
}),
experience: z.string({
experiences: z.string({
required_error: "Required",
}),
company: z.string({
@ -51,12 +54,34 @@ const FormSchema = z.object({
}),
});
export type Placements = {
index: number;
roleId?: string;
userLevelId?: number;
}
export default function AddExpertForm() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
const [incrementId, setIncrementId] = useState(1);
const [placementRows, setPlacementRows] = useState<Placements[]>([{ index: 0, roleId: "", userLevelId: 0 }]);
const [userCompetencies, setUserCompetencies] = useState<any>();
const [userExperiences, setUserExperiences] = useState<any>();
const [userLevels, setUserLevels] = useState<any>();
const roleSelection = [
{
id: "11",
name: "Koor Kurator",
},
{
id: "12",
name: "Kurator",
}
];
const onSubmit = async (data: z.infer<typeof FormSchema>) => {
MySwal.fire({
@ -77,9 +102,85 @@ export default function AddExpertForm() {
const save = async (data: z.infer<typeof FormSchema>) => {
console.log("data", data);
// successSubmit();
const dataReq = {
firstName: data.name,
username: data.username,
email: data.email,
password: data.password,
adress: "",
roleId: "EXP-ID",
phoneNumber: data.phoneNumber,
userCompetencyId: data.skills,
userExperienceId: data.experiences,
companyName: data.company,
}
loading();
const res = await saveUserInternal(dataReq);
const resData = res?.data?.data;
const userProfileId = resData.id;
var placementArr: any[] = [];
placementRows.forEach((row: any) => {
placementArr.push({
roleId: Number(row.roleId),
userLevelId: Number(row.userLevelId),
userProfileId: userProfileId,
});
});
const dataReq2 = {
userId: userProfileId,
placements: placementArr
}
const res2 = await saveUserRolePlacements(dataReq2);
const resData2 = res2?.data?.data;
success("/admin/add-experts");
};
function success(redirect: string): void {
MySwal.fire({
title: '<p class="text-green-600 font-bold">Sukses</p>',
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: '<span class="text-white">OK</span>',
allowOutsideClick: false,
}).then((result) => {
if (result.isConfirmed) {
router.push(redirect);
}
});
}
useEffect(() => {
getDataAdditional();
}, []);
async function getDataAdditional() {
const resCompetencies = await getListCompetencies();
setUserCompetencies(resCompetencies?.data?.data);
const resExperiences = await getListExperiences();
setUserExperiences(resExperiences?.data?.data);
const resUserLevels = await AdministrationLevelList();
const data = resUserLevels?.data?.data;
var levelsArr: any[] = [];
data.forEach((levels: any) => {
levelsArr.push({
id: levels.id,
label: levels.name,
name: levels.name,
value: String(levels.id),
levelNumber: levels.levelNumber,
});
});
setUserLevels(levelsArr);
}
function successSubmit() {
MySwal.fire({
title: "Sukses",
@ -92,6 +193,29 @@ export default function AddExpertForm() {
}
});
}
const handleSelectionChange = (index: number, type: "roleId" | "userLevelId", value: string) => {
setPlacementRows((prevRows) =>
prevRows.map((row) =>
row.index === index ? { ...row, [type]: value } : row
)
);
};
const handleRemoveRow = (index: number) => {
console.log(index);
console.log(placementRows);
const newPlacements = placementRows.filter((row) => row.index != index);
console.log(newPlacements);
setPlacementRows(newPlacements);
};
const handleAddRow = () => {
setPlacementRows((prevRows: any) => [...prevRows, { index: incrementId, roleId: "", userLevelId: 0 }]);
setIncrementId((prevId) => prevId + 1);
};
return (
<div>
<SiteBreadcrumb />
@ -118,6 +242,22 @@ export default function AddExpertForm() {
</FormItem>
)}
/>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<Input
value={field.value}
placeholder="Masukkan Username"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phoneNumber"
@ -152,45 +292,16 @@ export default function AddExpertForm() {
/>
<FormField
control={form.control}
name="position"
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Posisi</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih Region" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="koor-kurator">Koor Kurator</SelectItem>
<SelectItem value="kurator">Kurator</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<FormItem>
<FormLabel>Wilayah</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih Region" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="nasional">Nasional</SelectItem>
<SelectItem value="jakarta">DKI Jakarta</SelectItem>
<SelectItem value="jabar">Jawa Barat</SelectItem>
</SelectContent>
</Select>
<FormLabel>Password</FormLabel>
<Input
type="password"
value={field.value}
placeholder="Masukkan Password"
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
@ -208,19 +319,20 @@ export default function AddExpertForm() {
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="komunikasi">Komunikasi</SelectItem>
<SelectItem value="hukum">Hukum</SelectItem>
<SelectItem value="bahasa">Bahasa</SelectItem>
{userCompetencies?.map((item: any) => (
<SelectItem key={item.id} value={String(item.id)}>
{item.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="experience"
name="experiences"
render={({ field }) => (
<FormItem>
<FormLabel>Pengalaman</FormLabel>
@ -231,15 +343,13 @@ export default function AddExpertForm() {
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="akademisi">Akademisi</SelectItem>
<SelectItem value="praktisi">Praktisi</SelectItem>
<SelectItem value="akademisi+praktisi">
Akademisi + Praktisi
</SelectItem>
{userExperiences?.map((item: any) => (
<SelectItem key={item.id} value={String(item.id)}>
{item.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
@ -261,6 +371,59 @@ export default function AddExpertForm() {
)}
/>
<div className="mt-4">
<FormLabel>Penempatan</FormLabel>
{placementRows?.map((row: any) => (
<div key={row.index} className="flex items-center gap-2 my-2">
<Select onValueChange={(e) => handleSelectionChange(row.index, "roleId", e)} >
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih Role" />
</SelectTrigger>
</FormControl>
<SelectContent>
{roleSelection?.map((item: any) => (
<SelectItem key={item.id} value={String(item.id)}>
{item.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Select onValueChange={(e) => handleSelectionChange(row.index, "userLevelId", e)}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Pilih User Level" />
</SelectTrigger>
</FormControl>
<SelectContent>
{userLevels?.map((item: any) => (
<SelectItem key={item.id} value={String(item.id)}>
{item.name}
</SelectItem>
))}
</SelectContent>
</Select>
{placementRows.length > 1 && (
<Button
type="button"
size="md"
color="destructive"
onClick={() => handleRemoveRow(row.index)}
>
Hapus
</Button>
)}
</div>
))}
<Button
type="button"
size="md"
onClick={() => handleAddRow()}
>
Tambah
</Button>
</div>
<div className="flex flex-row justify-end gap-2 mt-4 pt-4">
<Button
size="md"

View File

@ -434,7 +434,7 @@ export default function FormContestDetail() {
fileTypeId: string,
duration: string
) {
console.log(idx, id, file, fileTypeId, duration);
console.log("Param Upload : ", idx, id, file, fileTypeId, duration);
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token;
@ -449,7 +449,7 @@ export default function FormContestDetail() {
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
assignmentId: id,
contestId: id,
filename: file.name,
contentType: file.type,
fileTypeId: fileTypeId,

View File

@ -160,7 +160,8 @@ const LoginForm = () => {
Number(profile?.data?.data?.roleId) == 10 ||
Number(profile?.data?.data?.roleId) == 11 ||
Number(profile?.data?.data?.roleId) == 12 ||
Number(profile?.data?.data?.roleId) == 18
Number(profile?.data?.data?.roleId) == 18 ||
Number(profile?.data?.data?.roleId) == 19
) {
if (profile?.data?.data?.roleId === 18) {
window.location.href = "/in/dashboard/executive";

View File

@ -81,7 +81,7 @@ const ProfileInfo = () => {
<div className="text-sm font-medium capitalize lg:block hidden">
{detail?.fullname}
</div>
<p className="text-xs">({detail?.fullname})</p>
<p className="text-xs">({detail?.username})</p>
</div>
<span className="text-base me-2.5 lg:inline-block hidden">
<Icon icon="heroicons-outline:chevron-down"></Icon>

View File

@ -0,0 +1,285 @@
"use client"
import * as React from "react"
import { ChevronsUpDown, Check, CirclePlus } from 'lucide-react';
import { cn, getCookiesDecrypt } from "@/lib/utils"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { useConfig } from "@/hooks/use-config";
import { useMediaQuery } from "@/hooks/use-media-query";
import { motion, AnimatePresence } from "framer-motion";
import { useMenuHoverConfig } from "@/hooks/use-menu-hover";
import { getInfoProfile } from "@/service/auth";
import { getUserRolePlacements, saveUserRolePlacements } from "@/service/management-user/management-user";
// var groups = [
// {
// label: "Wilayah Tugas",
// teams: [],
// },
// ]
// type Team = (typeof groups)[number]["teams"][number]
type PopoverTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverTrigger>
interface TeamSwitcherProps extends PopoverTriggerProps { }
const scaleVariants = {
collapsed: { scale: 0.8 },
expanded: { scale: 1 }
};
export default function TeamWorkspaceSwitcher({ className }: TeamSwitcherProps) {
const [config] = useConfig();
const [hoverConfig] = useMenuHoverConfig();
const { hovered } = hoverConfig;
const [detail, setDetail] = React.useState<any>();
const userId = getCookiesDecrypt("uie");
const [open, setOpen] = React.useState(false);
const [showNewTeamDialog, setShowNewTeamDialog] = React.useState(false);
const [groups, setGroups] = React.useState<any>()
const [selectedTeam, setSelectedTeam] = React.useState<any>({ label: "", value: "" });
const isDesktop = useMediaQuery("(min-width: 1280px)")
if (config.showSwitcher === false || config.sidebar === 'compact') return null
React.useEffect(() => {
async function initState() {
const response = await getInfoProfile();
const details = response?.data?.data;
setDetail(details);
console.log("data", details);
}
async function getPlacement() {
const response = await getUserRolePlacements(Number(userId));
const data = response?.data?.data;
var placementArr: any[] = [];
data?.forEach((row: any) => {
placementArr.push({
label: row.roleName + " | " + row.userLevelName,
value: Number(row.id),
});
});
const groupsTemp = [
{
label: "Wilayah Tugas",
teams: placementArr,
}
];
setGroups(groupsTemp);
}
initState();
getPlacement();
}, []);
return (
<Dialog open={showNewTeamDialog} onOpenChange={setShowNewTeamDialog}>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<motion.div
key={(config.collapsed && !hovered) ? "collapsed" : "expanded"}
initial={{ scale: 0.9 }}
animate={{ scale: 1 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
>
{(config.collapsed && !hovered) ? <Button
variant="outline"
color="secondary"
role="combobox"
fullWidth
aria-expanded={open}
aria-label="Select a team"
className={cn(" h-14 w-14 mx-auto p-0 md:p-0 dark:border-secondary ring-offset-sidebar", className)}
>
<Avatar className="">
<AvatarImage
height={24}
width={24}
// src={session?.user?.image as any}
alt={selectedTeam.label}
className="grayscale"
/>
<AvatarFallback>{detail?.username}</AvatarFallback>
</Avatar>
</Button> : <Button
variant="outline"
color="secondary"
role="combobox"
fullWidth
aria-expanded={open}
aria-label="Select a team"
className={cn(" h-auto py-3 md:px-3 px-3 justify-start dark:border-secondary ring-offset-sidebar", className)}
>
<div className=" flex gap-2 flex-1 items-center">
<Avatar className=" flex-none h-[38px] w-[38px]">
{/* <AvatarImage
height={38}
width={38}
// src={session?.user?.image as any}
alt={selectedTeam.label}
className="grayscale"
/> */}
<AvatarFallback><p className="text-md uppercase">{detail?.username[0]}</p></AvatarFallback>
</Avatar>
<div className="flex-1 text-start w-[100px]">
<div className=" text-sm font-semibold text-default-900">{detail?.username}</div>
<div className=" text-xs font-normal text-default-500 dark:text-default-700 truncate ">{selectedTeam.label}</div>
</div>
<div className="">
<ChevronsUpDown className="ml-auto h-5 w-5 shrink-0 text-default-500 dark:text-default-700" />
</div>
</div>
</Button>}
</motion.div>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandList>
<CommandInput placeholder="Search team..." className=" placeholder:text-xs" />
<CommandEmpty>No team found.</CommandEmpty>
{groups?.map((group: any) => (
<CommandGroup key={group.label} heading={group.label}>
{group.teams.map((team: any) => (
<CommandItem
key={team.value}
onSelect={() => {
setSelectedTeam(team)
setOpen(false)
}}
className="text-sm font-normal"
>
{team.label}
<Check
className={cn(
"ml-auto h-4 w-4",
selectedTeam.value === team.value
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
{/* <CommandSeparator /> */}
{/* <CommandList>
<CommandGroup>
<DialogTrigger asChild>
<CommandItem
onSelect={() => {
setOpen(false)
setShowNewTeamDialog(true)
}}
>
<CirclePlus className="mr-2 h-5 w-5" />
Create Team
</CommandItem>
</DialogTrigger>
</CommandGroup>
</CommandList> */}
</Command>
</PopoverContent>
</Popover>
<DialogContent>
<DialogHeader>
<DialogTitle>Create team</DialogTitle>
<DialogDescription>
Add a new team to manage products and customers.
</DialogDescription>
</DialogHeader>
<div>
<div className="space-y-4 py-2 pb-4">
<div className="space-y-2">
<Label htmlFor="name">Team name</Label>
<Input id="name" placeholder="Acme Inc." />
</div>
<div className="space-y-2">
<Label htmlFor="plan">Subscription plan</Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a plan" />
</SelectTrigger>
<SelectContent>
<SelectItem value="free">
<span className="font-medium">Free</span> -{" "}
<span className="text-muted-foreground">
Trial for two weeks
</span>
</SelectItem>
<SelectItem value="pro">
<span className="font-medium">Pro</span> -{" "}
<span className="text-muted-foreground">
$9/month per user
</span>
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowNewTeamDialog(false)}>
Cancel
</Button>
<Button type="submit">Continue</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@ -3,7 +3,7 @@
import React from 'react'
import { Ellipsis, LogOut } from "lucide-react";
import { usePathname } from "@/components/navigation";
import { cn } from "@/lib/utils";
import { cn, getCookiesDecrypt } from "@/lib/utils";
import { getMenuList } from "@/lib/menus";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
@ -26,6 +26,7 @@ import Logo from '@/components/logo';
import SidebarHoverToggle from '@/components/partials/sidebar/sidebar-hover-toggle';
import { useMenuHoverConfig } from '@/hooks/use-menu-hover';
import { useMediaQuery } from '@/hooks/use-media-query';
import TeamWorkspaceSwitcher from '../common/team-workspace-switcher';
export function MenuClassic({ }) {
@ -36,7 +37,7 @@ export function MenuClassic({ }) {
const direction = getLangDir(params?.locale ?? '');
const isDesktop = useMediaQuery('(min-width: 1280px)')
const userRoleId = getCookiesDecrypt("urie");
const menuList = getMenuList(pathname, t);
const [config, setConfig] = useConfig()
@ -71,17 +72,16 @@ export function MenuClassic({ }) {
<ScrollArea className="[&>div>div[style]]:!block" dir={direction}>
{/* {isDesktop && (
{isDesktop && Number(userRoleId) == 19 ? (
<div className={cn(' space-y-3 mt-6 ', {
'px-4': !collapsed || hovered,
'text-center': collapsed || !hovered
})}>
<TeamSwitcher />
<SearchBar />
<TeamWorkspaceSwitcher />
{/* <SearchBar /> */}
</div>
)} */}
) : ""}
<nav className="mt-4 h-full w-full">
<ul className=" h-full flex flex-col min-h-[calc(100vh-48px-36px-16px-32px)] lg:min-h-[calc(100vh-32px-40px-32px)] items-start space-y-1 px-4">

View File

@ -1,3 +1,4 @@
import { Router } from "next/router";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
@ -29,7 +30,7 @@ export function error(msg: string): void {
});
}
export function success(redirect: string): void {
export function success(router: Router, redirect: string): void {
MySwal.fire({
title: '<p class="text-green-600 font-bold">Sukses</p>',
icon: "success",
@ -38,7 +39,7 @@ export function success(redirect: string): void {
allowOutsideClick: false,
}).then((result) => {
if (result.isConfirmed) {
window.location.href = redirect;
router.push(redirect);
}
});
}

View File

@ -2661,7 +2661,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
],
},
];
} else if (Number(roleId) == 11) {
} else if (Number(roleId) == 11 || Number(roleId) == 19) {
menusSelected = [
{
groupLabel: t("apps"),

View File

@ -17,7 +17,6 @@ const axiosInterceptorInstance = axios.create({
// Request interceptor
axiosInterceptorInstance.interceptors.request.use(
(config) => {
console.log("Config interceptor : ", config);
const accessToken = Cookies.get("access_token");
if (accessToken) {
if (config.headers)

View File

@ -30,16 +30,32 @@ export async function getListSchools() {
const url = "users/user-schools/list";
return httpGetInterceptor(url);
}
export async function getListCompetencies() {
const url = "users/user-competencies/list";
return httpGetInterceptor(url);
}
export async function getListExperiences() {
const url = "users/user-experiences/list";
return httpGetInterceptor(url);
}
export async function saveUserInternal(data: any) {
const url = "users/save";
return httpPostInterceptor(url, data);
}
export async function saveUserRolePlacements(data: any) {
const url = "users/role-placements";
return httpPostInterceptor(url, data);
}
export async function getUserRolePlacements(userId: number) {
const url = `users/role-placements?userId=${userId}`;
return httpGetInterceptor(url);
}
export async function getUserById(id: string) {
const url = `users?id=${id}`;
return httpGetInterceptor(url);