import React, { useState, useEffect } from 'react'; import { useService } from '../../../core/di/ServiceContainer'; import { IPayrollCalculator } from '../services/interfaces/IPayrollCalculator'; import { PayrollBatch, PayrollEntry, PayrollStatus, PayrollPeriod, PayrollCalculationInput, PayrollSummary } from '../types/payroll.types'; interface PayrollManagementProps { className?: string; userRole?: 'admin' | 'hr' | 'manager'; } export const PayrollManagement: React.FC = ({ className = '', userRole = 'admin' }) => { const payrollCalculator = useService('payroll-calculator'); const [batches, setBatches] = useState([]); const [selectedBatch, setSelectedBatch] = useState(null); const [batchEntries, setBatchEntries] = useState([]); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState<'batches' | 'calculate' | 'reports'>('batches'); const [calculationForm, setCalculationForm] = useState({ period: PayrollPeriod.MONTHLY, startDate: new Date(new Date().getFullYear(), new Date().getMonth(), 1), endDate: new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0) }); useEffect(() => { if (payrollCalculator) { loadPayrollData(); } }, [payrollCalculator]); const loadPayrollData = async () => { try { setLoading(true); setError(null); if (!payrollCalculator) { throw new Error('Payroll calculator service not available'); } const allBatches = await payrollCalculator.getPayrollBatches({ limit: 50, offset: 0 }); setBatches(allBatches); // Load summary for current month const currentMonth = new Date(); const startOfMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1); const endOfMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0); if (!payrollCalculator) { throw new Error('Payroll calculator service not available'); } const monthlySummary = await payrollCalculator.getPayrollSummary(startOfMonth, endOfMonth); setSummary(monthlySummary); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load payroll data'); } finally { setLoading(false); } }; const handleCalculatePayroll = async () => { try { setLoading(true); if (!payrollCalculator) { throw new Error('Payroll calculator service not available'); } const result = await payrollCalculator.calculatePayroll(calculationForm); // Refresh batches list await loadPayrollData(); // Select the newly created batch const newBatch = await payrollCalculator.getPayrollBatchById(result.batchId); if (newBatch) { setSelectedBatch(newBatch); setBatchEntries(newBatch.entries); } alert(`Payroll berhasil dihitung! Batch ID: ${result.batchId}`); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to calculate payroll'); } finally { setLoading(false); } }; const handleBatchAction = async (batchId: string, action: 'approve' | 'process' | 'cancel') => { try { setLoading(true); if (!payrollCalculator) { throw new Error('Payroll calculator service not available'); } let updatedBatch: PayrollBatch; switch (action) { case 'approve': updatedBatch = await payrollCalculator.approvePayrollBatch(batchId, 'current-user'); break; case 'process': updatedBatch = await payrollCalculator.processPayrollBatch(batchId); break; case 'cancel': const reason = prompt('Alasan pembatalan:'); if (!reason) return; updatedBatch = await payrollCalculator.cancelPayrollBatch(batchId, reason, 'current-user'); break; default: return; } // Update local state setBatches(prev => prev.map(b => b.id === batchId ? updatedBatch : b)); if (selectedBatch?.id === batchId) { setSelectedBatch(updatedBatch); } alert(`Batch ${action} berhasil!`); } catch (err) { setError(err instanceof Error ? err.message : `Failed to ${action} batch`); } finally { setLoading(false); } }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(amount); }; const getStatusBadge = (status: PayrollStatus) => { const statusConfig = { [PayrollStatus.DRAFT]: { label: 'Draft', className: 'bg-gray-100 text-gray-800' }, [PayrollStatus.CALCULATING]: { label: 'Menghitung', className: 'bg-blue-100 text-blue-800' }, [PayrollStatus.PENDING_APPROVAL]: { label: 'Menunggu Persetujuan', className: 'bg-yellow-100 text-yellow-800' }, [PayrollStatus.APPROVED]: { label: 'Disetujui', className: 'bg-green-100 text-green-800' }, [PayrollStatus.PROCESSING]: { label: 'Memproses', className: 'bg-purple-100 text-purple-800' }, [PayrollStatus.COMPLETED]: { label: 'Selesai', className: 'bg-blue-100 text-blue-800' }, [PayrollStatus.FAILED]: { label: 'Gagal', className: 'bg-red-100 text-red-800' } }; const config = statusConfig[status]; return ( {config.label} ); }; const getPeriodLabel = (period: PayrollPeriod) => { const labels = { [PayrollPeriod.WEEKLY]: 'Mingguan', [PayrollPeriod.BIWEEKLY]: 'Dua Mingguan', [PayrollPeriod.MONTHLY]: 'Bulanan', [PayrollPeriod.QUARTERLY]: 'Kuartalan' }; return labels[period] || period; }; if (loading && batches.length === 0) { return (
); } return (
{/* Header */}

Manajemen Payroll

Kelola perhitungan dan pembayaran payroll

{/* Tabs */}
{error && (
⚠️

Error

{error}
)} {/* Batches Tab */} {activeTab === 'batches' && (
{/* Summary Cards */} {summary && (
💰

Total Amount

{formatCurrency(summary.totalAmount)}

👥

Participants

{summary.participantCount}

📊

Average

{formatCurrency(summary.averageAmount)}

🏆

Top Earner

{summary.topEarners[0] ? formatCurrency(summary.topEarners[0].amount) : '-'}

)} {/* Batches List */}
{batches.map((batch) => ( ))}
Batch ID Periode Tanggal Participants Total Amount Status Actions
{batch.id.substring(0, 8)}... {getPeriodLabel(batch.period)} {batch.startDate.toLocaleDateString('id-ID')} - {batch.endDate.toLocaleDateString('id-ID')} {batch.totalParticipants} {formatCurrency(batch.totalAmount)} {getStatusBadge(batch.status)}
{batch.status === PayrollStatus.PENDING_APPROVAL && userRole === 'admin' && ( )} {batch.status === PayrollStatus.APPROVED && ( )} {[PayrollStatus.DRAFT, PayrollStatus.PENDING_APPROVAL].includes(batch.status) && ( )}
{batches.length === 0 && (
📊

Belum ada batch payroll

Buat perhitungan payroll pertama Anda

)}
)} {/* Calculate Tab */} {activeTab === 'calculate' && (

Hitung Payroll Baru

setCalculationForm(prev => ({ ...prev, startDate: new Date(e.target.value) }))} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
setCalculationForm(prev => ({ ...prev, endDate: new Date(e.target.value) }))} className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
)} {/* Reports Tab */} {activeTab === 'reports' && (

Laporan Payroll

📈

Fitur laporan akan segera hadir

)}
{/* Batch Detail Modal */} {selectedBatch && (

Batch Detail: {selectedBatch.id.substring(0, 8)}...

Periode

{getPeriodLabel(selectedBatch.period)}

Status

{getStatusBadge(selectedBatch.status)}

Total Amount

{formatCurrency(selectedBatch.totalAmount)}

Participants

{selectedBatch.totalParticipants}

Entries

{batchEntries.map((entry) => ( ))}
User ID Amount Rewards
{entry.userId} {formatCurrency(entry.totalAmount)} {entry.rewardBreakdown.length} rewards
)}
); };