Quality-of-life enhancements: CSV import/export, opening balances, interest rates, mobile UX

- CSV import/export for Units, Projects, and Vendors with match-on-name/number upsert
- Cash Flow report toggle for Cash Only vs Cash + Investments
- Per-account and bulk opening balance setting with as-of date
- Interest rate field on normal accounts with estimated monthly/annual interest display
- Mobile sidebar auto-close on navigation
- Shared CSV parsing/export utility extracted to frontend/src/utils/csv.ts

DB migration needed for existing tenants:
  ALTER TABLE accounts ADD COLUMN IF NOT EXISTS interest_rate DECIMAL(6,4);

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 09:13:51 -05:00
parent 32af961173
commit 45a267d787
21 changed files with 1015 additions and 128 deletions

View File

@@ -14,7 +14,7 @@ import { Sidebar } from './Sidebar';
import logoSrc from '../../assets/logo.svg';
export function AppLayout() {
const [opened, { toggle }] = useDisclosure();
const [opened, { toggle, close }] = useDisclosure();
const { user, currentOrg, logout } = useAuthStore();
const navigate = useNavigate();
@@ -98,7 +98,7 @@ export function AppLayout() {
</AppShell.Header>
<AppShell.Navbar>
<Sidebar />
<Sidebar onNavigate={close} />
</AppShell.Navbar>
<AppShell.Main>

View File

@@ -77,11 +77,20 @@ const navSections = [
},
];
export function Sidebar() {
interface SidebarProps {
onNavigate?: () => void;
}
export function Sidebar({ onNavigate }: SidebarProps) {
const navigate = useNavigate();
const location = useLocation();
const user = useAuthStore((s) => s.user);
const go = (path: string) => {
navigate(path);
onNavigate?.();
};
return (
<ScrollArea p="sm">
{navSections.map((section, sIdx) => (
@@ -109,7 +118,7 @@ export function Sidebar() {
key={child.path}
label={child.label}
active={location.pathname === child.path}
onClick={() => navigate(child.path)}
onClick={() => go(child.path)}
/>
))}
</NavLink>
@@ -119,7 +128,7 @@ export function Sidebar() {
label={item.label}
leftSection={<item.icon size={18} />}
active={location.pathname === item.path}
onClick={() => navigate(item.path!)}
onClick={() => go(item.path!)}
/>
),
)}
@@ -136,7 +145,7 @@ export function Sidebar() {
label="Admin Panel"
leftSection={<IconCrown size={18} />}
active={location.pathname === '/admin'}
onClick={() => navigate('/admin')}
onClick={() => go('/admin')}
color="red"
/>
</>