Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions backend/src/default-values/default-values.controller.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -74,9 +74,9 @@ export class DefaultValuesController {
async updateDefaultValue(
@Body() body: UpdateDefaultValueBody,
): Promise<DefaultValuesResponse> {
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;
}
}
5 changes: 5 additions & 0 deletions frontend/src/external/bcanSatchel/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}));
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/external/bcanSatchel/mutators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
removeProfilePic,
fetchCashflowRevenues,
fetchCashflowCosts,
setCashflowSettings
} from "./actions";
import { getAppStore, persistToSessionStorage } from "./store";

Expand Down Expand Up @@ -237,3 +238,12 @@ mutator(removeProfilePic, () => {

persistToSessionStorage();
});

/**
* setCashflowSettings mutator
*/

mutator(setCashflowSettings, (actionMessage) => {
const store = getAppStore();
store.cashflowSettings = actionMessage.cashflowSettings;
});
3 changes: 3 additions & 0 deletions frontend/src/external/bcanSatchel/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +30,7 @@ export interface AppState {
userQuery: string;
revenueSources: CashflowRevenue[];
costSources: CashflowCost[];
cashflowSettings: CashflowSettings | null;
}

// Define initial state
Expand All @@ -54,6 +56,7 @@ const initialState: AppState = {
userQuery: '',
revenueSources: [],
costSources: [],
cashflowSettings: null,
};

/**
Expand Down
33 changes: 29 additions & 4 deletions frontend/src/main-page/cash-flow/components/CashAnnualSettings.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>) => {
if (!cashflowSettings) return;
setCashflowSettings({
...cashflowSettings,
salaryIncrease: e.target.valueAsNumber,
});
};

const handleBenefitsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!cashflowSettings) return;
setCashflowSettings({
...cashflowSettings,
benefitsIncrease: e.target.valueAsNumber,
});
};

return (
<div className="chart-container col-span-2 h-full">
Expand All @@ -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=""
/>
<InputField
type="number"
id="benefits_increase"
label="Personnel Benefits Increase (%)"
value={"4.0"}
value={cashflowSettings?.benefitsIncrease ?? 0}
onChange={handleBenefitsChange}
/>
</div>
</div>
);
}
});

export default CashAnnualSettings;
23 changes: 20 additions & 3 deletions frontend/src/main-page/cash-flow/components/CashPosition.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>) => {
if (!cashflowSettings) return;
setCashflowSettings({
...cashflowSettings,
startingCash: e.target.valueAsNumber,
});
};

export default function CashPosition() {
return (
<div className="chart-container col-span-2 h-full">
<div className="text-lg lg:text-xl mb-2 w-full text-left font-bold">
Expand All @@ -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}
/>
</div>
);
}
});

export default CashPosition;
26 changes: 23 additions & 3 deletions frontend/src/main-page/cash-flow/processCashflowData.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 };
};
36 changes: 34 additions & 2 deletions frontend/src/main-page/cash-flow/processCashflowDataEditSave.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -211,4 +212,35 @@ export const deleteCost = async (costId: any) => {
);
console.error("Full error:", err);
}
};
};

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.",
};
}
};
7 changes: 6 additions & 1 deletion frontend/src/main-page/navbar/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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");
Expand Down
5 changes: 5 additions & 0 deletions middle-layer/types/CashflowSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface CashflowSettings {
startingCash: number;
salaryIncrease: number;
benefitsIncrease: number;
}
Loading