import React, { useMemo, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Slider } from "@/components/ui/slider"; import { Badge } from "@/components/ui/badge"; import { Calculator, ShieldCheck, DollarSign, Users, Car, CreditCard, Home, AlertCircle } from "lucide-react"; const currency = new Intl.NumberFormat("en-AU", { style: "currency", currency: "AUD", maximumFractionDigits: 0, }); const number = new Intl.NumberFormat("en-AU", { maximumFractionDigits: 0, }); function toNumber(value) { const parsed = Number(String(value || "").replace(/[^0-9.-]/g, "")); return Number.isFinite(parsed) ? parsed : 0; } function annualise(amount, frequency) { const value = toNumber(amount); if (frequency === "weekly") return value * 52; if (frequency === "fortnightly") return value * 26; if (frequency === "monthly") return value * 12; return value; } function monthlyise(amount, frequency) { const value = toNumber(amount); if (frequency === "weekly") return (value * 52) / 12; if (frequency === "fortnightly") return (value * 26) / 12; if (frequency === "annual") return value / 12; return value; } function calculateAustralianTax(income, hasHelpDebt) { const taxable = Math.max(0, income); let tax = 0; if (taxable <= 18200) tax = 0; else if (taxable <= 45000) tax = (taxable - 18200) * 0.16; else if (taxable <= 135000) tax = 4288 + (taxable - 45000) * 0.30; else if (taxable <= 190000) tax = 31288 + (taxable - 135000) * 0.37; else tax = 51638 + (taxable - 190000) * 0.45; const medicare = taxable > 26000 ? taxable * 0.02 : 0; // Simplified HELP/HECS estimate. Actual repayment thresholds change each year. let help = 0; if (hasHelpDebt) { if (taxable >= 160000) help = taxable * 0.10; else if (taxable >= 140000) help = taxable * 0.09; else if (taxable >= 120000) help = taxable * 0.08; else if (taxable >= 100000) help = taxable * 0.065; else if (taxable >= 80000) help = taxable * 0.045; else if (taxable >= 60000) help = taxable * 0.025; } return { tax, medicare, help, netAnnual: taxable - tax - medicare - help }; } function repaymentMonthly(principal, annualRate, years) { const r = annualRate / 100 / 12; const n = years * 12; if (r <= 0) return principal / n; return principal * (r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1); } function loanFromRepayment(monthlyRepayment, annualRate, years) { const r = annualRate / 100 / 12; const n = years * 12; if (monthlyRepayment <= 0) return 0; if (r <= 0) return monthlyRepayment * n; return monthlyRepayment * (Math.pow(1 + r, n) - 1) / (r * Math.pow(1 + r, n)); } const hemTable = { single: { 0: 2430, 1: 3150, 2: 3850, 3: 4550, 4: 5250, }, joint: { 0: 4100, 1: 4700, 2: 5325, 3: 5900, 4: 6500, }, }; function getHem(applicationType, dependants) { const capped = Math.min(Number(dependants || 0), 4); return hemTable[applicationType]?.[capped] || 5325; } function Field({ label, value, onChange, placeholder, helper, prefix = "$", type = "text" }) { return (
{helper}
}); } function ResultRow({ label, value }) { return (
); } export default function PoliceBorrowingPowerCalculator() { const [applicationType, setApplicationType] = useState("joint"); const [dependants, setDependants] = useState("2"); const [payFrequency, setPayFrequency] = useState("annual"); const [declaredExpenses, setDeclaredExpenses] = useState(""); const [a1Base, setA1Base] = useState("86000"); const [a1Loading, setA1Loading] = useState("8190"); const [a1Overtime, setA1Overtime] = useState("9932"); const [a1Shift, setA1Shift] = useState(""); const [a1Higher, setA1Higher] = useState(""); const [a1Other, setA1Other] = useState(""); const [a2Base, setA2Base] = useState("95000"); const [a2Other, setA2Other] = useState(""); const [creditCardLimit, setCreditCardLimit] = useState("20000"); const [personalLoanMonthly, setPersonalLoanMonthly] = useState(""); const [carLoanMonthly, setCarLoanMonthly] = useState(""); const [existingMortgageMonthly, setExistingMortgageMonthly] = useState(""); const [novatedPreTax, setNovatedPreTax] = useState(""); const [novatedPostTax, setNovatedPostTax] = useState(""); const [hasHelpDebt, setHasHelpDebt] = useState("no"); const [commitmentFrequency, setCommitmentFrequency] = useState("fortnightly"); const [actualRate, setActualRate] = useState(6.25); const [buffer, setBuffer] = useState(3.0); const [floorRate, setFloorRate] = useState(8.5); const [loanTerm, setLoanTerm] = useState(30); const [minimumSurplus, setMinimumSurplus] = useState(1000); const result = useMemo(() => { const a1BaseAnnual = annualise(a1Base, payFrequency); const a1LoadingAnnual = annualise(a1Loading, payFrequency); const a1OvertimeAnnual = annualise(a1Overtime, payFrequency); const a1ShiftAnnual = annualise(a1Shift, payFrequency); const a1HigherAnnual = annualise(a1Higher, payFrequency); const a1OtherAnnual = annualise(a1Other, payFrequency); const a2BaseAnnual = applicationType === "joint" ? annualise(a2Base, payFrequency) : 0; const a2OtherAnnual = applicationType === "joint" ? annualise(a2Other, payFrequency) : 0; const variableIncome = a1LoadingAnnual + a1OvertimeAnnual + a1ShiftAnnual + a1HigherAnnual + a1OtherAnnual; const baseIncome = a1BaseAnnual + a2BaseAnnual + a2OtherAnnual; const conservativeIncome = baseIncome + variableIncome * 0.8; const policeFriendlyIncome = baseIncome + variableIncome; const conservativeTax = calculateAustralianTax(conservativeIncome, hasHelpDebt === "yes"); const policeFriendlyTax = calculateAustralianTax(policeFriendlyIncome, hasHelpDebt === "yes"); const hem = getHem(applicationType, dependants); const livingExpenses = Math.max(hem, toNumber(declaredExpenses)); const creditCardMonthly = toNumber(creditCardLimit) * 0.038; const novatedMonthly = monthlyise(toNumber(novatedPreTax) + toNumber(novatedPostTax), commitmentFrequency); const monthlyCommitments = creditCardMonthly + toNumber(personalLoanMonthly) + toNumber(carLoanMonthly) + toNumber(existingMortgageMonthly) + novatedMonthly; const assessmentRate = Math.max(Number(actualRate) + Number(buffer), Number(floorRate)); const conservativeNetMonthly = conservativeTax.netAnnual / 12; const policeFriendlyNetMonthly = policeFriendlyTax.netAnnual / 12; const conservativeAvailable = conservativeNetMonthly - livingExpenses - monthlyCommitments - Number(minimumSurplus); const policeFriendlyAvailable = policeFriendlyNetMonthly - livingExpenses - monthlyCommitments - Number(minimumSurplus); const conservativeLoan = loanFromRepayment(conservativeAvailable, assessmentRate, Number(loanTerm)); const policeFriendlyLoan = loanFromRepayment(policeFriendlyAvailable, assessmentRate, Number(loanTerm)); const dtiConservative = conservativeIncome > 0 ? conservativeLoan / conservativeIncome : 0; const dtiPoliceFriendly = policeFriendlyIncome > 0 ? policeFriendlyLoan / policeFriendlyIncome : 0; return { baseIncome, variableIncome, conservativeIncome, policeFriendlyIncome, conservativeTax, policeFriendlyTax, hem, livingExpenses, creditCardMonthly, novatedMonthly, monthlyCommitments, assessmentRate, conservativeNetMonthly, policeFriendlyNetMonthly, conservativeAvailable, policeFriendlyAvailable, conservativeLoan: Math.max(0, conservativeLoan), policeFriendlyLoan: Math.max(0, policeFriendlyLoan), dtiConservative, dtiPoliceFriendly, repaymentPer500k: repaymentMonthly(500000, assessmentRate, Number(loanTerm)), }; }, [ applicationType, dependants, payFrequency, declaredExpenses, a1Base, a1Loading, a1Overtime, a1Shift, a1Higher, a1Other, a2Base, a2Other, creditCardLimit, personalLoanMonthly, carLoanMonthly, existingMortgageMonthly, novatedPreTax, novatedPostTax, hasHelpDebt, commitmentFrequency, actualRate, buffer, floorRate, loanTerm, minimumSurplus, ]); return (
Police Officer Borrowing Power Calculator
Estimate borrowing capacity using police payslip income such as base salary, loading allowance, overtime, shift penalties, dependants, credit cards and novated lease deductions.
1. About you
2. Copy figures from the police payslip
Enter the same frequency selected above. For annual salary, use annual figures.
Applicant 1 police income
Applicant 2 income
3. Existing commitments
Novated lease deductions
4. Servicing assumptions
Estimated borrowing range
Based on conservative and police-friendly income recognition.
Conservative
{currency.format(result.conservativeLoan)}
80% OT/allowances
Police-friendly
{currency.format(result.policeFriendlyLoan)}
100% OT/allowances
Income assessment
Servicing summary
Strength indicators
This is an estimate only. Lenders apply different HEM tables, assessment rates, overtime treatment, novated lease rules and policy overlays. It is designed for pre-qualification, not credit approval.
); }