feat: Implement new General Ledger frontend section with comprehensive report screens and cash/bank account management
- Added a new Ledgers sidebar section for statutory-format financial reports and cash/bank-account management. - Introduced dedicated GL client for API interactions, handling response envelopes and error management. - Developed report screens for Trial Balance, Balance Sheet, General Ledger, Profit & Loss, Cash Flow, Budget vs Actual, and a new Tax Report. - Implemented CSV download functionality alongside existing PDF downloads for all report screens. - Separated Cash and Bank accounts into distinct tables/endpoints, with updated create forms and unified list view. - Created a new Accounts section for Cheque Management, moving Cash/Bank Accounts from the Ledgers section. - Updated RBAC navigation to include new permissions and sub-navigation items for the added features. - Ensured compliance with GL's updated API contract, including renaming fields and adjusting response shapes. - Addressed various bugs and presentation issues, enhancing user experience across the new module.
This commit is contained in:
@@ -5,26 +5,36 @@ import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import {
|
||||
Banknote,
|
||||
BadgeDollarSign,
|
||||
BookOpen,
|
||||
BookText,
|
||||
Boxes,
|
||||
Building2,
|
||||
CalendarCheck,
|
||||
CalendarClock,
|
||||
ChevronRight,
|
||||
ClipboardList,
|
||||
CreditCard,
|
||||
Factory,
|
||||
FileBarChart,
|
||||
FileText,
|
||||
HelpCircle,
|
||||
IdCard,
|
||||
Inbox,
|
||||
Landmark,
|
||||
LayoutGrid,
|
||||
LayoutTemplate,
|
||||
LineChart,
|
||||
ListTree,
|
||||
Menu,
|
||||
Package,
|
||||
PackageCheck,
|
||||
PackageX,
|
||||
PlayCircle,
|
||||
PieChart,
|
||||
Receipt,
|
||||
Ruler,
|
||||
Scale,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
ShoppingCart,
|
||||
@@ -32,6 +42,7 @@ import {
|
||||
Tag,
|
||||
Truck,
|
||||
Users,
|
||||
Wallet,
|
||||
Warehouse,
|
||||
X,
|
||||
type LucideIcon,
|
||||
@@ -120,6 +131,34 @@ const navItems: {
|
||||
{ title: "Settings", code: "hrm.settings", href: "/dashboard/hrm/settings", icon: SlidersHorizontal },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Ledgers",
|
||||
code: "ledgers",
|
||||
href: "/dashboard/ledgers",
|
||||
icon: Landmark,
|
||||
chevron: true,
|
||||
children: [
|
||||
{ title: "Trial Balance", code: "ledgers.trial-balance", href: "/dashboard/ledgers/trial-balance", icon: Scale },
|
||||
{ title: "Balance Sheet", code: "ledgers.balance-sheet", href: "/dashboard/ledgers/balance-sheet", icon: Landmark },
|
||||
{ title: "General Ledger", code: "ledgers.general-ledger", href: "/dashboard/ledgers/general-ledger", icon: BookOpen },
|
||||
{ title: "Profit & Loss", code: "ledgers.profit-and-loss", href: "/dashboard/ledgers/profit-and-loss", icon: LineChart },
|
||||
{ title: "Cash Flow", code: "ledgers.cash-flow", href: "/dashboard/ledgers/cash-flow", icon: PieChart },
|
||||
{ title: "Budget vs Actual", code: "ledgers.budget-vs-actual", href: "/dashboard/ledgers/budget-vs-actual", icon: BadgeDollarSign },
|
||||
{ title: "Tax Report", code: "ledgers.tax-report", href: "/dashboard/ledgers/tax-report", icon: Receipt },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Accounts",
|
||||
code: "accounts",
|
||||
href: "/dashboard/accounts",
|
||||
icon: CreditCard,
|
||||
chevron: true,
|
||||
children: [
|
||||
{ title: "Cash / Bank Accounts", code: "accounts.bank-accounts", href: "/dashboard/accounts/bank-accounts", icon: Wallet },
|
||||
{ title: "Cheque Books", code: "accounts.cheque-books", href: "/dashboard/accounts/cheque-books", icon: BookText },
|
||||
{ title: "Received Cheques", code: "accounts.received-cheques", href: "/dashboard/accounts/received-cheques", icon: Inbox },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
code: "settings",
|
||||
@@ -157,7 +196,13 @@ function SidebarContent({
|
||||
// route auto-expanded; user toggles are preserved across navigation.
|
||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({})
|
||||
|
||||
useEffect(() => {
|
||||
// Adjusted during render (not in an effect) each time pathname or the
|
||||
// available item set changes — `items` starts empty while auth/nav codes
|
||||
// are loading, so this also needs to re-run once the real list arrives.
|
||||
const autoExpandKey = `${pathname}::${items.map((i) => i.code).join(",")}`
|
||||
const [lastAutoExpandKey, setLastAutoExpandKey] = useState<string | null>(null)
|
||||
if (autoExpandKey !== lastAutoExpandKey) {
|
||||
setLastAutoExpandKey(autoExpandKey)
|
||||
const parent = items.find((i) => {
|
||||
if (!i.children?.length) return false
|
||||
if (i.children.some((c) => pathname === c.href || pathname.startsWith(`${c.href}/`))) return true
|
||||
@@ -168,7 +213,7 @@ function SidebarContent({
|
||||
if (parent) {
|
||||
setExpanded((prev) => (prev[parent.code] ? prev : { ...prev, [parent.code]: true }))
|
||||
}
|
||||
}, [pathname, items])
|
||||
}
|
||||
|
||||
const toggleExpand = (code: string) =>
|
||||
setExpanded((prev) => ({ ...prev, [code]: !prev[code] }))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname, useRouter } from "next/navigation"
|
||||
import { ArrowLeft, Bell, LogOut, Settings, User } from "lucide-react"
|
||||
@@ -41,6 +41,27 @@ const PROCUREMENT_TITLES: Record<string, string> = {
|
||||
"/dashboard/procurement/purchase-returns/new": "New Purchase Return",
|
||||
}
|
||||
|
||||
const LEDGER_TITLES: Record<string, string> = {
|
||||
"/dashboard/ledgers": "Ledgers",
|
||||
"/dashboard/ledgers/trial-balance": "Trial Balance",
|
||||
"/dashboard/ledgers/balance-sheet": "Balance Sheet",
|
||||
"/dashboard/ledgers/general-ledger": "General Ledger",
|
||||
"/dashboard/ledgers/profit-and-loss": "Profit & Loss",
|
||||
"/dashboard/ledgers/cash-flow": "Cash Flow",
|
||||
"/dashboard/ledgers/budget-vs-actual": "Budget vs Actual",
|
||||
"/dashboard/ledgers/tax-report": "Tax Report",
|
||||
}
|
||||
|
||||
const ACCOUNTS_TITLES: Record<string, string> = {
|
||||
"/dashboard/accounts": "Accounts",
|
||||
"/dashboard/accounts/bank-accounts": "Cash / Bank Accounts",
|
||||
"/dashboard/accounts/bank-accounts/new": "New Bank Account",
|
||||
"/dashboard/accounts/cheque-books": "Cheque Books",
|
||||
"/dashboard/accounts/cheque-books/new": "New Cheque Book",
|
||||
"/dashboard/accounts/received-cheques": "Received Cheques",
|
||||
"/dashboard/accounts/received-cheques/new": "New Received Cheque",
|
||||
}
|
||||
|
||||
const STOCK_TITLES: Record<string, string> = {
|
||||
"/dashboard/stock": "Stock Management",
|
||||
"/dashboard/stock/enquiry": "Stock Enquiry",
|
||||
@@ -62,6 +83,11 @@ function titleFromPath(pathname: string) {
|
||||
if (pathname === "/dashboard/receiving/grn/new") return "Create Goods Receipt Note"
|
||||
if (/^\/dashboard\/receiving\/grn\/[^/]+$/.test(pathname)) return "Goods Receipt Note"
|
||||
|
||||
if (LEDGER_TITLES[pathname]) return LEDGER_TITLES[pathname]
|
||||
|
||||
if (ACCOUNTS_TITLES[pathname]) return ACCOUNTS_TITLES[pathname]
|
||||
if (/^\/dashboard\/accounts\/cheque-books\/[^/]+$/.test(pathname)) return "Cheque Book"
|
||||
|
||||
if (STOCK_TITLES[pathname]) return STOCK_TITLES[pathname]
|
||||
if (/^\/dashboard\/stock\/transfers\/[^/]+$/.test(pathname)) return "Stock Transfer"
|
||||
if (/^\/dashboard\/stock\/counts\/[^/]+$/.test(pathname)) return "Stock Count"
|
||||
@@ -140,8 +166,7 @@ export function Header() {
|
||||
|
||||
// Read after mount, not during render: localStorage doesn't exist on the server, and
|
||||
// reading it while rendering would desync the hydration pass.
|
||||
const [user, setUser] = useState<AuthUser | null>(null)
|
||||
useEffect(() => setUser(getStoredUser()), [])
|
||||
const [user] = useState<AuthUser | null>(() => getStoredUser())
|
||||
|
||||
const markAllAsRead = () =>
|
||||
setNotifications((prev) => prev.map((n) => ({ ...n, unread: false })))
|
||||
|
||||
Reference in New Issue
Block a user