diff --git a/backend/src/default-values/default-values.controller.ts b/backend/src/default-values/default-values.controller.ts index ed9db85..4dc5682 100644 --- a/backend/src/default-values/default-values.controller.ts +++ b/backend/src/default-values/default-values.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Patch, Logger, UseGuards} from '@nestjs/common'; +import { Body, Controller, Get, Logger, UseGuards, Put} from '@nestjs/common'; import { ApiBody, ApiResponse, ApiTags } from '@nestjs/swagger'; import { DefaultValuesService } from './default-values.service'; import { @@ -45,7 +45,7 @@ export class DefaultValuesController { * @param body - UpdateDefaultValueBody containing the key of the default value to update and the new value * @returns new DefaultValuesResponse with the updated default values */ - @Patch() + @Put() @UseGuards(VerifyAdminRoleGuard) @ApiBearerAuth() @ApiBody({ schema: { @@ -74,9 +74,9 @@ export class DefaultValuesController { async updateDefaultValue( @Body() body: UpdateDefaultValueBody, ): Promise { - this.logger.log(`PATCH /default-values - Updating default value for key: ${body.key}`); + this.logger.log(`PUT /default-values - Updating default value for key: ${body.key}`); const updatedValues = await this.defaultValuesService.updateDefaultValue(body.key, body.value); - this.logger.log(`PATCH /default-values - Successfully updated default value for key: ${body.key}`); + this.logger.log(`PUT /default-values - Successfully updated default value for key: ${body.key}`); return updatedValues; } } diff --git a/frontend/src/external/bcanSatchel/actions.ts b/frontend/src/external/bcanSatchel/actions.ts index 8c39099..424d222 100644 --- a/frontend/src/external/bcanSatchel/actions.ts +++ b/frontend/src/external/bcanSatchel/actions.ts @@ -5,6 +5,7 @@ import { Status } from '../../../../middle-layer/types/Status' import { Notification } from '../../../../middle-layer/types/Notification'; import { CashflowCost } from '../../../../middle-layer/types/CashflowCost'; import { CashflowRevenue } from '../../../../middle-layer/types/CashflowRevenue'; +import { CashflowSettings } from '../../../../middle-layer/types/CashflowSettings'; /** * Set whether the user is authenticated, update the user object, @@ -59,6 +60,10 @@ export const fetchCashflowCosts = action("fetchCashflowCosts", (costs: CashflowC costs, })); +export const setCashflowSettings = action("setCashflowSettings", + (cashflowSettings: CashflowSettings) => ({ cashflowSettings }) +); + export const updateFilter = action("updateFilter", (status: Status | null) => ({ status, })); diff --git a/frontend/src/external/bcanSatchel/mutators.ts b/frontend/src/external/bcanSatchel/mutators.ts index 588bdfa..7ab5dcc 100644 --- a/frontend/src/external/bcanSatchel/mutators.ts +++ b/frontend/src/external/bcanSatchel/mutators.ts @@ -22,6 +22,7 @@ import { removeProfilePic, fetchCashflowRevenues, fetchCashflowCosts, + setCashflowSettings } from "./actions"; import { getAppStore, persistToSessionStorage } from "./store"; @@ -237,3 +238,12 @@ mutator(removeProfilePic, () => { persistToSessionStorage(); }); + +/** + * setCashflowSettings mutator + */ + +mutator(setCashflowSettings, (actionMessage) => { + const store = getAppStore(); + store.cashflowSettings = actionMessage.cashflowSettings; +}); diff --git a/frontend/src/external/bcanSatchel/store.ts b/frontend/src/external/bcanSatchel/store.ts index f852429..a09d544 100644 --- a/frontend/src/external/bcanSatchel/store.ts +++ b/frontend/src/external/bcanSatchel/store.ts @@ -5,6 +5,7 @@ import { Status } from '../../../../middle-layer/types/Status' import { Notification } from '../../../../middle-layer/types/Notification' import { CashflowRevenue } from '../../../../middle-layer/types/CashflowRevenue' import { CashflowCost } from '../../../../middle-layer/types/CashflowCost' +import { CashflowSettings } from '../../../../middle-layer/types/CashflowSettings' export interface AppState { isAuthenticated: boolean; @@ -29,6 +30,7 @@ export interface AppState { userQuery: string; revenueSources: CashflowRevenue[]; costSources: CashflowCost[]; + cashflowSettings: CashflowSettings | null; } // Define initial state @@ -54,6 +56,7 @@ const initialState: AppState = { userQuery: '', revenueSources: [], costSources: [], + cashflowSettings: null, }; /** diff --git a/frontend/src/main-page/cash-flow/components/CashAnnualSettings.tsx b/frontend/src/main-page/cash-flow/components/CashAnnualSettings.tsx index e3ef825..2d8a126 100644 --- a/frontend/src/main-page/cash-flow/components/CashAnnualSettings.tsx +++ b/frontend/src/main-page/cash-flow/components/CashAnnualSettings.tsx @@ -1,6 +1,27 @@ import InputField from "../../../components/InputField"; +import { observer } from "mobx-react-lite"; +import { getAppStore } from "../../../external/bcanSatchel/store"; +import { setCashflowSettings } from "../../../external/bcanSatchel/actions"; -export default function CashAnnualSettings() { +const CashAnnualSettings = observer(() => { + + const { cashflowSettings } = getAppStore(); + + const handleSalaryChange = (e: React.ChangeEvent) => { + if (!cashflowSettings) return; + setCashflowSettings({ + ...cashflowSettings, + salaryIncrease: e.target.valueAsNumber, + }); + }; + + const handleBenefitsChange = (e: React.ChangeEvent) => { + if (!cashflowSettings) return; + setCashflowSettings({ + ...cashflowSettings, + benefitsIncrease: e.target.valueAsNumber, + }); + }; return (
@@ -12,16 +33,20 @@ export default function CashAnnualSettings() { type="number" id="salary_increase" label="Personnel Salary Increase (%)" - value={"3.5"} + value={cashflowSettings?.salaryIncrease ?? 0} + onChange={handleSalaryChange} className="" />
); -} +}); + +export default CashAnnualSettings; diff --git a/frontend/src/main-page/cash-flow/components/CashPosition.tsx b/frontend/src/main-page/cash-flow/components/CashPosition.tsx index 05e7055..d58c952 100644 --- a/frontend/src/main-page/cash-flow/components/CashPosition.tsx +++ b/frontend/src/main-page/cash-flow/components/CashPosition.tsx @@ -1,6 +1,20 @@ import InputField from "../../../components/InputField"; +import { observer } from "mobx-react-lite"; +import { getAppStore } from "../../../external/bcanSatchel/store"; +import { setCashflowSettings } from "../../../external/bcanSatchel/actions"; + +const CashPosition = observer(() => { + + const { cashflowSettings } = getAppStore(); + + const handleChange = (e: React.ChangeEvent) => { + if (!cashflowSettings) return; + setCashflowSettings({ + ...cashflowSettings, + startingCash: e.target.valueAsNumber, + }); + }; -export default function CashPosition() { return (
@@ -10,8 +24,11 @@ export default function CashPosition() { type="number" id="starting_balance" label="Current Cash Balance" - value={"25000"} + value={cashflowSettings?.startingCash ?? 0} + onChange={handleChange} />
); -} +}); + +export default CashPosition; \ No newline at end of file diff --git a/frontend/src/main-page/cash-flow/processCashflowData.ts b/frontend/src/main-page/cash-flow/processCashflowData.ts index b9abd30..fb807ed 100644 --- a/frontend/src/main-page/cash-flow/processCashflowData.ts +++ b/frontend/src/main-page/cash-flow/processCashflowData.ts @@ -1,8 +1,9 @@ import { useEffect } from "react"; import { getAppStore } from "../../external/bcanSatchel/store.ts"; -import { fetchCashflowCosts, fetchCashflowRevenues } from "../../external/bcanSatchel/actions.ts"; +import { fetchCashflowCosts, fetchCashflowRevenues, setCashflowSettings } from "../../external/bcanSatchel/actions.ts"; import {CashflowRevenue} from "../../../../middle-layer/types/CashflowRevenue.ts"; import {CashflowCost} from "../../../../middle-layer/types/CashflowCost.ts"; +import {CashflowSettings} from "../../../../middle-layer/types/CashflowSettings.ts"; import { api } from "../../api.ts"; // This has not been tested yet but the basic structure when implemented should be the same @@ -37,13 +38,27 @@ export const fetchRevenues = async () => { } }; +export const fetchCashflowSettings = async () => { + try { + const response = await api("/default-values"); + if (!response.ok) { + throw new Error(`HTTP Error, Status: ${response.status}`); + } + const settings: CashflowSettings = await response.json(); + setCashflowSettings(settings); + } catch (error) { + console.error("Error fetching cashflow settings:", error); + } +}; + // could contain callbacks for sorting and filtering line items // stores state for list of costs/revenues export const ProcessCashflowData = () => { const { costSources, - revenueSources + revenueSources, + cashflowSettings } = getAppStore(); // fetch costs on mount if empty @@ -56,5 +71,10 @@ export const ProcessCashflowData = () => { if (revenueSources.length === 0) fetchRevenues(); }, [revenueSources.length]); - return { costs: costSources, revenues: revenueSources }; + // fetch settings on mount if null + useEffect(() => { + if (!cashflowSettings) fetchCashflowSettings(); + }, [cashflowSettings]); + + return { costs: costSources, revenues: revenueSources, cashflowSettings }; }; diff --git a/frontend/src/main-page/cash-flow/processCashflowDataEditSave.ts b/frontend/src/main-page/cash-flow/processCashflowDataEditSave.ts index f529210..b9f6210 100644 --- a/frontend/src/main-page/cash-flow/processCashflowDataEditSave.ts +++ b/frontend/src/main-page/cash-flow/processCashflowDataEditSave.ts @@ -1,7 +1,8 @@ import {CashflowRevenue} from "../../../../middle-layer/types/CashflowRevenue.ts"; import {CashflowCost} from "../../../../middle-layer/types/CashflowCost.ts"; +import { CashflowSettings } from "../../../../middle-layer/types/CashflowSettings.ts"; import { api } from "../../api.ts"; -import { fetchCosts, fetchRevenues } from "./processCashflowData.ts"; +import { fetchCosts, fetchRevenues, fetchCashflowSettings } from "./processCashflowData.ts"; // This has not been tested yet but the basic structure when implemented should be the same // Mirrored format for processGrantDataEditSave.ts @@ -211,4 +212,35 @@ export const deleteCost = async (costId: any) => { ); console.error("Full error:", err); } - }; \ No newline at end of file + }; + +export const saveCashflowSettings = async (settings: CashflowSettings) => { + try { + const updates = [ + { key: "startingCash", value: settings.startingCash }, + { key: "salaryIncrease", value: settings.salaryIncrease }, + { key: "benefitsIncrease", value: settings.benefitsIncrease }, + ]; + + for (const update of updates) { + const response = await api("/default-values", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(update), + }); + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || `Failed to update ${update.key}`); + } + } + + await fetchCashflowSettings(); + return { success: true }; + } catch (error) { + console.error("Error saving cashflow settings:", error); + return { + success: false, + error: error instanceof Error ? error.message : "Server error. Please try again.", + }; + } +}; \ No newline at end of file diff --git a/frontend/src/main-page/navbar/NavBar.tsx b/frontend/src/main-page/navbar/NavBar.tsx index 8a16bd6..e22f9cf 100644 --- a/frontend/src/main-page/navbar/NavBar.tsx +++ b/frontend/src/main-page/navbar/NavBar.tsx @@ -11,6 +11,7 @@ import { UserStatus } from "../../../../middle-layer/types/UserStatus"; import NavTab, { NavTabProps } from "./NavTab.tsx"; import { faChartLine, faMoneyBill, faClipboardCheck } from "@fortawesome/free-solid-svg-icons"; import { NavBarBranding } from "../../translations/general.ts"; +import { saveCashflowSettings } from "../cash-flow/processCashflowDataEditSave"; const tabs: NavTabProps[] = [ { name: "Dashboard", linkTo: "/main/dashboard", icon: faChartLine }, @@ -26,7 +27,11 @@ const NavBar: React.FC = observer(() => { const user = getAppStore().user; const isAdmin = user?.position === UserStatus.Admin; - const handleLogout = () => { + const handleLogout = async () => { + const { cashflowSettings } = getAppStore(); + if (cashflowSettings) { + await saveCashflowSettings(cashflowSettings); + } logoutUser(); clearAllFilters(); navigate("/login"); diff --git a/middle-layer/types/CashflowSettings.ts b/middle-layer/types/CashflowSettings.ts new file mode 100644 index 0000000..e16415e --- /dev/null +++ b/middle-layer/types/CashflowSettings.ts @@ -0,0 +1,5 @@ +export interface CashflowSettings { + startingCash: number; + salaryIncrease: number; + benefitsIncrease: number; +} \ No newline at end of file