mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat: integrate Daily Checklist Dashboard to API integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/figma-make/components/base/select';
|
} from '@/figma-make/components/base/select';
|
||||||
import { Input } from '@/figma-make/components/base/input';
|
|
||||||
import { Badge } from '@/figma-make/components/base/badge';
|
import { Badge } from '@/figma-make/components/base/badge';
|
||||||
import {
|
import {
|
||||||
Calendar as CalendarIcon,
|
Calendar as CalendarIcon,
|
||||||
@@ -35,53 +34,17 @@ import {
|
|||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Cell,
|
Cell,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase';
|
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import useSWR from 'swr';
|
||||||
interface EmployeePerformance {
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
employee_id: string;
|
import { DailyChecklistSummary } from '@/types/api/daily-checklist/daily-checklist';
|
||||||
employee_name: string;
|
import { AxiosError } from 'axios';
|
||||||
kandang_id: string;
|
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||||
kandang_name: string;
|
import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist';
|
||||||
total_activities_in_category: number; // Total aktivitas di kategori
|
import { KandangApi } from '@/services/api/master-data';
|
||||||
completed_activities: number; // Aktivitas yang sudah di-check
|
import { useSelect } from '@/components/input/SelectInput';
|
||||||
completion_rate: number;
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
last_activity_date: string | null;
|
import { formatDate } from '@/lib/helper';
|
||||||
color: string; // Color based on kandang
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Kandang {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Category {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChecklistKandang {
|
|
||||||
id: string;
|
|
||||||
date: string;
|
|
||||||
kandang_id: string;
|
|
||||||
category: string;
|
|
||||||
kandang: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
} | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AssignmentEmployee {
|
|
||||||
id: string;
|
|
||||||
task_id: string;
|
|
||||||
employee_id: string;
|
|
||||||
checked: boolean;
|
|
||||||
updated_at: string;
|
|
||||||
employee: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
} | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const KANDANG_COLORS = [
|
const KANDANG_COLORS = [
|
||||||
'#0069e0', // Blue (primary)
|
'#0069e0', // Blue (primary)
|
||||||
@@ -102,312 +65,65 @@ const CATEGORY_LABELS: { [key: string]: string } = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [employeePerformance, setEmployeePerformance] = useState<
|
|
||||||
EmployeePerformance[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
// Master data
|
|
||||||
const [kandangList, setKandangList] = useState<Kandang[]>([]);
|
|
||||||
const [categoryList, setCategoryList] = useState<Category[]>([]);
|
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
const [dateFrom, setDateFrom] = useState('');
|
const [dateFrom, setDateFrom] = useState('');
|
||||||
const [dateTo, setDateTo] = useState('');
|
const [dateTo, setDateTo] = useState('');
|
||||||
const [kandangFilter, setKandangFilter] = useState('ALL');
|
const [kandangFilter, setKandangFilter] = useState('ALL');
|
||||||
const [categoryFilter, setCategoryFilter] = useState('ALL');
|
const [categoryFilter, setCategoryFilter] = useState('ALL');
|
||||||
|
|
||||||
// Color mapping for kandang
|
const {
|
||||||
const [kandangColorMap, setKandangColorMap] = useState<{
|
data: summaryResponse,
|
||||||
[key: string]: string;
|
isLoading: isLoadingSummary,
|
||||||
}>({});
|
mutate: refreshSummary,
|
||||||
|
} = useSWR<
|
||||||
useEffect(() => {
|
BaseApiResponse<DailyChecklistSummary | undefined>,
|
||||||
fetchMasterData();
|
AxiosError<BaseApiResponse>,
|
||||||
}, []);
|
SWRHttpKey
|
||||||
|
>(
|
||||||
useEffect(() => {
|
dateFrom && dateTo
|
||||||
// Only fetch when date filters are set
|
? `${DailyChecklistApi.basePath}/summary?date_from=${dateFrom}&date_to=${dateTo}&kandang_id=${kandangFilter === 'ALL' ? '' : kandangFilter}&category=${categoryFilter === 'ALL' ? '' : categoryFilter}`
|
||||||
if (dateFrom && dateTo) {
|
: '',
|
||||||
fetchEmployeePerformance();
|
httpClientFetcher,
|
||||||
} else {
|
{
|
||||||
setEmployeePerformance([]);
|
keepPreviousData: true,
|
||||||
}
|
}
|
||||||
}, [dateFrom, dateTo, kandangFilter, categoryFilter]);
|
);
|
||||||
|
|
||||||
const fetchMasterData = async () => {
|
const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
|
||||||
if (!isSupabaseConfigured()) return;
|
useSelect(KandangApi.basePath, 'id', 'name', 'search', {
|
||||||
|
page: '1',
|
||||||
try {
|
limit: '100',
|
||||||
// Fetch kandang
|
|
||||||
const { data: kandangData, error: kandangError } = await supabase
|
|
||||||
.from('kandang')
|
|
||||||
.select('id, name')
|
|
||||||
.order('name', { ascending: true });
|
|
||||||
|
|
||||||
if (kandangError) {
|
|
||||||
console.error('Error fetching kandang:', kandangError);
|
|
||||||
} else {
|
|
||||||
setKandangList(kandangData || []);
|
|
||||||
|
|
||||||
// Create color mapping
|
|
||||||
const colorMap: { [key: string]: string } = {};
|
|
||||||
(kandangData || []).forEach((k, index) => {
|
|
||||||
colorMap[k.id] = KANDANG_COLORS[index % KANDANG_COLORS.length];
|
|
||||||
});
|
|
||||||
setKandangColorMap(colorMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set categories from CATEGORY_LABELS (hardcoded list)
|
|
||||||
const categories: Category[] = Object.keys(CATEGORY_LABELS).map((id) => ({
|
|
||||||
id,
|
|
||||||
name: CATEGORY_LABELS[id],
|
|
||||||
}));
|
|
||||||
setCategoryList(categories);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching master data:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchEmployeePerformance = async () => {
|
|
||||||
if (!isSupabaseConfigured() || !dateFrom || !dateTo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// Step 1: Get all checklists in date range + filters
|
|
||||||
let checklistQuery = supabase
|
|
||||||
.from('daily_checklists')
|
|
||||||
.select(
|
|
||||||
`
|
|
||||||
id,
|
|
||||||
date,
|
|
||||||
kandang_id,
|
|
||||||
category,
|
|
||||||
kandang:kandang_id (
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
)
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.gte('date', dateFrom)
|
|
||||||
.lte('date', dateTo);
|
|
||||||
|
|
||||||
if (kandangFilter !== 'ALL') {
|
|
||||||
checklistQuery = checklistQuery.eq('kandang_id', kandangFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (categoryFilter !== 'ALL') {
|
|
||||||
checklistQuery = checklistQuery.eq('category', categoryFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: checklists, error: checklistError } = await checklistQuery;
|
|
||||||
|
|
||||||
if (checklistError) {
|
|
||||||
console.error('Error fetching checklists:', checklistError);
|
|
||||||
toast.error('Gagal memuat data checklist');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checklists || checklists.length === 0) {
|
|
||||||
setEmployeePerformance([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const checklistsData = checklists as unknown as ChecklistKandang[];
|
|
||||||
|
|
||||||
// Step 2: Get all tasks from these checklists
|
|
||||||
const checklistIds = checklistsData.map((c) => c.id);
|
|
||||||
const { data: tasks, error: tasksError } = await supabase
|
|
||||||
.from('daily_checklist_activity_tasks')
|
|
||||||
.select('id, checklist_id')
|
|
||||||
.in('checklist_id', checklistIds);
|
|
||||||
|
|
||||||
if (tasksError) {
|
|
||||||
console.error('Error fetching tasks:', tasksError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tasks || tasks.length === 0) {
|
|
||||||
setEmployeePerformance([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskIds = tasks.map((t) => t.id);
|
|
||||||
|
|
||||||
// Step 3: Get all assignments for these tasks
|
|
||||||
const { data: assignments, error: assignmentsError } = await supabase
|
|
||||||
.from('daily_checklist_activity_task_assignments')
|
|
||||||
.select(
|
|
||||||
`
|
|
||||||
id,
|
|
||||||
task_id,
|
|
||||||
employee_id,
|
|
||||||
checked,
|
|
||||||
updated_at,
|
|
||||||
employee:employee_id (
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
)
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.in('task_id', taskIds);
|
|
||||||
|
|
||||||
if (assignmentsError) {
|
|
||||||
console.error('Error fetching assignments:', assignmentsError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!assignments || assignments.length === 0) {
|
|
||||||
setEmployeePerformance([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const assignmentsData = assignments as unknown as AssignmentEmployee[];
|
|
||||||
|
|
||||||
// Step 4: Calculate total activities in selected category (if filtered)
|
|
||||||
let totalActivitiesInCategory = 0;
|
|
||||||
|
|
||||||
if (categoryFilter !== 'ALL') {
|
|
||||||
// Get total activities from master data for this category
|
|
||||||
const { data: phases } = await supabase
|
|
||||||
.from('phases')
|
|
||||||
.select('id')
|
|
||||||
.eq('category_id', categoryFilter);
|
|
||||||
|
|
||||||
if (phases && phases.length > 0) {
|
|
||||||
const phaseIds = phases.map((p) => p.id);
|
|
||||||
const { count } = await supabase
|
|
||||||
.from('activities')
|
|
||||||
.select('*', { count: 'exact', head: true })
|
|
||||||
.in('phase_id', phaseIds);
|
|
||||||
|
|
||||||
totalActivitiesInCategory = count || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Group by employee and calculate performance
|
|
||||||
const employeeMap = new Map<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
employee_id: string;
|
|
||||||
employee_name: string;
|
|
||||||
kandang_id: string;
|
|
||||||
kandang_name: string;
|
|
||||||
completed_count: number;
|
|
||||||
total_count: number;
|
|
||||||
last_activity_date: string | null;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
assignmentsData.forEach((assignment) => {
|
|
||||||
const task = tasks.find((t) => t.id === assignment.task_id);
|
|
||||||
if (!task) return;
|
|
||||||
|
|
||||||
const checklist = checklistsData.find(
|
|
||||||
(c) => c.id === task.checklist_id
|
|
||||||
);
|
|
||||||
if (!checklist) return;
|
|
||||||
|
|
||||||
const employeeId = assignment.employee_id;
|
|
||||||
const employeeName = assignment.employee?.name || 'Unknown';
|
|
||||||
const kandangId = checklist.kandang_id;
|
|
||||||
const kandangName = checklist.kandang?.name || 'Unknown';
|
|
||||||
|
|
||||||
if (!employeeMap.has(employeeId)) {
|
|
||||||
employeeMap.set(employeeId, {
|
|
||||||
employee_id: employeeId,
|
|
||||||
employee_name: employeeName,
|
|
||||||
kandang_id: kandangId,
|
|
||||||
kandang_name: kandangName,
|
|
||||||
completed_count: 0,
|
|
||||||
total_count: 0,
|
|
||||||
last_activity_date: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const empData = employeeMap.get(employeeId)!;
|
|
||||||
empData.total_count += 1;
|
|
||||||
|
|
||||||
if (assignment.checked) {
|
|
||||||
empData.completed_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update last activity date
|
|
||||||
if (assignment.updated_at) {
|
|
||||||
if (
|
|
||||||
!empData.last_activity_date ||
|
|
||||||
assignment.updated_at > empData.last_activity_date
|
|
||||||
) {
|
|
||||||
empData.last_activity_date = assignment.updated_at;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step 6: Convert to array and add calculated fields
|
|
||||||
const performanceData: EmployeePerformance[] = Array.from(
|
|
||||||
employeeMap.values()
|
|
||||||
).map((emp) => {
|
|
||||||
// Use total activities in category if category is selected, otherwise use employee's assigned count
|
|
||||||
const totalActivities =
|
|
||||||
categoryFilter !== 'ALL' && totalActivitiesInCategory > 0
|
|
||||||
? totalActivitiesInCategory
|
|
||||||
: emp.total_count;
|
|
||||||
|
|
||||||
return {
|
|
||||||
employee_id: emp.employee_id,
|
|
||||||
employee_name: emp.employee_name,
|
|
||||||
kandang_id: emp.kandang_id,
|
|
||||||
kandang_name: emp.kandang_name,
|
|
||||||
total_activities_in_category: totalActivities,
|
|
||||||
completed_activities: emp.completed_count,
|
|
||||||
completion_rate:
|
|
||||||
totalActivities > 0
|
|
||||||
? Math.round((emp.completed_count / totalActivities) * 100)
|
|
||||||
: 0,
|
|
||||||
last_activity_date: emp.last_activity_date,
|
|
||||||
color: kandangColorMap[emp.kandang_id] || '#0069e0',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort by employee name
|
|
||||||
performanceData.sort((a, b) =>
|
|
||||||
a.employee_name.localeCompare(b.employee_name)
|
|
||||||
);
|
|
||||||
|
|
||||||
setEmployeePerformance(performanceData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching employee performance:', error);
|
|
||||||
toast.error('Terjadi kesalahan saat memuat data');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDate = (dateString: string | null) => {
|
|
||||||
if (!dateString) return '-';
|
|
||||||
const date = new Date(dateString);
|
|
||||||
return date.toLocaleDateString('id-ID', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric',
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const hasFilters = dateFrom && dateTo;
|
const kandangColorMap: { [key: string]: string } = {};
|
||||||
|
(kandangOptions || []).forEach((k, index) => {
|
||||||
|
kandangColorMap[k.value] = KANDANG_COLORS[index % KANDANG_COLORS.length];
|
||||||
|
});
|
||||||
|
|
||||||
// Prepare chart data
|
const employeePerformance = isResponseSuccess(summaryResponse)
|
||||||
const chartData = employeePerformance.map((emp) => ({
|
? summaryResponse.data?.tracking_abk.map((abk) => {
|
||||||
|
return {
|
||||||
|
...abk,
|
||||||
|
color: kandangColorMap[abk.kandang_id] || '#0069e0',
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const chartData = employeePerformance?.map((emp) => ({
|
||||||
name: emp.employee_name,
|
name: emp.employee_name,
|
||||||
completed: emp.completed_activities,
|
completed: emp.activity_done,
|
||||||
remaining: emp.total_activities_in_category - emp.completed_activities,
|
remaining: emp.activity_left,
|
||||||
total: emp.total_activities_in_category,
|
total: emp.total_activity,
|
||||||
color: emp.color,
|
color: emp.color,
|
||||||
kandang: emp.kandang_name,
|
kandang: emp.kandang_name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const hasFilters = dateFrom && dateTo;
|
||||||
|
|
||||||
|
if (summaryResponse && isResponseError(summaryResponse)) {
|
||||||
|
toast.error('Gagal memuat data: ' + summaryResponse.message);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='min-h-screen'>
|
<div className='min-h-screen'>
|
||||||
<div className='p-6'>
|
<div className='p-6'>
|
||||||
@@ -457,9 +173,12 @@ export function Dashboard() {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value='ALL'>Semua Kandang</SelectItem>
|
<SelectItem value='ALL'>Semua Kandang</SelectItem>
|
||||||
{kandangList.map((kandang) => (
|
{kandangOptions.map((kandang) => (
|
||||||
<SelectItem key={kandang.id} value={kandang.id}>
|
<SelectItem
|
||||||
{kandang.name}
|
key={kandang.value}
|
||||||
|
value={String(kandang.value)}
|
||||||
|
>
|
||||||
|
{kandang.label}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -482,9 +201,9 @@ export function Dashboard() {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value='ALL'>Semua Kategori</SelectItem>
|
<SelectItem value='ALL'>Semua Kategori</SelectItem>
|
||||||
{categoryList.map((category) => (
|
{Object.keys(CATEGORY_LABELS).map((category) => (
|
||||||
<SelectItem key={category.id} value={category.id}>
|
<SelectItem key={category} value={category}>
|
||||||
{category.name}
|
{CATEGORY_LABELS[category]}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -523,11 +242,11 @@ export function Dashboard() {
|
|||||||
melihat performance ABK.
|
melihat performance ABK.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : loading ? (
|
) : isLoadingSummary ? (
|
||||||
<div className='text-center py-16 text-gray-500'>
|
<div className='text-center py-16 text-gray-500'>
|
||||||
Memuat data...
|
Memuat data...
|
||||||
</div>
|
</div>
|
||||||
) : employeePerformance.length === 0 ? (
|
) : employeePerformance && employeePerformance.length === 0 ? (
|
||||||
<div className='flex flex-col items-center justify-center py-16 text-center'>
|
<div className='flex flex-col items-center justify-center py-16 text-center'>
|
||||||
<Users className='w-16 h-16 text-gray-300 mb-4' />
|
<Users className='w-16 h-16 text-gray-300 mb-4' />
|
||||||
<h3 className='text-lg font-semibold text-gray-700 mb-2'>
|
<h3 className='text-lg font-semibold text-gray-700 mb-2'>
|
||||||
@@ -582,7 +301,7 @@ export function Dashboard() {
|
|||||||
fill='#10B981'
|
fill='#10B981'
|
||||||
radius={[0, 0, 0, 0]}
|
radius={[0, 0, 0, 0]}
|
||||||
>
|
>
|
||||||
{chartData.map((entry, index) => (
|
{chartData?.map((entry, index) => (
|
||||||
<Cell
|
<Cell
|
||||||
key={`cell-completed-${index}`}
|
key={`cell-completed-${index}`}
|
||||||
fill={entry.color}
|
fill={entry.color}
|
||||||
@@ -595,7 +314,7 @@ export function Dashboard() {
|
|||||||
fill='#E5E7EB'
|
fill='#E5E7EB'
|
||||||
radius={[4, 4, 0, 0]}
|
radius={[4, 4, 0, 0]}
|
||||||
>
|
>
|
||||||
{chartData.map((entry, index) => (
|
{chartData?.map((entry, index) => (
|
||||||
<Cell
|
<Cell
|
||||||
key={`cell-remaining-${index}`}
|
key={`cell-remaining-${index}`}
|
||||||
fill={`${entry.color}33`}
|
fill={`${entry.color}33`}
|
||||||
@@ -610,102 +329,103 @@ export function Dashboard() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Employee Tracking Table */}
|
{/* Employee Tracking Table */}
|
||||||
{hasFilters && employeePerformance.length > 0 && (
|
{hasFilters &&
|
||||||
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white'>
|
employeePerformance &&
|
||||||
<CardHeader>
|
employeePerformance.length > 0 && (
|
||||||
<CardTitle className='text-lg'>Tracking ABK</CardTitle>
|
<Card className='border-gray-200/60 shadow-sm rounded-xl bg-white'>
|
||||||
<p className='text-sm text-gray-500 mt-1'>
|
<CardHeader>
|
||||||
Detail performance masing-masing ABK
|
<CardTitle className='text-lg'>Tracking ABK</CardTitle>
|
||||||
</p>
|
<p className='text-sm text-gray-500 mt-1'>
|
||||||
</CardHeader>
|
Detail performance masing-masing ABK
|
||||||
<CardContent>
|
</p>
|
||||||
<div className='overflow-x-auto'>
|
</CardHeader>
|
||||||
<table className='w-full border border-gray-200 rounded-lg'>
|
<CardContent>
|
||||||
<thead>
|
<div className='overflow-x-auto'>
|
||||||
<tr className='bg-gray-50 border-b border-gray-200'>
|
<table className='w-full border border-gray-200 rounded-lg'>
|
||||||
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
|
<thead>
|
||||||
Nama ABK
|
<tr className='bg-gray-50 border-b border-gray-200'>
|
||||||
</th>
|
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
|
||||||
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
|
Nama ABK
|
||||||
Kandang
|
</th>
|
||||||
</th>
|
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
|
||||||
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
Kandang
|
||||||
Total Aktivitas
|
</th>
|
||||||
</th>
|
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
||||||
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
Total Aktivitas
|
||||||
Aktivitas Selesai
|
</th>
|
||||||
</th>
|
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
||||||
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
Aktivitas Selesai
|
||||||
Aktivitas Tersisa
|
</th>
|
||||||
</th>
|
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
||||||
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
Aktivitas Tersisa
|
||||||
Completion Rate
|
</th>
|
||||||
</th>
|
<th className='text-center py-3 px-4 text-sm font-semibold text-gray-700'>
|
||||||
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
|
Completion Rate
|
||||||
Last Activity
|
</th>
|
||||||
</th>
|
<th className='text-left py-3 px-4 text-sm font-semibold text-gray-700'>
|
||||||
</tr>
|
Last Activity
|
||||||
</thead>
|
</th>
|
||||||
<tbody>
|
|
||||||
{employeePerformance.map((emp, index) => (
|
|
||||||
<tr
|
|
||||||
key={emp.employee_id}
|
|
||||||
className={
|
|
||||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<td className='py-3 px-4 text-sm text-gray-900 font-medium'>
|
|
||||||
{emp.employee_name}
|
|
||||||
</td>
|
|
||||||
<td className='py-3 px-4'>
|
|
||||||
<Badge
|
|
||||||
style={{
|
|
||||||
backgroundColor: `${emp.color}15`,
|
|
||||||
color: emp.color,
|
|
||||||
borderColor: `${emp.color}30`,
|
|
||||||
}}
|
|
||||||
className='border'
|
|
||||||
>
|
|
||||||
{emp.kandang_name}
|
|
||||||
</Badge>
|
|
||||||
</td>
|
|
||||||
<td className='py-3 px-4 text-center text-sm text-gray-900'>
|
|
||||||
{emp.total_activities_in_category}
|
|
||||||
</td>
|
|
||||||
<td className='py-3 px-4 text-center text-sm font-semibold text-green-700'>
|
|
||||||
{emp.completed_activities}
|
|
||||||
</td>
|
|
||||||
<td className='py-3 px-4 text-center text-sm text-gray-600'>
|
|
||||||
{emp.total_activities_in_category -
|
|
||||||
emp.completed_activities}
|
|
||||||
</td>
|
|
||||||
<td className='py-3 px-4 text-center'>
|
|
||||||
<div className='flex items-center justify-center gap-2'>
|
|
||||||
<div className='w-24 bg-gray-200 rounded-full h-2'>
|
|
||||||
<div
|
|
||||||
className='h-2 rounded-full transition-all'
|
|
||||||
style={{
|
|
||||||
width: `${emp.completion_rate}%`,
|
|
||||||
backgroundColor: emp.color,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className='text-sm text-gray-700 font-medium'>
|
|
||||||
{emp.completion_rate}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className='py-3 px-4 text-sm text-gray-600'>
|
|
||||||
{formatDate(emp.last_activity_date)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{employeePerformance?.map((emp, index) => (
|
||||||
</div>
|
<tr
|
||||||
</CardContent>
|
key={emp.employee_id}
|
||||||
</Card>
|
className={
|
||||||
)}
|
index % 2 === 0 ? 'bg-white' : 'bg-gray-50/50'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<td className='py-3 px-4 text-sm text-gray-900 font-medium'>
|
||||||
|
{emp.employee_name}
|
||||||
|
</td>
|
||||||
|
<td className='py-3 px-4'>
|
||||||
|
<Badge
|
||||||
|
style={{
|
||||||
|
backgroundColor: `${emp.color}15`,
|
||||||
|
color: emp.color,
|
||||||
|
borderColor: `${emp.color}30`,
|
||||||
|
}}
|
||||||
|
className='border'
|
||||||
|
>
|
||||||
|
{emp.kandang_name}
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className='py-3 px-4 text-center text-sm text-gray-900'>
|
||||||
|
{emp.total_activity}
|
||||||
|
</td>
|
||||||
|
<td className='py-3 px-4 text-center text-sm font-semibold text-green-700'>
|
||||||
|
{emp.activity_done}
|
||||||
|
</td>
|
||||||
|
<td className='py-3 px-4 text-center text-sm text-gray-600'>
|
||||||
|
{emp.activity_left}
|
||||||
|
</td>
|
||||||
|
<td className='py-3 px-4 text-center'>
|
||||||
|
<div className='flex items-center justify-center gap-2'>
|
||||||
|
<div className='w-24 bg-gray-200 rounded-full h-2'>
|
||||||
|
<div
|
||||||
|
className='h-2 rounded-full transition-all'
|
||||||
|
style={{
|
||||||
|
width: `${emp.completion_rate}%`,
|
||||||
|
backgroundColor: emp.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className='text-sm text-gray-700 font-medium'>
|
||||||
|
{emp.completion_rate}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className='py-3 px-4 text-sm text-gray-600'>
|
||||||
|
{formatDate(emp.last_activity, 'DD MMM YYYY')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user