fix
This commit is contained in:
parent
7ca52e1092
commit
b02e84b48b
|
|
@ -311,9 +311,9 @@ export default function AbsensiPage() {
|
|||
}}
|
||||
formatter={(value, name) => {
|
||||
if (name === t('attendance.persentase')) {
|
||||
return [`${value}%`, name];
|
||||
return [`${Number(value).toLocaleString('id-ID', {maximumFractionDigits: 2})}%`, name];
|
||||
}
|
||||
return [value, name];
|
||||
return [`${Number(value).toLocaleString('id-ID')} ${t('common.hariKerja')}`, name];
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
|
|
|
|||
|
|
@ -110,6 +110,12 @@ export default function HrcostPage() {
|
|||
value: hrCost.total_bpjs_ks,
|
||||
valueInMillions: hrCost.total_bpjs_ks / 1000000000,
|
||||
color: COLORS[2]
|
||||
},
|
||||
{
|
||||
name: t('hrcost.thr'),
|
||||
value: hrCost.total_thr,
|
||||
valueInMillions: hrCost.total_thr / 1000000000,
|
||||
color: COLORS[3]
|
||||
}
|
||||
] : [];
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,18 @@ export default function ProductivityPage() {
|
|||
const totalTonnageAge = productivityByAge?.reduce((sum, item) => sum + (item.tonnage / 1000), 0) || 0;
|
||||
const totalTonnageTenure = productivityByTenure?.reduce((sum, item) => sum + (item.tonnage / 1000), 0) || 0;
|
||||
|
||||
// Calculate number of days between start_date and end_date
|
||||
const calculateDays = (startDate: string, endDate: string) => {
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
const timeDiff = end.getTime() - start.getTime();
|
||||
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)) + 1; // +1 to include both start and end dates
|
||||
return daysDiff;
|
||||
};
|
||||
|
||||
const filterDays = filter.start_date && filter.end_date ?
|
||||
calculateDays(filter.start_date, filter.end_date) : 1;
|
||||
|
||||
// Prepare data for pie charts (convert kg to tons)
|
||||
const ageData = productivityByAge?.map(item => ({
|
||||
name: item.age_range,
|
||||
|
|
@ -54,29 +66,36 @@ export default function ProductivityPage() {
|
|||
percentage: totalTonnageTenure > 0 ? (((item.tonnage / 1000) / totalTonnageTenure) * 100).toFixed(1) : 0
|
||||
})) || [];
|
||||
|
||||
// Prepare data for charts (convert kg to tons and calculate ratio)
|
||||
// Prepare data for charts (convert kg to tons and calculate ratio per employee per day)
|
||||
const regionData = productivityByRegion?.map(item => ({
|
||||
...item,
|
||||
tonnage: item.tonnage / 1000, // Convert kg to tons
|
||||
ratioPerEmployee: item.count > 0 ? (item.tonnage / 1000) / item.count : 0
|
||||
ratioPerEmployee: item.count > 0 && filterDays > 0 ?
|
||||
((item.tonnage / 1000) / item.count) / filterDays : 0
|
||||
})).sort((a, b) => b.ratioPerEmployee - a.ratioPerEmployee) || []; // Sort by ratio descending
|
||||
|
||||
const ageBarData = productivityByAge?.map(item => ({
|
||||
...item,
|
||||
tonnage: item.tonnage / 1000, // Convert kg to tons
|
||||
ratioPerEmployee: item.count > 0 ? (item.tonnage / 1000) / item.count : 0
|
||||
ratioPerEmployee: item.count > 0 && filterDays > 0 ?
|
||||
((item.tonnage / 1000) / item.count) / filterDays : 0
|
||||
})).sort((a, b) => b.ratioPerEmployee - a.ratioPerEmployee) || []; // Sort by ratio descending
|
||||
|
||||
const tenureBarData = productivityByTenure?.map(item => ({
|
||||
...item,
|
||||
tonnage: item.tonnage / 1000, // Convert kg to tons
|
||||
ratioPerEmployee: item.count > 0 ? (item.tonnage / 1000) / item.count : 0
|
||||
ratioPerEmployee: item.count > 0 && filterDays > 0 ?
|
||||
((item.tonnage / 1000) / item.count) / filterDays : 0
|
||||
})).sort((a, b) => b.ratioPerEmployee - a.ratioPerEmployee) || []; // Sort by ratio descending
|
||||
|
||||
// Prepare data for tonnage harvest group employee pie chart
|
||||
const tonnageGroupData = tonnageHarvestGroupEmployee?.map(item => {
|
||||
// Parse tonnage range from kg format (e.g., "0.0 - 20499.9") and convert to tons
|
||||
const parseRange = (range: string) => {
|
||||
if (!range || range.endsWith("+")){
|
||||
return (Number(range.replace("+", "")) / 1000).toFixed(1) + "+ ton"
|
||||
}
|
||||
|
||||
const parts = range.split(' - ');
|
||||
if (parts.length === 2) {
|
||||
const min = parseFloat(parts[0]) / 1000; // Convert kg to tons
|
||||
|
|
@ -243,13 +262,28 @@ export default function ProductivityPage() {
|
|||
{tonnageGroupData && tonnageGroupData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<PieChart>
|
||||
<Legend
|
||||
verticalAlign="middle"
|
||||
align="right"
|
||||
layout="vertical"
|
||||
iconType="circle"
|
||||
wrapperStyle={{ paddingLeft: '20px' }}
|
||||
formatter={(value, entry) => {
|
||||
const totalEmployees = tonnageGroupData.reduce((sum, item) => sum + item.value, 0);
|
||||
const calculatedPercentage = totalEmployees > 0 ? ((entry.payload?.value / totalEmployees) * 100).toFixed(2) : 0;
|
||||
return (
|
||||
<span style={{ color: entry.color }} className="z-0">
|
||||
{value}: {entry.payload?.value?.toLocaleString('id-ID', { maximumFractionDigits: 2 })} {t('common.ton')} ({calculatedPercentage}%)
|
||||
</span>
|
||||
)
|
||||
}}/>
|
||||
<Pie
|
||||
data={tonnageGroupData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={120}
|
||||
innerRadius={80}
|
||||
outerRadius={90}
|
||||
innerRadius={60}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
>
|
||||
|
|
@ -265,14 +299,23 @@ export default function ProductivityPage() {
|
|||
labelFormatter={(label, payload) => {
|
||||
return `${t('productivity.rangeTonase')}: ${label}`;
|
||||
}}
|
||||
content={({ active, payload, label }) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload;
|
||||
const totalEmployees = tonnageGroupData.reduce((sum, item) => sum + item.value, 0);
|
||||
const calculatedPercentage = totalEmployees > 0 ? ((data.value / totalEmployees) * 100).toFixed(2) : 0;
|
||||
return (
|
||||
<div className="bg-white p-3 border rounded shadow z-10">
|
||||
<p className="font-semibold">{`${t('productivity.rangeTonase')}: ${payload[0].name}`}</p>
|
||||
<p className="text-blue-600">{`${t('common.karyawan')}: ${data.value.toLocaleString('id-ID')} ${t('common.orang')}`}</p>
|
||||
<p className="text-gray-600">{`${t('productivity.persentase')}: ${calculatedPercentage}%`}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<Legend
|
||||
verticalAlign="middle"
|
||||
align="right"
|
||||
layout="vertical"
|
||||
iconType="circle"
|
||||
wrapperStyle={{ paddingLeft: '20px' }}
|
||||
/>
|
||||
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
|
|
@ -287,12 +330,28 @@ export default function ProductivityPage() {
|
|||
<div className="h-80">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Legend
|
||||
verticalAlign="middle"
|
||||
align="right"
|
||||
layout="vertical"
|
||||
iconSize={12}
|
||||
wrapperStyle={{ paddingLeft: '10px' }}
|
||||
formatter={(value, entry) => {
|
||||
const totalEmployees = employeeSalaryData.reduce((sum, item) => sum + item.count, 0);
|
||||
const calculatedPercentage = totalEmployees > 0 ? (((entry.payload as any).count / totalEmployees) * 100).toFixed(2) : 0;
|
||||
return (
|
||||
<span style={{ color: entry.color }}>
|
||||
{value}: {entry.payload?.value?.toLocaleString('id-ID', { maximumFractionDigits: 2 })} {t('common.ton')} ({calculatedPercentage}%)
|
||||
</span>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Pie
|
||||
data={employeeSalaryData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={"80%"}
|
||||
innerRadius={"55%"}
|
||||
outerRadius={90}
|
||||
innerRadius={60}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
// label={({ name, percentage }) => `${name}: ${percentage}%`}
|
||||
|
|
@ -311,30 +370,20 @@ export default function ProductivityPage() {
|
|||
content={({ active, payload, label }) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload;
|
||||
const totalEmployees = employeeSalaryData.reduce((sum, item) => sum + item.count, 0);
|
||||
const calculatedPercentage = totalEmployees > 0 ? ((data.count / totalEmployees) * 100).toFixed(2) : 0;
|
||||
return (
|
||||
<div className="bg-white p-3 border rounded shadow">
|
||||
<p className="font-semibold">{`${t('productivity.gaji')}: ${payload[0].name}`}</p>
|
||||
<p className="text-blue-600">{`${t('productivity.output')}: ${data.value.toLocaleString('id-ID')} ${t('common.ton')}`}</p>
|
||||
<p className="text-green-600">{`${t('common.karyawan')}: ${data.count.toLocaleString('id-ID')} ${t('common.orang')}`}</p>
|
||||
<p className="text-gray-600">{`${t('productivity.persentase')}: ${data.percentage}%`}</p>
|
||||
<p className="text-gray-600">{`${t('productivity.persentase')}: ${calculatedPercentage}%`}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<Legend
|
||||
verticalAlign="middle"
|
||||
align="right"
|
||||
layout="vertical"
|
||||
iconSize={12}
|
||||
wrapperStyle={{ paddingLeft: '20px' }}
|
||||
formatter={(value, entry) => (
|
||||
<span style={{ color: entry.color }}>
|
||||
{value}: {entry.payload?.value?.toLocaleString('id-ID')} {t('common.ton')} ({(entry.payload as any)?.percentage}%)
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { useAppSelector } from "@/lib/hooks";
|
||||
import { useDimensions } from "@/lib/hooks/dimension";
|
||||
import { useLocale } from "@/lib/hooks/useLocale";
|
||||
import { useGetEmployeeMovementQuery, useGetMppRecruitmentQuery, useGetResignCategoryQuery, useGetResignReasonQuery, useGetResignSummaryQuery, useGetResignTypeQuery } from "@/services/api";
|
||||
import { useGetEmployeeMovementQuery, useGetMppRecruitmentQuery, useGetResignCategoryQuery, useGetResignReasonQuery, useGetResignSummaryQuery, useGetResignTypeQuery, useLazyGetMonthlyEmployeeQuery } from "@/services/api";
|
||||
import { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
|
||||
import { Loader, LucideLoader2 } from "lucide-react";
|
||||
import React from "react";
|
||||
|
|
@ -24,10 +24,17 @@ export default function TurnoverPage() {
|
|||
const {data: employeeMovement, isLoading: isLoadingEmployeeMovement, isFetching: isFetchingEmployeeMovement} = useGetEmployeeMovementQuery(filter);
|
||||
const {data: mppRecruitment, isLoading: isLoadingMppRecruitment, isFetching: isFetchingMppRecruitment} = useGetMppRecruitmentQuery(filter);
|
||||
|
||||
// Lazy query for monthly employee data to calculate active employees
|
||||
const [getMonthlyEmployee, {isLoading: isLoadingActiveEmployees}] = useLazyGetMonthlyEmployeeQuery();
|
||||
|
||||
// State for active employee data by organization
|
||||
const [activeEmployeesByOrg, setActiveEmployeesByOrg] = React.useState<{[key: string]: number}>({});
|
||||
|
||||
// Combine all loading states (both initial loading and refetching)
|
||||
const isLoading = isLoadingResignSummary || isLoadingResignType ||
|
||||
isLoadingResignCategory || isLoadingResignReason ||
|
||||
isLoadingEmployeeMovement || isLoadingMppRecruitment ||
|
||||
isLoadingActiveEmployees ||
|
||||
isFetchingResignSummary || isFetchingResignType ||
|
||||
isFetchingResignCategory || isFetchingResignReason ||
|
||||
isFetchingEmployeeMovement || isFetchingMppRecruitment;
|
||||
|
|
@ -41,7 +48,8 @@ export default function TurnoverPage() {
|
|||
const [totalRecruitment, setTotalRecruitment] = React.useState(0);
|
||||
const [totalActiveEmployees, setTotalActiveEmployees] = React.useState(0);
|
||||
|
||||
const [maxResignType, setMaxResignType] = React.useState(0);
|
||||
const [totalResignType, setTotalResignType] = React.useState(0);
|
||||
const [totalResignReason, setTotalResignReason] = React.useState(0);
|
||||
|
||||
// MPP Recruitment data state
|
||||
const [mppRecruitmentSummary, setMppRecruitmentSummary] = React.useState<{
|
||||
|
|
@ -59,40 +67,125 @@ export default function TurnoverPage() {
|
|||
active: number;
|
||||
}[]>([]);
|
||||
|
||||
// Function to calculate active employees for each organization
|
||||
const calculateActiveEmployees = React.useCallback(async (organizations: {organization_code: string, organization_name: string}[]) => {
|
||||
console.log('🔍 calculateActiveEmployees called with:', { organizations: organizations.map(o => o.organization_code), filter });
|
||||
const activeEmployeesMap: {[key: string]: number} = {};
|
||||
|
||||
for (const org of organizations) {
|
||||
try {
|
||||
const result = await getMonthlyEmployee({
|
||||
...filter,
|
||||
organization_code: org.organization_code
|
||||
});
|
||||
|
||||
console.log(`📊 Monthly employee data for ${org.organization_code}:`, result.data);
|
||||
|
||||
if (result.data && result.data.length > 0) {
|
||||
// Find the record that matches the end_date from filter
|
||||
let matchingData = null;
|
||||
|
||||
if (filter.end_date) {
|
||||
// Convert filter end_date to match the format in the API response
|
||||
const filterEndDate = new Date(filter.end_date);
|
||||
const filterYear = filterEndDate.getFullYear();
|
||||
const filterMonth = filterEndDate.getMonth() + 1; // getMonth() returns 0-11
|
||||
|
||||
console.log(`🎯 Looking for data matching: ${filterYear}-${filterMonth.toString().padStart(2, '0')}`);
|
||||
|
||||
matchingData = result.data.find(item => {
|
||||
const itemDate = new Date(item.date);
|
||||
const itemYear = itemDate.getFullYear();
|
||||
const itemMonth = itemDate.getMonth() + 1;
|
||||
console.log(`📅 Checking item: ${itemYear}-${itemMonth.toString().padStart(2, '0')} (count: ${item.count})`);
|
||||
return itemYear === filterYear && itemMonth === filterMonth;
|
||||
});
|
||||
}
|
||||
|
||||
// If no matching date found, use the latest data as fallback
|
||||
if (!matchingData) {
|
||||
matchingData = result.data[result.data.length - 1];
|
||||
console.log(`⚠️ No matching date found for ${org.organization_code}, using latest:`, matchingData);
|
||||
} else {
|
||||
console.log(`✅ Found matching data for ${org.organization_code}:`, matchingData);
|
||||
}
|
||||
|
||||
activeEmployeesMap[org.organization_code] = matchingData.count;
|
||||
} else {
|
||||
console.log(`❌ No data found for ${org.organization_code}`);
|
||||
activeEmployeesMap[org.organization_code] = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching active employees for ${org.organization_code}:`, error);
|
||||
activeEmployeesMap[org.organization_code] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🎉 Final activeEmployeesMap:', activeEmployeesMap);
|
||||
setActiveEmployeesByOrg(activeEmployeesMap);
|
||||
return activeEmployeesMap;
|
||||
}, [getMonthlyEmployee, filter]);
|
||||
|
||||
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(parseFloat((totalResign / totalActive * 100).toFixed(2)));
|
||||
setResign(totalResign);
|
||||
|
||||
// Calculate active employees for all organizations in resignSummary
|
||||
const organizations = resignSummary.map(item => ({
|
||||
organization_code: item.organization_code,
|
||||
organization_name: item.organization_name
|
||||
}));
|
||||
|
||||
calculateActiveEmployees(organizations).then((activeEmployeesMap) => {
|
||||
const totalActive = Object.values(activeEmployeesMap).reduce((acc, count) => acc + count, 0);
|
||||
setActive(totalActive);
|
||||
|
||||
// Sort by total employees (resign + active) and limit to top 10
|
||||
// Calculate turnover ratio
|
||||
if (totalActive > 0) {
|
||||
setTurnOverRatio(parseFloat((totalResign / totalActive * 100).toFixed(2)));
|
||||
} else {
|
||||
setTurnOverRatio(0);
|
||||
}
|
||||
|
||||
// Now update limitedResignSummary with active employee data
|
||||
const sortedResignData = [...resignSummary]
|
||||
.sort((a, b) => (b.count ) - (a.count ))
|
||||
.slice(0, 10);
|
||||
.map(item => ({
|
||||
...item,
|
||||
active: activeEmployeesMap[item.organization_code] || 0
|
||||
}));
|
||||
|
||||
setLimitedResignSummary(sortedResignData);
|
||||
});
|
||||
}
|
||||
}, [resignSummary]);
|
||||
}, [resignSummary, calculateActiveEmployees]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if(resignType){
|
||||
const max = resignType.reduce((acc, curr) => Math.max(acc, curr.count), 0);
|
||||
setMaxResignType(max);
|
||||
const total = resignType.reduce((acc, curr) => acc + curr.count, 0);
|
||||
setTotalResignType(total);
|
||||
}
|
||||
|
||||
}, [resignType]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if(resignReason){
|
||||
const total = resignReason.reduce((acc, curr) => acc + curr.count, 0);
|
||||
setTotalResignReason(total);
|
||||
}
|
||||
}, [resignReason]);
|
||||
|
||||
// Process employee movement data for recruitment calculations
|
||||
React.useEffect(() => {
|
||||
if(employeeMovement && resignSummary) {
|
||||
if(employeeMovement && Object.keys(activeEmployeesByOrg).length > 0) {
|
||||
// Calculate total recruitment from employee movement data
|
||||
const totalRecruitmentCount = employeeMovement.reduce((acc, curr) => {
|
||||
return acc + (curr.recruitment || 0);
|
||||
}, 0);
|
||||
|
||||
// Use the same active employee data as resignation component
|
||||
const totalActiveCount = resignSummary.reduce((acc, curr) => acc + curr.active, 0);
|
||||
// Use the calculated active employee data
|
||||
const totalActiveCount = Object.values(activeEmployeesByOrg).reduce((acc, count) => acc + count, 0);
|
||||
|
||||
// Calculate recruitment ratio (recruitment / total active * 100)
|
||||
const ratio = totalActiveCount > 0 ? parseFloat(((totalRecruitmentCount / totalActiveCount) * 100).toFixed(2)) : 0;
|
||||
|
|
@ -101,7 +194,7 @@ export default function TurnoverPage() {
|
|||
setTotalActiveEmployees(totalActiveCount);
|
||||
setRecruitmentRatio(ratio);
|
||||
}
|
||||
}, [employeeMovement, resignSummary]);
|
||||
}, [employeeMovement, activeEmployeesByOrg]);
|
||||
|
||||
// Process MPP recruitment data for chart
|
||||
React.useEffect(() => {
|
||||
|
|
@ -194,11 +287,11 @@ export default function TurnoverPage() {
|
|||
</div>
|
||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanBaru')}</div>
|
||||
<div className="text-xl font-bold">{totalRecruitment}</div>
|
||||
<div className="text-xl font-bold">{totalRecruitment.toLocaleString('id-ID')}</div>
|
||||
</div>
|
||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanAktif')}</div>
|
||||
<div className="text-xl font-bold">{totalActiveEmployees}</div>
|
||||
<div className="text-xl font-bold">{totalActiveEmployees.toLocaleString('id-ID')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -262,11 +355,11 @@ export default function TurnoverPage() {
|
|||
</div>
|
||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanResign')}</div>
|
||||
<div className="text-xl font-bold">{resign}</div>
|
||||
<div className="text-xl font-bold">{resign.toLocaleString('id-ID')}</div>
|
||||
</div>
|
||||
<div className="px-3 py-4 flex-1 flex flex-col border border-[#DDE8F8] rounded-lg">
|
||||
<div className="text-sm text-[#5A5A5A]">{t('turnover.karyawanAktif')}</div>
|
||||
<div className="text-xl font-bold">{active}</div>
|
||||
<div className="text-xl font-bold">{active.toLocaleString('id-ID')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -292,9 +385,9 @@ export default function TurnoverPage() {
|
|||
formatter={(value, name, props) => {
|
||||
const orgData = limitedResignSummary.find(e => e.organization_code === props.payload.organization_code);
|
||||
if (name === t('turnover.resign')) {
|
||||
return [`${orgData?.count || 0} ${t('common.employees')}`, t('turnover.resign')];
|
||||
return [`${orgData?.count?.toLocaleString('id-ID') || 0} ${t('common.employees')}`, t('turnover.resign')];
|
||||
} else {
|
||||
return [`${orgData?.active || 0} ${t('common.employees')}`, t('turnover.active')];
|
||||
return [`${orgData?.active?.toLocaleString('id-ID') || 0} ${t('common.employees')}`, t('turnover.active')];
|
||||
}
|
||||
}}
|
||||
labelFormatter={(label) => {
|
||||
|
|
@ -316,7 +409,7 @@ export default function TurnoverPage() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[480px] flex flex-col">
|
||||
<div className="text-xl font-bold">{t('turnover.jenisPemutusanHubungan')}</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) => (
|
||||
|
|
@ -324,7 +417,7 @@ export default function TurnoverPage() {
|
|||
<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 className="absolute top-0 left-0 h-6 bg-[#F7B500] rounded-lg" style={{width: `${totalResignType > 0 ? (resign.count / totalResignType * 100) : 0}%`}}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -332,7 +425,7 @@ export default function TurnoverPage() {
|
|||
<ReactTooltip 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="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[480px] flex flex-col">
|
||||
<div className="text-xl font-bold">{t('turnover.kategoriResign')}</div>
|
||||
<div className="w-4/5 flex-1 flex mx-auto">
|
||||
{resignCategory && resignCategory.length > 0 ? (
|
||||
|
|
@ -342,7 +435,8 @@ export default function TurnoverPage() {
|
|||
data={resignCategory.map(e => ({name: e.category, value: e.count}))}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
outerRadius={80}
|
||||
outerRadius={120}
|
||||
innerRadius={90}
|
||||
dataKey="value"
|
||||
label={({ name }) => name}
|
||||
labelLine={false}
|
||||
|
|
@ -367,7 +461,7 @@ export default function TurnoverPage() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[420px] flex flex-col">
|
||||
<div className="col-span-4 bg-white py-4 px-4 rounded-lg max-h-[480px] flex flex-col">
|
||||
<div className="text-xl font-bold">{t('turnover.alasanPemutusanHubungan')}</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) => (
|
||||
|
|
@ -375,7 +469,7 @@ export default function TurnoverPage() {
|
|||
<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 className="absolute top-0 left-0 h-6 bg-[#F7B500] rounded-lg" style={{width: `${totalResignReason > 0 ? (resign.count / totalResignReason * 100) : 0}%`}}/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -263,7 +263,8 @@
|
|||
"idKaryawan": "员工ID",
|
||||
"nama": "姓名",
|
||||
"alamat": "地址",
|
||||
"gajiIdr": "薪资 (印尼盾)"
|
||||
"gajiIdr": "薪资 (印尼盾)",
|
||||
"thr": " THR"
|
||||
},
|
||||
"forms": {
|
||||
"required": "必填项",
|
||||
|
|
|
|||
|
|
@ -280,7 +280,8 @@
|
|||
"idKaryawan": "ID Karyawan",
|
||||
"nama": "Nama",
|
||||
"alamat": "Alamat",
|
||||
"gajiIdr": "Gaji (IDR)"
|
||||
"gajiIdr": "Gaji (IDR)",
|
||||
"thr": "THR"
|
||||
},
|
||||
"forms": {
|
||||
"required": "Wajib diisi",
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export const api = createApi({
|
|||
})
|
||||
|
||||
export const { useGetFilterOptionsQuery, useGetEmployeeSummaryQuery, useGetMonthlyEmployeeQuery, useGetMonthlyAttendanceQuery,
|
||||
useGetOrganizationAttendanceQuery, useGetAttendanceRangeQuery, useGetResignSummaryQuery, useGetResignTypeQuery,
|
||||
useGetOrganizationAttendanceQuery, useGetAttendanceRangeQuery, useGetResignSummaryQuery, useGetResignTypeQuery, useLazyGetMonthlyEmployeeQuery,
|
||||
useLoginMutation, useAuthCheckQuery, useGetSanctionSummaryQuery,
|
||||
useGetResignCategoryQuery, useGetResignReasonQuery, useGetEmployeeMovementQuery, useGetMppRecruitmentQuery,
|
||||
useGetProductivityByRegionQuery, useGetProductivityByAgeQuery, useGetProductivityByTenureQuery, useGetTonnageHarvestGroupEmployeeQuery, useGetTonnageHarvestByEmployeeOriginQuery, useGetTonnageHarvestByEmployeeSalaryQuery, useGetTargetTonnageQuery, useGetHrCostByJobQuery, useGetHrCostQuery, useGetHrCostByOrganizationQuery, useGetHrCostPerMonthQuery, useGetHrCostPerEmployeeQuery, useGetCostQuery } = api
|
||||
|
|
@ -80,7 +80,7 @@ export type ResignSummary = {
|
|||
organization_code: string;
|
||||
organization_name: string;
|
||||
count: number;
|
||||
active: number;
|
||||
// active: number;
|
||||
}
|
||||
|
||||
export type ResignationType = {
|
||||
|
|
@ -175,6 +175,7 @@ export type HrCost = {
|
|||
total_salary: number;
|
||||
total_bpjs_tk: number;
|
||||
total_bpjs_ks: number;
|
||||
total_thr: number;
|
||||
}
|
||||
|
||||
export type HrCostByOrganization = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue