diff --git a/backend/package-lock.json b/backend/package-lock.json index cc40666..0bbf241 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "hoa-ledgeriq-backend", - "version": "2026.3.7-beta", + "version": "2026.03.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hoa-ledgeriq-backend", - "version": "2026.3.7-beta", + "version": "2026.03.10", "dependencies": { "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", diff --git a/backend/package.json b/backend/package.json index 40f613d..459ae78 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "hoa-ledgeriq-backend", - "version": "2026.3.7-beta", + "version": "2026.03.10", "description": "HOA LedgerIQ - Backend API", "private": true, "scripts": { diff --git a/backend/src/main.ts b/backend/src/main.ts index cdec0c3..1c29cec 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -67,7 +67,7 @@ async function bootstrap() { const config = new DocumentBuilder() .setTitle('HOA LedgerIQ API') .setDescription('API for the HOA LedgerIQ') - .setVersion('2026.3.7') + .setVersion('2026.03.10') .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 80b0f0e..9f545cd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "hoa-ledgeriq-frontend", - "version": "2026.3.7-beta", + "version": "2026.03.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hoa-ledgeriq-frontend", - "version": "2026.3.7-beta", + "version": "2026.03.10", "dependencies": { "@mantine/core": "^7.15.3", "@mantine/dates": "^7.15.3", diff --git a/frontend/package.json b/frontend/package.json index b67c119..630271e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "hoa-ledgeriq-frontend", - "version": "2026.3.7-beta", + "version": "2026.03.10", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/pages/budgets/BudgetsPage.tsx b/frontend/src/pages/budgets/BudgetsPage.tsx index 0b51142..0cbf8ed 100644 --- a/frontend/src/pages/budgets/BudgetsPage.tsx +++ b/frontend/src/pages/budgets/BudgetsPage.tsx @@ -4,7 +4,7 @@ import { Select, Loader, Center, Badge, Card, Alert, } from '@mantine/core'; import { notifications } from '@mantine/notifications'; -import { IconDeviceFloppy, IconUpload, IconDownload, IconInfoCircle } from '@tabler/icons-react'; +import { IconDeviceFloppy, IconUpload, IconDownload, IconInfoCircle, IconPencil, IconX } from '@tabler/icons-react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import api from '../../services/api'; import { useIsReadOnly } from '../../stores/authStore'; @@ -96,6 +96,7 @@ function parseCSV(text: string): Record[] { export function BudgetsPage() { const [year, setYear] = useState(new Date().getFullYear().toString()); const [budgetData, setBudgetData] = useState([]); + const [isEditing, setIsEditing] = useState(false); const queryClient = useQueryClient(); const fileInputRef = useRef(null); const isReadOnly = useIsReadOnly(); @@ -105,6 +106,11 @@ export function BudgetsPage() { const incomeSectionBg = isDark ? 'var(--mantine-color-green-9)' : '#e6f9e6'; const expenseSectionBg = isDark ? 'var(--mantine-color-red-9)' : '#fde8e8'; + // Budget exists when there is data loaded for the selected year + const hasBudget = budgetData.length > 0; + // Cells are editable only when editing an existing budget or creating a new one (no data yet) + const cellsEditable = !isReadOnly && (isEditing || !hasBudget); + const { isLoading } = useQuery({ queryKey: ['budgets', year], queryFn: async () => { @@ -112,25 +118,27 @@ export function BudgetsPage() { // Hydrate each line: ensure numbers and compute annual_total const hydrated = (data as any[]).map(hydrateBudgetLine); setBudgetData(hydrated); + setIsEditing(false); // Reset to view mode when year changes or data reloads return hydrated; }, }); const saveMutation = useMutation({ mutationFn: async () => { - const lines = budgetData + const payload = budgetData .filter((b) => months.some((m) => (b as any)[m] > 0)) .map((b) => ({ - account_id: b.account_id, - fund_type: b.fund_type, + accountId: b.account_id, + fundType: b.fund_type, jan: b.jan, feb: b.feb, mar: b.mar, apr: b.apr, may: b.may, jun: b.jun, jul: b.jul, aug: b.aug, - sep: b.sep, oct: b.oct, nov: b.nov, dec_amt: b.dec_amt, + sep: b.sep, oct: b.oct, nov: b.nov, dec: b.dec_amt, })); - return api.put(`/budgets/${year}`, { lines }); + return api.put(`/budgets/${year}`, payload); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['budgets', year] }); + setIsEditing(false); notifications.show({ message: 'Budget saved', color: 'green' }); }, onError: (err: any) => { @@ -227,6 +235,12 @@ export function BudgetsPage() { event.target.value = ''; }; + const handleCancelEdit = () => { + setIsEditing(false); + // Re-fetch to discard unsaved changes + queryClient.invalidateQueries({ queryKey: ['budgets', year] }); + }; + const updateCell = (idx: number, month: string, value: number) => { const updated = [...budgetData]; (updated[idx] as any)[month] = value || 0; @@ -281,9 +295,35 @@ export function BudgetsPage() { accept=".csv,.txt" onChange={handleFileChange} /> - + {hasBudget && !isEditing ? ( + + ) : ( + <> + {isEditing && ( + + )} + + + )} )} @@ -397,16 +437,21 @@ export function BudgetsPage() { {months.map((m) => ( - updateCell(idx, m, Number(v) || 0)} - size="xs" - hideControls - decimalScale={2} - min={0} - disabled={isReadOnly} - styles={{ input: { textAlign: 'right', fontFamily: 'monospace' } }} - /> + {cellsEditable ? ( + updateCell(idx, m, Number(v) || 0)} + size="xs" + hideControls + decimalScale={2} + min={0} + styles={{ input: { textAlign: 'right', fontFamily: 'monospace' } }} + /> + ) : ( + + {fmt((line as any)[m] || 0)} + + )} ))} diff --git a/frontend/src/pages/settings/SettingsPage.tsx b/frontend/src/pages/settings/SettingsPage.tsx index f23ff51..2583714 100644 --- a/frontend/src/pages/settings/SettingsPage.tsx +++ b/frontend/src/pages/settings/SettingsPage.tsx @@ -117,7 +117,7 @@ export function SettingsPage() { Version - 2026.3.7 (Beta) + 2026.03.10 API