"use client"; import { useState, useEffect, useCallback } from "react"; import { Loader2, Play, Save, CheckCircle, AlertCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { fetchAccrualRules, fetchLeaveTypesForAccrual, saveAccrualRule, runAccrual, type AccrualRuleWithLeaveType, } from "@/lib/supabase/leave-accrual-actions"; interface LeaveTypeOption { id: string; name: string; } interface RuleFormState { accrualRate: string; frequency: "monthly" | "fortnightly" | "weekly"; maxBalance: string; } function Row({ leaveType, existingRule, onSave, }: { leaveType: LeaveTypeOption; existingRule: AccrualRuleWithLeaveType | null; onSave: () => void; }) { const [form, setForm] = useState({ accrualRate: existingRule ? String(existingRule.accrual_rate) : "", frequency: existingRule?.accrual_frequency || "monthly", maxBalance: existingRule?.max_balance != null ? String(existingRule.max_balance) : "", }); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const handleSave = useCallback(async () => { const rate = parseFloat(form.accrualRate); if (isNaN(rate) || rate < 0) return; const maxBalance = form.maxBalance.trim() === "" ? null : parseFloat(form.maxBalance); setSaving(true); const result = await saveAccrualRule(leaveType.id, rate, form.frequency, isNaN(maxBalance as number) ? null : maxBalance); setSaving(false); if (result.success) { setSaved(true); setTimeout(() => setSaved(false), 2000); onSave(); } }, [form, leaveType.id, onSave]); return (
{/* Leave type name */}

{leaveType.name}

{/* Accrual rate */}
setForm((f) => ({ ...f, accrualRate: e.target.value }))} className="h-9" />
{/* Frequency */}
{/* Max balance */}
setForm((f) => ({ ...f, maxBalance: e.target.value }))} className="h-9" />
{/* Save button */}
); } export default function LeaveAccrualPage() { const [leaveTypes, setLeaveTypes] = useState([]); const [rules, setRules] = useState([]); const [loading, setLoading] = useState(true); const [runningAccrual, setRunningAccrual] = useState(false); const [accrualResult, setAccrualResult] = useState<{ success: boolean; count?: number; error?: string } | null>(null); const loadData = useCallback(() => { Promise.all([fetchAccrualRules(), fetchLeaveTypesForAccrual()]) .then(([fetchedRules, fetchedTypes]) => { setRules(fetchedRules); setLeaveTypes(fetchedTypes); }) .catch(console.error) .finally(() => setLoading(false)); }, []); useEffect(() => { loadData(); }, [loadData]); const handleRunAccrual = useCallback(async () => { setRunningAccrual(true); setAccrualResult(null); try { const result = await runAccrual(); setAccrualResult(result); } catch (err) { setAccrualResult({ success: false, error: err instanceof Error ? err.message : "Unknown error" }); } finally { setRunningAccrual(false); } }, []); // Build a map of existing rules by leave_type_id const rulesMap = new Map(); for (const rule of rules) { rulesMap.set(rule.leave_type_id, rule); } if (loading) { return (
); } return (
{/* Header */}

Leave Accrual

Configure automatic leave accrual for each leave type

{/* Accrual result banner */} {accrualResult && ( {accrualResult.success ? ( ) : ( )}

{accrualResult.success ? `Accrual complete — ${accrualResult.count ?? 0} employee(s) updated` : `Error: ${accrualResult.error}`}

)} {/* Info card */} How accrual works Set an accrual rate for each leave type. When you run accrual, the system calculates how many days each employee has earned based on their start date and the selected frequency, then creates an accrual record. The max balance caps how much leave can accumulate. {/* Accrual rules */} {leaveTypes.length === 0 ? ( No leave types found. Create leave types first in the Leave settings. ) : ( Accrual rules Configure how leave accrues for each leave type
{/* Header row */}
Leave type
Accrual rate
Frequency
Max balance
{leaveTypes.map((lt) => ( ))}
)}
); }