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:
@@ -27,8 +27,25 @@ interface BudgetVsActualData {
|
||||
total_expense_actual: number;
|
||||
}
|
||||
|
||||
const monthFilterOptions = [
|
||||
{ value: '', label: 'Full Year' },
|
||||
{ value: '1', label: 'January' },
|
||||
{ value: '2', label: 'February' },
|
||||
{ value: '3', label: 'March' },
|
||||
{ value: '4', label: 'April' },
|
||||
{ value: '5', label: 'May' },
|
||||
{ value: '6', label: 'June' },
|
||||
{ value: '7', label: 'July' },
|
||||
{ value: '8', label: 'August' },
|
||||
{ value: '9', label: 'September' },
|
||||
{ value: '10', label: 'October' },
|
||||
{ value: '11', label: 'November' },
|
||||
{ value: '12', label: 'December' },
|
||||
];
|
||||
|
||||
export function BudgetVsActualPage() {
|
||||
const [year, setYear] = useState(new Date().getFullYear().toString());
|
||||
const [month, setMonth] = useState('');
|
||||
|
||||
const yearOptions = Array.from({ length: 5 }, (_, i) => {
|
||||
const y = new Date().getFullYear() - 2 + i;
|
||||
@@ -36,9 +53,10 @@ export function BudgetVsActualPage() {
|
||||
});
|
||||
|
||||
const { data, isLoading } = useQuery<BudgetVsActualData>({
|
||||
queryKey: ['budget-vs-actual', year],
|
||||
queryKey: ['budget-vs-actual', year, month],
|
||||
queryFn: async () => {
|
||||
const { data } = await api.get(`/budgets/${year}/vs-actual`);
|
||||
const params = month ? `?month=${month}` : '';
|
||||
const { data } = await api.get(`/budgets/${year}/vs-actual${params}`);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
@@ -127,7 +145,17 @@ export function BudgetVsActualPage() {
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title order={2}>Budget vs. Actual</Title>
|
||||
<Select data={yearOptions} value={year} onChange={(v) => v && setYear(v)} w={120} />
|
||||
<Group>
|
||||
<Select data={yearOptions} value={year} onChange={(v) => v && setYear(v)} w={100} />
|
||||
<Select
|
||||
data={monthFilterOptions}
|
||||
value={month}
|
||||
onChange={(v) => setMonth(v || '')}
|
||||
w={150}
|
||||
placeholder="Month"
|
||||
clearable={false}
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 4 }}>
|
||||
|
||||
Reference in New Issue
Block a user