From f15518e3c411aa5c00216a9d9fa5fb15e8a0377b Mon Sep 17 00:00:00 2001 From: Jonus Green Date: Mon, 19 Jan 2026 10:34:38 +0300 Subject: [PATCH 01/14] Merge branch 'main' of https://github.com/Exela-Tech/Propeerty_Management From 172d6b9385b2554dd4f0a19b91cfe19480bdf822 Mon Sep 17 00:00:00 2001 From: Jonus Green Date: Thu, 22 Jan 2026 16:47:20 +0300 Subject: [PATCH 02/14] undeposited fixed and landlord payments --- app/(dashboard)/accounting/actions.ts | 163 ++++--- .../accounting/cash-management/page.tsx | 402 ++++++++++----- app/(dashboard)/expenses/actions.ts | 75 ++- .../landlords/[id]/payments/page.tsx | 42 +- app/(dashboard)/landlords/payment-actions.ts | 168 +++++-- app/(dashboard)/landlords/payments/page.tsx | 459 ++++++++++-------- app/(dashboard)/payments/actions.ts | 26 +- app/(dashboard)/properties/[id]/edit/page.tsx | 4 +- app/(dashboard)/tenants/actions.ts | 70 ++- app/(dashboard)/units/actions.ts | 17 + components/edit-property-form.tsx | 33 +- components/property-payment-card.tsx | 296 +++++++++++ components/record-payment-dialog.tsx | 42 +- 13 files changed, 1278 insertions(+), 519 deletions(-) create mode 100644 components/property-payment-card.tsx diff --git a/app/(dashboard)/accounting/actions.ts b/app/(dashboard)/accounting/actions.ts index a87acb2..54233c6 100644 --- a/app/(dashboard)/accounting/actions.ts +++ b/app/(dashboard)/accounting/actions.ts @@ -13,7 +13,7 @@ export async function getChartOfAccounts() { .order("account_code") if (error) { - console.error(" Error fetching chart of accounts:", error) + console.error("[v0] Error fetching chart of accounts:", error) throw new Error("Failed to fetch chart of accounts") } @@ -34,7 +34,7 @@ export async function getAccountBalances() { const { data, error } = await supabase.from("account_balances").select("*").order("account_code") if (error) { - console.error(" Error fetching account balances:", error) + console.error("[v0] Error fetching account balances:", error) throw new Error("Failed to fetch account balances") } @@ -64,7 +64,7 @@ export async function getGeneralLedger(accountId?: string, startDate?: string, e const { data, error } = await query.limit(500) if (error) { - console.error(" Error fetching general ledger:", error) + console.error("[v0] Error fetching general ledger:", error) throw new Error("Failed to fetch general ledger") } @@ -100,13 +100,17 @@ export async function syncTenantPaymentToGL(paymentId: string) { throw new Error("Required trust accounts not found") } + // Build descriptive payment description with tenant name + const tenantName = payment.tenant ? `${payment.tenant.first_name} ${payment.tenant.last_name}` : "Unknown Tenant" + const paymentDescription = `Tenant payment received - ${tenantName}` + const entries = [ { account_id: undepositedAccount.id, transaction_date: payment.payment_date, reference_id: payment.id, reference_type: "tenant_payment", - description: `Rent received from tenant (Unit: ${payment.tenant.unit_id})`, + description: paymentDescription, debit: payment.amount, credit: 0, }, @@ -115,7 +119,7 @@ export async function syncTenantPaymentToGL(paymentId: string) { transaction_date: payment.payment_date, reference_id: payment.id, reference_type: "tenant_payment", - description: `Rent received from tenant (Unit: ${payment.tenant.unit_id})`, + description: paymentDescription, debit: 0, credit: payment.amount, }, @@ -124,11 +128,12 @@ export async function syncTenantPaymentToGL(paymentId: string) { const { error } = await supabase.from("general_ledger").insert(entries) if (error) { - console.error(" Error syncing payment to GL:", error) + console.error("[v0] Error syncing payment to GL:", error) throw new Error("Failed to sync payment to general ledger") } revalidatePath("/accounting") + revalidatePath("/accounting/cash-management") } export async function getBankAccounts() { @@ -140,7 +145,7 @@ export async function getBankAccounts() { .order("account_name", { ascending: true }) if (error) { - console.error(" Error fetching bank accounts:", error) + console.error("[v0] Error fetching bank accounts:", error) throw new Error("Failed to fetch bank accounts") } @@ -197,7 +202,7 @@ export async function getPaymentDeposits(bankAccountId?: string) { const { data, error } = await query if (error) { - console.error(" Error fetching deposits:", error) + console.error("[v0] Error fetching deposits:", error) throw new Error("Failed to fetch deposits") } @@ -213,18 +218,18 @@ export async function createPaymentDeposit( ) { const supabase = getServiceClient() - console.log(" Creating deposit for bank:", bankAccountId) - console.log(" Payment IDs:", paymentIds) + console.log("[v0] Creating deposit for bank:", bankAccountId) + console.log("[v0] Payment IDs:", paymentIds) const { data: tenantPayments, error: tenantError } = await supabase .from("tenant_payments") .select("id, amount, payment_date, tenant_id, tenants!inner(first_name, last_name)") .in("id", paymentIds) - console.log(" Tenant payments fetched:", tenantPayments) + console.log("[v0] Tenant payments fetched:", tenantPayments) if (tenantError) { - console.log(" Error fetching tenant payments:", tenantError.message) + console.log("[v0] Error fetching tenant payments:", tenantError.message) } let landlordPaymentsWithNames: any[] = [] @@ -234,10 +239,10 @@ export async function createPaymentDeposit( .select("id, amount, payment_date, landlord_id") .in("id", paymentIds) - console.log(" Landlord payments fetched:", landlordPayments) + console.log("[v0] Landlord payments fetched:", landlordPayments) if (landlordError) { - console.log(" Error fetching landlord payments:", landlordError.message) + console.log("[v0] Error fetching landlord payments:", landlordError.message) } if (landlordPayments && landlordPayments.length > 0) { @@ -249,7 +254,7 @@ export async function createPaymentDeposit( .in("id", landlordIds) if (profileError) { - console.log(" Error fetching landlord profiles:", profileError.message) + console.log("[v0] Error fetching landlord profiles:", profileError.message) } else if (landlordProfiles) { landlordPaymentsWithNames = landlordPayments.map((payment) => { const profile = landlordProfiles.find((p) => p.id === payment.landlord_id) @@ -269,42 +274,42 @@ export async function createPaymentDeposit( throw new Error("No valid payments found to deposit") } - console.log(" Total amount to deposit:", totalAmount) + console.log("[v0] Total amount to deposit:", totalAmount) - let depositDescription = "Deposit: " const payerNames: string[] = [] if (tenantPayments && tenantPayments.length > 0) { tenantPayments.forEach((p: any) => { - console.log(" Processing tenant payment:", p) + console.log("[v0] Processing tenant payment:", p) if (p.tenants) { - payerNames.push(`${p.tenants.first_name} ${p.tenants.last_name}`) + payerNames.push(`Tenant payment received - ${p.tenants.first_name} ${p.tenants.last_name}`) } }) } if (landlordPaymentsWithNames && landlordPaymentsWithNames.length > 0) { landlordPaymentsWithNames.forEach((p: any) => { - console.log(" Processing landlord payment:", p) + console.log("[v0] Processing landlord payment:", p) if (p.profiles) { - payerNames.push(`${p.profiles.first_name} ${p.profiles.last_name}`) + payerNames.push(`Landlord payment received - ${p.profiles.first_name} ${p.profiles.last_name}`) } }) } - console.log(" Payer names collected:", payerNames) + console.log("[v0] Payer names collected:", payerNames) + let depositDescription = "" if (payerNames.length > 0) { - depositDescription += payerNames.join(", ") + depositDescription = payerNames.join(", ") } else { - depositDescription += "Multiple payments" + depositDescription = "Bank deposit" } if (depositReference) { depositDescription += ` (Ref: ${depositReference})` } - console.log(" Final deposit description:", depositDescription) + console.log("[v0] Final deposit description:", depositDescription) const { data: deposit, error: depositError } = await supabase .from("payment_deposits") @@ -320,7 +325,7 @@ export async function createPaymentDeposit( .single() if (depositError) { - console.error(" Error creating deposit:", depositError) + console.error("[v0] Error creating deposit:", depositError) throw new Error("Failed to create deposit: " + depositError.message) } @@ -346,7 +351,7 @@ export async function createPaymentDeposit( if (depositItems.length > 0) { const { error: itemsError } = await supabase.from("deposit_items").insert(depositItems) if (itemsError) { - console.error(" Error creating deposit items:", itemsError) + console.error("[v0] Error creating deposit items:", itemsError) throw new Error("Failed to create deposit items: " + itemsError.message) } } @@ -378,7 +383,7 @@ export async function createPaymentDeposit( .single() if (!bankAccount?.gl_account_id) { - console.error(" Bank account has no GL account linkage!") + console.error("[v0] Bank account has no GL account linkage!") throw new Error("Bank account is not linked to a GL account") } @@ -389,14 +394,14 @@ export async function createPaymentDeposit( .single() if (!undepositedAccount) { - console.error(" Undeposited Funds account (1015) not found!") + console.error("[v0] Undeposited Funds account (1015) not found!") throw new Error("Undeposited Funds GL account not found") } - console.log(" Creating GL entries...") - console.log(" Bank GL Account ID:", bankAccount.gl_account_id) - console.log(" Undeposited GL Account ID:", undepositedAccount.id) - console.log(" Amount:", totalAmount) + console.log("[v0] Creating GL entries...") + console.log("[v0] Bank GL Account ID:", bankAccount.gl_account_id) + console.log("[v0] Undeposited GL Account ID:", undepositedAccount.id) + console.log("[v0] Amount:", totalAmount) const { data: glEntries, error: glError } = await supabase .from("general_ledger") @@ -423,11 +428,11 @@ export async function createPaymentDeposit( .select() if (glError) { - console.error(" CRITICAL: GL entry creation failed:", glError) + console.error("[v0] CRITICAL: GL entry creation failed:", glError) throw new Error("Failed to create GL entries: " + glError.message) } - console.log(" GL entries created successfully:", glEntries?.length || 0) + console.log("[v0] GL entries created successfully:", glEntries?.length || 0) revalidatePath("/accounting") revalidatePath("/accounting/cash-management") @@ -440,7 +445,7 @@ export async function getLandlordStatements() { const { data, error } = await supabase.from("landlord_balances").select("*").order("landlord_name") if (error) { - console.error(" Error fetching landlord statements:", error) + console.error("[v0] Error fetching landlord statements:", error) throw new Error("Failed to fetch landlord statements") } @@ -457,7 +462,7 @@ export async function getLandlordSubledger(landlordId: string) { .order("transaction_date", { ascending: false }) if (error) { - console.error(" Error fetching landlord subledger:", error) + console.error("[v0] Error fetching landlord subledger:", error) throw new Error("Failed to fetch landlord subledger") } @@ -483,7 +488,7 @@ export async function getProfitAndLossStatement(startDate: string, endDate: stri .lte("transaction_date", endDate) if (error) { - console.error(" Error fetching GL data:", error) + console.error("[v0] Error fetching GL data:", error) return { period: { startDate, endDate }, income: [], totalIncome: 0, expenses: [], totalExpenses: 0, netIncome: 0 } } @@ -597,7 +602,7 @@ export async function getTrialBalance(asOfDate: string) { const { data: balances, error } = await supabase.from("account_balances").select("*").order("account_code") if (error) { - console.error(" Error fetching trial balance:", error) + console.error("[v0] Error fetching trial balance:", error) throw new Error("Failed to fetch trial balance") } @@ -660,7 +665,7 @@ export async function getTaxConfiguration() { const { data, error } = await supabase.from("tax_configuration").select("*").single() if (error && error.code !== "PGRST116") { - console.error(" Error fetching tax configuration:", error) + console.error("[v0] Error fetching tax configuration:", error) throw new Error("Failed to fetch tax configuration") } @@ -739,7 +744,7 @@ export async function recordTaxTransaction(taxType: string, amount: number, desc }) if (error) { - console.error(" Error recording tax transaction:", error) + console.error("[v0] Error recording tax transaction:", error) throw new Error("Failed to record tax transaction") } @@ -827,7 +832,7 @@ export async function recordReconciliation( }) if (error) { - console.error(" Error recording reconciliation:", error) + console.error("[v0] Error recording reconciliation:", error) throw new Error("Failed to record reconciliation") } @@ -844,7 +849,7 @@ export async function getReconciliationHistory(limit = 50) { .limit(limit) if (error) { - console.error(" Error fetching reconciliation history:", error) + console.error("[v0] Error fetching reconciliation history:", error) throw new Error("Failed to fetch reconciliation history") } @@ -882,7 +887,7 @@ export async function getAccountingDashboard() { export async function getBankReconciliationSummary() { const supabase = getServiceClient() - const { data: banks } = await supabase.from("bank_accounts").select("id, account_name, bank_name, current_balance") + const { data: banks } = await supabase.from("bank_accounts").select("id, account_name, bank_name, balance") if (!banks || banks.length === 0) { return { @@ -896,9 +901,9 @@ export async function getBankReconciliationSummary() { accounts: banks.map((bank) => ({ id: bank.id, name: `${bank.bank_name} - ${bank.account_name}`, - balance: bank.current_balance, + balance: bank.balance, })), - totalBalance: banks.reduce((sum, b) => sum + (b.current_balance || 0), 0), + totalBalance: banks.reduce((sum, b) => sum + (b.balance || 0), 0), isReconciled: true, } } @@ -949,7 +954,7 @@ export async function createBankAccount(data: { .single() if (error) { - console.error(" Error creating bank account:", error) + console.error("[v0] Error creating bank account:", error) throw new Error("Failed to create bank account") } @@ -1016,7 +1021,7 @@ export async function updateBankAccount( .single() if (error) { - console.error(" Error updating bank account:", error) + console.error("[v0] Error updating bank account:", error) throw new Error("Failed to update bank account") } @@ -1026,7 +1031,7 @@ export async function updateBankAccount( export async function getBankTransactions(bankAccountId: string) { const supabase = getServiceClient() - console.log(" Fetching transactions for bank:", bankAccountId) + console.log("[v0] Fetching transactions for bank:", bankAccountId) const { data: bankAccount } = await supabase .from("bank_accounts") @@ -1038,7 +1043,7 @@ export async function getBankTransactions(bankAccountId: string) { throw new Error("Bank account not found") } - console.log(" Bank account GL ID:", bankAccount.gl_account_id) + console.log("[v0] Bank account GL ID:", bankAccount.gl_account_id) const { data: transactions, error } = await supabase .from("general_ledger") @@ -1048,11 +1053,11 @@ export async function getBankTransactions(bankAccountId: string) { .order("created_at", { ascending: false }) .limit(100) - console.log(" Found transactions:", transactions?.length) - console.log(" First transaction:", transactions?.[0]) + console.log("[v0] Found transactions:", transactions?.length) + console.log("[v0] First transaction:", transactions?.[0]) if (error) { - console.error(" Error fetching bank transactions:", error) + console.error("[v0] Error fetching bank transactions:", error) throw new Error("Failed to fetch bank transactions") } @@ -1075,9 +1080,59 @@ export async function getChartOfAccountsForBanks() { .order("account_code", { ascending: true }) if (error) { - console.error(" Error fetching GL accounts:", error) + console.error("[v0] Error fetching GL accounts:", error) throw new Error("Failed to fetch GL accounts") } return data || [] } + +// New function to get undeposited funds transaction history +export async function getUndepositedFundsHistory(startDate?: string, endDate?: string) { + const supabase = getServiceClient() + + console.log("[v0] Fetching undeposited funds history") + + // Get the Undeposited Funds GL account (1015) + const { data: undepositedAccount } = await supabase + .from("chart_of_accounts") + .select("id, account_code, account_name") + .eq("account_code", "1015") + .single() + + if (!undepositedAccount) { + throw new Error("Undeposited Funds account not found") + } + + console.log("[v0] Undeposited Funds GL Account ID:", undepositedAccount.id) + + // Build query for GL transactions + let query = supabase + .from("general_ledger") + .select("*") + .eq("account_id", undepositedAccount.id) + .order("transaction_date", { ascending: false }) + .order("created_at", { ascending: false }) + + if (startDate) { + query = query.gte("transaction_date", startDate) + } + + if (endDate) { + query = query.lte("transaction_date", endDate) + } + + const { data: transactions, error } = await query.limit(200) + + console.log("[v0] Found undeposited funds transactions:", transactions?.length) + + if (error) { + console.error("[v0] Error fetching undeposited funds history:", error) + throw new Error("Failed to fetch undeposited funds history") + } + + return { + undepositedAccount, + transactions: transactions || [], + } +} diff --git a/app/(dashboard)/accounting/cash-management/page.tsx b/app/(dashboard)/accounting/cash-management/page.tsx index f95bbaf..ad1cdee 100644 --- a/app/(dashboard)/accounting/cash-management/page.tsx +++ b/app/(dashboard)/accounting/cash-management/page.tsx @@ -12,6 +12,7 @@ import { getPaymentDeposits, createPaymentDeposit, getBankTransactions, + getUndepositedFundsHistory, } from "../actions" export default function CashManagementPage() { @@ -28,6 +29,12 @@ export default function CashManagementPage() { const [selectedBankInfo, setSelectedBankInfo] = useState(null) const [loadingTransactions, setLoadingTransactions] = useState(false) + const [undepositedHistory, setUndepositedHistory] = useState([]) + const [undepositedInfo, setUndepositedInfo] = useState(null) + const [loadingUndepositedHistory, setLoadingUndepositedHistory] = useState(false) + const [startDate, setStartDate] = useState("") + const [endDate, setEndDate] = useState("") + useEffect(() => { const fetchData = async () => { try { @@ -45,7 +52,7 @@ export default function CashManagementPage() { setSelectedBankForView(accounts[0].id) } } catch (error) { - console.error(" Error loading cash management:", error) + console.error("[v0] Error loading cash management:", error) } finally { setLoading(false) } @@ -63,7 +70,7 @@ export default function CashManagementPage() { setBankTransactions(result.transactions) setSelectedBankInfo(result.bankAccount) } catch (error) { - console.error(" Error loading bank transactions:", error) + console.error("[v0] Error loading bank transactions:", error) } finally { setLoadingTransactions(false) } @@ -72,6 +79,23 @@ export default function CashManagementPage() { loadBankTransactions() }, [selectedBankForView]) + useEffect(() => { + loadUndepositedHistory() + }, [startDate, endDate]) // Added startDate and endDate to trigger reload when filters change + + const loadUndepositedHistory = async () => { + setLoadingUndepositedHistory(true) + try { + const result = await getUndepositedFundsHistory(startDate || undefined, endDate || undefined) + setUndepositedHistory(result.transactions) + setUndepositedInfo(result.undepositedAccount) + } catch (error) { + console.error("[v0] Error loading undeposited funds history:", error) + } finally { + setLoadingUndepositedHistory(false) + } + } + const handleDepositPayments = async () => { if (selectedPayments.size === 0 || !selectedBank) { alert("Please select payments and a bank account") @@ -79,6 +103,9 @@ export default function CashManagementPage() { } try { + const bankName = bankAccounts.find((b) => b.id === selectedBank)?.account_name || "Bank" + const paymentCount = selectedPayments.size + await createPaymentDeposit( selectedBank, Array.from(selectedPayments), @@ -87,21 +114,44 @@ export default function CashManagementPage() { ) setSelectedPayments(new Set()) setDepositRef("") - // Refresh data + + // Refresh all data after deposit const [funds, deps] = await Promise.all([getUndepositedFunds(), getPaymentDeposits()]) setUndepositedFunds(funds) setDeposits(deps) + // Reload the undeposited funds history to show the new credit entry + await loadUndepositedHistory() + + // Reload bank transactions if viewing a bank if (selectedBankForView) { const result = await getBankTransactions(selectedBankForView) setBankTransactions(result.transactions) } + + alert(`Successfully deposited ${paymentCount} payment(s) to ${bankName}. Check the transaction history below to see the credit entry.`) } catch (error) { - console.error(" Error creating deposit:", error) - alert("Failed to create deposit") + console.error("[v0] Error creating deposit:", error) + alert("Failed to create deposit: " + (error instanceof Error ? error.message : "Unknown error")) } } + const handleRefreshHistory = async () => { + await loadUndepositedHistory() + } + + const handleClearFilters = () => { + setStartDate("") + setEndDate("") + } + + const totalUndeposited = undepositedFunds.reduce((sum, p) => sum + p.amount, 0) + + const tenantPayments = undepositedFunds.filter((p) => p.type === "tenant_payment") + const landlordPayments = undepositedFunds.filter((p) => p.type === "landlord_payment") + const tenantTotal = tenantPayments.reduce((sum, p) => sum + p.amount, 0) + const landlordTotal = landlordPayments.reduce((sum, p) => sum + p.amount, 0) + const transactionsWithBalance = bankTransactions .map((transaction, index) => { const previousTransactions = bankTransactions.slice(index + 1) @@ -117,14 +167,24 @@ export default function CashManagementPage() { runningBalance, } }) - .reverse() // Reverse to show oldest first with running balance + .reverse() - const totalUndeposited = undepositedFunds.reduce((sum, p) => sum + p.amount, 0) + const undepositedHistoryWithBalance = undepositedHistory + .map((transaction, index) => { + const previousTransactions = undepositedHistory.slice(index + 1) + const runningBalance = + previousTransactions.reduce((balance, t) => { + return balance + (t.debit || 0) - (t.credit || 0) + }, 0) + + (transaction.debit || 0) - + (transaction.credit || 0) - const tenantPayments = undepositedFunds.filter((p) => p.type === "tenant_payment") - const landlordPayments = undepositedFunds.filter((p) => p.type === "landlord_payment") - const tenantTotal = tenantPayments.reduce((sum, p) => sum + p.amount, 0) - const landlordTotal = landlordPayments.reduce((sum, p) => sum + p.amount, 0) + return { + ...transaction, + runningBalance, + } + }) + .reverse() return (
@@ -187,73 +247,85 @@ export default function CashManagementPage() { -
- Undeposited Funds - General Ledger View - - {totalUndeposited.toLocaleString("en-US", { - style: "currency", - currency: "UGX", - minimumFractionDigits: 0, - })} - -
+ Undeposited Funds Transaction History

- Showing GL entries where cash is received but not yet deposited to bank + Double-entry ledger showing payments received (debit) and deposits made to banks (credit)

-
- - -
+
+
+

+ Deposit Payments to Bank +

+ + {undepositedFunds.length} pending | {selectedPayments.size} selected + +
-
- - setDepositRef(e.target.value)} - /> -
+
+
+ + +
+
+ + setDepositRef(e.target.value)} + placeholder="e.g., Daily deposit #123" + className="border-2 border-amber-300 focus:ring-2 focus:ring-amber-500" + /> +
+
-
- - - - - - - - - - - - - {undepositedFunds.map((payment) => ( - - - - - - - - - ))} - - - - - - -
- 0} - onChange={(e) => { - if (e.target.checked) { - setSelectedPayments(new Set(undepositedFunds.map((p) => p.id))) - } else { - setSelectedPayments(new Set()) - } - }} - /> - DateFromTypeDebitCredit
+
+
+ + Select Payments to Deposit: + + {undepositedFunds.length > 0 && ( + + )} +
+
+ {undepositedFunds.length === 0 ? ( +
+ No pending payments to deposit. All funds have been deposited to bank. +
+ ) : ( + undepositedFunds.map((payment) => ( +
{payment.payment_date}{payment.payerName} - {payment.type === "tenant_payment" ? "Tenant" : "Landlord"} - - {payment.amount.toLocaleString("en-US", { - style: "currency", - currency: "UGX", - minimumFractionDigits: 0, - })} - -
- Total: - - {totalUndeposited.toLocaleString("en-US", { - style: "currency", - currency: "UGX", - minimumFractionDigits: 0, - })} - -
+ + {payment.payer_name} - {new Date(payment.payment_date).toLocaleDateString("en-GB")} + + + {payment.amount.toLocaleString("en-US", { + style: "currency", + currency: "UGX", + minimumFractionDigits: 0, + })} + + + )) + )} +
+
+ +
+
+
+ + setStartDate(e.target.value)} + placeholder="Start Date" + /> +
+
+ + setEndDate(e.target.value)} + placeholder="End Date" + /> +
+ + +
+ + {loadingUndepositedHistory ? ( +
Loading transaction history...
+ ) : ( +
+ + + + + + + + + + + + {undepositedHistoryWithBalance.length === 0 ? ( + + + + ) : ( + undepositedHistoryWithBalance.map((transaction) => ( + + + + + + + + )) + )} + +
DateDescriptionDebit (Money In)Credit (Money Out)Balance
+ No transactions found for the selected period +
+ {new Date(transaction.transaction_date).toLocaleDateString("en-GB")} + +
+ {transaction.description} + {transaction.reference_type && ( + + {transaction.reference_type.replace("_", " ")} + + )} +
+
+ {transaction.debit > 0 ? ( + + {transaction.debit.toLocaleString("en-US", { + style: "currency", + currency: "UGX", + minimumFractionDigits: 0, + })} + + ) : ( + - + )} + + {transaction.credit > 0 ? ( + + {transaction.credit.toLocaleString("en-US", { + style: "currency", + currency: "UGX", + minimumFractionDigits: 0, + })} + + ) : ( + - + )} + + {transaction.runningBalance.toLocaleString("en-US", { + style: "currency", + currency: "UGX", + minimumFractionDigits: 0, + })} +
+
+ )} +
-

