Phase 2 tweaks: admin tenant creation, unit delete, frequency, UI overhaul

- Admin panel: create tenants with org + first user, manage org status
  (active/suspended/archived), contract number and plan level fields
- Units: delete with invoice check, assessment group dropdown binding
- Assessment groups: frequency field (monthly/quarterly/annual) with
  income calculations normalized to monthly equivalents
- Sidebar: grouped nav sections (Financials, Assessments, Transactions,
  Planning, Reports, Admin), renamed Chart of Accounts to Accounts
- Header: replaced text with SVG logo
- Capital projects: Kanban as default view, table-only PDF export,
  Future category (beyond 5-year plan)
- Auth: block login for suspended/archived organizations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 20:00:16 -05:00
parent 01502e07bc
commit 17fdacc0f2
20 changed files with 992 additions and 148 deletions

View File

@@ -12,7 +12,6 @@ import {
IconShieldCheck,
IconPigMoney,
IconBuildingBank,
IconCalendarEvent,
IconUsers,
IconFileText,
IconSettings,
@@ -21,33 +20,67 @@ import {
} from '@tabler/icons-react';
import { useAuthStore } from '../../stores/authStore';
const navItems = [
{ label: 'Dashboard', icon: IconDashboard, path: '/dashboard' },
{ label: 'Chart of Accounts', icon: IconListDetails, path: '/accounts' },
{ label: 'Transactions', icon: IconReceipt, path: '/transactions' },
{ label: 'Units / Homeowners', icon: IconHome, path: '/units' },
{ label: 'Assessment Groups', icon: IconCategory, path: '/assessment-groups' },
{ label: 'Invoices', icon: IconFileInvoice, path: '/invoices' },
{ label: 'Payments', icon: IconCash, path: '/payments' },
{ label: 'Budgets', icon: IconReportAnalytics, path: '/budgets/2026' },
const navSections = [
{
label: 'Reports',
icon: IconChartSankey,
children: [
{ label: 'Balance Sheet', path: '/reports/balance-sheet' },
{ label: 'Income Statement', path: '/reports/income-statement' },
{ label: 'Cash Flow', path: '/reports/cash-flow' },
{ label: 'Budget vs Actual', path: '/reports/budget-vs-actual' },
{ label: 'Aging Report', path: '/reports/aging' },
{ label: 'Sankey Diagram', path: '/reports/sankey' },
items: [
{ label: 'Dashboard', icon: IconDashboard, path: '/dashboard' },
],
},
{
label: 'Financials',
items: [
{ label: 'Accounts', icon: IconListDetails, path: '/accounts' },
{ label: 'Budgets', icon: IconReportAnalytics, path: '/budgets/2026' },
{ label: 'Investments', icon: IconPigMoney, path: '/investments' },
],
},
{
label: 'Assessments',
items: [
{ label: 'Units / Homeowners', icon: IconHome, path: '/units' },
{ label: 'Assessment Groups', icon: IconCategory, path: '/assessment-groups' },
],
},
{
label: 'Transactions',
items: [
{ label: 'Transactions', icon: IconReceipt, path: '/transactions' },
{ label: 'Invoices', icon: IconFileInvoice, path: '/invoices' },
{ label: 'Payments', icon: IconCash, path: '/payments' },
],
},
{
label: 'Planning',
items: [
{ label: 'Capital Projects', icon: IconBuildingBank, path: '/capital-projects' },
{ label: 'Reserves', icon: IconShieldCheck, path: '/reserves' },
{ label: 'Vendors', icon: IconUsers, path: '/vendors' },
],
},
{
label: 'Reports',
items: [
{
label: 'Reports',
icon: IconChartSankey,
children: [
{ label: 'Balance Sheet', path: '/reports/balance-sheet' },
{ label: 'Income Statement', path: '/reports/income-statement' },
{ label: 'Cash Flow', path: '/reports/cash-flow' },
{ label: 'Budget vs Actual', path: '/reports/budget-vs-actual' },
{ label: 'Aging Report', path: '/reports/aging' },
{ label: 'Sankey Diagram', path: '/reports/sankey' },
],
},
],
},
{
label: 'Admin',
items: [
{ label: 'Year-End', icon: IconFileText, path: '/year-end' },
{ label: 'Settings', icon: IconSettings, path: '/settings' },
],
},
{ label: 'Reserves', icon: IconShieldCheck, path: '/reserves' },
{ label: 'Investments', icon: IconPigMoney, path: '/investments' },
{ label: 'Capital Projects', icon: IconBuildingBank, path: '/capital-projects' },
{ label: 'Vendors', icon: IconUsers, path: '/vendors' },
{ label: 'Year-End', icon: IconFileText, path: '/year-end' },
{ label: 'Settings', icon: IconSettings, path: '/settings' },
];
export function Sidebar() {
@@ -57,35 +90,47 @@ export function Sidebar() {
return (
<ScrollArea p="sm">
{navItems.map((item) =>
item.children ? (
<NavLink
key={item.label}
label={item.label}
leftSection={<item.icon size={18} />}
defaultOpened={item.children.some((c) =>
location.pathname.startsWith(c.path),
)}
>
{item.children.map((child) => (
{navSections.map((section, sIdx) => (
<div key={sIdx}>
{section.label && (
<>
{sIdx > 0 && <Divider my={6} />}
<Text size="xs" c="dimmed" fw={700} tt="uppercase" px="sm" pb={2} pt={sIdx > 0 ? 4 : 0}>
{section.label}
</Text>
</>
)}
{section.items.map((item: any) =>
item.children ? (
<NavLink
key={child.path}
label={child.label}
active={location.pathname === child.path}
onClick={() => navigate(child.path)}
key={item.label}
label={item.label}
leftSection={<item.icon size={18} />}
defaultOpened={item.children.some((c: any) =>
location.pathname.startsWith(c.path),
)}
>
{item.children.map((child: any) => (
<NavLink
key={child.path}
label={child.label}
active={location.pathname === child.path}
onClick={() => navigate(child.path)}
/>
))}
</NavLink>
) : (
<NavLink
key={item.path}
label={item.label}
leftSection={<item.icon size={18} />}
active={location.pathname === item.path}
onClick={() => navigate(item.path!)}
/>
))}
</NavLink>
) : (
<NavLink
key={item.path}
label={item.label}
leftSection={<item.icon size={18} />}
active={location.pathname === item.path}
onClick={() => navigate(item.path!)}
/>
),
)}
),
)}
</div>
))}
{user?.isSuperadmin && (
<>