Sprint 6: Monthly actuals input, reconciliation, and file attachments
Add spreadsheet-style Monthly Actuals page for entering monthly actuals against budget with auto-generated journal entries and reconciliation flag. Add file attachment support (PDF, images, spreadsheets) on journal entries for receipts and invoices. Enhance Budget vs Actual report with month filter dropdown. Add reconciled badge to Transactions page. Replace bcrypt with bcryptjs to fix Docker cross-platform native binding issues. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,8 @@ import { DateInput } from '@mantine/dates';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconPlus, IconEye, IconCheck, IconX, IconTrash } from '@tabler/icons-react';
|
||||
import { IconPlus, IconEye, IconCheck, IconX, IconTrash, IconShieldCheck } from '@tabler/icons-react';
|
||||
import { AttachmentPanel } from '../../components/attachments/AttachmentPanel';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import api from '../../services/api';
|
||||
|
||||
@@ -30,6 +31,7 @@ interface JournalEntry {
|
||||
entry_type: string;
|
||||
is_posted: boolean;
|
||||
is_void: boolean;
|
||||
is_reconciled?: boolean;
|
||||
created_at: string;
|
||||
lines?: JournalEntryLine[];
|
||||
total_debit?: string;
|
||||
@@ -190,13 +192,22 @@ export function TransactionsPage() {
|
||||
<Table.Td ta="right" ff="monospace">{fmt(e.total_debit || '0')}</Table.Td>
|
||||
<Table.Td ta="right" ff="monospace">{fmt(e.total_credit || '0')}</Table.Td>
|
||||
<Table.Td>
|
||||
{e.is_void ? (
|
||||
<Badge color="red" variant="light" size="sm">Void</Badge>
|
||||
) : e.is_posted ? (
|
||||
<Badge color="green" variant="light" size="sm">Posted</Badge>
|
||||
) : (
|
||||
<Badge color="yellow" variant="light" size="sm">Draft</Badge>
|
||||
)}
|
||||
<Group gap={4}>
|
||||
{e.is_void ? (
|
||||
<Badge color="red" variant="light" size="sm">Void</Badge>
|
||||
) : e.is_posted ? (
|
||||
<Badge color="green" variant="light" size="sm">Posted</Badge>
|
||||
) : (
|
||||
<Badge color="yellow" variant="light" size="sm">Draft</Badge>
|
||||
)}
|
||||
{e.is_reconciled && (
|
||||
<Tooltip label="Reconciled">
|
||||
<Badge color="teal" variant="light" size="sm" leftSection={<IconShieldCheck size={12} />}>
|
||||
Reconciled
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Group>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap="xs">
|
||||
@@ -353,6 +364,11 @@ export function TransactionsPage() {
|
||||
</Group>
|
||||
<Text><strong>Description:</strong> {viewEntry.description}</Text>
|
||||
{viewEntry.reference_number && <Text><strong>Ref #:</strong> {viewEntry.reference_number}</Text>}
|
||||
{viewEntry.is_reconciled && (
|
||||
<Badge color="teal" variant="light" leftSection={<IconShieldCheck size={14} />}>
|
||||
Reconciled
|
||||
</Badge>
|
||||
)}
|
||||
<Table>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
@@ -373,6 +389,10 @@ export function TransactionsPage() {
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
|
||||
{/* Attachments */}
|
||||
<Text fw={500} mt="md">Attachments</Text>
|
||||
<AttachmentPanel journalEntryId={viewEntry.id} />
|
||||
</Stack>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user