import { Alert, Badge, Button, Card, Table, Tabs, Text, } from "@mantine/core"; import { IconAlertTriangle, IconSeeding } from "@tabler/icons-react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import { Can } from "@/auth/Can"; import { HR_PERMS } from "@/auth/permissions"; import { apiErrorMessage } from "@/auth/http"; import { PageHeader } from "@/shared/components/PageHeader"; import { localized } from "@/shared/lib/localizedName"; import { listComponents, listStatutoryRates, listStructures, listTaxBrackets, seedPayrollStatutory, } from "./api"; import { money } from "./components/PayslipView"; export function PayrollSettingsPage() { const queryClient = useQueryClient(); const { i18n } = useTranslation(); const components = useQuery({ queryKey: ["payroll-components"], queryFn: listComponents, }); const structures = useQuery({ queryKey: ["payroll-structures"], queryFn: listStructures, }); const brackets = useQuery({ queryKey: ["tax-brackets"], queryFn: listTaxBrackets }); const rates = useQuery({ queryKey: ["statutory-rates"], queryFn: listStatutoryRates }); const seed = useMutation({ mutationFn: seedPayrollStatutory, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["payroll-components"] }); queryClient.invalidateQueries({ queryKey: ["tax-brackets"] }); queryClient.invalidateQueries({ queryKey: ["statutory-rates"] }); }, }); return ( <> } /> {seed.isError && ( }> {apiErrorMessage(seed.error)} )} {seed.data && ( Components: {seed.data.components.created.length || "none"} added. Tax bands: {seed.data.taxBands}. Rates:{" "} {seed.data.rates.created.join(", ") || "none added"}. )} Income tax Components Structures {/* The single most important caveat in the module. These bands decide every employee's tax and this codebase cannot know whether a later schedule has been enacted. */} }> Verify these bands before running real payroll. They are seeded from Income Tax Proclamation 979/2016 and decide every employee's tax. The table is effective-dated, so a newer schedule is added as rows with a later start date — payslips for earlier months keep resolving the bands that applied to them. From To Rate Less deduction In force from {(brackets.data ?? []).map((band) => ( {money(band.lowerBound)} {band.upperBound ? money(band.upperBound) : "and above"} {(Number(band.rate) * 100).toFixed(0)}% {money(band.deduction)} {band.effectiveFrom} · {band.statuteReference ?? "—"} ))} {(brackets.data ?? []).length === 0 && ( No bands configured — payroll cannot be calculated until there are. )}
Statutory rates {(rates.data ?? []).map((rate) => ( {localized(rate.name, i18n.language) || rate.code} {(Number(rate.rate) * 100).toFixed(0)}% {rate.statuteReference ?? "—"} ))}
Component Type How it is computed Taxable Pensionable {(components.data ?? []).map((component) => ( {localized(component.name, i18n.language) || component.code} {component.code} {component.statuteReference && ` · ${component.statuteReference}`} {component.componentType.replace("_", " ").toLowerCase()} {component.calculation === "STATUTORY" ? "from the statutory table" : component.calculation.replace(/_/g, " ").toLowerCase()} {component.taxExemptAmount && ( exempt to the lower of {money(component.taxExemptAmount)} and{" "} {component.taxExemptRateOfBasic ? `${(Number(component.taxExemptRateOfBasic) * 100).toFixed(0)}% of basic` : "—"} )} {component.isTaxable ? "yes" : "no"} {component.isPensionable ? "yes" : "no"} ))}
Structure Lines Active {(structures.data ?? []).map((structure) => ( {localized(structure.name, i18n.language) || structure.code} {structure.code} {structure.lines?.length ?? "—"} {structure.isActive ? "yes" : "no"} ))} {(structures.data ?? []).length === 0 && ( No structures yet. Without one an employee is paid basic plus the statutory lines only. )}
); }