Accounting Entry:

+

Double-Entry Accounting:

  • - Debit: Undeposited Funds (Asset) - Cash received - increases this asset account + Debit (Money In) - When payments are received, increases + Undeposited Funds balance
  • - Credit: Rental Income (Revenue) - Not shown here, posted to income statement + Credit (Money Out) - When deposited to bank, decreases + Undeposited Funds balance
- - @@ -345,11 +514,11 @@ export default function CashManagementPage() { {selectedBankInfo && ( -
+

{selectedBankInfo.account_name}

-

{selectedBankInfo.bank_name}

+

Account Code: {selectedBankInfo.account_code}

Current Balance

@@ -389,7 +558,7 @@ export default function CashManagementPage() { ) : ( - transactionsWithBalance.map((transaction, index) => ( + transactionsWithBalance.map((transaction) => ( {new Date(transaction.transaction_date).toLocaleDateString("en-GB")} @@ -449,11 +618,10 @@ export default function CashManagementPage() {

Understanding Bank Transactions:

  • - Debit - Money coming INTO the bank (deposits, income) + Debit - Money coming INTO the bank (deposits from undeposited funds)
  • - Credit - Money going OUT of the bank (expenses, - withdrawals) + Credit - Money going OUT of the bank (expenses, withdrawals)
  • Balance - Running total showing current bank account balance diff --git a/app/(dashboard)/expenses/actions.ts b/app/(dashboard)/expenses/actions.ts index c44d1d5..8066a81 100644 --- a/app/(dashboard)/expenses/actions.ts +++ b/app/(dashboard)/expenses/actions.ts @@ -2,9 +2,6 @@ import { createClient } from "@supabase/supabase-js" import { revalidatePath } from "next/cache" -import { logger } from "@/lib/logger" - -const log = logger.child("expenses:actions") function getServiceClient() { return createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { @@ -20,7 +17,7 @@ export async function getProperties() { const { data, error } = await supabase.from("properties").select("id, name, property_type").order("name") if (error) { - log.error("Error fetching properties", error) + console.error("[v0] Error fetching properties:", error) throw new Error("Failed to fetch properties") } @@ -36,7 +33,7 @@ export async function getBankAccounts() { .order("account_name", { ascending: true }) if (error) { - console.error(" Error fetching bank accounts:", error) + console.error("[v0] Error fetching bank accounts:", error) return [] } @@ -64,54 +61,38 @@ async function recordExpenseToGL( } // Map expense category to GL account - // Based on chart_of_accounts: 5010=Maintenance, 5020=Salaries, 5030=Utilities, - // 5040=Insurance, 5050=Commission, 5060=Administrative, 5070=Transportation, 5080=Office Rent const categoryToAccount: { [key: string]: string } = { - maintenance: "5010", // Maintenance & Repairs - salary: "5020", // Salaries & Wages - wage: "5020", // Salaries & Wages (same as salary) - utilities: "5030", // Utilities Expense - internet: "5060", // Administrative Expense (communications) - cleaning: "5010", // Maintenance & Repairs - field_expense: "5070", // Transportation Expense - transport: "5070", // Transportation Expense - office_rent: "5080", // Office Rent Expense - other: "5060", // Administrative Expense (fallback) + salary: "5010", + transport: "5020", + wage: "5030", + internet: "5040", + field_expense: "5050", + office_rent: "5060", + utilities: "5070", + cleaning: "5080", + maintenance: "5090", + other: "5099", } - const expenseAccountCode = categoryToAccount[category] || "5060" // Default to Administrative Expense + const expenseAccountCode = categoryToAccount[category] || "5099" - const { data: expenseAccounts, error: accountError } = await supabase + const { data: expenseAccounts } = await supabase .from("chart_of_accounts") - .select("id, account_code, account_name") + .select("id, account_code") .eq("account_code", expenseAccountCode) - .eq("is_active", true) .single() - if (accountError || !expenseAccounts) { - log.error("Expense GL account not found", { - accountCode: expenseAccountCode, - category, - error: accountError, - }) - throw new Error( - `Expense GL account ${expenseAccountCode} not found. Please ensure the chart of accounts is properly set up.` - ) + if (!expenseAccounts) { + throw new Error(`Expense GL account ${expenseAccountCode} not found`) } - log.debug("Found expense GL account", { - accountCode: expenseAccountCode, - accountName: expenseAccounts.account_name, - accountId: expenseAccounts.id, - }) - const glEntries = [ { account_id: expenseAccounts.id, debit: amount, credit: 0, transaction_date: transactionDate, - description: `Expense: ${description}`, + description: description, reference_type: "expense", reference_id: expenseId, }, @@ -120,7 +101,7 @@ async function recordExpenseToGL( debit: 0, credit: amount, transaction_date: transactionDate, - description: `Expense payment: ${description}`, + description: description, reference_type: "expense", reference_id: expenseId, }, @@ -129,7 +110,7 @@ async function recordExpenseToGL( const { error } = await supabase.from("general_ledger").insert(glEntries) if (error) { - console.error(" Error posting expense to GL:", error) + console.error("[v0] Error posting expense to GL:", error) throw new Error("Failed to post expense to general ledger") } } @@ -159,24 +140,24 @@ export async function createExpense(formData: FormData) { type: "expense", } - log.debug("Creating expense", { category, amount, currency }) + console.log("[v0] Creating expense with data:", expenseData) const { data, error } = await supabase.from("transactions").insert([expenseData]).select() if (error) { - log.error("Error creating expense", error) + console.error("[v0] Error creating expense:", error) throw new Error(error.message) } - log.info("Expense created successfully", { expenseId: data?.[0]?.id }) + console.log("[v0] Expense created successfully:", data) if (data && data.length > 0) { const expenseId = data[0].id try { await recordExpenseToGL(expenseId, category, amount, description, transactionDate, bankAccountId) - console.log(" Expense posted to GL successfully") + console.log("[v0] Expense posted to GL successfully") } catch (glError) { - console.error(" Failed to post expense to GL:", glError) + console.error("[v0] Failed to post expense to GL:", glError) // Rollback the expense await supabase.from("transactions").delete().eq("id", expenseId) throw glError @@ -196,11 +177,10 @@ export async function deleteExpense(expenseId: string) { const { error } = await supabase.from("transactions").delete().eq("id", expenseId) if (error) { - log.error("Error deleting expense", error, { expenseId }) + console.error("[v0] Error deleting expense:", error) throw new Error(error.message) } - log.info("Expense deleted successfully", { expenseId }) revalidatePath("/expenses") } @@ -225,10 +205,9 @@ export async function updateExpense(expenseId: string, formData: FormData) { .eq("id", expenseId) if (error) { - log.error("Error updating expense", error, { expenseId }) + console.error("Error updating expense:", error) throw new Error(error.message) } - log.info("Expense updated successfully", { expenseId }) revalidatePath("/expenses") } diff --git a/app/(dashboard)/landlords/[id]/payments/page.tsx b/app/(dashboard)/landlords/[id]/payments/page.tsx index aa107ac..a4d2b5f 100644 --- a/app/(dashboard)/landlords/[id]/payments/page.tsx +++ b/app/(dashboard)/landlords/[id]/payments/page.tsx @@ -26,7 +26,6 @@ interface LandlordPaymentData { name: string email: string phone: string - commission_percentage: number } payments: Payment[] totalPaid: number @@ -54,7 +53,7 @@ export default function LandlordPaymentHistoryPage() { const result = await response.json() setData(result) } catch (err) { - console.error(" Error fetching payment history:", err) + console.error("[v0] Error fetching payment history:", err) setError("Failed to load payment history") } finally { setLoading(false) @@ -74,7 +73,7 @@ export default function LandlordPaymentHistoryPage() { ) } - if (error || !data) { + if (error || !data || !data.landlord) { return (
    @@ -87,6 +86,9 @@ export default function LandlordPaymentHistoryPage() { ) } + const landlord = data.landlord + const payments = data.payments || [] + const getPaymentMethodLabel = (method: string): string => { const labels: Record = { bank_transfer: "Bank Transfer", @@ -123,9 +125,9 @@ export default function LandlordPaymentHistoryPage() {
    -

    {data.landlord.name}'S PAYMENT HISTORY

    +

    {landlord.name}'S PAYMENT HISTORY

    - {data.landlord.email} • {data.landlord.phone} + {landlord.email} • {landlord.phone}

    @@ -179,7 +181,7 @@ export default function LandlordPaymentHistoryPage() {

    {formatCurrency(data.totalCommissionDeducted)}

    -

    {data.landlord.commission_percentage}% of expected

    +

    Based on property commissions

    @@ -205,7 +207,7 @@ export default function LandlordPaymentHistoryPage() {

    {formatCurrency(data.totalPaid)}

    -

    {data.payments.length} payments

    +

    {payments.length} payments

@@ -218,7 +220,7 @@ export default function LandlordPaymentHistoryPage() { COMMISSION BREAKDOWN - Commission is calculated at {data.landlord.commission_percentage}% of EXPECTED rent from tenants + Commission is calculated per property (fixed amount or percentage) @@ -230,7 +232,7 @@ export default function LandlordPaymentHistoryPage() {

Commission Rate

-

{data.landlord.commission_percentage}%

+

Per Property

@@ -261,7 +263,7 @@ export default function LandlordPaymentHistoryPage() { Complete record of all payments made to this landlord - {data.payments.length === 0 ? ( + {payments.length === 0 ? (
No payments recorded yet
) : (
@@ -278,7 +280,7 @@ export default function LandlordPaymentHistoryPage() { - {data.payments.map((payment) => ( + {payments.map((payment) => ( {payment.receipt_number} {new Date(payment.payment_date).toLocaleDateString()} @@ -298,11 +300,15 @@ export default function LandlordPaymentHistoryPage() { - - - + ))} @@ -314,14 +320,14 @@ export default function LandlordPaymentHistoryPage() { {/* Notes Section */} - {data.payments.some((p) => p.notes) && ( + {payments.some((p) => p.notes) && ( PAYMENT NOTES
- {data.payments + {payments .filter((p) => p.notes) .map((payment) => (
diff --git a/app/(dashboard)/landlords/payment-actions.ts b/app/(dashboard)/landlords/payment-actions.ts index 7390f0c..d709702 100644 --- a/app/(dashboard)/landlords/payment-actions.ts +++ b/app/(dashboard)/landlords/payment-actions.ts @@ -78,13 +78,32 @@ export async function calculateLandlordOwed(landlordId: string, periodStart: str const totalCollected = payments?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0 - // Get landlord data including commission percentage - const { data: landlord } = await supabase.from("owners").select("commission_percentage").eq("id", landlordId).single() - - const commissionPercentage = landlord?.commission_percentage || 10 + // Get properties with commission settings + const { data: propertiesWithCommission } = await supabase + .from("properties") + .select("id, commission_type, commission_value") + .in("id", propertyIds) + + // Calculate commission per property based on collected rent + let totalCommissionDeducted = 0 + for (const prop of propertiesWithCommission || []) { + const propTenants = tenants.filter(t => t.property_id === prop.id) + const propCollected = payments + ?.filter(p => propTenants.some(t => t.id === p.tenant_id)) + .reduce((sum, p) => sum + (p.amount || 0), 0) || 0 + + const commissionType = prop.commission_type || "percentage" + const commissionValue = prop.commission_value || 10 + + if (commissionType === "fixed") { + totalCommissionDeducted += commissionValue + } else { + totalCommissionDeducted += (propCollected * commissionValue) / 100 + } + } - const commissionDeducted = (expectedRent * commissionPercentage) / 100 - const netPayout = expectedRent - commissionDeducted + const commissionDeducted = totalCommissionDeducted + const netPayout = totalCollected - commissionDeducted // Get previous payments to landlord during this period const { data: previousPayments } = await supabase @@ -132,6 +151,8 @@ export async function calculateLandlordOwed(landlordId: string, periodStart: str } }) + const commissionPercentage = 10 // Declare commissionPercentage here + return { owed, breakdown, @@ -152,7 +173,8 @@ export async function recordLandlordPayment(formData: FormData) { const supabase = getServiceClient() const landlord_id = formData.get("landlord_id") as string - const amount = Number.parseFloat(formData.get("amount") as string) + const property_id = formData.get("property_id") as string + const grossAmount = Number.parseFloat(formData.get("amount") as string) const payment_date = formData.get("payment_date") as string const payment_method = formData.get("payment_method") as string const period_start = formData.get("period_start") as string @@ -165,11 +187,47 @@ export async function recordLandlordPayment(formData: FormData) { return { success: false, error: "Bank account selection is required" } } + if (!property_id) { + return { success: false, error: "Property selection is required" } + } + + // Get landlord name + const { data: landlord } = await supabase + .from("owners") + .select("name") + .eq("id", landlord_id) + .single() + + // Get property's commission settings + const { data: property } = await supabase + .from("properties") + .select("commission_type, commission_value, name") + .eq("id", property_id) + .single() + + const commissionType = property?.commission_type || "percentage" + const commissionValue = property?.commission_value || 10 + const commissionPercentage = property?.commission_value || 10 // Declare commissionPercentage here + + // Calculate management fee based on commission type + let managementFee: number + if (commissionType === "fixed") { + managementFee = commissionValue + } else { + managementFee = (grossAmount * commissionValue) / 100 + } + const netAmount = grossAmount - managementFee + const { data: paymentRecord, error } = await supabase .from("landlord_payments") .insert({ landlord_id, - amount, + property_id, + amount: netAmount, + gross_amount: grossAmount, + management_fee: managementFee, + commission_type: commissionType, + commission_value: commissionValue, payment_date, payment_method, period_start, @@ -183,17 +241,36 @@ export async function recordLandlordPayment(formData: FormData) { .single() if (error) { - console.error(" Error recording landlord payment:", error) + console.error("[v0] Error recording landlord payment:", error) return { success: false, error: error.message } } - await postLandlordPaymentToGL(supabase, amount, payment_date, landlord_id, paymentRecord.id, bank_account_id) + // Post the net payment to landlord and management fee to income + await postLandlordPaymentToGL( + supabase, + netAmount, + managementFee, + payment_date, + landlord_id, + paymentRecord.id, + bank_account_id, + landlord?.name || "Landlord" + ) revalidatePath("/landlords/payments") revalidatePath("/dashboard") revalidatePath("/accounting/cash-management") - return { success: true, receipt_number } + return { + success: true, + receipt_number, + grossAmount, + managementFee, + netAmount, + commissionType, + commissionValue, + propertyName: property?.name + } } export async function getLandlordPayments(landlordId: string) { @@ -206,7 +283,7 @@ export async function getLandlordPayments(landlordId: string) { .order("payment_date", { ascending: false }) if (error) { - console.error(" Error fetching landlord payments:", error) + console.error("[v0] Error fetching landlord payments:", error) return [] } @@ -215,11 +292,13 @@ export async function getLandlordPayments(landlordId: string) { async function postLandlordPaymentToGL( supabase: any, - amount: number, + netAmount: number, + managementFee: number, payment_date: string, landlord_id: string, reference_id: string, bank_account_id: string, + landlordName: string, ) { const { data: bankAccount, error: bankError } = await supabase .from("bank_accounts") @@ -228,28 +307,40 @@ async function postLandlordPaymentToGL( .single() if (bankError || !bankAccount?.gl_account_id) { - console.error(" Bank account GL linkage not found:", bankError) + console.error("[v0] Bank account GL linkage not found:", bankError) throw new Error("Bank account must be linked to a GL account") } - const { data: accounts } = await supabase + // Get Landlord Payout Expense account (5020) + const { data: expenseAccount } = await supabase .from("chart_of_accounts") .select("id, account_code") .eq("account_code", "5020") .single() - if (!accounts) { - console.error(" Missing Landlord Payout Expense account (5020)") + if (!expenseAccount) { + console.error("[v0] Missing Landlord Payout Expense account (5020)") throw new Error("Landlord expense account not found in chart of accounts") } - const { data: landlord } = await supabase.from("owners").select("name").eq("id", landlord_id).single() + // Get Management Fee Income account (4010) + const { data: incomeAccount } = await supabase + .from("chart_of_accounts") + .select("id, account_code") + .eq("account_code", "4010") + .single() + + if (!incomeAccount) { + console.error("[v0] Missing Management Fee Income account (4010)") + throw new Error("Management fee income account not found in chart of accounts") + } - const landlordName = landlord?.name || "Landlord" + const totalAmount = netAmount + managementFee - const { error: debitError } = await supabase.from("general_ledger").insert({ - account_id: accounts.id, - debit: amount, + // 1. Debit Landlord Expense (what we owe landlord - net amount) + const { error: expenseError } = await supabase.from("general_ledger").insert({ + account_id: expenseAccount.id, + debit: netAmount, credit: 0, transaction_date: payment_date, description: `Landlord payout to ${landlordName}`, @@ -257,25 +348,42 @@ async function postLandlordPaymentToGL( reference_id: reference_id, }) - if (debitError) { - console.error(" Failed to create debit GL entry:", debitError) + if (expenseError) { + console.error("[v0] Failed to create expense GL entry:", expenseError) throw new Error("Failed to post landlord expense to GL") } - const { error: creditError } = await supabase.from("general_ledger").insert({ + // 2. Credit Management Fee Income (our 10% fee) + if (managementFee > 0) { + const { error: incomeError } = await supabase.from("general_ledger").insert({ + account_id: incomeAccount.id, + debit: 0, + credit: managementFee, + transaction_date: payment_date, + description: `Management fee from ${landlordName}`, + reference_type: "landlord_payment", + reference_id: reference_id, + }) + + if (incomeError) { + console.error("[v0] Failed to create income GL entry:", incomeError) + throw new Error("Failed to post management fee income to GL") + } + } + + // 3. Credit Bank (total amount leaving bank = net to landlord) + const { error: bankCreditError } = await supabase.from("general_ledger").insert({ account_id: bankAccount.gl_account_id, debit: 0, - credit: amount, + credit: netAmount, transaction_date: payment_date, description: `Payment to ${landlordName} via ${bankAccount.account_name}`, reference_type: "landlord_payment", reference_id: reference_id, }) - if (creditError) { - console.error(" Failed to create credit GL entry:", creditError) + if (bankCreditError) { + console.error("[v0] Failed to create bank credit GL entry:", bankCreditError) throw new Error("Failed to reduce bank balance in GL") } - - console.log(" Landlord payment GL entries created successfully") } diff --git a/app/(dashboard)/landlords/payments/page.tsx b/app/(dashboard)/landlords/payments/page.tsx index 8ad6aee..e9917c6 100644 --- a/app/(dashboard)/landlords/payments/page.tsx +++ b/app/(dashboard)/landlords/payments/page.tsx @@ -1,62 +1,60 @@ import { createServerClient } from "@supabase/ssr" import { cookies } from "next/headers" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { Calendar, AlertCircle, CheckCircle2 } from "lucide-react" +import { Calendar, Building2, Users, ChevronDown, ChevronUp } from "lucide-react" import Link from "next/link" -import { calculateLandlordOwed } from "../payment-actions" -import { RecordPaymentDialog } from "@/components/record-payment-dialog" +import { PropertyPaymentCard } from "@/components/property-payment-card" -interface LandlordWithPaymentInfo { +interface Property { + id: string + name: string + location: string + commission_type: "percentage" | "fixed" + commission_value: number + totalCollected: number + expectedRent: number + commissionAmount: number + netPayable: number + tenantCount: number + paidToLandlord: number + balance: number +} + +interface LandlordWithProperties { id: string name: string email: string phone: string payment_due_day: number - commission_percentage: number - owed: number + properties: Property[] + totalExpected: number totalCollected: number - totalPaidToLandlord: number - tenantCount: number - paymentCount: number - expectedRent: number - collectionRate: number - commissionDeducted: number - netPayout: number + totalCommission: number + totalNetPayable: number + totalPaid: number + totalBalance: number } function getDayLabel(day: number): string { - if (day === 30) return "End of Month" - if (day === 5) return "5th" - if (day === 15) return "15th" - return `${day}th` + if (day === 30 || day === 31) return "End of Month" + const suffix = day === 1 || day === 21 || day === 31 ? "st" : day === 2 || day === 22 ? "nd" : day === 3 || day === 23 ? "rd" : "th" + return `${day}${suffix}` } -function getPaymentStatus( - dueDay: number, -): { status: "due"; label: string; color: string } | { status: "upcoming"; label: string; color: string } { +function getPaymentStatusBadge(dueDay: number) { const today = new Date() const currentDay = today.getDate() if (currentDay >= dueDay) { - return { status: "due", label: "Payment Due", color: "bg-red-100 text-red-800" } + return Payment Due } else { const daysUntil = dueDay - currentDay - return { - status: "upcoming", - label: `Due in ${daysUntil} days`, - color: "bg-blue-100 text-blue-800", - } + return Due in {daysUntil} days } } -function getCollectionRateColor(rate: number): string { - if (rate >= 95) return "text-green-600" - if (rate >= 80) return "text-yellow-600" - return "text-red-600" -} - export default async function LandlordPaymentsPage() { const cookieStore = await cookies() const supabase = createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { @@ -72,13 +70,21 @@ export default async function LandlordPaymentsPage() { }, }) + // Get current period + const today = new Date() + const currentMonth = today.toISOString().substring(0, 7) + const [year, month] = currentMonth.split("-") + const periodStart = `${year}-${month}-01` + const periodEnd = `${year}-${month}-${new Date(Number.parseInt(year), Number.parseInt(month), 0).getDate()}` + + // Fetch all landlords const { data: landlords, error } = await supabase .from("owners") - .select("id, name, email, phone, payment_due_day, commission_percentage") + .select("id, name, email, phone, payment_due_day") .order("payment_due_day", { ascending: true }) if (error) { - console.error(" Error fetching landlords:", error) + console.error("[v0] Error fetching landlords:", error) return (
Failed to load landlord payment schedule
@@ -86,213 +92,252 @@ export default async function LandlordPaymentsPage() { ) } - // Calculate owed amounts for each landlord - const today = new Date() - const currentMonth = today.toISOString().substring(0, 7) - const [year, month] = currentMonth.split("-") - const periodStart = `${year}-${month}-01` - const periodEnd = `${year}-${month}-${new Date(Number.parseInt(year), Number.parseInt(month), 0).getDate()}` - - const landlordData: LandlordWithPaymentInfo[] = await Promise.all( + // Build landlord data with properties + const landlordData: LandlordWithProperties[] = await Promise.all( (landlords || []).map(async (landlord) => { - const { data: properties } = await supabase.from("properties").select("id").eq("owner_id", landlord.id) - - const propertyIds = properties?.map((p) => p.id) || [] - - let expectedRent = 0 - let tenantCount = 0 - let paymentCount = 0 - - if (propertyIds.length > 0) { - const { data: tenants } = await supabase - .from("tenants") - .select("id, monthly_rent") - .in("property_id", propertyIds) - .eq("status", "active") - - tenantCount = tenants?.length || 0 - expectedRent = tenants?.reduce((sum, t) => sum + (t.monthly_rent || 0), 0) || 0 - - if (tenants && tenants.length > 0) { - const tenantIds = tenants.map((t) => t.id) - - const { count: paymentRecords } = await supabase - .from("tenant_payments") - .select("*", { count: "exact", head: true }) - .in("tenant_id", tenantIds) + // Get all properties for this landlord with commission settings + const { data: properties } = await supabase + .from("properties") + .select("id, name, location, commission_type, commission_value") + .or(`owner_id.eq.${landlord.id},landlord_id.eq.${landlord.id}`) + + const propertiesWithData: Property[] = await Promise.all( + (properties || []).map(async (property) => { + // Get active tenants for this property + const { data: tenants } = await supabase + .from("tenants") + .select("id, monthly_rent") + .eq("property_id", property.id) + .eq("status", "active") + + const tenantIds = tenants?.map((t) => t.id) || [] + const expectedRent = tenants?.reduce((sum, t) => sum + (t.monthly_rent || 0), 0) || 0 + + // Get payments collected this period + let totalCollected = 0 + if (tenantIds.length > 0) { + const { data: payments } = await supabase + .from("tenant_payments") + .select("amount") + .in("tenant_id", tenantIds) + .gte("payment_date", periodStart) + .lte("payment_date", periodEnd) + + totalCollected = payments?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0 + } + + // Calculate commission based on property settings + const commissionType = property.commission_type || "percentage" + const commissionValue = property.commission_value || 10 + let commissionAmount: number + + if (commissionType === "fixed") { + commissionAmount = commissionValue + } else { + commissionAmount = (totalCollected * commissionValue) / 100 + } + + const netPayable = totalCollected - commissionAmount + + // Get payments already made to landlord for this property this period + const { data: landlordPayments } = await supabase + .from("landlord_payments") + .select("amount") + .eq("landlord_id", landlord.id) + .eq("property_id", property.id) .gte("payment_date", periodStart) .lte("payment_date", periodEnd) - paymentCount = paymentRecords || 0 - } - } - - const result = await calculateLandlordOwed( - landlord.id, - periodStart, - periodEnd, + const paidToLandlord = landlordPayments?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0 + const balance = Math.max(0, netPayable - paidToLandlord) + + return { + id: property.id, + name: property.name, + location: property.location || "", + commission_type: commissionType as "percentage" | "fixed", + commission_value: commissionValue, + totalCollected, + expectedRent, + commissionAmount, + netPayable, + tenantCount: tenants?.length || 0, + paidToLandlord, + balance, + } + }) ) - const { owed, totalCollected, totalPaidToLandlord, commissionDeducted = 0, netPayout = 0 } = result - - const collectionRate = expectedRent > 0 ? (totalCollected / expectedRent) * 100 : 0 + // Calculate totals + const totalExpected = propertiesWithData.reduce((sum, p) => sum + p.expectedRent, 0) + const totalCollected = propertiesWithData.reduce((sum, p) => sum + p.totalCollected, 0) + const totalCommission = propertiesWithData.reduce((sum, p) => sum + p.commissionAmount, 0) + const totalNetPayable = propertiesWithData.reduce((sum, p) => sum + p.netPayable, 0) + const totalPaid = propertiesWithData.reduce((sum, p) => sum + p.paidToLandlord, 0) + const totalBalance = propertiesWithData.reduce((sum, p) => sum + p.balance, 0) return { ...landlord, - owed, + properties: propertiesWithData, + totalExpected, totalCollected, - totalPaidToLandlord, - tenantCount, - paymentCount, - expectedRent, - collectionRate, - commissionDeducted, - netPayout, + totalCommission, + totalNetPayable, + totalPaid, + totalBalance, } - }), + }) ) - // Group by payment due day - const groupedByDueDay: { [key: number]: LandlordWithPaymentInfo[] } = {} - landlordData.forEach((landlord) => { - const day = landlord.payment_due_day || 30 - if (!groupedByDueDay[day]) { - groupedByDueDay[day] = [] - } - groupedByDueDay[day].push(landlord) - }) - - const sortedDays = Object.keys(groupedByDueDay) - .map(Number) - .sort((a, b) => a - b) + // Filter landlords with properties + const landlordsWithProperties = landlordData.filter((l) => l.properties.length > 0) return (
-

LANDLORD PAYMENT SCHEDULE

+

Landlord Payments

- Track landlord payments with collected rent and amounts owed. Integrated with rent collection data. + Period: {new Date(periodStart).toLocaleDateString("en-GB", { month: "long", year: "numeric" })}

-
- - - - - - -
+ + +
- - - + {/* Summary Cards */} +
+ + + Total Collected + + +

+ UGX {landlordData.reduce((sum, l) => sum + l.totalCollected, 0).toLocaleString()} +

+
+
+ + + Total Commission + + +

+ UGX {landlordData.reduce((sum, l) => sum + l.totalCommission, 0).toLocaleString()} +

+
+
+ + + Total Paid Out + + +

+ UGX {landlordData.reduce((sum, l) => sum + l.totalPaid, 0).toLocaleString()} +

+
+
+ + + Total Balance Due + + +

+ UGX {landlordData.reduce((sum, l) => sum + l.totalBalance, 0).toLocaleString()} +

+
+
+
+ {/* Landlord List */}
- {sortedDays.map((day) => { - const landlordsList = groupedByDueDay[day] - const status = getPaymentStatus(day) - - return ( - - -
-
- + {landlordsWithProperties.length === 0 ? ( + + + No landlords with properties found + + + ) : ( + landlordsWithProperties.map((landlord) => ( + + +
+
+
+ +
- {getDayLabel(day)} - {landlordsList.length} landlords + {landlord.name} +

{landlord.email} | {landlord.phone}

+
+ + Due: {getDayLabel(landlord.payment_due_day || 30)} + {getPaymentStatusBadge(landlord.payment_due_day || 30)} +
+
+
+
+
+

Total Balance Due

+

UGX {landlord.totalBalance.toLocaleString()}

+ + +
- - {status.status === "due" ? ( - - ) : ( - - )} - {status.label} -
-
- {landlordsList.map((landlord) => ( -
- {/* Landlord Info */} -
-
-

{landlord.name}

-

{landlord.email}

-

{landlord.phone}

-
- -
-

EXPECTED RENT

-

UGX {Math.round(landlord.expectedRent).toLocaleString()}

-
- -
-

COLLECTED

-

- UGX {Math.round(landlord.totalCollected).toLocaleString()} -

-
- -
-

- COMMISSION ({landlord.commission_percentage}%) -

-

- UGX {Math.round(landlord.commissionDeducted).toLocaleString()} -

-
- -
-

NET PAYOUT

-

- UGX {Math.round(landlord.netPayout).toLocaleString()} -

-
- -
-
-

Amount Owed

-

- UGX {Math.round(landlord.owed).toLocaleString()} -

-
- -
-
+ {/* Landlord Totals */} +
+
+

Expected

+

UGX {landlord.totalExpected.toLocaleString()}

+
+
+

Collected

+

UGX {landlord.totalCollected.toLocaleString()}

+
+
+

Commission

+

UGX {landlord.totalCommission.toLocaleString()}

+
+
+

Net Payable

+

UGX {landlord.totalNetPayable.toLocaleString()}

+
+
+

Already Paid

+

UGX {landlord.totalPaid.toLocaleString()}

+
+
-
-
- Tenants: {landlord.tenantCount} -
-
- Payments: {landlord.paymentCount} -
-
- - View Payment History → - -
-
-
- ))} + {/* Properties */} +
+

+ + Properties ({landlord.properties.length}) +

+
+ {landlord.properties.map((property) => ( + + ))} +
- ) - })} + )) + )}
) diff --git a/app/(dashboard)/payments/actions.ts b/app/(dashboard)/payments/actions.ts index 26347e9..aece7c9 100644 --- a/app/(dashboard)/payments/actions.ts +++ b/app/(dashboard)/payments/actions.ts @@ -39,7 +39,7 @@ async function postPaymentToGL( .in("account_code", ["1015", "4010"]) if (!accounts || accounts.length < 2) { - console.error(" Missing GL accounts for payment posting") + console.error("[v0] Missing GL accounts for payment posting") return } @@ -185,10 +185,15 @@ export async function deletePayment(paymentId: string) { .single() if (fetchError || !payment) { - console.error(" Error fetching payment:", fetchError) + console.error("[v0] Error fetching payment:", fetchError) throw new Error("Payment not found") } + // Check if payment has been deposited + if (payment.deposit_id) { + throw new Error("Cannot delete a payment that has already been deposited to bank. Please reverse the deposit first.") + } + const { data: tenant, error: tenantError } = await supabase .from("tenants") .select("balance, prepaid_balance, total_paid") @@ -199,10 +204,22 @@ export async function deletePayment(paymentId: string) { throw new Error("Tenant not found") } + // Delete the general_ledger entries for this payment (undeposited funds history) + const { error: glDeleteError } = await supabase + .from("general_ledger") + .delete() + .eq("reference_id", paymentId) + .eq("reference_type", "tenant_payment") + + if (glDeleteError) { + console.error("[v0] Error deleting GL entries:", glDeleteError) + // Continue anyway - the payment should still be deleted + } + const { error: deleteError } = await supabase.from("tenant_payments").delete().eq("id", paymentId) if (deleteError) { - console.error(" Error deleting payment:", deleteError) + console.error("[v0] Error deleting payment:", deleteError) throw new Error(deleteError.message) } @@ -221,7 +238,7 @@ export async function deletePayment(paymentId: string) { .eq("id", payment.tenant_id) if (updateError) { - console.error(" Error updating tenant after deletion:", updateError) + console.error("[v0] Error updating tenant after deletion:", updateError) throw new Error(updateError.message) } @@ -229,6 +246,7 @@ export async function deletePayment(paymentId: string) { revalidatePath("/tenants") revalidatePath("/financials") revalidatePath("/reports") + revalidatePath("/accounting/cash-management") revalidatePath("/") } diff --git a/app/(dashboard)/properties/[id]/edit/page.tsx b/app/(dashboard)/properties/[id]/edit/page.tsx index 4110ba0..caac231 100644 --- a/app/(dashboard)/properties/[id]/edit/page.tsx +++ b/app/(dashboard)/properties/[id]/edit/page.tsx @@ -20,7 +20,7 @@ export default async function EditPropertyPage({ params }: { params: Promise<{ i try { cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options)) } catch (error) { - console.error(" Error setting cookies:", error) + console.error("[v0] Error setting cookies:", error) } }, }, @@ -28,7 +28,7 @@ export default async function EditPropertyPage({ params }: { params: Promise<{ i const { data: propertyData, error: propertyError } = await supabase .from("properties") - .select("id, name, property_type, location, total_units, owner_id, description, management_fee") + .select("id, name, property_type, location, total_units, owner_id, description, commission_type, commission_value") .eq("id", propertyId) .limit(1) diff --git a/app/(dashboard)/tenants/actions.ts b/app/(dashboard)/tenants/actions.ts index 907dd98..32452e2 100644 --- a/app/(dashboard)/tenants/actions.ts +++ b/app/(dashboard)/tenants/actions.ts @@ -2,9 +2,6 @@ import { createClient } from "@supabase/supabase-js" import { revalidatePath } from "next/cache" -import { logger } from "@/lib/logger" - -const log = logger.child("tenants:actions") function getServiceClient() { return createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { @@ -22,13 +19,13 @@ export async function getProperties() { const { data, error } = await supabase.from("properties").select("id, name, property_type").order("name") if (error) { - log.error("Error fetching properties", error) + console.error("[v0] Error fetching properties:", error) throw error } return data || [] } catch (error) { - log.error("getProperties failed", error) + console.error("[v0] getProperties failed:", error) return [] } } @@ -45,13 +42,13 @@ export async function getVacantUnits(propertyId: string) { .order("unit_number") if (error) { - log.error("Error fetching units", error, { propertyId }) + console.error("[v0] Error fetching units:", error) throw error } return data || [] } catch (error) { - log.error("getVacantUnits failed", error, { propertyId }) + console.error("[v0] getVacantUnits failed:", error) return [] } } @@ -81,7 +78,7 @@ export async function createTenant(formData: FormData) { const { data, error } = await supabase.from("tenants").insert([tenantData]).select().single() if (error) { - log.error("Error creating tenant", error) + console.error("[v0] Error creating tenant:", error) return { success: false, error: error.message } } @@ -93,10 +90,9 @@ export async function createTenant(formData: FormData) { revalidatePath("/tenants") revalidatePath("/dashboard") - log.info("Tenant created successfully", { tenantId: data?.id }) return { success: true, data } } catch (error: any) { - log.error("createTenant failed", error) + console.error("[v0] createTenant failed:", error) return { success: false, error: error.message || "Failed to create tenant" } } } @@ -108,13 +104,13 @@ export async function getTenant(id: string) { const { data, error } = await supabase.from("tenants").select("*").eq("id", id).single() if (error) { - log.error("Error fetching tenant", error, { tenantId: id }) + console.error("[v0] Error fetching tenant:", error) throw error } return data } catch (error) { - log.error("getTenant failed", error, { tenantId: id }) + console.error("[v0] getTenant failed:", error) return null } } @@ -159,13 +155,24 @@ export async function updateTenant(id: string, formData: FormData) { const { data, error } = await supabase.from("tenants").update(tenantData).eq("id", id).select().single() if (error) { - log.error("Error updating tenant", error, { tenantId: id }) + console.error("[v0] Error updating tenant:", error) return { success: false, error: error.message } } // Handle unit status changes - // If unit changed, mark old unit as vacant and new unit as occupied - if (oldUnitId !== newUnitId) { + const newStatus = formData.get("status") as string + const oldStatus = oldTenantData?.status + + // If tenant is being disabled, mark their unit as vacant + if (newStatus === "inactive" && oldStatus === "active" && oldUnitId) { + await supabase.from("units").update({ status: "vacant" }).eq("id", oldUnitId) + } + // If tenant is being reactivated, mark their unit as occupied + else if (newStatus === "active" && oldStatus === "inactive" && newUnitId) { + await supabase.from("units").update({ status: "occupied" }).eq("id", newUnitId) + } + // If unit changed (and tenant is active), mark old unit as vacant and new unit as occupied + else if (oldUnitId !== newUnitId && newStatus === "active") { if (oldUnitId) { await supabase.from("units").update({ status: "vacant" }).eq("id", oldUnitId) } @@ -175,12 +182,12 @@ export async function updateTenant(id: string, formData: FormData) { } revalidatePath("/tenants") + revalidatePath("/units") revalidatePath("/dashboard") - log.info("Tenant updated successfully", { tenantId: id }) return { success: true, data } } catch (error: any) { - log.error("updateTenant failed", error, { tenantId: id }) + console.error("[v0] updateTenant failed:", error) return { success: false, error: error.message || "Failed to update tenant" } } } @@ -189,19 +196,42 @@ export async function toggleTenantStatus(tenantId: string, newStatus: "active" | try { const supabase = getServiceClient() + // Get the tenant's unit_id before updating status + const { data: tenant } = await supabase + .from("tenants") + .select("unit_id") + .eq("id", tenantId) + .single() + const { error } = await supabase.from("tenants").update({ status: newStatus }).eq("id", tenantId) if (error) { - log.error("Error updating tenant status", error, { tenantId, newStatus }) + console.error("[v0] Error updating tenant status:", error) throw new Error(error.message) } - log.info("Tenant status updated", { tenantId, newStatus }) + // If tenant is being disabled/deactivated and they have a unit, mark it as vacant + if (newStatus === "inactive" && tenant?.unit_id) { + await supabase + .from("units") + .update({ status: "vacant" }) + .eq("id", tenant.unit_id) + } + + // If tenant is being reactivated and they have a unit, mark it as occupied + if (newStatus === "active" && tenant?.unit_id) { + await supabase + .from("units") + .update({ status: "occupied" }) + .eq("id", tenant.unit_id) + } + revalidatePath("/tenants") + revalidatePath("/units") revalidatePath("/reports") revalidatePath("/dashboard") } catch (error: any) { - log.error("toggleTenantStatus failed", error, { tenantId, newStatus }) + console.error("[v0] toggleTenantStatus failed:", error) throw error } } diff --git a/app/(dashboard)/units/actions.ts b/app/(dashboard)/units/actions.ts index 80b3daa..4a8c5c9 100644 --- a/app/(dashboard)/units/actions.ts +++ b/app/(dashboard)/units/actions.ts @@ -101,7 +101,24 @@ export async function updateUnit(id: string, formData: FormData) { return { success: false, error: error.message } } + // Update the monthly_rent for any active tenant in this unit + // This only updates their future rent charges, not past payments + const { error: tenantError } = await supabase + .from("tenants") + .update({ + monthly_rent: rent_amount, + currency, + }) + .eq("unit_id", id) + .eq("status", "active") + + if (tenantError) { + console.error("Error updating tenant rent:", tenantError) + // Don't fail the whole operation, just log the error + } + revalidatePath("/units") + revalidatePath("/tenants") revalidatePath("/dashboard") return { success: true, data } diff --git a/components/edit-property-form.tsx b/components/edit-property-form.tsx index eaa6155..6acdcf8 100644 --- a/components/edit-property-form.tsx +++ b/components/edit-property-form.tsx @@ -19,6 +19,7 @@ interface EditPropertyFormProps { export function EditPropertyForm({ property, landlords }: EditPropertyFormProps) { const [propertyType, setPropertyType] = useState(property.property_type) const [landlordId, setLandlordId] = useState(property.owner_id || "") + const [commissionType, setCommissionType] = useState(property.commission_type || "percentage") const [managementFeeType, setManagementFeeType] = useState(property.management_fee_type || "percentage") const [isPending, startTransition] = useTransition() @@ -27,6 +28,7 @@ export function EditPropertyForm({ property, landlords }: EditPropertyFormProps) const formData = new FormData(e.currentTarget) formData.set("property_type", propertyType) formData.set("landlord_id", landlordId) + formData.set("commission_type", commissionType) formData.set("management_fee_type", managementFeeType) startTransition(() => { updateProperty(formData) @@ -107,35 +109,40 @@ export function EditPropertyForm({ property, landlords }: EditPropertyFormProps)
-

Management Fee Configuration

+

Management Commission Settings

- - + + Percentage (%) - Fixed Amount + Fixed Amount (UGX)
-
diff --git a/components/property-payment-card.tsx b/components/property-payment-card.tsx new file mode 100644 index 0000000..2a343a7 --- /dev/null +++ b/components/property-payment-card.tsx @@ -0,0 +1,296 @@ +"use client" + +import React from "react" + +import { useState } from "react" +import { Card, CardContent } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +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 { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Building2, Users, Banknote } from "lucide-react" +import { recordLandlordPayment } from "@/app/(dashboard)/landlords/payment-actions" +import { getBankAccounts } from "@/app/(dashboard)/accounting/actions" + +interface Property { + id: string + name: string + location: string + commission_type: "percentage" | "fixed" + commission_value: number + totalCollected: number + expectedRent: number + commissionAmount: number + netPayable: number + tenantCount: number + paidToLandlord: number + balance: number +} + +interface BankAccount { + id: string + account_name: string + bank_name: string + currency: string + current_balance: number +} + +interface PropertyPaymentCardProps { + property: Property + landlordId: string + landlordName: string + periodStart: string + periodEnd: string +} + +export function PropertyPaymentCard({ + property, + landlordId, + landlordName, + periodStart, + periodEnd, +}: PropertyPaymentCardProps) { + const [open, setOpen] = useState(false) + const [amount, setAmount] = useState(property.balance.toString()) + const [paymentMethod, setPaymentMethod] = useState("bank_transfer") + const [bankAccountId, setBankAccountId] = useState("") + const [bankAccounts, setBankAccounts] = useState([]) + const [loading, setLoading] = useState(false) + + // Calculate management fee based on entered amount + const grossAmount = Number(amount) || 0 + const managementFee = property.commission_type === "fixed" + ? property.commission_value + : (grossAmount * property.commission_value) / 100 + const netToLandlord = grossAmount - managementFee + + const loadBankAccounts = async () => { + const accounts = await getBankAccounts() + setBankAccounts(accounts) + if (accounts.length > 0) { + setBankAccountId(accounts[0].id) + } + } + + const handleOpenChange = (isOpen: boolean) => { + setOpen(isOpen) + if (isOpen) { + loadBankAccounts() + setAmount(property.balance.toString()) + } + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + + try { + const formData = new FormData() + formData.append("landlord_id", landlordId) + formData.append("property_id", property.id) + formData.append("amount", amount) + formData.append("payment_date", new Date().toISOString().split("T")[0]) + formData.append("payment_method", paymentMethod) + formData.append("period_start", periodStart) + formData.append("period_end", periodEnd) + formData.append("bank_account_id", bankAccountId) + + const result = await recordLandlordPayment(formData) + + if (result.success) { + const feeLabel = result.commissionType === "fixed" + ? `Fixed Fee: UGX ${result.managementFee?.toLocaleString()}` + : `Commission (${result.commissionValue}%): UGX ${result.managementFee?.toLocaleString()}` + + alert( + `Payment recorded!\n\nReceipt: ${result.receipt_number}\nProperty: ${result.propertyName}\nGross: UGX ${result.grossAmount?.toLocaleString()}\n${feeLabel}\nNet to Landlord: UGX ${result.netAmount?.toLocaleString()}` + ) + setOpen(false) + window.location.reload() + } else { + alert(`Error: ${result.error}`) + } + } catch (error) { + console.error("[v0] Error:", error) + alert("Failed to record payment") + } finally { + setLoading(false) + } + } + + return ( + + +
+ {/* Property Info */} +
+
+ + {property.name} +
+

{property.location}

+
+ + {property.tenantCount} tenant(s) +
+
+ + {/* Collected */} +
+

Collected

+

UGX {property.totalCollected.toLocaleString()}

+
+ + {/* Commission */} +
+

Commission

+ + {property.commission_type === "fixed" + ? `UGX ${property.commission_value.toLocaleString()}` + : `${property.commission_value}%`} + +

+ - UGX {Math.round(property.commissionAmount).toLocaleString()} +

+
+ + {/* Net Payable */} +
+

Net Payable

+

UGX {Math.round(property.netPayable).toLocaleString()}

+
+ + {/* Balance */} +
+

Balance Due

+

0 ? "text-red-600" : "text-green-600"}`}> + UGX {Math.round(property.balance).toLocaleString()} +

+ {property.paidToLandlord > 0 && ( +

+ (Paid: UGX {property.paidToLandlord.toLocaleString()}) +

+ )} +
+ + {/* Action */} +
+ + + + + + + Pay {landlordName} +

Property: {property.name}

+
+
+ {/* Commission Info */} +
+

+ Commission: {property.commission_type === "fixed" + ? `Fixed UGX ${property.commission_value.toLocaleString()}` + : `${property.commission_value}% of amount`} +

+
+ + {/* Amount */} +
+ + setAmount(e.target.value)} + step="1" + min="0" + required + /> +
+ + {/* Breakdown */} + {grossAmount > 0 && ( +
+
+ Gross Amount: + UGX {grossAmount.toLocaleString()} +
+
+ + {property.commission_type === "fixed" ? "Fixed Fee:" : `Commission (${property.commission_value}%):`} + + - UGX {Math.round(managementFee).toLocaleString()} +
+
+ Net to Landlord: + UGX {Math.round(netToLandlord).toLocaleString()} +
+
+ )} + + {/* Bank Account */} +
+ + +
+ + {/* Payment Method */} +
+ + +
+ + {/* Actions */} +
+ + +
+
+
+
+
+
+
+
+ ) +} diff --git a/components/record-payment-dialog.tsx b/components/record-payment-dialog.tsx index d02a6bf..b764882 100644 --- a/components/record-payment-dialog.tsx +++ b/components/record-payment-dialog.tsx @@ -17,6 +17,7 @@ interface LandlordWithPaymentInfo { email: string phone: string payment_due_day: number + commission_percentage: number owed: number totalCollected: number totalPaidToLandlord: number @@ -45,9 +46,16 @@ export function RecordPaymentDialog({ useEffect(() => { if (open) { getBankAccounts().then((accounts) => { - setBankAccounts(accounts) - if (accounts.length > 0) { - setBankAccountId(accounts[0].id) + const mappedAccounts = accounts.map((account: any) => ({ + id: account.id, + account_name: account.account_name, + bank_name: account.bank_name, + currency: account.currency || "UGX", + current_balance: account.current_balance || 0, + })) + setBankAccounts(mappedAccounts) + if (mappedAccounts.length > 0) { + setBankAccountId(mappedAccounts[0].id) } }) } @@ -70,14 +78,16 @@ export function RecordPaymentDialog({ const result = await recordLandlordPayment(formData) if (result.success) { - alert(`Payment recorded! Receipt: ${result.receipt_number}`) + alert( + `Payment recorded!\n\nReceipt: ${result.receipt_number}\nGross: UGX ${result.grossAmount?.toLocaleString()}\nManagement Fee: UGX ${result.managementFee?.toLocaleString()}\nNet to Landlord: UGX ${result.netAmount?.toLocaleString()}` + ) setOpen(false) window.location.reload() } else { alert(`Error: ${result.error}`) } } catch (error) { - console.error(" Error:", error) + console.error("[v0] Error:", error) alert("Failed to record payment") } finally { setLoading(false) @@ -97,7 +107,7 @@ export function RecordPaymentDialog({
- + + {Number(amount) > 0 && ( +
+
+ Gross Amount: + UGX {Number(amount).toLocaleString()} +
+
+ Management Fee ({landlord.commission_percentage || 10}%): + + - UGX {Math.round((Number(amount) * (landlord.commission_percentage || 10)) / 100).toLocaleString()} + +
+
+ Net to Landlord: + + UGX {Math.round(Number(amount) * (1 - (landlord.commission_percentage || 10) / 100)).toLocaleString()} + +
+
+ )}
From fedc5f0b2ecfff62e7732ca7741a2e6549df5bbf Mon Sep 17 00:00:00 2001 From: Jonus Green Date: Thu, 22 Jan 2026 17:27:39 +0300 Subject: [PATCH 03/14] payment history resolved --- app/api/landlords/[id]/payments/route.ts | 186 ++++++++++++++--------- 1 file changed, 117 insertions(+), 69 deletions(-) diff --git a/app/api/landlords/[id]/payments/route.ts b/app/api/landlords/[id]/payments/route.ts index b82ee5f..78d400f 100644 --- a/app/api/landlords/[id]/payments/route.ts +++ b/app/api/landlords/[id]/payments/route.ts @@ -1,102 +1,149 @@ -import { createServerClient } from "@supabase/ssr" -import { cookies } from "next/headers" -import { successResponse, notFoundResponse, handleApiError } from "@/lib/api-response" -import { validateUUID } from "@/lib/api-validation" -import { logger } from "@/lib/logger" +import { createClient } from "@supabase/supabase-js" +import { NextRequest, NextResponse } from "next/server" -const log = logger.child("api:landlords:payments") +const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SERVICE_ROLE_KEY! +) -export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { - try { - const { id: landlordId } = await params - - // Validate UUID format - if (!validateUUID(landlordId)) { - return notFoundResponse("Landlord") - } - - const cookieStore = await cookies() - const supabase = createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { - cookies: { - getAll() { - return cookieStore.getAll() - }, - setAll(cookiesToSet) { - try { - cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options)) - } catch {} - }, - }, - }) +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const { id: landlordId } = await params + console.log("[v0] Landlord payments API called for:", landlordId) + try { + // Get landlord details const { data: landlord, error: landlordError } = await supabase .from("owners") - .select("id, name, email, phone, commission_percentage") + .select("id, name, email, phone") .eq("id", landlordId) .single() + console.log("[v0] Landlord query result:", landlord, landlordError) + if (landlordError || !landlord) { - log.error("Landlord not found", landlordError, { landlordId }) - return notFoundResponse("Landlord") + console.log("[v0] Landlord not found:", landlordError) + return NextResponse.json({ error: "Landlord not found" }, { status: 404 }) } + // Get all payments made to this landlord const { data: payments, error: paymentsError } = await supabase .from("landlord_payments") - .select("*") + .select(` + id, + amount, + gross_amount, + management_fee, + commission_type, + commission_value, + payment_date, + payment_method, + period_start, + period_end, + receipt_number, + status, + notes, + property_id, + properties ( + name + ) + `) .eq("landlord_id", landlordId) .order("payment_date", { ascending: false }) if (paymentsError) { - return handleApiError(paymentsError, "landlords:[id]:payments:GET") + console.error("[v0] Error fetching payments:", paymentsError) + return NextResponse.json({ error: "Failed to fetch payments" }, { status: 500 }) } - const totalPaid = payments?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0 - const averagePayment = payments && payments.length > 0 ? totalPaid / payments.length : 0 - const lastPaymentDate = payments && payments.length > 0 ? payments[0].payment_date : null - - const paymentMethodBreakdown: Record = {} - payments?.forEach((payment) => { - const method = payment.payment_method || "unknown" - paymentMethodBreakdown[method] = (paymentMethodBreakdown[method] || 0) + 1 - }) + // Get landlord's properties (check both owner_id and landlord_id) + const { data: properties, error: propertiesError } = await supabase + .from("properties") + .select("id, commission_type, commission_value") + .or(`owner_id.eq.${landlordId},landlord_id.eq.${landlordId}`) - const commissionPercentage = landlord.commission_percentage || 10 + console.log("[v0] Properties query result:", properties, propertiesError) - // Get all properties for this landlord to calculate total collected - const { data: properties } = await supabase.from("properties").select("id").eq("owner_id", landlordId) + const propertyIds = properties?.map((p) => p.id) || [] - let totalCollected = 0 - let totalCommissionDeducted = 0 - let netPayoutCalculated = 0 + // Calculate expected rent from active tenants let expectedRentTotal = 0 + let totalCollected = 0 - if (properties && properties.length > 0) { - const propertyIds = properties.map((p) => p.id) - - const { data: tenants } = await supabase - .from("tenants") - .select("id, monthly_rent") + if (propertyIds.length > 0) { + // Get units for these properties + const { data: units } = await supabase + .from("units") + .select("id") .in("property_id", propertyIds) - .eq("status", "active") - - if (tenants && tenants.length > 0) { - const tenantIds = tenants.map((t) => t.id) - - expectedRentTotal = tenants.reduce((sum, t) => sum + (t.monthly_rent || 0), 0) - const { data: tenantPayments } = await supabase - .from("tenant_payments") - .select("amount") - .in("tenant_id", tenantIds) + const unitIds = units?.map((u) => u.id) || [] + console.log("[v0] Units found:", unitIds.length) + + if (unitIds.length > 0) { + const { data: tenants, error: tenantsError } = await supabase + .from("tenants") + .select("id, monthly_rent") + .in("unit_id", unitIds) + .eq("status", "active") + + console.log("[v0] Tenants query result:", tenants, tenantsError) + + expectedRentTotal = tenants?.reduce((sum, t) => sum + (t.monthly_rent || 0), 0) || 0 + + // Get tenant payments for current period + const today = new Date() + const currentMonth = today.toISOString().substring(0, 7) + const [year, month] = currentMonth.split("-") + const periodStart = `${year}-${month}-01` + const periodEnd = `${year}-${month}-${new Date(Number.parseInt(year), Number.parseInt(month), 0).getDate()}` + + if (tenants && tenants.length > 0) { + const tenantIds = tenants.map((t) => t.id) + const { data: tenantPayments } = await supabase + .from("tenant_payments") + .select("amount") + .in("tenant_id", tenantIds) + .gte("payment_date", periodStart) + .lte("payment_date", periodEnd) + + totalCollected = tenantPayments?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0 + } + } + } - totalCollected = tenantPayments?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0 + // Calculate totals + const totalPaid = payments?.reduce((sum, p) => sum + (p.amount || 0), 0) || 0 + const averagePayment = payments && payments.length > 0 ? totalPaid / payments.length : 0 + const lastPaymentDate = payments && payments.length > 0 ? payments[0].payment_date : null - totalCommissionDeducted = (expectedRentTotal * commissionPercentage) / 100 - netPayoutCalculated = expectedRentTotal - totalCommissionDeducted + // Calculate commission - use property-level commission if available + let totalCommissionDeducted = 0 + if (properties && properties.length > 0) { + // For simplicity, use the first property's commission or average + const firstProperty = properties[0] + if (firstProperty.commission_type === "fixed") { + totalCommissionDeducted = firstProperty.commission_value || 0 + } else { + totalCommissionDeducted = (expectedRentTotal * (firstProperty.commission_value || 10)) / 100 } + } else { + // Fallback to default 10% + totalCommissionDeducted = (expectedRentTotal * 10) / 100 } - return successResponse({ + const netPayoutCalculated = expectedRentTotal - totalCommissionDeducted + + // Payment method breakdown + const paymentMethodBreakdown: Record = {} + payments?.forEach((p) => { + const method = p.payment_method || "unknown" + paymentMethodBreakdown[method] = (paymentMethodBreakdown[method] || 0) + (p.amount || 0) + }) + + return NextResponse.json({ landlord, payments: payments || [], totalPaid, @@ -109,6 +156,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ id: netPayoutCalculated, }) } catch (error) { - return handleApiError(error, "landlords:[id]:payments:GET") + console.error("[v0] Error in landlord payments API:", error) + return NextResponse.json({ error: "Internal server error" }, { status: 500 }) } } From e1e81d94517b0f923f59b3dc02f69987341a8995 Mon Sep 17 00:00:00 2001 From: Jonus Green Date: Thu, 22 Jan 2026 18:40:32 +0300 Subject: [PATCH 04/14] changes to paycard --- .../payments/[id]/receipt/page.tsx | 239 +++++++++--------- app/api/payments/[id]/receipt/route.ts | 178 ++++++++----- components/property-payment-card.tsx | 13 +- 3 files changed, 247 insertions(+), 183 deletions(-) diff --git a/app/(dashboard)/payments/[id]/receipt/page.tsx b/app/(dashboard)/payments/[id]/receipt/page.tsx index 67967a4..c271108 100644 --- a/app/(dashboard)/payments/[id]/receipt/page.tsx +++ b/app/(dashboard)/payments/[id]/receipt/page.tsx @@ -42,7 +42,11 @@ interface PaymentReceipt { export default function PaymentReceiptPage() { const params = useParams() - const paymentId = params.id as string + + // ✅ SAFE PARAM HANDLING (CRITICAL FIX) + const paymentId = + typeof params?.id === "string" ? params.id : null + const [receipt, setReceipt] = useState(null) const [loading, setLoading] = useState(true) const [isClient, setIsClient] = useState(false) @@ -51,11 +55,26 @@ export default function PaymentReceiptPage() { setIsClient(true) }, []) + // ✅ SAFE FETCH (WILL NOT RUN UNTIL ID EXISTS) useEffect(() => { + if (!paymentId) return + async function loadReceipt() { try { - const response = await fetch(`/api/payments/${paymentId}/receipt`) - if (!response.ok) throw new Error("Failed to load receipt") + const response = await fetch( + `/api/payments/${paymentId}/receipt` + ) + + if (!response.ok) { + const text = await response.text() + console.error( + "Receipt API error:", + response.status, + text + ) + throw new Error("Failed to load receipt") + } + const data = await response.json() setReceipt(data) } catch (error) { @@ -68,173 +87,149 @@ export default function PaymentReceiptPage() { loadReceipt() }, [paymentId]) - if (loading) return
Loading...
- if (!receipt) return
Receipt not found
+ // ---------------- UI STATES ---------------- + + if (!paymentId) { + return
Preparing receipt...
+ } + + if (loading) { + return
Loading receipt...
+ } + + if (!receipt) { + return
Receipt not found
+ } + + // ---------------- DATA ---------------- const { tenant, property, unit } = receipt - const amountInWords = numberToWords(Math.floor(receipt.amount)) + const amountInWords = numberToWords( + Math.floor(receipt.amount) + ) const formatPaymentBreakdown = () => { - if (!receipt.paymentBreakdown || receipt.paymentBreakdown.length === 0) return "N/A" + if (!receipt.paymentBreakdown?.length) return "N/A" return receipt.paymentBreakdown - .map((breakdown) => { - const monthDate = new Date(breakdown.month + "-01") - const monthStr = monthDate.toLocaleDateString("en-US", { month: "short", year: "2-digit" }) - - if (breakdown.type === "full_payment") { - return `Rent for ${monthStr}` - } else if (breakdown.type === "overpayment_credit") { - return `Credit for ${monthStr} - Balance ${tenant.currency} ${Number(breakdown.amount).toLocaleString()}` - } else { - return `Partial payment on ${monthStr} balance ${tenant.currency} ${Number(breakdown.amount).toLocaleString()}` + .map((b) => { + const date = new Date(b.month + "-01") + const month = date.toLocaleDateString("en-US", { + month: "short", + year: "2-digit", + }) + + if (b.type === "full_payment") { + return `Rent for ${month}` + } + + if (b.type === "partial_payment") { + return `Partial rent for ${month} (${tenant.currency} ${b.amount.toLocaleString()})` } + + return `Credit for ${month} (${tenant.currency} ${b.amount.toLocaleString()})` }) .join(" and ") } - const handlePrint = () => { - window.print() - } + const handlePrint = () => window.print() + + // ---------------- UI ---------------- return (
-
+ {/* Print Button */} +
-
-
-

PAYMENT RECEIPT

-

Receipt #{receipt.receipt_number}

-
+ {/* Receipt */} +
+
+

Payment Receipt

+

Receipt #{receipt.receipt_number}

-
- {/* Tenant and Property Info */} -
-

+

+ {/* Tenant */} +
+

{tenant.first_name} {tenant.last_name}

-

{tenant.phone}

-

- {property?.name || "N/A"} - Room {unit?.room_number || unit?.unit_number || "N/A"} +

{tenant.phone}

+

+ {property?.name} — Unit{" "} + {unit?.room_number || unit?.unit_number}

{/* Payment Details */} -
-
- DATE: - {new Date(receipt.payment_date).toLocaleDateString()} -
+
- PERIOD: - - {receipt.payment_period - ? new Date(receipt.payment_period + "-01").toLocaleDateString("en-US", { - month: "short", - year: "2-digit", - }) - : "N/A"} + Date + + {new Date(receipt.payment_date).toLocaleDateString()}
- METHOD: - {receipt.payment_method?.replace("_", " ")} + Method + + {receipt.payment_method?.replace("_", " ")} +
{/* Payment For */} -
-

Being Payment For:

-

{formatPaymentBreakdown()}

+
+

Being Payment For

+

{formatPaymentBreakdown()}

{/* Amount */} -
-
-

Amount in Words:

-

- {amountInWords} {tenant.currency} -

-
-
-

Amount Paid:

-

- {tenant.currency} {Number(receipt.amount || 0).toLocaleString()} -

-
+
+

+ Amount in Words +

+

+ {amountInWords} {tenant.currency} +

+

+ {tenant.currency}{" "} + {receipt.amount.toLocaleString()} +

{/* Balance */} -
-
-

Outstanding Balance:

-

0 ? "text-red-600" : "text-green-600"}`} - > - {tenant.currency} {Number(tenant.balanceAtPayment || 0).toLocaleString()} -

-
- {receipt.overpayment_credit > 0 && ( -
-

Credit for Next Period:

-

- {tenant.currency} {Number(receipt.overpayment_credit || 0).toLocaleString()} -

-
- )} +
+

+ Outstanding Balance +

+

0 + ? "text-red-600" + : "text-green-600" + }`} + > + {tenant.currency}{" "} + {tenant.balanceAtPayment.toLocaleString()} +

{/* Footer */} -
-

Thank you for your payment

-
-

{isClient ? new Date().toLocaleDateString() : ""}

+
+

Thank you for your payment

+ {isClient && ( +

+ {new Date().toLocaleDateString()} +

+ )}
- -
) } diff --git a/app/api/payments/[id]/receipt/route.ts b/app/api/payments/[id]/receipt/route.ts index c0077e2..8ddce2f 100644 --- a/app/api/payments/[id]/receipt/route.ts +++ b/app/api/payments/[id]/receipt/route.ts @@ -1,115 +1,177 @@ import { createServerClient } from "@supabase/ssr" import { cookies } from "next/headers" -import { successResponse, notFoundResponse, handleApiError } from "@/lib/api-response" +import { + successResponse, + notFoundResponse, + handleApiError, +} from "@/lib/api-response" import { validateUUID } from "@/lib/api-validation" import { logger } from "@/lib/logger" const log = logger.child("api:payments:receipt") -function getServiceClient(cookieStore: any) { - return createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { - cookies: { - getAll() { - return cookieStore.getAll() +/** + * Create Supabase server client (request-scoped) + */ +async function getServerClient(cookieStore: Awaited>) { + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + getAll() { + return cookieStore.getAll() + }, }, - }, - }) + } + ) } -export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { +export async function GET( + request: Request, + { params }: { params: { id: string } } +) { try { - const resolvedParams = await params - const { id } = resolvedParams + const { id } = params - // Validate UUID format + // 1️⃣ Validate UUID if (!validateUUID(id)) { return notFoundResponse("Payment") } const cookieStore = await cookies() - const supabase = getServiceClient(cookieStore) - - const [paymentResult, tenantsPaymentsResult] = await Promise.all([ - supabase - .from("tenant_payments") - .select( - "*, tenant:tenant_id(id, first_name, last_name, email, phone, currency, balance, monthly_rent, prepaid_balance, property_id, unit_id, property:property_id(id, name), unit:unit_id(id, unit_number, status, bedrooms, bathrooms, monthly_rent))", + const supabase = await getServerClient(cookieStore) + + // 2️⃣ Fetch payment + tenant details + const { data: payment, error: paymentError } = await supabase + .from("tenant_payments") + .select(` + id, + amount, + payment_date, + payment_period, + tenant:tenant_id ( + id, + first_name, + last_name, + email, + phone, + currency, + balance, + monthly_rent, + prepaid_balance, + property_id, + unit_id, + property:property_id ( + id, + name + ), + unit:unit_id ( + id, + unit_number, + status, + bedrooms, + bathrooms, + monthly_rent + ) ) - .eq("id", id) - .single(), - - supabase - .from("tenant_payments") - .select("id, amount, payment_date, payment_period") - .limit(100) // Limit historical payments - .order("payment_date", { ascending: true }), - ]) - - const { data: payment, error: paymentError } = paymentResult - const { data: allPayments } = tenantsPaymentsResult + `) + .eq("id", id) + .single() if (paymentError || !payment) { log.error("Payment not found", paymentError, { paymentId: id }) return notFoundResponse("Payment") } - const tenant = payment.tenant - if (!tenant) { + if (!payment.tenant || !Array.isArray(payment.tenant) || payment.tenant.length === 0) { log.warn("Tenant not found for payment", { paymentId: id }) return notFoundResponse("Tenant") } - let balanceAtPayment = tenant?.monthly_rent || 0 - if (allPayments && allPayments.length > 0) { - const sumOfPayments = allPayments.reduce((sum, p) => sum + (p.amount || 0), 0) - balanceAtPayment = Math.max(0, (tenant?.monthly_rent || 0) - sumOfPayments) + const tenant = payment.tenant[0] + + // 3️⃣ Fetch tenant payment history (ONLY this tenant) + const { data: paymentsHistory, error: historyError } = await supabase + .from("tenant_payments") + .select("amount, payment_date, payment_period") + .eq("tenant_id", tenant.id) + .order("payment_date", { ascending: true }) + + if (historyError) { + log.error("Failed to fetch payment history", historyError) } - const paymentBreakdown = [] + const history = paymentsHistory || [] + + // 4️⃣ Calculate balance at payment time + const totalPaid = history.reduce( + (sum, p) => sum + (p.amount || 0), + 0 + ) + + const monthlyRent = tenant.monthly_rent || 0 + const balanceAtPayment = Math.max(0, monthlyRent - totalPaid) + + // 5️⃣ Build payment breakdown + const paymentBreakdown: { + month: string + amount: number + type: "full_payment" | "partial_payment" | "overpayment_credit" + }[] = [] + let remainingAmount = payment.amount - const currentPaymentPeriod = payment.payment_period + const currentPeriod = payment.payment_period - if (currentPaymentPeriod && remainingAmount > 0) { - const monthlyRent = tenant?.monthly_rent || 0 + if (currentPeriod && remainingAmount > 0) { + const previousPayments = history.filter( + (p) => p.payment_date < payment.payment_date + ) - // Get outstanding balance before this payment - const previousPayments = allPayments?.filter((p) => p.payment_date < payment.payment_date) || [] - const sumOfPreviousPayments = previousPayments.reduce((sum, p) => sum + (p.amount || 0), 0) - const outstandingForCurrentMonth = Math.max(0, monthlyRent - sumOfPreviousPayments) + const previouslyPaid = previousPayments.reduce( + (sum, p) => sum + (p.amount || 0), + 0 + ) + + const outstanding = Math.max(0, monthlyRent - previouslyPaid) + + if (outstanding > 0) { + const applied = Math.min(remainingAmount, outstanding) - if (outstandingForCurrentMonth > 0 && remainingAmount > 0) { - const appliedAmount = Math.min(remainingAmount, outstandingForCurrentMonth) - const isFullPayment = - appliedAmount >= outstandingForCurrentMonth && remainingAmount <= outstandingForCurrentMonth paymentBreakdown.push({ - month: currentPaymentPeriod, - amount: appliedAmount, - type: isFullPayment ? "full_payment" : "partial_payment", + month: currentPeriod, + amount: applied, + type: + applied === outstanding + ? "full_payment" + : "partial_payment", }) - remainingAmount -= appliedAmount + + remainingAmount -= applied } + // Overpayment → next month credit if (remainingAmount > 0) { - const nextMonth = new Date(currentPaymentPeriod + "-01") + const nextMonth = new Date(`${currentPeriod}-01`) nextMonth.setMonth(nextMonth.getMonth() + 1) - const nextMonthStr = nextMonth.toISOString().substring(0, 7) paymentBreakdown.push({ - month: nextMonthStr, + month: nextMonth.toISOString().slice(0, 7), amount: remainingAmount, type: "overpayment_credit", }) } } + // 6️⃣ Return receipt return successResponse({ ...payment, tenant: { ...tenant, balanceAtPayment, }, - property: payment.tenant?.property, - unit: payment.tenant?.unit, + property: tenant.property, + unit: tenant.unit, paymentBreakdown, }) } catch (error) { diff --git a/components/property-payment-card.tsx b/components/property-payment-card.tsx index 2a343a7..8167301 100644 --- a/components/property-payment-card.tsx +++ b/components/property-payment-card.tsx @@ -74,9 +74,16 @@ export function PropertyPaymentCard({ const loadBankAccounts = async () => { const accounts = await getBankAccounts() - setBankAccounts(accounts) - if (accounts.length > 0) { - setBankAccountId(accounts[0].id) + const mappedAccounts: BankAccount[] = accounts.map((account: any) => ({ + id: account.id, + account_name: account.account_name, + bank_name: account.bank_name, + currency: account.currency || "UGX", + current_balance: account.current_balance || 0, + })) + setBankAccounts(mappedAccounts) + if (mappedAccounts.length > 0) { + setBankAccountId(mappedAccounts[0].id) } } From b8520e47d52b9ddef4df8613f38710db5112aee5 Mon Sep 17 00:00:00 2001 From: Jonus Green Date: Thu, 22 Jan 2026 21:40:00 +0300 Subject: [PATCH 05/14] code cleaning --- app/(dashboard)/accounting/actions.ts | 96 +++++++++---------- .../accounting/cash-management/page.tsx | 8 +- app/(dashboard)/expenses/actions.ts | 18 ++-- .../landlords/[id]/payments/page.tsx | 2 +- app/(dashboard)/landlords/payment-actions.ts | 16 ++-- app/(dashboard)/landlords/payments/page.tsx | 2 +- app/(dashboard)/payments/actions.ts | 10 +- app/(dashboard)/tenants/actions.ts | 24 ++--- app/api/landlords/[id]/payments/route.ts | 16 ++-- components/property-payment-card.tsx | 2 +- components/record-payment-dialog.tsx | 2 +- 11 files changed, 98 insertions(+), 98 deletions(-) diff --git a/app/(dashboard)/accounting/actions.ts b/app/(dashboard)/accounting/actions.ts index 54233c6..cd3758b 100644 --- a/app/(dashboard)/accounting/actions.ts +++ b/app/(dashboard)/accounting/actions.ts @@ -13,7 +13,7 @@ export async function getChartOfAccounts() { .order("account_code") if (error) { - console.error("[v0] Error fetching chart of accounts:", error) + console.error("Error fetching chart of accounts:", error) throw new Error("Failed to fetch chart of accounts") } @@ -34,7 +34,7 @@ export async function getAccountBalances() { const { data, error } = await supabase.from("account_balances").select("*").order("account_code") if (error) { - console.error("[v0] Error fetching account balances:", error) + console.error("Error fetching account balances:", error) throw new Error("Failed to fetch account balances") } @@ -64,7 +64,7 @@ export async function getGeneralLedger(accountId?: string, startDate?: string, e const { data, error } = await query.limit(500) if (error) { - console.error("[v0] Error fetching general ledger:", error) + console.error("Error fetching general ledger:", error) throw new Error("Failed to fetch general ledger") } @@ -128,7 +128,7 @@ export async function syncTenantPaymentToGL(paymentId: string) { const { error } = await supabase.from("general_ledger").insert(entries) if (error) { - console.error("[v0] Error syncing payment to GL:", error) + console.error("Error syncing payment to GL:", error) throw new Error("Failed to sync payment to general ledger") } @@ -145,7 +145,7 @@ export async function getBankAccounts() { .order("account_name", { ascending: true }) if (error) { - console.error("[v0] Error fetching bank accounts:", error) + console.error("Error fetching bank accounts:", error) throw new Error("Failed to fetch bank accounts") } @@ -202,7 +202,7 @@ export async function getPaymentDeposits(bankAccountId?: string) { const { data, error } = await query if (error) { - console.error("[v0] Error fetching deposits:", error) + console.error("Error fetching deposits:", error) throw new Error("Failed to fetch deposits") } @@ -218,18 +218,18 @@ export async function createPaymentDeposit( ) { const supabase = getServiceClient() - console.log("[v0] Creating deposit for bank:", bankAccountId) - console.log("[v0] Payment IDs:", paymentIds) + console.log("Creating deposit for bank:", bankAccountId) + console.log("Payment IDs:", paymentIds) const { data: tenantPayments, error: tenantError } = await supabase .from("tenant_payments") .select("id, amount, payment_date, tenant_id, tenants!inner(first_name, last_name)") .in("id", paymentIds) - console.log("[v0] Tenant payments fetched:", tenantPayments) + console.log("Tenant payments fetched:", tenantPayments) if (tenantError) { - console.log("[v0] Error fetching tenant payments:", tenantError.message) + console.log("Error fetching tenant payments:", tenantError.message) } let landlordPaymentsWithNames: any[] = [] @@ -239,10 +239,10 @@ export async function createPaymentDeposit( .select("id, amount, payment_date, landlord_id") .in("id", paymentIds) - console.log("[v0] Landlord payments fetched:", landlordPayments) + console.log("Landlord payments fetched:", landlordPayments) if (landlordError) { - console.log("[v0] Error fetching landlord payments:", landlordError.message) + console.log("Error fetching landlord payments:", landlordError.message) } if (landlordPayments && landlordPayments.length > 0) { @@ -254,7 +254,7 @@ export async function createPaymentDeposit( .in("id", landlordIds) if (profileError) { - console.log("[v0] Error fetching landlord profiles:", profileError.message) + console.log("Error fetching landlord profiles:", profileError.message) } else if (landlordProfiles) { landlordPaymentsWithNames = landlordPayments.map((payment) => { const profile = landlordProfiles.find((p) => p.id === payment.landlord_id) @@ -274,13 +274,13 @@ export async function createPaymentDeposit( throw new Error("No valid payments found to deposit") } - console.log("[v0] Total amount to deposit:", totalAmount) + console.log("Total amount to deposit:", totalAmount) const payerNames: string[] = [] if (tenantPayments && tenantPayments.length > 0) { tenantPayments.forEach((p: any) => { - console.log("[v0] Processing tenant payment:", p) + console.log("Processing tenant payment:", p) if (p.tenants) { payerNames.push(`Tenant payment received - ${p.tenants.first_name} ${p.tenants.last_name}`) } @@ -289,14 +289,14 @@ export async function createPaymentDeposit( if (landlordPaymentsWithNames && landlordPaymentsWithNames.length > 0) { landlordPaymentsWithNames.forEach((p: any) => { - console.log("[v0] Processing landlord payment:", p) + console.log("Processing landlord payment:", p) if (p.profiles) { payerNames.push(`Landlord payment received - ${p.profiles.first_name} ${p.profiles.last_name}`) } }) } - console.log("[v0] Payer names collected:", payerNames) + console.log("Payer names collected:", payerNames) let depositDescription = "" if (payerNames.length > 0) { @@ -309,7 +309,7 @@ export async function createPaymentDeposit( depositDescription += ` (Ref: ${depositReference})` } - console.log("[v0] Final deposit description:", depositDescription) + console.log("Final deposit description:", depositDescription) const { data: deposit, error: depositError } = await supabase .from("payment_deposits") @@ -325,7 +325,7 @@ export async function createPaymentDeposit( .single() if (depositError) { - console.error("[v0] Error creating deposit:", depositError) + console.error("Error creating deposit:", depositError) throw new Error("Failed to create deposit: " + depositError.message) } @@ -351,7 +351,7 @@ export async function createPaymentDeposit( if (depositItems.length > 0) { const { error: itemsError } = await supabase.from("deposit_items").insert(depositItems) if (itemsError) { - console.error("[v0] Error creating deposit items:", itemsError) + console.error("Error creating deposit items:", itemsError) throw new Error("Failed to create deposit items: " + itemsError.message) } } @@ -383,7 +383,7 @@ export async function createPaymentDeposit( .single() if (!bankAccount?.gl_account_id) { - console.error("[v0] Bank account has no GL account linkage!") + console.error("Bank account has no GL account linkage!") throw new Error("Bank account is not linked to a GL account") } @@ -394,14 +394,14 @@ export async function createPaymentDeposit( .single() if (!undepositedAccount) { - console.error("[v0] Undeposited Funds account (1015) not found!") + console.error("Undeposited Funds account (1015) not found!") throw new Error("Undeposited Funds GL account not found") } - console.log("[v0] Creating GL entries...") - console.log("[v0] Bank GL Account ID:", bankAccount.gl_account_id) - console.log("[v0] Undeposited GL Account ID:", undepositedAccount.id) - console.log("[v0] Amount:", totalAmount) + console.log("Creating GL entries...") + console.log("Bank GL Account ID:", bankAccount.gl_account_id) + console.log("Undeposited GL Account ID:", undepositedAccount.id) + console.log("Amount:", totalAmount) const { data: glEntries, error: glError } = await supabase .from("general_ledger") @@ -428,11 +428,11 @@ export async function createPaymentDeposit( .select() if (glError) { - console.error("[v0] CRITICAL: GL entry creation failed:", glError) + console.error("CRITICAL: GL entry creation failed:", glError) throw new Error("Failed to create GL entries: " + glError.message) } - console.log("[v0] GL entries created successfully:", glEntries?.length || 0) + console.log("GL entries created successfully:", glEntries?.length || 0) revalidatePath("/accounting") revalidatePath("/accounting/cash-management") @@ -445,7 +445,7 @@ export async function getLandlordStatements() { const { data, error } = await supabase.from("landlord_balances").select("*").order("landlord_name") if (error) { - console.error("[v0] Error fetching landlord statements:", error) + console.error("Error fetching landlord statements:", error) throw new Error("Failed to fetch landlord statements") } @@ -462,7 +462,7 @@ export async function getLandlordSubledger(landlordId: string) { .order("transaction_date", { ascending: false }) if (error) { - console.error("[v0] Error fetching landlord subledger:", error) + console.error("Error fetching landlord subledger:", error) throw new Error("Failed to fetch landlord subledger") } @@ -488,7 +488,7 @@ export async function getProfitAndLossStatement(startDate: string, endDate: stri .lte("transaction_date", endDate) if (error) { - console.error("[v0] Error fetching GL data:", error) + console.error("Error fetching GL data:", error) return { period: { startDate, endDate }, income: [], totalIncome: 0, expenses: [], totalExpenses: 0, netIncome: 0 } } @@ -602,7 +602,7 @@ export async function getTrialBalance(asOfDate: string) { const { data: balances, error } = await supabase.from("account_balances").select("*").order("account_code") if (error) { - console.error("[v0] Error fetching trial balance:", error) + console.error("Error fetching trial balance:", error) throw new Error("Failed to fetch trial balance") } @@ -665,7 +665,7 @@ export async function getTaxConfiguration() { const { data, error } = await supabase.from("tax_configuration").select("*").single() if (error && error.code !== "PGRST116") { - console.error("[v0] Error fetching tax configuration:", error) + console.error("Error fetching tax configuration:", error) throw new Error("Failed to fetch tax configuration") } @@ -744,7 +744,7 @@ export async function recordTaxTransaction(taxType: string, amount: number, desc }) if (error) { - console.error("[v0] Error recording tax transaction:", error) + console.error("Error recording tax transaction:", error) throw new Error("Failed to record tax transaction") } @@ -832,7 +832,7 @@ export async function recordReconciliation( }) if (error) { - console.error("[v0] Error recording reconciliation:", error) + console.error("Error recording reconciliation:", error) throw new Error("Failed to record reconciliation") } @@ -849,7 +849,7 @@ export async function getReconciliationHistory(limit = 50) { .limit(limit) if (error) { - console.error("[v0] Error fetching reconciliation history:", error) + console.error("Error fetching reconciliation history:", error) throw new Error("Failed to fetch reconciliation history") } @@ -954,7 +954,7 @@ export async function createBankAccount(data: { .single() if (error) { - console.error("[v0] Error creating bank account:", error) + console.error("Error creating bank account:", error) throw new Error("Failed to create bank account") } @@ -1021,7 +1021,7 @@ export async function updateBankAccount( .single() if (error) { - console.error("[v0] Error updating bank account:", error) + console.error("Error updating bank account:", error) throw new Error("Failed to update bank account") } @@ -1031,7 +1031,7 @@ export async function updateBankAccount( export async function getBankTransactions(bankAccountId: string) { const supabase = getServiceClient() - console.log("[v0] Fetching transactions for bank:", bankAccountId) + console.log("Fetching transactions for bank:", bankAccountId) const { data: bankAccount } = await supabase .from("bank_accounts") @@ -1043,7 +1043,7 @@ export async function getBankTransactions(bankAccountId: string) { throw new Error("Bank account not found") } - console.log("[v0] Bank account GL ID:", bankAccount.gl_account_id) + console.log("Bank account GL ID:", bankAccount.gl_account_id) const { data: transactions, error } = await supabase .from("general_ledger") @@ -1053,11 +1053,11 @@ export async function getBankTransactions(bankAccountId: string) { .order("created_at", { ascending: false }) .limit(100) - console.log("[v0] Found transactions:", transactions?.length) - console.log("[v0] First transaction:", transactions?.[0]) + console.log("Found transactions:", transactions?.length) + console.log("First transaction:", transactions?.[0]) if (error) { - console.error("[v0] Error fetching bank transactions:", error) + console.error("Error fetching bank transactions:", error) throw new Error("Failed to fetch bank transactions") } @@ -1080,7 +1080,7 @@ export async function getChartOfAccountsForBanks() { .order("account_code", { ascending: true }) if (error) { - console.error("[v0] Error fetching GL accounts:", error) + console.error("Error fetching GL accounts:", error) throw new Error("Failed to fetch GL accounts") } @@ -1091,7 +1091,7 @@ export async function getChartOfAccountsForBanks() { export async function getUndepositedFundsHistory(startDate?: string, endDate?: string) { const supabase = getServiceClient() - console.log("[v0] Fetching undeposited funds history") + console.log("Fetching undeposited funds history") // Get the Undeposited Funds GL account (1015) const { data: undepositedAccount } = await supabase @@ -1104,7 +1104,7 @@ export async function getUndepositedFundsHistory(startDate?: string, endDate?: s throw new Error("Undeposited Funds account not found") } - console.log("[v0] Undeposited Funds GL Account ID:", undepositedAccount.id) + console.log("Undeposited Funds GL Account ID:", undepositedAccount.id) // Build query for GL transactions let query = supabase @@ -1124,10 +1124,10 @@ export async function getUndepositedFundsHistory(startDate?: string, endDate?: s const { data: transactions, error } = await query.limit(200) - console.log("[v0] Found undeposited funds transactions:", transactions?.length) + console.log("Found undeposited funds transactions:", transactions?.length) if (error) { - console.error("[v0] Error fetching undeposited funds history:", error) + console.error("Error fetching undeposited funds history:", error) throw new Error("Failed to fetch undeposited funds history") } diff --git a/app/(dashboard)/accounting/cash-management/page.tsx b/app/(dashboard)/accounting/cash-management/page.tsx index ad1cdee..330a52f 100644 --- a/app/(dashboard)/accounting/cash-management/page.tsx +++ b/app/(dashboard)/accounting/cash-management/page.tsx @@ -52,7 +52,7 @@ export default function CashManagementPage() { setSelectedBankForView(accounts[0].id) } } catch (error) { - console.error("[v0] Error loading cash management:", error) + console.error("Error loading cash management:", error) } finally { setLoading(false) } @@ -70,7 +70,7 @@ export default function CashManagementPage() { setBankTransactions(result.transactions) setSelectedBankInfo(result.bankAccount) } catch (error) { - console.error("[v0] Error loading bank transactions:", error) + console.error("Error loading bank transactions:", error) } finally { setLoadingTransactions(false) } @@ -90,7 +90,7 @@ export default function CashManagementPage() { setUndepositedHistory(result.transactions) setUndepositedInfo(result.undepositedAccount) } catch (error) { - console.error("[v0] Error loading undeposited funds history:", error) + console.error("Error loading undeposited funds history:", error) } finally { setLoadingUndepositedHistory(false) } @@ -131,7 +131,7 @@ export default function CashManagementPage() { alert(`Successfully deposited ${paymentCount} payment(s) to ${bankName}. Check the transaction history below to see the credit entry.`) } catch (error) { - console.error("[v0] Error creating deposit:", error) + console.error("Error creating deposit:", error) alert("Failed to create deposit: " + (error instanceof Error ? error.message : "Unknown error")) } } diff --git a/app/(dashboard)/expenses/actions.ts b/app/(dashboard)/expenses/actions.ts index 8066a81..829941d 100644 --- a/app/(dashboard)/expenses/actions.ts +++ b/app/(dashboard)/expenses/actions.ts @@ -17,7 +17,7 @@ export async function getProperties() { const { data, error } = await supabase.from("properties").select("id, name, property_type").order("name") if (error) { - console.error("[v0] Error fetching properties:", error) + console.error("Error fetching properties:", error) throw new Error("Failed to fetch properties") } @@ -33,7 +33,7 @@ export async function getBankAccounts() { .order("account_name", { ascending: true }) if (error) { - console.error("[v0] Error fetching bank accounts:", error) + console.error("Error fetching bank accounts:", error) return [] } @@ -110,7 +110,7 @@ async function recordExpenseToGL( const { error } = await supabase.from("general_ledger").insert(glEntries) if (error) { - console.error("[v0] Error posting expense to GL:", error) + console.error("Error posting expense to GL:", error) throw new Error("Failed to post expense to general ledger") } } @@ -140,24 +140,24 @@ export async function createExpense(formData: FormData) { type: "expense", } - console.log("[v0] Creating expense with data:", expenseData) + console.log("Creating expense with data:", expenseData) const { data, error } = await supabase.from("transactions").insert([expenseData]).select() if (error) { - console.error("[v0] Error creating expense:", error) + console.error("Error creating expense:", error) throw new Error(error.message) } - console.log("[v0] Expense created successfully:", data) + console.log("Expense created successfully:", data) if (data && data.length > 0) { const expenseId = data[0].id try { await recordExpenseToGL(expenseId, category, amount, description, transactionDate, bankAccountId) - console.log("[v0] Expense posted to GL successfully") + console.log("Expense posted to GL successfully") } catch (glError) { - console.error("[v0] Failed to post expense to GL:", glError) + console.error("Failed to post expense to GL:", glError) // Rollback the expense await supabase.from("transactions").delete().eq("id", expenseId) throw glError @@ -177,7 +177,7 @@ export async function deleteExpense(expenseId: string) { const { error } = await supabase.from("transactions").delete().eq("id", expenseId) if (error) { - console.error("[v0] Error deleting expense:", error) + console.error("Error deleting expense:", error) throw new Error(error.message) } diff --git a/app/(dashboard)/landlords/[id]/payments/page.tsx b/app/(dashboard)/landlords/[id]/payments/page.tsx index a74128b..70d2fa7 100644 --- a/app/(dashboard)/landlords/[id]/payments/page.tsx +++ b/app/(dashboard)/landlords/[id]/payments/page.tsx @@ -53,7 +53,7 @@ export default function LandlordPaymentHistoryPage() { const result = await response.json() setData(result) } catch (err) { - console.error("[v0] Error fetching payment history:", err) + console.error(" Error fetching payment history:", err) setError("Failed to load payment history") } finally { setLoading(false) diff --git a/app/(dashboard)/landlords/payment-actions.ts b/app/(dashboard)/landlords/payment-actions.ts index d709702..0ff5baf 100644 --- a/app/(dashboard)/landlords/payment-actions.ts +++ b/app/(dashboard)/landlords/payment-actions.ts @@ -241,7 +241,7 @@ export async function recordLandlordPayment(formData: FormData) { .single() if (error) { - console.error("[v0] Error recording landlord payment:", error) + console.error(" Error recording landlord payment:", error) return { success: false, error: error.message } } @@ -283,7 +283,7 @@ export async function getLandlordPayments(landlordId: string) { .order("payment_date", { ascending: false }) if (error) { - console.error("[v0] Error fetching landlord payments:", error) + console.error(" Error fetching landlord payments:", error) return [] } @@ -307,7 +307,7 @@ async function postLandlordPaymentToGL( .single() if (bankError || !bankAccount?.gl_account_id) { - console.error("[v0] Bank account GL linkage not found:", bankError) + console.error(" Bank account GL linkage not found:", bankError) throw new Error("Bank account must be linked to a GL account") } @@ -319,7 +319,7 @@ async function postLandlordPaymentToGL( .single() if (!expenseAccount) { - console.error("[v0] Missing Landlord Payout Expense account (5020)") + console.error(" Missing Landlord Payout Expense account (5020)") throw new Error("Landlord expense account not found in chart of accounts") } @@ -331,7 +331,7 @@ async function postLandlordPaymentToGL( .single() if (!incomeAccount) { - console.error("[v0] Missing Management Fee Income account (4010)") + console.error(" Missing Management Fee Income account (4010)") throw new Error("Management fee income account not found in chart of accounts") } @@ -349,7 +349,7 @@ async function postLandlordPaymentToGL( }) if (expenseError) { - console.error("[v0] Failed to create expense GL entry:", expenseError) + console.error(" Failed to create expense GL entry:", expenseError) throw new Error("Failed to post landlord expense to GL") } @@ -366,7 +366,7 @@ async function postLandlordPaymentToGL( }) if (incomeError) { - console.error("[v0] Failed to create income GL entry:", incomeError) + console.error(" Failed to create income GL entry:", incomeError) throw new Error("Failed to post management fee income to GL") } } @@ -383,7 +383,7 @@ async function postLandlordPaymentToGL( }) if (bankCreditError) { - console.error("[v0] Failed to create bank credit GL entry:", bankCreditError) + console.error(" Failed to create bank credit GL entry:", bankCreditError) throw new Error("Failed to reduce bank balance in GL") } } diff --git a/app/(dashboard)/landlords/payments/page.tsx b/app/(dashboard)/landlords/payments/page.tsx index e9917c6..47bd8e2 100644 --- a/app/(dashboard)/landlords/payments/page.tsx +++ b/app/(dashboard)/landlords/payments/page.tsx @@ -84,7 +84,7 @@ export default async function LandlordPaymentsPage() { .order("payment_due_day", { ascending: true }) if (error) { - console.error("[v0] Error fetching landlords:", error) + console.error(" Error fetching landlords:", error) return (
Failed to load landlord payment schedule
diff --git a/app/(dashboard)/payments/actions.ts b/app/(dashboard)/payments/actions.ts index aece7c9..15b0afd 100644 --- a/app/(dashboard)/payments/actions.ts +++ b/app/(dashboard)/payments/actions.ts @@ -39,7 +39,7 @@ async function postPaymentToGL( .in("account_code", ["1015", "4010"]) if (!accounts || accounts.length < 2) { - console.error("[v0] Missing GL accounts for payment posting") + console.error(" Missing GL accounts for payment posting") return } @@ -185,7 +185,7 @@ export async function deletePayment(paymentId: string) { .single() if (fetchError || !payment) { - console.error("[v0] Error fetching payment:", fetchError) + console.error(" Error fetching payment:", fetchError) throw new Error("Payment not found") } @@ -212,14 +212,14 @@ export async function deletePayment(paymentId: string) { .eq("reference_type", "tenant_payment") if (glDeleteError) { - console.error("[v0] Error deleting GL entries:", glDeleteError) + console.error(" Error deleting GL entries:", glDeleteError) // Continue anyway - the payment should still be deleted } const { error: deleteError } = await supabase.from("tenant_payments").delete().eq("id", paymentId) if (deleteError) { - console.error("[v0] Error deleting payment:", deleteError) + console.error(" Error deleting payment:", deleteError) throw new Error(deleteError.message) } @@ -238,7 +238,7 @@ export async function deletePayment(paymentId: string) { .eq("id", payment.tenant_id) if (updateError) { - console.error("[v0] Error updating tenant after deletion:", updateError) + console.error(" Error updating tenant after deletion:", updateError) throw new Error(updateError.message) } diff --git a/app/(dashboard)/tenants/actions.ts b/app/(dashboard)/tenants/actions.ts index 32452e2..7aa59ed 100644 --- a/app/(dashboard)/tenants/actions.ts +++ b/app/(dashboard)/tenants/actions.ts @@ -19,13 +19,13 @@ export async function getProperties() { const { data, error } = await supabase.from("properties").select("id, name, property_type").order("name") if (error) { - console.error("[v0] Error fetching properties:", error) + console.error(" Error fetching properties:", error) throw error } return data || [] } catch (error) { - console.error("[v0] getProperties failed:", error) + console.error(" getProperties failed:", error) return [] } } @@ -42,13 +42,13 @@ export async function getVacantUnits(propertyId: string) { .order("unit_number") if (error) { - console.error("[v0] Error fetching units:", error) + console.error(" Error fetching units:", error) throw error } return data || [] } catch (error) { - console.error("[v0] getVacantUnits failed:", error) + console.error(" getVacantUnits failed:", error) return [] } } @@ -78,7 +78,7 @@ export async function createTenant(formData: FormData) { const { data, error } = await supabase.from("tenants").insert([tenantData]).select().single() if (error) { - console.error("[v0] Error creating tenant:", error) + console.error(" Error creating tenant:", error) return { success: false, error: error.message } } @@ -92,7 +92,7 @@ export async function createTenant(formData: FormData) { return { success: true, data } } catch (error: any) { - console.error("[v0] createTenant failed:", error) + console.error(" createTenant failed:", error) return { success: false, error: error.message || "Failed to create tenant" } } } @@ -104,13 +104,13 @@ export async function getTenant(id: string) { const { data, error } = await supabase.from("tenants").select("*").eq("id", id).single() if (error) { - console.error("[v0] Error fetching tenant:", error) + console.error(" Error fetching tenant:", error) throw error } return data } catch (error) { - console.error("[v0] getTenant failed:", error) + console.error(" getTenant failed:", error) return null } } @@ -155,7 +155,7 @@ export async function updateTenant(id: string, formData: FormData) { const { data, error } = await supabase.from("tenants").update(tenantData).eq("id", id).select().single() if (error) { - console.error("[v0] Error updating tenant:", error) + console.error(" Error updating tenant:", error) return { success: false, error: error.message } } @@ -187,7 +187,7 @@ export async function updateTenant(id: string, formData: FormData) { return { success: true, data } } catch (error: any) { - console.error("[v0] updateTenant failed:", error) + console.error(" updateTenant failed:", error) return { success: false, error: error.message || "Failed to update tenant" } } } @@ -206,7 +206,7 @@ export async function toggleTenantStatus(tenantId: string, newStatus: "active" | const { error } = await supabase.from("tenants").update({ status: newStatus }).eq("id", tenantId) if (error) { - console.error("[v0] Error updating tenant status:", error) + console.error(" Error updating tenant status:", error) throw new Error(error.message) } @@ -231,7 +231,7 @@ export async function toggleTenantStatus(tenantId: string, newStatus: "active" | revalidatePath("/reports") revalidatePath("/dashboard") } catch (error: any) { - console.error("[v0] toggleTenantStatus failed:", error) + console.error(" toggleTenantStatus failed:", error) throw error } } diff --git a/app/api/landlords/[id]/payments/route.ts b/app/api/landlords/[id]/payments/route.ts index 78d400f..28b09c6 100644 --- a/app/api/landlords/[id]/payments/route.ts +++ b/app/api/landlords/[id]/payments/route.ts @@ -11,7 +11,7 @@ export async function GET( { params }: { params: Promise<{ id: string }> } ) { const { id: landlordId } = await params - console.log("[v0] Landlord payments API called for:", landlordId) + console.log(" Landlord payments API called for:", landlordId) try { // Get landlord details @@ -21,10 +21,10 @@ export async function GET( .eq("id", landlordId) .single() - console.log("[v0] Landlord query result:", landlord, landlordError) + console.log(" Landlord query result:", landlord, landlordError) if (landlordError || !landlord) { - console.log("[v0] Landlord not found:", landlordError) + console.log(" Landlord not found:", landlordError) return NextResponse.json({ error: "Landlord not found" }, { status: 404 }) } @@ -54,7 +54,7 @@ export async function GET( .order("payment_date", { ascending: false }) if (paymentsError) { - console.error("[v0] Error fetching payments:", paymentsError) + console.error(" Error fetching payments:", paymentsError) return NextResponse.json({ error: "Failed to fetch payments" }, { status: 500 }) } @@ -64,7 +64,7 @@ export async function GET( .select("id, commission_type, commission_value") .or(`owner_id.eq.${landlordId},landlord_id.eq.${landlordId}`) - console.log("[v0] Properties query result:", properties, propertiesError) + console.log(" Properties query result:", properties, propertiesError) const propertyIds = properties?.map((p) => p.id) || [] @@ -80,7 +80,7 @@ export async function GET( .in("property_id", propertyIds) const unitIds = units?.map((u) => u.id) || [] - console.log("[v0] Units found:", unitIds.length) + console.log(" Units found:", unitIds.length) if (unitIds.length > 0) { const { data: tenants, error: tenantsError } = await supabase @@ -89,7 +89,7 @@ export async function GET( .in("unit_id", unitIds) .eq("status", "active") - console.log("[v0] Tenants query result:", tenants, tenantsError) + console.log(" Tenants query result:", tenants, tenantsError) expectedRentTotal = tenants?.reduce((sum, t) => sum + (t.monthly_rent || 0), 0) || 0 @@ -156,7 +156,7 @@ export async function GET( netPayoutCalculated, }) } catch (error) { - console.error("[v0] Error in landlord payments API:", error) + console.error(" Error in landlord payments API:", error) return NextResponse.json({ error: "Internal server error" }, { status: 500 }) } } diff --git a/components/property-payment-card.tsx b/components/property-payment-card.tsx index 12d2ca4..ae7b838 100644 --- a/components/property-payment-card.tsx +++ b/components/property-payment-card.tsx @@ -128,7 +128,7 @@ export function PropertyPaymentCard({ alert(`Error: ${result.error}`) } } catch (error) { - console.error("[v0] Error:", error) + console.error(" Error:", error) alert("Failed to record payment") } finally { setLoading(false) diff --git a/components/record-payment-dialog.tsx b/components/record-payment-dialog.tsx index 1a6220b..c769a03 100644 --- a/components/record-payment-dialog.tsx +++ b/components/record-payment-dialog.tsx @@ -88,7 +88,7 @@ if (open) { alert(`Error: ${result.error}`) } } catch (error) { - console.error("[v0] Error:", error) + console.error(" Error:", error) alert("Failed to record payment") } finally { setLoading(false) From 4dc93a85c0ee9b8578a9032b7014217193137acd Mon Sep 17 00:00:00 2001 From: Jonus Green <86344954+jonusgreen@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:49:21 +0300 Subject: [PATCH 06/14] Update and rename .github/copilot-instructions.md to .github-instructions.md --- .github/copilot-instructions.md => .github-instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/copilot-instructions.md => .github-instructions.md (99%) diff --git a/.github/copilot-instructions.md b/.github-instructions.md similarity index 99% rename from .github/copilot-instructions.md rename to .github-instructions.md index 77e3f88..3536998 100644 --- a/.github/copilot-instructions.md +++ b/.github-instructions.md @@ -1,4 +1,4 @@ -# Copilot Instructions for Property Management System +# Instructions for Property Management System ## Project Overview **Exela Property Management Software** is a Next.js-based internal admin/staff system for property managers. It tracks properties, units, tenants, payments, maintenance, and accounting—but **landlords are data records only** with no login access. Only admin/staff users can access the system. From 51741dd2f15c9f6414a866e9f814c9a151508377 Mon Sep 17 00:00:00 2001 From: Jonus Green <86344954+jonusgreen@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:50:32 +0300 Subject: [PATCH 07/14] Delete .github-instructions.md --- .github-instructions.md | 317 ---------------------------------------- 1 file changed, 317 deletions(-) delete mode 100644 .github-instructions.md diff --git a/.github-instructions.md b/.github-instructions.md deleted file mode 100644 index 3536998..0000000 --- a/.github-instructions.md +++ /dev/null @@ -1,317 +0,0 @@ -# Instructions for Property Management System - -## Project Overview -**Exela Property Management Software** is a Next.js-based internal admin/staff system for property managers. It tracks properties, units, tenants, payments, maintenance, and accounting—but **landlords are data records only** with no login access. Only admin/staff users can access the system. - -## Architecture - -### Stack & Key Technologies -- **Frontend**: Next.js 14+ with React, TypeScript, Server Components -- **Backend**: Next.js API routes with Server Actions -- **Database**: Supabase (PostgreSQL) with RLS (Row Level Security) -- **Auth**: Supabase Auth with JWT + cookie-based sessions -- **UI**: Radix UI components + Tailwind CSS -- **Form Handling**: React Hook Form + Zod validation -- **Payment**: Stripe integration -- **Rate Limiting**: Upstash Redis - -### Data Layer Architecture -``` -lib/supabase/ - ├── server.ts → Server-side client (cookies from request context) - ├── client.ts → Browser client (cookies from document.cookie) - └── middleware.ts → Auth middleware for route protection -``` - -**Critical**: Use `createServerClient` from `@supabase/ssr` in API routes (request-scoped), not from `@supabase/supabase-js`. - -### API Response Pattern -All API routes use standardized error handling via `lib/api-response.ts`: -```typescript -// Success: { success: true, data: T, message?: string } -// Error: { success: false, error: { message, code?, details? } } -successResponse(data, statusCode) -notFoundResponse(resource) -handleApiError(error) -``` - -Validation: Use `validateUUID()` for IDs before DB queries. - -### Role-Based Access Control (RBAC) -- **Super Admin**: Full system access, manage admins -- **Admin**: Full feature access, manage staff/landlords/properties -- **Landlord**: Manage own properties (but can't log in—data only) -- **Tenant**: View own lease, submit maintenance, view payments -- **Maintenance Staff**: View/update assigned requests - -Database table: `role_permissions` (resource, role, can_view/create/edit/delete/approve). Use `hasPermission(role, resource, action)` from `lib/permissions.ts` before sensitive operations. - -## Development Workflow - -### Build & Run -```bash -npm run dev # Start dev server (port 3000) -npm run build # Build for production -npm run start # Run production build -npm run lint # Run ESLint -``` - -### Database Schema -Core tables: -- `tenants` – User records with financial state (balance, monthly_rent, prepaid_balance) -- `tenant_payments` – Payment records (amount, payment_date, payment_period, receipt_number) -- `properties` – Property records linked to landlords via owner_id -- `units` – Units within properties (monthly_rent, currency) -- `profiles` – Auth users (role, is_active, status) -- `chart_of_accounts` – GL account definitions -- `general_ledger` – All financial transactions -- `bank_accounts`, `payment_deposits` – Cash management - -## Code Patterns & Conventions - -### Server Actions Pattern (Mutations) -```typescript -// In app/(dashboard)/[resource]/actions.ts -"use server" - -export async function updateProperty(formData: FormData) { - const supabase = await createClient() - - // 1. Validate user auth - const { data: { user } } = await supabase.auth.getUser() - if (!user) throw new Error("Unauthorized") - - // 2. Check permissions - const canEdit = await hasPermission(userRole, "properties", "edit") - if (!canEdit) throw new Error("Forbidden") - - // 3. Validate input - const id = formData.get("id") - if (!validateUUID(id)) throw new Error("Invalid ID") - - // 4. Execute mutation - const { data, error } = await supabase - .from("properties") - .update(payload) - .eq("id", id) - .select() - .single() - - if (error) throw new Error(error.message) - return data -} -``` - -### API Route Pattern – Separate Queries for Related Data -**Important**: When fetching related data (tenant + property + unit), **fetch separately** rather than using nested selects. This avoids Supabase join ambiguities: - -```typescript -// In app/api/payments/[id]/receipt/route.ts -export async function GET(request, { params: { id } }) { - try { - // 1. Fetch payment - const { data: payment, error: paymentError } = await supabase - .from("tenant_payments") - .select("*") - .eq("id", id) - .single() - - if (paymentError || !payment) return notFoundResponse("Payment") - - // 2. Fetch tenant (separate query) - const { data: tenant } = await supabase - .from("tenants") - .select("*") - .eq("id", payment.tenant_id) - .single() - - if (!tenant) return notFoundResponse("Tenant") - - // 3. Fetch property (separate query) - const { data: property } = await supabase - .from("properties") - .select("id, name") - .eq("id", tenant.property_id) - .single() - - // 4. Fetch unit (separate query) - const { data: unit } = await supabase - .from("units") - .select("id, unit_number") - .eq("id", tenant.unit_id) - .single() - - // 5. Return combined response - return successResponse({ - ...payment, - tenant: { ...tenant, ... }, - property, - unit, - }) - } catch (error) { - return handleApiError(error) - } -} -``` - -### Client Component Pattern -```typescript -"use client" - -import { useState, useEffect, useTransition } from "react" -import { createBrowserClient } from "@/lib/supabase/client" - -export function MyComponent() { - const [state, setState] = useState() - const [isPending, startTransition] = useTransition() - - // For queries: useEffect + fetch API - useEffect(() => { - async function load() { - const res = await fetch("/api/resource") - const data = await res.json() - setState(data) - } - load() - }, []) - - // For mutations: startTransition + server action - const handleUpdate = () => { - startTransition(async () => { - await updateProperty(formData) - }) - } - - return (...) -} -``` - -## Component Structure - -### Key Directories -- `app/(dashboard)/` → Protected routes (wrapped in RoleGuard) -- `app/auth/` → Auth pages (login, signup, password reset) -- `app/api/` → API endpoints (organized by resource) -- `components/` → Reusable UI components - - `role-guard.tsx` → Wraps routes requiring specific roles - - `edit-property-form.tsx` → Form example using React Hook Form - - `property-actions.tsx` → Server action buttons - -### Forms Pattern -Use React Hook Form + Zod: -```tsx -import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" - -const schema = z.object({ - name: z.string().min(1), - amount: z.number().positive(), -}) - -export function MyForm() { - const form = useForm({ resolver: zodResolver(schema) }) - - const onSubmit = async (data) => { - startTransition(async () => await serverAction(data)) - } - - return ( - - - - ) -} -``` - -## Important Security Patterns - -### RLS (Row Level Security) -- Supabase RLS policies enforce user isolation -- API routes must verify auth before querying -- Never trust client-side role checks—always verify on server - -### Input Validation -- Use `validateUUID()` for IDs -- API validation: `lib/api-validation.ts` -- Form validation: Zod schemas -- **Always validate on server**, even if client validates - -### Activity Logging -```typescript -await logActivity(userId, "update", "properties", propertyId, { changes }) -// Logged to `activity_log` table -``` - -## Known Patterns to Follow - -### Payment Receipt Generation -See [app/(dashboard)/payments/[id]/receipt/page.tsx](app/(dashboard)/payments/%5Bid%5D/receipt/page.tsx): -- Fetches payment data + breakdown from API -- Formats dates per user locale -- Supports print styles via `@media print` -- Displays balance, overpayment credits, payment breakdown - -### Form Submission Flow -1. Client: Use `startTransition()` to wrap server action -2. Server: Validate, check auth/permissions, execute mutation -3. Return: Success data or throw error (caught by useTransition) -4. Client: Show toast notification (via `useToast()` hook) - -### List Filtering/Search -- Implement on server side (server action or API route) -- Use database queries, not client-side filtering -- Pagination: Use `offset` + `limit` pattern - -## File Organization Tips - -- **Actions**: Group by feature in `app/[feature]/actions.ts` -- **API Routes**: Mirror URL structure in `app/api/[resource]/[id]/route.ts` -- **Components**: Named exports, co-locate with usage or in `components/ui/` -- **Types**: Centralize in `lib/types.ts`; extend as needed -- **Constants**: `lib/constants.ts` - -## Common Issues & Debugging - -### Payment Receipt 404 Error -**Issue**: Receipt API returns 404 "Payment not found" -**Root Cause**: Nested Supabase selects fail due to ambiguous joins -**Solution**: Fetch payment → tenant → property → unit in separate queries (see API Route Pattern above) - -### RLS Blocking Queries -Check that RLS policies in Supabase dashboard allow the user's role to access data. - -### Logging -```typescript -import { logger } from "@/lib/logger" -const log = logger.child("context:name") -log.info("message", { details }) -``` - -## When Modifying This Codebase - -1. **Adding a new feature**: Create `app/(dashboard)/[feature]/(pages)`, `app/api/[feature]/route.ts`, `lib/types.ts` entry -2. **Changing auth logic**: Update `lib/supabase/{server,client}.ts`, `middleware.ts`, test login flow -3. **Adding permissions**: Update `role_permissions` table + `hasPermission()` checks -4. **Modifying data schema**: Create numbered migration in `scripts/`, test migrations thoroughly -5. **Adding external API**: Document in `.env.example`, validate via `lib/api-validation.ts` - -## Environment & Deployment - -### Required `.env.local` -``` -NEXT_PUBLIC_SUPABASE_URL=https://[project].supabase.co -NEXT_PUBLIC_SUPABASE_ANON_KEY=[key] -STRIPE_PUBLIC_KEY=pk_... -STRIPE_SECRET_KEY=sk_... -UPSTASH_REDIS_REST_URL=[url] -UPSTASH_REDIS_REST_TOKEN=[token] -``` - -### Build Configuration -- TypeScript errors ignored in `next.config.mjs` (see IMPROVEMENTS.md #2) -- Images unoptimized for self-hosting -- Source maps disabled in production - ---- - -**Last Updated**: January 2026 | **Framework**: Next.js 14+ | **Database**: Supabase From 75f9295febc07511a78d81f06927928602e48bf9 Mon Sep 17 00:00:00 2001 From: Jonus Green <86344954+jonusgreen@users.noreply.github.com> Date: Thu, 22 Jan 2026 22:05:22 +0300 Subject: [PATCH 08/14] Update actions.ts --- app/(dashboard)/accounting/actions.ts | 42 +++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/(dashboard)/accounting/actions.ts b/app/(dashboard)/accounting/actions.ts index cd3758b..814e449 100644 --- a/app/(dashboard)/accounting/actions.ts +++ b/app/(dashboard)/accounting/actions.ts @@ -13,7 +13,7 @@ export async function getChartOfAccounts() { .order("account_code") if (error) { - console.error("Error fetching chart of accounts:", error) + console.error(" Error fetching chart of accounts:", error) throw new Error("Failed to fetch chart of accounts") } @@ -34,7 +34,7 @@ export async function getAccountBalances() { const { data, error } = await supabase.from("account_balances").select("*").order("account_code") if (error) { - console.error("Error fetching account balances:", error) + console.error(" Error fetching account balances:", error) throw new Error("Failed to fetch account balances") } @@ -64,7 +64,7 @@ export async function getGeneralLedger(accountId?: string, startDate?: string, e const { data, error } = await query.limit(500) if (error) { - console.error("Error fetching general ledger:", error) + console.error(" Error fetching general ledger:", error) throw new Error("Failed to fetch general ledger") } @@ -97,7 +97,7 @@ export async function syncTenantPaymentToGL(paymentId: string) { .single() if (!undepositedAccount || !rentTrustAccount) { - throw new Error("Required trust accounts not found") + throw new Error(" Required trust accounts not found") } // Build descriptive payment description with tenant name @@ -128,7 +128,7 @@ export async function syncTenantPaymentToGL(paymentId: string) { const { error } = await supabase.from("general_ledger").insert(entries) if (error) { - console.error("Error syncing payment to GL:", error) + console.error(" Error syncing payment to GL:", error) throw new Error("Failed to sync payment to general ledger") } @@ -145,7 +145,7 @@ export async function getBankAccounts() { .order("account_name", { ascending: true }) if (error) { - console.error("Error fetching bank accounts:", error) + console.error(" Error fetching bank accounts:", error) throw new Error("Failed to fetch bank accounts") } @@ -202,7 +202,7 @@ export async function getPaymentDeposits(bankAccountId?: string) { const { data, error } = await query if (error) { - console.error("Error fetching deposits:", error) + console.error(" Error fetching deposits:", error) throw new Error("Failed to fetch deposits") } @@ -229,7 +229,7 @@ export async function createPaymentDeposit( console.log("Tenant payments fetched:", tenantPayments) if (tenantError) { - console.log("Error fetching tenant payments:", tenantError.message) + console.log(" Error fetching tenant payments:", tenantError.message) } let landlordPaymentsWithNames: any[] = [] @@ -242,7 +242,7 @@ export async function createPaymentDeposit( console.log("Landlord payments fetched:", landlordPayments) if (landlordError) { - console.log("Error fetching landlord payments:", landlordError.message) + console.log(" Error fetching landlord payments:", landlordError.message) } if (landlordPayments && landlordPayments.length > 0) { @@ -254,7 +254,7 @@ export async function createPaymentDeposit( .in("id", landlordIds) if (profileError) { - console.log("Error fetching landlord profiles:", profileError.message) + console.log(" Error fetching landlord profiles:", profileError.message) } else if (landlordProfiles) { landlordPaymentsWithNames = landlordPayments.map((payment) => { const profile = landlordProfiles.find((p) => p.id === payment.landlord_id) @@ -271,7 +271,7 @@ export async function createPaymentDeposit( (landlordPaymentsWithNames || []).reduce((sum, p) => sum + (p.amount || 0), 0) if (totalAmount === 0) { - throw new Error("No valid payments found to deposit") + throw new Error(" No valid payments found to deposit") } console.log("Total amount to deposit:", totalAmount) @@ -325,8 +325,8 @@ export async function createPaymentDeposit( .single() if (depositError) { - console.error("Error creating deposit:", depositError) - throw new Error("Failed to create deposit: " + depositError.message) + console.error(" Error creating deposit:", depositError) + throw new Error(" Failed to create deposit: " + depositError.message) } const depositItems = [ @@ -351,8 +351,8 @@ export async function createPaymentDeposit( if (depositItems.length > 0) { const { error: itemsError } = await supabase.from("deposit_items").insert(depositItems) if (itemsError) { - console.error("Error creating deposit items:", itemsError) - throw new Error("Failed to create deposit items: " + itemsError.message) + console.error(" Error creating deposit items:", itemsError) + throw new Error(" Failed to create deposit items: " + itemsError.message) } } @@ -383,8 +383,8 @@ export async function createPaymentDeposit( .single() if (!bankAccount?.gl_account_id) { - console.error("Bank account has no GL account linkage!") - throw new Error("Bank account is not linked to a GL account") + console.error(" Bank account has no GL account linkage!") + throw new Error(" Bank account is not linked to a GL account") } const { data: undepositedAccount } = await supabase @@ -394,8 +394,8 @@ export async function createPaymentDeposit( .single() if (!undepositedAccount) { - console.error("Undeposited Funds account (1015) not found!") - throw new Error("Undeposited Funds GL account not found") + console.error(" Undeposited Funds account (1015) not found!") + throw new Error(" Undeposited Funds GL account not found") } console.log("Creating GL entries...") @@ -445,8 +445,8 @@ export async function getLandlordStatements() { const { data, error } = await supabase.from("landlord_balances").select("*").order("landlord_name") if (error) { - console.error("Error fetching landlord statements:", error) - throw new Error("Failed to fetch landlord statements") + console.error(" Error fetching landlord statements:", error) + throw new Error(" Failed to fetch landlord statements") } return data || [] From 136c777e4d3e122f73a1d226348672406f6d8146 Mon Sep 17 00:00:00 2001 From: Jonus Green <86344954+jonusgreen@users.noreply.github.com> Date: Thu, 22 Jan 2026 22:06:27 +0300 Subject: [PATCH 09/14] Update actions.ts --- app/(dashboard)/accounting/actions.ts | 42 +++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/(dashboard)/accounting/actions.ts b/app/(dashboard)/accounting/actions.ts index 814e449..cd3758b 100644 --- a/app/(dashboard)/accounting/actions.ts +++ b/app/(dashboard)/accounting/actions.ts @@ -13,7 +13,7 @@ export async function getChartOfAccounts() { .order("account_code") if (error) { - console.error(" Error fetching chart of accounts:", error) + console.error("Error fetching chart of accounts:", error) throw new Error("Failed to fetch chart of accounts") } @@ -34,7 +34,7 @@ export async function getAccountBalances() { const { data, error } = await supabase.from("account_balances").select("*").order("account_code") if (error) { - console.error(" Error fetching account balances:", error) + console.error("Error fetching account balances:", error) throw new Error("Failed to fetch account balances") } @@ -64,7 +64,7 @@ export async function getGeneralLedger(accountId?: string, startDate?: string, e const { data, error } = await query.limit(500) if (error) { - console.error(" Error fetching general ledger:", error) + console.error("Error fetching general ledger:", error) throw new Error("Failed to fetch general ledger") } @@ -97,7 +97,7 @@ export async function syncTenantPaymentToGL(paymentId: string) { .single() if (!undepositedAccount || !rentTrustAccount) { - throw new Error(" Required trust accounts not found") + throw new Error("Required trust accounts not found") } // Build descriptive payment description with tenant name @@ -128,7 +128,7 @@ export async function syncTenantPaymentToGL(paymentId: string) { const { error } = await supabase.from("general_ledger").insert(entries) if (error) { - console.error(" Error syncing payment to GL:", error) + console.error("Error syncing payment to GL:", error) throw new Error("Failed to sync payment to general ledger") } @@ -145,7 +145,7 @@ export async function getBankAccounts() { .order("account_name", { ascending: true }) if (error) { - console.error(" Error fetching bank accounts:", error) + console.error("Error fetching bank accounts:", error) throw new Error("Failed to fetch bank accounts") } @@ -202,7 +202,7 @@ export async function getPaymentDeposits(bankAccountId?: string) { const { data, error } = await query if (error) { - console.error(" Error fetching deposits:", error) + console.error("Error fetching deposits:", error) throw new Error("Failed to fetch deposits") } @@ -229,7 +229,7 @@ export async function createPaymentDeposit( console.log("Tenant payments fetched:", tenantPayments) if (tenantError) { - console.log(" Error fetching tenant payments:", tenantError.message) + console.log("Error fetching tenant payments:", tenantError.message) } let landlordPaymentsWithNames: any[] = [] @@ -242,7 +242,7 @@ export async function createPaymentDeposit( console.log("Landlord payments fetched:", landlordPayments) if (landlordError) { - console.log(" Error fetching landlord payments:", landlordError.message) + console.log("Error fetching landlord payments:", landlordError.message) } if (landlordPayments && landlordPayments.length > 0) { @@ -254,7 +254,7 @@ export async function createPaymentDeposit( .in("id", landlordIds) if (profileError) { - console.log(" Error fetching landlord profiles:", profileError.message) + console.log("Error fetching landlord profiles:", profileError.message) } else if (landlordProfiles) { landlordPaymentsWithNames = landlordPayments.map((payment) => { const profile = landlordProfiles.find((p) => p.id === payment.landlord_id) @@ -271,7 +271,7 @@ export async function createPaymentDeposit( (landlordPaymentsWithNames || []).reduce((sum, p) => sum + (p.amount || 0), 0) if (totalAmount === 0) { - throw new Error(" No valid payments found to deposit") + throw new Error("No valid payments found to deposit") } console.log("Total amount to deposit:", totalAmount) @@ -325,8 +325,8 @@ export async function createPaymentDeposit( .single() if (depositError) { - console.error(" Error creating deposit:", depositError) - throw new Error(" Failed to create deposit: " + depositError.message) + console.error("Error creating deposit:", depositError) + throw new Error("Failed to create deposit: " + depositError.message) } const depositItems = [ @@ -351,8 +351,8 @@ export async function createPaymentDeposit( if (depositItems.length > 0) { const { error: itemsError } = await supabase.from("deposit_items").insert(depositItems) if (itemsError) { - console.error(" Error creating deposit items:", itemsError) - throw new Error(" Failed to create deposit items: " + itemsError.message) + console.error("Error creating deposit items:", itemsError) + throw new Error("Failed to create deposit items: " + itemsError.message) } } @@ -383,8 +383,8 @@ export async function createPaymentDeposit( .single() if (!bankAccount?.gl_account_id) { - console.error(" Bank account has no GL account linkage!") - throw new Error(" Bank account is not linked to a GL account") + console.error("Bank account has no GL account linkage!") + throw new Error("Bank account is not linked to a GL account") } const { data: undepositedAccount } = await supabase @@ -394,8 +394,8 @@ export async function createPaymentDeposit( .single() if (!undepositedAccount) { - console.error(" Undeposited Funds account (1015) not found!") - throw new Error(" Undeposited Funds GL account not found") + console.error("Undeposited Funds account (1015) not found!") + throw new Error("Undeposited Funds GL account not found") } console.log("Creating GL entries...") @@ -445,8 +445,8 @@ export async function getLandlordStatements() { const { data, error } = await supabase.from("landlord_balances").select("*").order("landlord_name") if (error) { - console.error(" Error fetching landlord statements:", error) - throw new Error(" Failed to fetch landlord statements") + console.error("Error fetching landlord statements:", error) + throw new Error("Failed to fetch landlord statements") } return data || [] From 1781b0d97a748b11448e8009191bb2d42ed55cae Mon Sep 17 00:00:00 2001 From: Ouma Ronald Date: Fri, 23 Jan 2026 11:00:42 +0300 Subject: [PATCH 10/14] Improve reconciliation UI and bank account handling - Add safer loading and empty state handling - Improve bank account selection in payments dialog Co-authored-by: Ouma Ronald --- .../bank-reconciliation/page.tsx | 82 ++++++-------- components/record-payment-dialog.tsx | 101 +++++++----------- 2 files changed, 74 insertions(+), 109 deletions(-) diff --git a/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx b/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx index e030400..a12c9fd 100644 --- a/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx +++ b/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx @@ -9,19 +9,21 @@ import { getBankReconciliation } from "@/app/(dashboard)/accounting/actions" import { formatCurrency } from "@/lib/utils" import { CheckCircle2, AlertCircle } from "lucide-react" +interface OutstandingItem { + id: string + date: string + description: string + amount: number + reconciled: boolean +} + interface BankReconciliation { bankAccountId: string asOfDate: string glBalance: number bankBalance: number difference: number - outstandingItems: Array<{ - id: string - date: string - description: string - amount: number - reconciled: boolean - }> + outstandingItems: OutstandingItem[] } export default function BankReconciliationPage() { @@ -31,12 +33,21 @@ export default function BankReconciliationPage() { useEffect(() => { const loadData = async () => { try { - // TODO: Get bank account ID and date from props or state const bankAccountId = "" const asOfDate = new Date().toISOString().split("T")[0] if (bankAccountId) { - const data = await getBankReconciliation(bankAccountId, asOfDate) - setReconciliation(data) + const raw = await getBankReconciliation(bankAccountId, asOfDate) + + const normalized: BankReconciliation = { + bankAccountId, + asOfDate, + glBalance: raw.glBalance ?? 0, + bankBalance: raw.bankBalance ?? 0, + difference: raw.difference ?? 0, + outstandingItems: raw.outstandingItems ?? [], + } + + setReconciliation(normalized) } } catch (error) { console.error("Error loading reconciliation:", error) @@ -51,48 +62,23 @@ export default function BankReconciliationPage() { return (
-
-

Bank Reconciliation

-

Match GL entries with bank statements

-
+

Bank Reconciliation

- - - GL Balance - - -

{formatCurrency(reconciliation?.glBalance || 0)}

-
-
- - - - Bank Balance - - -

{formatCurrency(reconciliation?.bankBalance || 0)}

-
-
- - - - Difference - - -

- {formatCurrency(reconciliation?.difference || 0)} -

-
-
+ {["glBalance", "bankBalance", "difference"].map((key) => ( + + {key} + +

+ {formatCurrency((reconciliation as any)?.[key] || 0)} +

+
+
+ ))}
- - Outstanding Items - + Outstanding Items @@ -105,7 +91,7 @@ export default function BankReconciliationPage() { - {reconciliation?.outstandingItems?.map((item: { id: string; date: string; description: string; amount: number; reconciled: boolean }) => ( + {reconciliation?.outstandingItems.map(item => ( {item.date} {item.description} diff --git a/components/record-payment-dialog.tsx b/components/record-payment-dialog.tsx index c769a03..43a76ad 100644 --- a/components/record-payment-dialog.tsx +++ b/components/record-payment-dialog.tsx @@ -31,6 +31,14 @@ interface BankAccount { current_balance: number } +interface GroupedBankAccounts { + asset?: BankAccount[] + liability?: BankAccount[] + equity?: BankAccount[] + income?: BankAccount[] + expense?: BankAccount[] +} + export function RecordPaymentDialog({ landlord, periodStart, @@ -44,22 +52,26 @@ export function RecordPaymentDialog({ const [loading, setLoading] = useState(false) useEffect(() => { -if (open) { - getBankAccounts().then((accountsByType) => { - // Combine all account arrays into one flat array - const allAccounts: BankAccount[] = [ - ...(accountsByType.asset || []), - ...(accountsByType.liability || []), - ...(accountsByType.equity || []), - ...(accountsByType.income || []), - ...(accountsByType.expense || []), - ] - setBankAccounts(allAccounts) - if (allAccounts.length > 0) { - setBankAccountId(allAccounts[0].id) + if (!open) return + + const loadAccounts = async () => { + const accountsByType: GroupedBankAccounts = await getBankAccounts() + + const allAccounts: BankAccount[] = [ + ...(accountsByType.asset || []), + ...(accountsByType.liability || []), + ...(accountsByType.equity || []), + ...(accountsByType.income || []), + ...(accountsByType.expense || []), + ] + + setBankAccounts(allAccounts) + if (allAccounts.length > 0) { + setBankAccountId(allAccounts[0].id) + } } - }) -} + + loadAccounts() }, [open]) const handleSubmit = async (e: React.FormEvent) => { @@ -106,40 +118,15 @@ if (open) { Record Payment to {landlord.name} +
- - setAmount(e.target.value)} - step="1" - required - /> - {Number(amount) > 0 && ( -
-
- Gross Amount: - UGX {Number(amount).toLocaleString()} -
-
- Management Fee ({landlord.commission_percentage || 10}%): - - - UGX {Math.round((Number(amount) * (landlord.commission_percentage || 10)) / 100).toLocaleString()} - -
-
- Net to Landlord: - - UGX {Math.round(Number(amount) * (1 - (landlord.commission_percentage || 10) / 100)).toLocaleString()} - -
-
- )} + + setAmount(e.target.value)} required />
+
- + -

- Select which bank account to use for this payment. The bank balance will be reduced. -

+
- +
-
- - -
+ + From 92cd8ee4b32fa47b8e70fc147877027ad575ec76 Mon Sep 17 00:00:00 2001 From: Ouma Ronald Date: Fri, 23 Jan 2026 11:17:00 +0300 Subject: [PATCH 11/14] fixes to the build failures --- app/(dashboard)/accounting/dashboard/page.tsx | 11 ++++- .../financial-reports/cash-flow/page.tsx | 25 +++++------ .../bank-reconciliation/page.tsx | 18 ++++---- .../accounting/reconciliation/page.tsx | 44 +++++++++++++++---- app/api/payments/[id]/receipt/route.ts | 4 +- components/record-payment-dialog.tsx | 34 ++++++++++---- 6 files changed, 95 insertions(+), 41 deletions(-) diff --git a/app/(dashboard)/accounting/dashboard/page.tsx b/app/(dashboard)/accounting/dashboard/page.tsx index 2d05f8b..c5e7ff0 100644 --- a/app/(dashboard)/accounting/dashboard/page.tsx +++ b/app/(dashboard)/accounting/dashboard/page.tsx @@ -43,7 +43,16 @@ export default function AccountingDashboardPage() { const loadData = async () => { try { const dashboardData = await getAccountingDashboard() - setData(dashboardData) + setData({ + metrics: { + totalIncome: dashboardData.totalAssets || 0, + totalExpenses: dashboardData.totalLiabilities || 0, + netProfit: dashboardData.totalEquity || 0, + trustBalance: dashboardData.totalEquity || 0, + }, + chartData: [], + expensesByCategory: [], + }) } catch (error) { console.error("Error loading dashboard:", error) } finally { diff --git a/app/(dashboard)/accounting/financial-reports/cash-flow/page.tsx b/app/(dashboard)/accounting/financial-reports/cash-flow/page.tsx index 9fc83aa..7857ff6 100644 --- a/app/(dashboard)/accounting/financial-reports/cash-flow/page.tsx +++ b/app/(dashboard)/accounting/financial-reports/cash-flow/page.tsx @@ -6,12 +6,16 @@ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Responsive import { getCashFlowStatement } from "@/app/(dashboard)/accounting/actions" import { formatCurrency } from "@/lib/utils" +type CashFlowData = { + period: { startDate: string; endDate: string } + operatingActivities: number + investingActivities: number + financingActivities: number + netCashFlow: number +} + export default function CashFlowPage() { - const [cashFlow, setCashFlow] = useState<{ - startDate: string - endDate: string - transactions: any[] - } | null>(null) + const [cashFlow, setCashFlow] = useState(null) const [isLoading, setIsLoading] = useState(true) useEffect(() => { @@ -47,14 +51,9 @@ export default function CashFlowPage() { ) } - // Simplified cash flow - categorize transactions - const operatingInflow = (cashFlow.transactions || []).filter((t: any) => - t.chart_of_accounts?.account_type === 'income' - ).reduce((sum: number, t: any) => sum + (t.credit || 0), 0) - - const operatingOutflow = (cashFlow.transactions || []).filter((t: any) => - t.chart_of_accounts?.account_type === 'expense' - ).reduce((sum: number, t: any) => sum + (t.debit || 0), 0) + // Use the data from API response + const operatingInflow = cashFlow.operatingActivities > 0 ? cashFlow.operatingActivities : 0 + const operatingOutflow = cashFlow.operatingActivities < 0 ? Math.abs(cashFlow.operatingActivities) : 0 const chartData = [ { diff --git a/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx b/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx index a12c9fd..1ee246f 100644 --- a/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx +++ b/app/(dashboard)/accounting/reconciliation/bank-reconciliation/page.tsx @@ -19,8 +19,12 @@ interface OutstandingItem { interface BankReconciliation { bankAccountId: string - asOfDate: string + statementDate: string + bankStatement: number glBalance: number + discrepancy: number + isReconciled: boolean + entries: any[] bankBalance: number difference: number outstandingItems: OutstandingItem[] @@ -36,15 +40,13 @@ export default function BankReconciliationPage() { const bankAccountId = "" const asOfDate = new Date().toISOString().split("T")[0] if (bankAccountId) { - const raw = await getBankReconciliation(bankAccountId, asOfDate) + const data = await getBankReconciliation(bankAccountId, asOfDate) const normalized: BankReconciliation = { - bankAccountId, - asOfDate, - glBalance: raw.glBalance ?? 0, - bankBalance: raw.bankBalance ?? 0, - difference: raw.difference ?? 0, - outstandingItems: raw.outstandingItems ?? [], + ...data, + bankBalance: data.bankStatement ?? 0, + difference: data.discrepancy ?? 0, + outstandingItems: data.entries || [], } setReconciliation(normalized) diff --git a/app/(dashboard)/accounting/reconciliation/page.tsx b/app/(dashboard)/accounting/reconciliation/page.tsx index a653a2f..e651ecb 100644 --- a/app/(dashboard)/accounting/reconciliation/page.tsx +++ b/app/(dashboard)/accounting/reconciliation/page.tsx @@ -59,15 +59,43 @@ export default function ReconciliationPage() { getBankReconciliationSummary(), getAccountReconciliationSummary(), ]) - setBankReconciliation(bankData) - setAccountReconciliation(accountData) - setBankList(bankData.accounts || []) - setAccountList(accountData.accounts || []) - if (bankData.accounts?.length > 0) { - setSelectedBank(bankData.accounts[0].id) + + // Map bank accounts to expected shape + const mappedBanks = (bankData.accounts || []).map((acc: any) => ({ + id: acc.id, + account_name: acc.name || acc.account_name, + bank_name: acc.bank_name || "Unknown Bank", + current_balance: acc.balance || acc.current_balance || 0, + gl_account_id: acc.gl_account_id || "", + })) + setBankList(mappedBanks) + + // Map account reconciliation to expected shape + const mappedAccounts = (accountData.accounts || []).map((acc: any) => ({ + id: acc.id, + account_code: acc.account_code || acc.code || "", + account_name: acc.account_name || acc.name, + account_type: acc.account_type, + })) + setAccountList(mappedAccounts) + + // Set bank reconciliation with mapped accounts + setBankReconciliation({ + ...bankData, + accounts: mappedBanks, + }) + + // Set account reconciliation with mapped accounts + setAccountReconciliation({ + ...accountData, + accounts: mappedAccounts, + }) + + if (mappedBanks.length > 0) { + setSelectedBank(mappedBanks[0].id) } - if (accountData.accounts?.length > 0) { - setSelectedAccount(accountData.accounts[0].id) + if (mappedAccounts.length > 0) { + setSelectedAccount(mappedAccounts[0].id) } } catch (error) { console.error("Error loading reconciliation data:", error) diff --git a/app/api/payments/[id]/receipt/route.ts b/app/api/payments/[id]/receipt/route.ts index 1788688..376b1fd 100644 --- a/app/api/payments/[id]/receipt/route.ts +++ b/app/api/payments/[id]/receipt/route.ts @@ -29,10 +29,10 @@ function getServiceClient() { export async function GET( request: Request, - { params }: { params: { id: string } } + { params }: { params: Promise<{ id: string }> } ) { try { - const { id } = params + const { id } = await params log.info("Receipt request received", { paymentId: id }) diff --git a/components/record-payment-dialog.tsx b/components/record-payment-dialog.tsx index 43a76ad..bd87f1e 100644 --- a/components/record-payment-dialog.tsx +++ b/components/record-payment-dialog.tsx @@ -55,15 +55,31 @@ export function RecordPaymentDialog({ if (!open) return const loadAccounts = async () => { - const accountsByType: GroupedBankAccounts = await getBankAccounts() - - const allAccounts: BankAccount[] = [ - ...(accountsByType.asset || []), - ...(accountsByType.liability || []), - ...(accountsByType.equity || []), - ...(accountsByType.income || []), - ...(accountsByType.expense || []), - ] + const accounts = await getBankAccounts() + + // Handle both flat array and grouped structure + let allAccounts: BankAccount[] = [] + + if (Array.isArray(accounts)) { + // If it's a flat array, use it directly + allAccounts = accounts.map((acc: any) => ({ + id: acc.id, + account_name: acc.account_name || acc.name, + bank_name: acc.bank_name, + currency: acc.currency || "UGX", + current_balance: acc.current_balance || acc.balance || 0, + })) + } else { + // If it's grouped, flatten it + const accountsByType = accounts as GroupedBankAccounts + allAccounts = [ + ...(accountsByType.asset || []), + ...(accountsByType.liability || []), + ...(accountsByType.equity || []), + ...(accountsByType.income || []), + ...(accountsByType.expense || []), + ] + } setBankAccounts(allAccounts) if (allAccounts.length > 0) { From d715993e460563e05f56cff8a0a911c8ab33e29b Mon Sep 17 00:00:00 2001 From: Ouma Ronald Date: Fri, 23 Jan 2026 11:25:45 +0300 Subject: [PATCH 12/14] fixes to the build failures --- .../accounting/chart-of-accounts/page.tsx | 15 +++++++++-- .../accounting/general-ledger/page.tsx | 11 +++++++- .../accounting/trial-balance/page.tsx | 11 +++++++- app/(dashboard)/dashboard/page.tsx | 3 +++ app/(dashboard)/payments/page.tsx | 3 +++ app/(dashboard)/properties/page.tsx | 25 +++++++++++++++---- app/(dashboard)/tenants/page.tsx | 3 +++ app/(dashboard)/units/page.tsx | 3 +++ 8 files changed, 65 insertions(+), 9 deletions(-) diff --git a/app/(dashboard)/accounting/chart-of-accounts/page.tsx b/app/(dashboard)/accounting/chart-of-accounts/page.tsx index fd56762..6f45cf4 100644 --- a/app/(dashboard)/accounting/chart-of-accounts/page.tsx +++ b/app/(dashboard)/accounting/chart-of-accounts/page.tsx @@ -2,6 +2,9 @@ import { getChartOfAccounts, getAccountBalances } from "../actions" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export const metadata = { title: "Chart of Accounts", description: "View all accounts and their balances", @@ -17,8 +20,16 @@ interface AccountBalance { } export default async function ChartOfAccountsPage() { - const accounts = await getChartOfAccounts() - const balances = await getAccountBalances() + let accounts, balances + + try { + accounts = await getChartOfAccounts() + balances = await getAccountBalances() + } catch (error) { + console.error("Error fetching chart of accounts:", error) + accounts = { asset: [], liability: [], equity: [], income: [], expense: [] } + balances = [] + } const accountBalanceMap = new Map(balances.map((b: AccountBalance) => [b.id, b])) diff --git a/app/(dashboard)/accounting/general-ledger/page.tsx b/app/(dashboard)/accounting/general-ledger/page.tsx index df8144c..5090836 100644 --- a/app/(dashboard)/accounting/general-ledger/page.tsx +++ b/app/(dashboard)/accounting/general-ledger/page.tsx @@ -2,13 +2,22 @@ import { getGeneralLedger } from "../actions" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export const metadata = { title: "General Ledger", description: "View all transactions and journal entries", } export default async function GeneralLedgerPage() { - const ledgerEntries = await getGeneralLedger() + let ledgerEntries + try { + ledgerEntries = await getGeneralLedger() + } catch (error) { + console.error("Error fetching general ledger:", error) + ledgerEntries = [] + } const referenceTypeColors = { tenant_payment: "bg-blue-100 text-blue-800", diff --git a/app/(dashboard)/accounting/trial-balance/page.tsx b/app/(dashboard)/accounting/trial-balance/page.tsx index 07293f0..ef46d06 100644 --- a/app/(dashboard)/accounting/trial-balance/page.tsx +++ b/app/(dashboard)/accounting/trial-balance/page.tsx @@ -1,13 +1,22 @@ import { getAccountBalances } from "../actions" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export const metadata = { title: "Trial Balance", description: "View trial balance summary", } export default async function TrialBalancePage() { - const accounts = await getAccountBalances() + let accounts + try { + accounts = await getAccountBalances() + } catch (error) { + console.error("Error fetching account balances:", error) + accounts = [] + } let totalDebits = 0 let totalCredits = 0 diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx index 37a871d..3fa29b6 100644 --- a/app/(dashboard)/dashboard/page.tsx +++ b/app/(dashboard)/dashboard/page.tsx @@ -2,6 +2,9 @@ import { getServiceClient } from "@/lib/supabase/server" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Building2, Users, Wrench, DollarSign, Building, UserCircle } from "lucide-react" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export default async function DashboardPage() { const supabase = getServiceClient() diff --git a/app/(dashboard)/payments/page.tsx b/app/(dashboard)/payments/page.tsx index 6d9645b..6e74778 100644 --- a/app/(dashboard)/payments/page.tsx +++ b/app/(dashboard)/payments/page.tsx @@ -7,6 +7,9 @@ import Link from "next/link" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { PaymentActionButtons } from "./payment-action-buttons" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + function getServiceClient() { return createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { auth: { diff --git a/app/(dashboard)/properties/page.tsx b/app/(dashboard)/properties/page.tsx index 9b3179b..a2416d0 100644 --- a/app/(dashboard)/properties/page.tsx +++ b/app/(dashboard)/properties/page.tsx @@ -5,6 +5,9 @@ import Link from "next/link" import { createClient } from "@supabase/supabase-js" import { PropertyActionButtons } from "./property-action-buttons" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export default async function PropertiesPage() { const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { auth: { @@ -13,10 +16,22 @@ export default async function PropertiesPage() { }, }) - const [{ data: properties, error: propertiesError }, { data: owners, error: ownersError }] = await Promise.all([ - supabase.from("properties").select("*").order("created_at", { ascending: false }), - supabase.from("owners").select("id, name"), - ]) + let properties, owners, propertiesError, ownersError + + try { + const results = await Promise.all([ + supabase.from("properties").select("*").order("created_at", { ascending: false }), + supabase.from("owners").select("id, name"), + ]) + properties = results[0].data + propertiesError = results[0].error + owners = results[1].data + ownersError = results[1].error + } catch (error) { + console.error("Error loading properties:", error) + properties = [] + owners = [] + } if (propertiesError) { console.error("Error loading properties:", propertiesError) @@ -27,7 +42,7 @@ export default async function PropertiesPage() { const propertiesWithOwners = properties?.map((property) => ({ ...property, ownerName: ownerMap.get(property.owner_id) || "N/A", - })) + })) || [] return (
diff --git a/app/(dashboard)/tenants/page.tsx b/app/(dashboard)/tenants/page.tsx index ca1c932..f52708e 100644 --- a/app/(dashboard)/tenants/page.tsx +++ b/app/(dashboard)/tenants/page.tsx @@ -5,6 +5,9 @@ import { Users, Plus } from "lucide-react" import Link from "next/link" import { TenantActionButtons } from "./tenant-action-buttons" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + function getServiceClient() { return createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { auth: { diff --git a/app/(dashboard)/units/page.tsx b/app/(dashboard)/units/page.tsx index 801baf5..7f080a9 100644 --- a/app/(dashboard)/units/page.tsx +++ b/app/(dashboard)/units/page.tsx @@ -4,6 +4,9 @@ import { Home, Plus } from "lucide-react" import Link from "next/link" import { UnitActionButtons } from "./unit-action-buttons" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { auth: { autoRefreshToken: false, From dd5170cc2321062b426f5177579831c8b7ea629c Mon Sep 17 00:00:00 2001 From: Ouma Ronald Date: Fri, 23 Jan 2026 11:29:57 +0300 Subject: [PATCH 13/14] fixes to the build failures --- app/(dashboard)/properties/page.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/(dashboard)/properties/page.tsx b/app/(dashboard)/properties/page.tsx index a2416d0..7c08f1a 100644 --- a/app/(dashboard)/properties/page.tsx +++ b/app/(dashboard)/properties/page.tsx @@ -16,7 +16,9 @@ export default async function PropertiesPage() { }, }) - let properties, owners, propertiesError, ownersError + let properties: any[] | null = null + let owners: Array<{ id: string; name: string }> | null = null + let propertiesError, ownersError try { const results = await Promise.all([ @@ -37,7 +39,7 @@ export default async function PropertiesPage() { console.error("Error loading properties:", propertiesError) } - const ownerMap = new Map(owners?.map((o) => [o.id, o.name]) || []) + const ownerMap = new Map(owners?.map((o: { id: string; name: string }) => [o.id, o.name]) || []) const propertiesWithOwners = properties?.map((property) => ({ ...property, From fca1b4261c157dd3e2bc58b2e656a945f2724648 Mon Sep 17 00:00:00 2001 From: Ouma Ronald Date: Fri, 23 Jan 2026 11:33:49 +0300 Subject: [PATCH 14/14] fixes to the build failures --- .../landlord-statements/[landlordId]/page.tsx | 14 ++++++++++++-- .../accounting/landlord-statements/page.tsx | 11 ++++++++++- app/(dashboard)/admin/approvals/page.tsx | 3 +++ app/(dashboard)/landlord/dashboard/page.tsx | 3 +++ app/(dashboard)/landlords/payments/page.tsx | 3 +++ app/(dashboard)/landlords/reconciliation/page.tsx | 3 +++ app/(dashboard)/maintenance/page.tsx | 3 +++ app/(dashboard)/team/page.tsx | 11 ++++++++++- .../team/team-member/dashboard/page.tsx | 3 +++ app/(dashboard)/tenant/dashboard/page.tsx | 3 +++ 10 files changed, 53 insertions(+), 4 deletions(-) diff --git a/app/(dashboard)/accounting/landlord-statements/[landlordId]/page.tsx b/app/(dashboard)/accounting/landlord-statements/[landlordId]/page.tsx index 64fce1f..830a8f6 100644 --- a/app/(dashboard)/accounting/landlord-statements/[landlordId]/page.tsx +++ b/app/(dashboard)/accounting/landlord-statements/[landlordId]/page.tsx @@ -4,13 +4,23 @@ import { Badge } from "@/components/ui/badge" import Link from "next/link" import { ArrowLeft } from "lucide-react" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export const metadata = { title: "Landlord Statement Details", description: "Detailed accounting statement for landlord", } -export default async function LandlordStatementDetailPage({ params }: { params: { landlordId: string } }) { - const subledger = await getLandlordSubledger(params.landlordId) +export default async function LandlordStatementDetailPage({ params }: { params: Promise<{ landlordId: string }> }) { + const { landlordId } = await params + let subledger + try { + subledger = await getLandlordSubledger(landlordId) + } catch (error) { + console.error("Error fetching landlord subledger:", error) + subledger = [] + } if (subledger.length === 0) { return ( diff --git a/app/(dashboard)/accounting/landlord-statements/page.tsx b/app/(dashboard)/accounting/landlord-statements/page.tsx index 7608961..8ae5974 100644 --- a/app/(dashboard)/accounting/landlord-statements/page.tsx +++ b/app/(dashboard)/accounting/landlord-statements/page.tsx @@ -3,13 +3,22 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import Link from "next/link" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export const metadata = { title: "Landlord Statements", description: "View accounting statements for all landlords", } export default async function LandlordStatementsPage() { - const statements = await getLandlordStatements() + let statements + try { + statements = await getLandlordStatements() + } catch (error) { + console.error("Error fetching landlord statements:", error) + statements = [] + } return (
diff --git a/app/(dashboard)/admin/approvals/page.tsx b/app/(dashboard)/admin/approvals/page.tsx index e4bac8b..609413d 100644 --- a/app/(dashboard)/admin/approvals/page.tsx +++ b/app/(dashboard)/admin/approvals/page.tsx @@ -5,6 +5,9 @@ import { Badge } from "@/components/ui/badge" import { getPendingActions } from "@/app/(dashboard)/team/pending-actions" import { ApprovalActions } from "./approval-actions" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export default async function AdminApprovalsPage() { const supabase = await createClient() diff --git a/app/(dashboard)/landlord/dashboard/page.tsx b/app/(dashboard)/landlord/dashboard/page.tsx index cda70b8..84169bb 100644 --- a/app/(dashboard)/landlord/dashboard/page.tsx +++ b/app/(dashboard)/landlord/dashboard/page.tsx @@ -3,6 +3,9 @@ import { createClient } from "@/lib/supabase/server" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Building2, DollarSign, Users, TrendingUp } from "lucide-react" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export default async function LandlordDashboardPage() { const supabase = await createClient() diff --git a/app/(dashboard)/landlords/payments/page.tsx b/app/(dashboard)/landlords/payments/page.tsx index 47bd8e2..b064105 100644 --- a/app/(dashboard)/landlords/payments/page.tsx +++ b/app/(dashboard)/landlords/payments/page.tsx @@ -7,6 +7,9 @@ import { Calendar, Building2, Users, ChevronDown, ChevronUp } from "lucide-react import Link from "next/link" import { PropertyPaymentCard } from "@/components/property-payment-card" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + interface Property { id: string name: string diff --git a/app/(dashboard)/landlords/reconciliation/page.tsx b/app/(dashboard)/landlords/reconciliation/page.tsx index ca52ddb..cff4c2f 100644 --- a/app/(dashboard)/landlords/reconciliation/page.tsx +++ b/app/(dashboard)/landlords/reconciliation/page.tsx @@ -9,6 +9,9 @@ import Link from "next/link" import { calculateLandlordOwed } from "../payment-actions" import { format } from "date-fns" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export default async function LandlordReconciliationPage() { const cookieStore = await cookies() const supabase = createServerClient( diff --git a/app/(dashboard)/maintenance/page.tsx b/app/(dashboard)/maintenance/page.tsx index ab5dea5..1b33897 100644 --- a/app/(dashboard)/maintenance/page.tsx +++ b/app/(dashboard)/maintenance/page.tsx @@ -8,6 +8,9 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { ApproveRejectButtons } from "./approve-reject-buttons" import { MaintenanceActionButtons } from "./maintenance-action-buttons" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + async function getMaintenanceRequests() { try { const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, { diff --git a/app/(dashboard)/team/page.tsx b/app/(dashboard)/team/page.tsx index 7b9c4c4..31aee74 100644 --- a/app/(dashboard)/team/page.tsx +++ b/app/(dashboard)/team/page.tsx @@ -5,6 +5,9 @@ import { Plus } from "lucide-react" import { getTeamMembers } from "./actions" import { TeamMemberActionButtons } from "./team-member-action-buttons" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + const ROLE_LABELS: Record = { admin: "Admin", property_manager: "Property Manager", @@ -26,7 +29,13 @@ const STATUS_COLORS: Record = { } export default async function TeamPage() { - const teamMembers = await getTeamMembers() + let teamMembers + try { + teamMembers = await getTeamMembers() + } catch (error) { + console.error("Error fetching team members:", error) + teamMembers = [] + } return (
diff --git a/app/(dashboard)/team/team-member/dashboard/page.tsx b/app/(dashboard)/team/team-member/dashboard/page.tsx index 352e300..3f14a31 100644 --- a/app/(dashboard)/team/team-member/dashboard/page.tsx +++ b/app/(dashboard)/team/team-member/dashboard/page.tsx @@ -7,6 +7,9 @@ import { getPendingActions } from "@/app/(dashboard)/team/pending-actions" import Link from "next/link" import { Clock, CheckCircle, XCircle, Plus } from "lucide-react" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export default async function TeamMemberDashboardPage() { const supabase = await createClient() diff --git a/app/(dashboard)/tenant/dashboard/page.tsx b/app/(dashboard)/tenant/dashboard/page.tsx index 05ea3df..3d8e6b9 100644 --- a/app/(dashboard)/tenant/dashboard/page.tsx +++ b/app/(dashboard)/tenant/dashboard/page.tsx @@ -5,6 +5,9 @@ import { Home, DollarSign, Wrench, FileText } from "lucide-react" import Link from "next/link" import { Button } from "@/components/ui/button" +export const dynamic = 'force-dynamic' +export const revalidate = 0 + export default async function TenantDashboardPage() { const supabase = await createClient()