Phase 3: Optimize & clean up — unified projects, account enhancements, new tenant fix

- Unify reserve_components + capital_projects into single projects model with
  full CRUD backend and new Projects page frontend
- Rewrite Capital Planning to read from unified projects/planning endpoint;
  add empty state directing users to Projects page when no planning items exist
- Add default designation to assessment groups with auto-set on first creation;
  units now require an assessment group (pre-populated with default)
- Add primary account designation (one per fund type) and balance adjustment
  via journal entries against equity offset accounts (3000/3100)
- Add computed investment fields (interest earned, maturity value, days remaining)
  with PostgreSQL date arithmetic fix for DATE - DATE integer result
- Restructure sidebar: investments in Accounts tab, Year-End under Reports,
  Planning section with Projects and Capital Planning
- Fix new tenant creation seeding unwanted default chart of accounts —
  new tenants now start with a blank slate

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 14:32:35 -05:00
parent 17fdacc0f2
commit 301f8a7bde
20 changed files with 1760 additions and 145 deletions

View File

@@ -1,13 +1,13 @@
import { useState } from 'react';
import {
Title, Text, Card, Table, SimpleGrid, Group, Stack, Badge, Loader, Center,
ThemeIcon, Button, Modal, TextInput, NumberInput, Textarea, Select, ActionIcon,
ThemeIcon, Button, Modal, TextInput, NumberInput, Textarea, Select, ActionIcon, Tooltip,
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { useDisclosure } from '@mantine/hooks';
import { notifications } from '@mantine/notifications';
import {
IconPlus, IconEdit, IconCategory, IconCash, IconHome, IconArchive,
IconPlus, IconEdit, IconCategory, IconCash, IconHome, IconArchive, IconStarFilled, IconStar,
} from '@tabler/icons-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import api from '../../services/api';
@@ -24,6 +24,7 @@ interface AssessmentGroup {
monthly_operating_income: string;
monthly_reserve_income: string;
total_monthly_income: string;
is_default: boolean;
is_active: boolean;
}
@@ -105,6 +106,17 @@ export function AssessmentGroupsPage() {
},
});
const setDefaultMutation = useMutation({
mutationFn: (id: string) => api.put(`/assessment-groups/${id}/set-default`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['assessment-groups'] });
notifications.show({ message: 'Default group updated', color: 'green' });
},
onError: (err: any) => {
notifications.show({ message: err.response?.data?.message || 'Error', color: 'red' });
},
});
const handleEdit = (group: AssessmentGroup) => {
setEditing(group);
form.setValues({
@@ -223,10 +235,17 @@ export function AssessmentGroupsPage() {
{groups.map((g) => (
<Table.Tr key={g.id} style={{ opacity: g.is_active ? 1 : 0.5 }}>
<Table.Td>
<div>
<Text fw={500}>{g.name}</Text>
{g.description && <Text size="xs" c="dimmed">{g.description}</Text>}
</div>
<Group gap={8}>
<div>
<Group gap={6}>
<Text fw={500}>{g.name}</Text>
{g.is_default && (
<Badge color="yellow" variant="light" size="xs">Default</Badge>
)}
</Group>
{g.description && <Text size="xs" c="dimmed">{g.description}</Text>}
</div>
</Group>
</Table.Td>
<Table.Td ta="center">
<Badge variant="light">{g.actual_unit_count || g.unit_count}</Badge>
@@ -256,6 +275,16 @@ export function AssessmentGroupsPage() {
</Table.Td>
<Table.Td>
<Group gap={4}>
<Tooltip label={g.is_default ? 'Default group' : 'Set as default'}>
<ActionIcon
variant="subtle"
color={g.is_default ? 'yellow' : 'gray'}
onClick={() => !g.is_default && setDefaultMutation.mutate(g.id)}
disabled={g.is_default}
>
{g.is_default ? <IconStarFilled size={16} /> : <IconStar size={16} />}
</ActionIcon>
</Tooltip>
<ActionIcon variant="subtle" onClick={() => handleEdit(g)}>
<IconEdit size={16} />
</ActionIcon>