progress
This commit is contained in:
parent
23585f0bb7
commit
0e338fa921
|
@ -0,0 +1,106 @@
|
|||
"use client"
|
||||
import { useAppSelector } from "@/lib/hooks";
|
||||
import { useGetAttendanceRangeQuery, useGetMonthlyAttendanceQuery, useGetOrganizationAttendanceQuery } from "@/services/api";
|
||||
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { BarChart, ChartsTooltip, LineChart, pieArcClasses, pieArcLabelClasses, PieChart } from "@mui/x-charts";
|
||||
import { ChartsNoDataOverlay } from "@mui/x-charts/ChartsOverlay";
|
||||
import { formatDate } from "date-fns";
|
||||
|
||||
export default function AbsensiPage() {
|
||||
const filter = useAppSelector(state => state.filter.filter);
|
||||
const {data: attendanceSummary} = useGetOrganizationAttendanceQuery(filter);
|
||||
const {data: attendanceRange} = useGetAttendanceRangeQuery(filter);
|
||||
const {data: montlyAttendance} = useGetMonthlyAttendanceQuery(filter);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[640px] flex flex-col">
|
||||
<div className="text-xl font-bold">Kehadiran Staff</div>
|
||||
<div className="flex-1 flex">
|
||||
{attendanceRange && <PieChart className="w-full" series={[
|
||||
{
|
||||
arcLabelMinAngle: 35,
|
||||
arcLabel: (v) => `${v.label} HK`,
|
||||
data: attendanceRange.map(e => ({label: e.range, value: e.count})),
|
||||
innerRadius: "50%",
|
||||
valueFormatter: (v) => v.value+" employees"
|
||||
},
|
||||
]} colors={[
|
||||
"#E65550","#8A5FB1","#69C9C9","#F08431","#F6C421"
|
||||
]}
|
||||
slotProps={{
|
||||
legend: {
|
||||
direction: "row",
|
||||
position: {vertical: "bottom", horizontal: "middle"}
|
||||
},
|
||||
pieArcLabel: {
|
||||
classes: {
|
||||
root: "text-white fill-current"
|
||||
}
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
[`& .${pieArcLabelClasses.root}`]: {
|
||||
color:"white",
|
||||
fontWeight: "bold"
|
||||
}}}
|
||||
margin={{top: 20, right: 20, bottom: 20, left: 20}}
|
||||
slots={{
|
||||
noDataOverlay: (props) => <ChartsNoDataOverlay {...props} message="Data belum tersedia" />
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[640px] flex flex-col">
|
||||
<div className="text-xl font-bold">Kehadiran Non Staff</div>
|
||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 max-h-[640px] flex flex-col gap-4">
|
||||
<div className="bg-white rounded-lg py-4 pl-4 flex flex-col">
|
||||
<div className="text-xl font-bold">Kehadiran Non Staff</div>
|
||||
<div className="flex-1 min-h-[253px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg py-4 pl-4 flex flex-col">
|
||||
<div className="text-xl font-bold">Kehadiran Non Staff</div>
|
||||
<div className="flex-1 min-h-[253px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Data Kehadiran Karyawan Setiap Perusahaan</div>
|
||||
<div className="flex-1 min-h-[300px]">
|
||||
{attendanceSummary && <BarChart className="w-full" dataset={attendanceSummary} series={[
|
||||
{dataKey: "count", label: (v) => v === "tooltip" ? "Attendance" : undefined!, color: "#2385DE", stack: "attendance", stackOffset: "expand", valueFormatter: (v) => v + " mandays"},
|
||||
{dataKey: "absent", label: (v) => v === "tooltip" ? "Absent" : undefined!, color: "#F7B500", stack: "attendance", stackOffset: "expand", valueFormatter: (v) => v + " mandays"},
|
||||
]} xAxis={[
|
||||
{dataKey: "organization_code", label: "Nama Perusahaan", scaleType: "band", valueFormatter: (v, context) => context.location === "tooltip" ? attendanceSummary.find(e => e.organization_code === v)?.organization_name : v}
|
||||
]}
|
||||
yAxis={[
|
||||
{dataKey: "count",scaleType: "linear", valueFormatter: (v) => (v*100)+"%"}
|
||||
]}
|
||||
slots={{
|
||||
noDataOverlay: ()=> <ChartsNoDataOverlay message="Data belum tersedia" />,
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Data Karyawan Perbulan</div>
|
||||
<div className="flex-1 min-h-[300px]">
|
||||
{montlyAttendance && <LineChart className="w-full" dataset={montlyAttendance} series={[
|
||||
{dataKey: "count", label: (v) => "Kehadiran" , area: false, color: "#F7CAA9"},
|
||||
{dataKey: "workdays", label: (v) => "Mandays", area: false, color: "#2385DE"},
|
||||
]} xAxis={[
|
||||
{dataKey: "date", label: "Bulan", scaleType: "band", valueFormatter: (v, context) => formatDate(new Date(v), context.location === "tooltip" ? "MMMM yyyy" : "MMM")}
|
||||
]}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
"use client"
|
||||
import { useAppSelector } from "@/lib/hooks";
|
||||
import { useGetEmployeeSummaryQuery, useGetMonthlyEmployeeQuery } from "@/services/api";
|
||||
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { BarChart, ChartsLegend, ChartsTooltip, ChartsXAxis, ChartsYAxis, LineChart } from "@mui/x-charts";
|
||||
import { formatDate } from "date-fns";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function KaryawanPage() {
|
||||
|
||||
const filter = useAppSelector(state => state.filter.filter);
|
||||
const {data: employeeSummary} = useGetEmployeeSummaryQuery(filter);
|
||||
const {data: montlyEmployee} = useGetMonthlyEmployeeQuery(filter);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(filter);
|
||||
}, [filter]);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Data Karyawan</div>
|
||||
<div className="flex-1 min-h-[300px]">
|
||||
{employeeSummary && <BarChart className="w-full" dataset={employeeSummary} series={[
|
||||
{dataKey: "count", label: (v) => v === "tooltip" ? "Jumlah Karyawan" : undefined!, color: "#2385DE"}
|
||||
]} xAxis={[
|
||||
{dataKey: "organization_code", label: "Nama Perusahaan", scaleType: "band", valueFormatter: (v, context) => context.location === "tooltip" ? employeeSummary.find(e => e.organization_code === v)?.organization_name : v}
|
||||
]} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Data Karyawan Perbulan</div>
|
||||
<div className="flex-1 min-h-[300px]">
|
||||
{montlyEmployee && <LineChart className="w-full" dataset={montlyEmployee} series={[
|
||||
{dataKey: "count", label: (v) => v === "tooltip" ? "Jumlah Karyawan" : undefined!, area: true, color: "#F7CAA9"}
|
||||
]} xAxis={[
|
||||
{dataKey: "date", label: "Bulan", scaleType: "band", valueFormatter: (v, context) => formatDate(new Date(v), context.location === "tooltip" ? "MMMM yyyy" : "MMM")}
|
||||
]} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Pergerakan Karyawan</div>
|
||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-8 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Ranking (Top 10) Total Pergerakan Karyawan Setiap Perusahaan</div>
|
||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Penjatuhan Sanksi</div>
|
||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-8 bg-white rounded-lg py-4 pl-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Ranking (Top 10) Total Penjatuhan Sanksi Setiap Perusahaan</div>
|
||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
"use client"
|
||||
import { DocumentCurrencyDollarIcon, HomeIcon, UsersIcon } from "@heroicons/react/24/outline";
|
||||
import { InputLabel, MenuItem, Select, TextField } from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import { BarChart2, CircleDollarSign, LogOut, RefreshCcw } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import logo from "../../../public/images/logo.png";
|
||||
import midsuit from "../../../public/images/midsuit.png";
|
||||
import Image from "next/image";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import React from "react";
|
||||
import { Filter } from "@/services/types";
|
||||
import { useAppDispatch, useAppSelector } from "@/lib/hooks";
|
||||
import { setFilter } from "@/lib/slice/filter";
|
||||
import { format } from "date-fns";
|
||||
|
||||
|
||||
export default function DashboardLayout({children}:{children: React.ReactNode}) {
|
||||
|
||||
const pathname = usePathname();
|
||||
const dispatch = useAppDispatch();
|
||||
const filter = useAppSelector((state) => state.filter.filter);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen">
|
||||
<div className="w-80 flex flex-col sticky h-screen">
|
||||
<div className="w-80">
|
||||
<Image src={logo} alt="logo" sizes="100" className="w-96"/>
|
||||
</div>
|
||||
<div className="h-4 bg-white"/>
|
||||
<div className="flex flex-col flex-1 from-[#A36A4D] bg-gradient-to-b to-[#5B2D14] text-white py-8">
|
||||
<div className="text-[20px] text-white ml-8 mb-4">Halaman</div>
|
||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/dashboard/karyawan' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/dashboard/karyawan">
|
||||
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||
<path d="M2 16H5V10H11V16H14V7L8 2.5L2 7V16ZM0 18V6L8 0L16 6V18H9V12H7V18H0Z" />
|
||||
</svg>
|
||||
<span className="font-semibold text-base">Data Karyawan</span>
|
||||
</Link>
|
||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/dashboard/absensi' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/dashboard/absensi">
|
||||
<svg width="23" height="16" viewBox="0 0 23 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||
<path d="M16.55 8L13 4.45L14.425 3.05L16.55 5.175L20.8 0.925L22.2 2.35L16.55 8ZM8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391667 6.9 0 8 0C9.1 0 10.0417 0.391667 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM0 16V13.2C0 12.6333 0.145833 12.1125 0.4375 11.6375C0.729167 11.1625 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.64583 4.75 9.3875C5.81667 9.12917 6.9 9 8 9C9.1 9 10.1833 9.12917 11.25 9.3875C12.3167 9.64583 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2708 11.1625 15.5625 11.6375C15.8542 12.1125 16 12.6333 16 13.2V16H0ZM2 14H14V13.2C14 13.0167 13.9542 12.85 13.8625 12.7C13.7708 12.55 13.65 12.4333 13.5 12.35C12.6 11.9 11.6917 11.5625 10.775 11.3375C9.85833 11.1125 8.93333 11 8 11C7.06667 11 6.14167 11.1125 5.225 11.3375C4.30833 11.5625 3.4 11.9 2.5 12.35C2.35 12.4333 2.22917 12.55 2.1375 12.7C2.04583 12.85 2 13.0167 2 13.2V14ZM8 6C8.55 6 9.02083 5.80417 9.4125 5.4125C9.80417 5.02083 10 4.55 10 4C10 3.45 9.80417 2.97917 9.4125 2.5875C9.02083 2.19583 8.55 2 8 2C7.45 2 6.97917 2.19583 6.5875 2.5875C6.19583 2.97917 6 3.45 6 4C6 4.55 6.19583 5.02083 6.5875 5.4125C6.97917 5.80417 7.45 6 8 6Z"/>
|
||||
</svg>
|
||||
<span className="font-semibold text-base">Absensi</span>
|
||||
</Link>
|
||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/dashboard/turnover' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/dashboard/turnover">
|
||||
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||
<path d="M7.75 15V11.5H6.25V7.9C6.25 7.51667 6.5125 7.1875 7.0375 6.9125C7.5625 6.6375 8.21667 6.5 9 6.5C9.78333 6.5 10.4375 6.6375 10.9625 6.9125C11.4875 7.1875 11.75 7.51667 11.75 7.9V11.5H10.25V15H7.75ZM9 6C8.56667 6 8.20833 5.85833 7.925 5.575C7.64167 5.29167 7.5 4.93333 7.5 4.5C7.5 4.06667 7.64167 3.70833 7.925 3.425C8.20833 3.14167 8.56667 3 9 3C9.43333 3 9.79167 3.14167 10.075 3.425C10.3583 3.70833 10.5 4.06667 10.5 4.5C10.5 4.93333 10.3583 5.29167 10.075 5.575C9.79167 5.85833 9.43333 6 9 6ZM9 18C6.5 18 4.375 17.125 2.625 15.375C0.875 13.625 0 11.5 0 9C0 7.75 0.2375 6.57917 0.7125 5.4875C1.1875 4.39583 1.82917 3.44583 2.6375 2.6375C3.44583 1.82917 4.39583 1.1875 5.4875 0.7125C6.57917 0.2375 7.75 0 9 0C10.25 0 11.4208 0.2375 12.5125 0.7125C13.6042 1.1875 14.5542 1.82917 15.3625 2.6375C16.1708 3.44583 16.8125 4.39583 17.2875 5.4875C17.7625 6.57917 18 7.75 18 9V9.2L19.825 7.35L21.25 8.75L17 13L12.75 8.75L14.175 7.35L16 9.175V9C16 7.06667 15.3167 5.41667 13.95 4.05C12.5833 2.68333 10.9333 2 9 2C7.06667 2 5.41667 2.68333 4.05 4.05C2.68333 5.41667 2 7.06667 2 9C2.01667 10.9333 2.70417 12.5833 4.0625 13.95C5.42083 15.3167 7.06667 16 9 16C9.95 16 10.8417 15.8208 11.675 15.4625C12.5083 15.1042 13.2417 14.6167 13.875 14L15.3 15.425C14.4833 16.225 13.5375 16.8542 12.4625 17.3125C11.3875 17.7708 10.2333 18 9 18Z" />
|
||||
</svg>
|
||||
<span className="font-semibold text-base">Turn Over Rate</span>
|
||||
</Link>
|
||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/dashboard/produktifitas' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/dashboard/produktifitas">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||
<path d="M0 16V8H4V16H0ZM6 16V0H10V16H6ZM12 16V5H16V16H12Z" />
|
||||
</svg>
|
||||
<span className="font-semibold text-base">Produktifitas Karyawan</span>
|
||||
</Link>
|
||||
<Link className={`flex gap-4 px-8 py-4 ${pathname === '/dashboard/hr-cost' ? `bg-white text-[#664228]`:`text-white hover:bg-white hover:text-[#664228]`} items-center`} href="/dashboard/hr-cost">
|
||||
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||
<path d="M13 9C12.1667 9 11.4583 8.70833 10.875 8.125C10.2917 7.54167 10 6.83333 10 6C10 5.16667 10.2917 4.45833 10.875 3.875C11.4583 3.29167 12.1667 3 13 3C13.8333 3 14.5417 3.29167 15.125 3.875C15.7083 4.45833 16 5.16667 16 6C16 6.83333 15.7083 7.54167 15.125 8.125C14.5417 8.70833 13.8333 9 13 9ZM6 12C5.45 12 4.97917 11.8042 4.5875 11.4125C4.19583 11.0208 4 10.55 4 10V2C4 1.45 4.19583 0.979167 4.5875 0.5875C4.97917 0.195833 5.45 0 6 0H20C20.55 0 21.0208 0.195833 21.4125 0.5875C21.8042 0.979167 22 1.45 22 2V10C22 10.55 21.8042 11.0208 21.4125 11.4125C21.0208 11.8042 20.55 12 20 12H6ZM8 10H18C18 9.45 18.1958 8.97917 18.5875 8.5875C18.9792 8.19583 19.45 8 20 8V4C19.45 4 18.9792 3.80417 18.5875 3.4125C18.1958 3.02083 18 2.55 18 2H8C8 2.55 7.80417 3.02083 7.4125 3.4125C7.02083 3.80417 6.55 4 6 4V8C6.55 8 7.02083 8.19583 7.4125 8.5875C7.80417 8.97917 8 9.45 8 10ZM19 16H2C1.45 16 0.979167 15.8042 0.5875 15.4125C0.195833 15.0208 0 14.55 0 14V3H2V14H19V16Z"/>
|
||||
</svg>
|
||||
<span className="font-semibold text-base">HR Cost</span>
|
||||
</Link>
|
||||
<Link className="mt-auto flex text-white gap-4 px-8 py-4 hover:bg-white hover:text-[#664228] items-center " href="/dashboard/hr-cost">
|
||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg" className="fill-current">
|
||||
<path d="M2 18.2554C1.45 18.2554 0.979167 18.0595 0.5875 17.6679C0.195833 17.2762 0 16.8054 0 16.2554V2.25537C0 1.70537 0.195833 1.23454 0.5875 0.842871C0.979167 0.451204 1.45 0.255371 2 0.255371H9V2.25537H2V16.2554H9V18.2554H2ZM13 14.2554L11.625 12.8054L14.175 10.2554H6V8.25537H14.175L11.625 5.70537L13 4.25537L18 9.25537L13 14.2554Z"/>
|
||||
</svg>
|
||||
<span className="font-semibold text-base">Log Out</span>
|
||||
</Link>
|
||||
<div className="flex gap-4 px-8 py-4 mt-4 items-center">
|
||||
<span className="text-[12px]">Powered by</span>
|
||||
<div>
|
||||
<Image src={midsuit} alt="midsuit" width={80} height={20}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex flex-col h-screen">
|
||||
<div className="flex bg-white p-4 justify-between">
|
||||
<div className="flex gap-1 items-center">
|
||||
<span>ID</span>
|
||||
<div className="w-0.5 bg-amber-800 h-4"/>
|
||||
<span>CN</span>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<DatePicker label="Start Date" className="w-40"
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: "small"
|
||||
}
|
||||
}}
|
||||
defaultValue={(new Date(new Date().getFullYear(), new Date().getMonth(), 1))}
|
||||
onChange={(date) => dispatch(setFilter({...filter, start_date: format(date ?? new Date(), "yyyy-MM-dd")}))}
|
||||
/>
|
||||
<DatePicker label="End Date" className="w-40"
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: "small",
|
||||
}
|
||||
}}
|
||||
defaultValue={new Date()}
|
||||
onChange={(date) => dispatch(setFilter({...filter, end_date: format(date ?? new Date(), "yyyy-MM-dd")}))}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 w-80">
|
||||
<TextField label="Company" size="small" select className="w-full">
|
||||
<MenuItem value="0">Semua Perusahaan</MenuItem>
|
||||
<MenuItem value="1">PT. A</MenuItem>
|
||||
<MenuItem value="2">PT. B</MenuItem>
|
||||
<MenuItem value="3">PT. C</MenuItem>
|
||||
</TextField>
|
||||
<TextField label="Lokasi" size="small" select className="w-full">
|
||||
<MenuItem value="0">Semua Lokasi</MenuItem>
|
||||
<MenuItem value="1">Lokasi 1</MenuItem>
|
||||
<MenuItem value="2">Lokasi 2</MenuItem>
|
||||
<MenuItem value="3">Lokasi 3</MenuItem>
|
||||
</TextField>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<TextField label="Posisi" size="small" className="w-40" select value={filter.job_name} onChange={(e) => dispatch(setFilter({...filter, job_name: e.target.value}))}>
|
||||
<MenuItem value="ALL">Semua</MenuItem>
|
||||
<MenuItem value="STAFF">Staff</MenuItem>
|
||||
<MenuItem value="NON-STAFF">Non Staff</MenuItem>
|
||||
<MenuItem value="HARVESTERS">Pemanen</MenuItem>
|
||||
<MenuItem value="MAINTENANCE">Perawatan</MenuItem>
|
||||
</TextField>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[#f0f0f0] h-full p-4 overflow-y-auto">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
"use client"
|
||||
import { useAppSelector } from "@/lib/hooks";
|
||||
import { useDimensions } from "@/lib/hooks/dimension";
|
||||
import { useGetResignCategoryQuery, useGetResignReasonQuery, useGetResignSummaryQuery, useGetResignTypeQuery } from "@/services/api";
|
||||
import { BarChart, pieArcLabelClasses, PieChart } from "@mui/x-charts";
|
||||
import { ChartsNoDataOverlay } from "@mui/x-charts/ChartsOverlay";
|
||||
import { Loader, LucideLoader2 } from "lucide-react";
|
||||
import React from "react";
|
||||
import { useRef } from "react";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
export default function TurnoverPage() {
|
||||
|
||||
const filter = useAppSelector(state => state.filter.filter);
|
||||
const resignRef = useRef<HTMLDivElement>(null);
|
||||
const {width, height} = useDimensions(resignRef);
|
||||
const {data : resignSummary, isFetching: resignSummaryLoading} = useGetResignSummaryQuery(filter);
|
||||
const {data : resignType, isFetching: resignTypeLoading} = useGetResignTypeQuery(filter);
|
||||
const {data : resignCategory, isFetching: resignCategoryLoading} = useGetResignCategoryQuery(filter);
|
||||
const {data : resignReason, isFetching: resignReasonLoading} = useGetResignReasonQuery(filter);
|
||||
|
||||
const [turnOverRatio, setTurnOverRatio] = React.useState(0);
|
||||
const [resign, setResign] = React.useState(0);
|
||||
const [active, setActive] = React.useState(0);
|
||||
|
||||
const [maxResignType, setMaxResignType] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
if(resignSummary){
|
||||
const totalResign = resignSummary.reduce((acc, curr) => acc + curr.count, 0);
|
||||
const totalActive = resignSummary.reduce((acc, curr) => acc + curr.active, 0);
|
||||
setTurnOverRatio(Math.round(totalResign / totalActive * 100));
|
||||
setResign(totalResign);
|
||||
setActive(totalActive);
|
||||
}
|
||||
}, [resignSummary]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if(resignType){
|
||||
const max = resignType.reduce((acc, curr) => Math.max(acc, curr.count), 0);
|
||||
setMaxResignType(max);
|
||||
}
|
||||
}, [resignType]);
|
||||
|
||||
const getColorByCategory = (category: string) => {
|
||||
switch(category){
|
||||
case "Whitelist":
|
||||
return "#EAEAEA";
|
||||
case "Blacklist":
|
||||
return "#3D3D3D";
|
||||
case "Yellowlist":
|
||||
return "#F5C41F";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-lg font-bold">Karyawan Baru Seluruh Perusahaan</div>
|
||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-8 bg-white rounded-lg py-4 px-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-lg font-bold">Man Power Planning per Perusahaan : Recruitment</div>
|
||||
<div className="flex-1 min-h-[300px] flex justify-center items-center flex-col">
|
||||
<span className="text-gray-600">Data belum tersedia</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white rounded-lg py-4 px-4 max-h-[420px] flex flex-col">
|
||||
<div className="text-lg font-bold">Karyawan Resign Seluruh Perusahaan</div>
|
||||
<div className="relative h-24 bg-[#C7EBEB] mt-8 rounded-xl" ref={resignRef}>
|
||||
<div className={`absolute top-0 left-0 h-24 bg-[#6AC8C8] rounded-xl transition-all duration-1000`} style={{width: `${width * turnOverRatio / 100}px`}}></div>
|
||||
</div>
|
||||
<div className="flex mt-6 justify-around gap-3">
|
||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||
<div className="text-sm text-[#5A5A5A]">Rasio Turn Over</div>
|
||||
<div className="text-xl font-bold">{turnOverRatio} %</div>
|
||||
</div>
|
||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||
<div className="text-sm text-[#5A5A5A]">Karyawan Resign</div>
|
||||
<div className="text-xl font-bold">{resign}</div>
|
||||
</div>
|
||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||
<div className="text-sm text-[#5A5A5A]">Karyawan Aktif</div>
|
||||
<div className="text-xl font-bold">{active}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-8 bg-white py-4 pl-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Resignment per Perusahaan : Jumlah Karyawan</div>
|
||||
<div className="flex-1 min-h-[300px] relative">
|
||||
{resignSummary && !resignSummaryLoading && <BarChart className="w-full" dataset={resignSummary} series={[
|
||||
{dataKey: "count", label: (v) => v === "tooltip" ? "Resign" : undefined!, color: "#F7B500", stack: "resign", stackOffset: "expand"},
|
||||
{dataKey: "active", label: (v) => v === "tooltip" ? "Active" : undefined!, color: "#2385DE", stack: "resign", stackOffset: "expand"},
|
||||
]} xAxis={[
|
||||
{dataKey: "organization_code", label: "Nama Perusahaan", scaleType: "band", valueFormatter: (v, context) => context.location === "tooltip" ? resignSummary.find(e => e.organization_code === v)?.organization_name : v}
|
||||
]}
|
||||
yAxis={[
|
||||
{dataKey: "count",scaleType: "linear", valueFormatter: (v) => (v*100)+"%"}
|
||||
]}
|
||||
slots={{
|
||||
noDataOverlay: ()=> <ChartsNoDataOverlay message="Data belum tersedia" />,
|
||||
}}
|
||||
/>}
|
||||
{resignSummaryLoading && <LucideLoader2 className="absolute top-1/2 left-1/2 animate-spin" />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Jenis Pemutusan Hubungan Kerja</div>
|
||||
<div className={`flex-1 min-h-[300px] gap-3 flex flex-col mt-8 ${resignType && resignType.length === 0 ? "justify-center items-center" : ""}`}>
|
||||
{resignType && resignType.map((resign, index) => (
|
||||
<div key={index} className="grid grid-cols-12 items-center" data-tooltip-id="tooltip-tor-type" data-tooltip-content={`${resign.count} Employee`}>
|
||||
<div className="text-sm col-span-3 text-gray-600">{resign.type}</div>
|
||||
<div className="col-span-1"/>
|
||||
<div className="relative col-span-8 h-6 bg-[#2385DE] rounded-lg">
|
||||
<div className="absolute top-0 left-0 h-6 bg-[#F7B500] rounded-lg" style={{width: `${resign.count / maxResignType * 100}%`}}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{resignType && resignType.length === 0 && <span className="text-black text-sm text-center">Data belum tersedia</span>}
|
||||
<Tooltip id="tooltip-tor-type" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Kategory Resign</div>
|
||||
<div className="w-4/5 flex-1 flex mx-auto">
|
||||
{resignCategory && <PieChart className="" series={[
|
||||
{
|
||||
arcLabelMinAngle: 35,
|
||||
arcLabel: (v) => `${v.label}`,
|
||||
data: resignCategory.map(e => ({label: e.category, value: e.count, color: getColorByCategory(e.category)})),
|
||||
valueFormatter: (v) => v.value+" employees",
|
||||
},
|
||||
]}
|
||||
slotProps={{
|
||||
legend: {
|
||||
direction: "row",
|
||||
position: {vertical: "bottom", horizontal: "middle"}
|
||||
},
|
||||
pieArcLabel: {
|
||||
classes: {
|
||||
root: "hidden"
|
||||
}
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
[`& .${pieArcLabelClasses.root}`]: {
|
||||
color:"white",
|
||||
fontWeight: "bold"
|
||||
}}}
|
||||
margin={{top: 20, right: 20, bottom: 60, left: 20}}
|
||||
slots={{
|
||||
noDataOverlay: (props) => <ChartsNoDataOverlay {...props} message="Data belum tersedia" />
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="text-xl font-bold">Jenis Pemutusan Hubungan Kerja</div>
|
||||
<div className={`flex-1 min-h-[300px] gap-3 flex flex-col mt-8 ${resignReason && resignReason.length === 0 ? "justify-center items-center" : ""}`}>
|
||||
{resignReason && resignReason.map((resign, index) => (
|
||||
<div key={index} className="grid grid-cols-12 items-center" data-tooltip-id="tooltip-tor-type" data-tooltip-content={`${resign.count} Employee`}>
|
||||
<div className="text-sm col-span-3 text-gray-600">{resign.reason}</div>
|
||||
<div className="col-span-1"/>
|
||||
<div className="relative col-span-8 h-6 bg-[#2385DE] rounded-lg">
|
||||
<div className="absolute top-0 left-0 h-6 bg-[#F7B500] rounded-lg" style={{width: `${resign.count / maxResignType * 100}%`}}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{resignReason && resignReason.length === 0 && <span className="text-black text-sm text-center">Data belum tersedia</span>}
|
||||
<Tooltip id="tooltip-tor-type" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue