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
2 changes: 1 addition & 1 deletion frontend/src/main-page/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function MainPage() {
<Route
path="/cash-flow"
element={
<PositionGuard adminOnly={false}>
<PositionGuard adminOnly={true}>
<CashFlowPage />
<Footer />
</PositionGuard>
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/main-page/cash-flow/CashFlowPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ import CashSourceList from "./components/CashSourceList";
import { ProcessCashflowData } from "./processCashflowData";
import CashCreateLineItem from "./components/CashCreateLineItem";

const CashFlowPage = observer(() => {
export const formatMoney = (amount: number) => {
return amount.toLocaleString("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
};

const CashFlowPage = observer(() => {
const { costs, revenues } = ProcessCashflowData();

return (
Expand All @@ -35,7 +42,7 @@ const CashFlowPage = observer(() => {
/>
<CashflowKPICard
text="Monthly Costs"
value="$50,000"
value={formatMoney(costs.reduce((accumulator, currentValue) => accumulator + currentValue.amount, 0))}
logo={faUserGroup}
className="text-primary"
/>
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/main-page/cash-flow/components/CashAddCosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ export default function CashAddCosts() {
<div className="text-lg lg:text-xl w-full text-left font-bold">
{"Add Cost Source"}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 w-full justify-between gap-4">
<div className="grid grid-cols-1 xl:grid-cols-2 w-full justify-between gap-4">
<div className="flex flex-col col-span-1 w-full gap-3">
<CashCategoryDropdown type={CostType} onChange={() => {}} />
<CashCategoryDropdown
type={CostType}
onChange={(value) => alert(value)}
value={CostType.Benefits}
/>
</div>
<div className="flex flex-col col-span-1 gap-3">
<InputField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ export default function CashAddRevenue() {
<div className="text-lg lg:text-xl w-full text-left font-bold">
{"Add Revenue Source"}
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 w-full justify-between gap-4">
<div className="grid grid-cols-1 xl:grid-cols-2 w-full justify-between gap-4">
<div className="flex flex-col col-span-1 w-full gap-2">
<CashCategoryDropdown type={RevenueType} onChange={() => {}} />
<CashCategoryDropdown
type={RevenueType}
onChange={(value) => alert(value)}
value={RevenueType.Fundraising}
/>
<InputField
type="number"
id="amount"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@ import { RevenueType } from "../../../../../middle-layer/types/RevenueType";

type CashCategoryDropdown = {
type: typeof RevenueType | typeof CostType;
onChange: () => void;
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
value: RevenueType | CostType;
};

export default function CashCategoryDropdown({
type,
onChange,
value,
}: CashCategoryDropdown) {
return (
<div className="w-full">
<label htmlFor={"Category"} className="block text-left font-semibold text-sm lg:text-base">
<label
htmlFor={"Category"}
className="block text-left font-semibold text-sm lg:text-base"
>
{"Category"}
</label>
<div className="mt-2 flex items-center rounded-md ">
<select
id="Category"
value={"Grants"}
value={value}
onChange={onChange}
className="block w-full rounded-md py-2.5 pl-4 pr-3 text-sm lg:text-base border-2 placeholder:text-grey-500 ${
border-grey-500 bg-grey-100 h-[3rem]"
border-grey-500 bg-grey-100 h-[2.71rem] lg:h-[2.95rem]"
>
{Object.values(type).map((r) => (
<option key={r} value={r}>
Expand Down
84 changes: 84 additions & 0 deletions frontend/src/main-page/cash-flow/components/CashEditCost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Button from "../../../components/Button";
import InputField from "../../../components/InputField";
import CashCategoryDropdown from "./CashCategoryDropdown";
import { CostType } from "../../../../../middle-layer/types/CostType";
import { useState } from "react";
import { CashflowCost } from "../../../../../middle-layer/types/CashflowCost";
import { saveCostEdits } from "../processCashflowDataEditSave";
import { formatMoney } from "../CashFlowPage";

type CashEditCostProps = {
costItem: CashflowCost;
onClose: () => void;
};

export default function CashEditCost({ costItem, onClose }: CashEditCostProps) {
const [type, setType] = useState<CostType>(costItem.type);
const [name, setName] = useState<string>(costItem.name);
const [amount, setAmount] = useState<number>(costItem.amount);

const handleChangeCategory = (e: React.ChangeEvent<HTMLSelectElement>) => {
setType(e.target.value as CostType);
};

const handleChangeName = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};

const handleChangeAmount = (e: React.ChangeEvent<HTMLInputElement>) => {
setAmount(e.target.valueAsNumber);
};

const handleSave = () => {
saveCostEdits({ name, type, amount, date: "2020-01-01" }, costItem.name);
onClose();
};

return (
<div>
<div className="grid grid-cols-1 lg:grid-cols-2 w-full justify-between gap-4 mb-2">
<div className="flex flex-col col-span-1 w-full gap-3">
<CashCategoryDropdown
type={CostType}
value={type}
onChange={handleChangeCategory}
/>
</div>
<div className="flex flex-col col-span-1 gap-3">
<InputField
type="text"
id="cost_name"
label="Cost Item Name"
value={name}
onChange={handleChangeName}
/>
</div>
</div>
<InputField
type="number"
id="amount"
label="Annual Amount ($)"
value={amount}
className="w-full"
onChange={handleChangeAmount}
/>

<div className="flex flex-row justify-end gap-2 mt-2 items-center">
<div className="font-semibold mr-auto text-start text-sm lg:text-base">
{formatMoney(amount / 12)}
{"/month"}
</div>
<Button
text="Cancel"
onClick={() => onClose()}
className="bg-white text-black border border-grey-500 mt-2 text-sm lg:text-base"
/>
<Button
text="Save"
onClick={() => handleSave()}
className="bg-primary-900 text-white mt-2 text-sm lg:text-base"
/>
</div>
</div>
);
}
35 changes: 28 additions & 7 deletions frontend/src/main-page/cash-flow/components/CashEditLineItem.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useState } from "react";
import Button from "../../../components/Button";
import { faTrash, faPenToSquare } from "@fortawesome/free-solid-svg-icons";
import ActionConfirmation from "../../../components/ActionConfirmation";

type CashEditLineItemProps = {
cardText: React.ReactNode;
children: React.ReactNode;
children: (onClose: () => void) => React.ReactNode;
sourceName: string;
onRemove: () => void;
};
Expand All @@ -15,22 +16,31 @@ export default function CashEditLineItem({
sourceName,
onRemove,
}: CashEditLineItemProps) {
const [editting, setEditing] = useState<Boolean>(false);
const [editing, setEditing] = useState<boolean>(false);

const onEdit = () => {
setEditing(true);
};

const [showDeleteModal, setShowDeleteModal] = useState(false);

const handleDelete = async () => {
onRemove();
setShowDeleteModal(false);
};

return (
<div className="rounded border border-grey-500 p-4">
{!editting && (
{!editing && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-2">
<div className="flex flex-col text-start">
<div className="text-lg lg:text-xl font-bold">{sourceName}</div>
<div className="text-lg lg:text-xl font-bold">
{sourceName}
</div>
{cardText}
</div>

<div className="flex flex-wrap gap-2 lg:ml-auto">
<div className="flex flex-wrap gap-2 justify-end h-fit">
<Button
text="Edit"
onClick={onEdit}
Expand All @@ -42,13 +52,24 @@ export default function CashEditLineItem({
text="Remove"
logo={faTrash}
logoPosition="right"
onClick={onRemove}
onClick={() => setShowDeleteModal(true)}
className="bg-red-light text-red text-sm lg:text-base"
/>
</div>
</div>
)}
{editting && <div>{children}</div>}
{editing && <div>{children(() => setEditing(false))}</div>}
<ActionConfirmation
isOpen={showDeleteModal}
onCloseDelete={() => setShowDeleteModal(false)}
onConfirmDelete={() => {
handleDelete();
}}
title={`Delete Cashflow Item`}
subtitle={"Are you sure you want to delete"}
boldSubtitle={sourceName}
warningMessage="By deleting this item, it won't be available in the system anymore."
/>
</div>
);
}
60 changes: 47 additions & 13 deletions frontend/src/main-page/cash-flow/components/CashSourceList.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,66 @@
import { CashflowCost } from "../../../../../middle-layer/types/CashflowCost";
import { CashflowRevenue } from "../../../../../middle-layer/types/CashflowRevenue";
import { deleteCost, deleteRevenue } from "../processCashflowDataEditSave";
import CashEditLineItem from "./CashEditLineItem";
import CashEditCost from "./CashEditCost";
import { formatMoney } from "../CashFlowPage";

type SourceProps = {
type: "Revenue" | "Cost";
lineItems: CashflowRevenue[] | CashflowCost[];
};

export default function CashSourceList({ type }: SourceProps) {
export default function CashSourceList({ type, lineItems }: SourceProps) {
return (
<div className="chart-container col-span-2 h-full">
<div className="chart-container col-span-2 h-fit">
<div className="text-lg lg:text-xl mb-2 w-full text-left font-bold">
{type}
{" Sources"}
</div>
{/* map over list of source and put casheditlineitem for each */}
<CashEditLineItem
cardText={
<div className="flex flex-col text-sm lg:text-base">
<div className="font-semibold">{"Individual Donations"}</div>
<div>{"$10,000 | 3/13/2323"}</div>
<div className="flex flex-col gap-2">
{lineItems.map((item) => (
<div key={item.name}>
<CashEditLineItem
cardText={
<div className="flex flex-col text-sm lg:text-base gap-1">
<div className="font-semibold">{item.type}</div>
{type === "Cost" && (
<div>
<div>
{formatMoney(item.amount)}
{"/year"}
</div>
<div>
{formatMoney(item.amount / 12)}
{"/month"}
</div>
</div>
)}
{type === "Revenue" && (
<div>{/* Revenue card info here */}</div>
)}
</div>
}
sourceName={item.name}
onRemove={() =>
type === "Cost"
? deleteCost(item.name)
: deleteRevenue(item.name)
}
>
{(onClose) =>
type === "Cost" && (
<CashEditCost
costItem={item as CashflowCost}
onClose={onClose}
/>
)
}
</CashEditLineItem>
</div>
}
sourceName="Source Name"
onRemove={() => alert("remove")}
>
<div>{"Edit form"}</div>
</CashEditLineItem>
))}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/src/main-page/cash-flow/processCashflowData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { api } from "../../api.ts";
export const fetchCosts = async () => {
try {
// Need to replace with actual endpoint
const response = await api("/cost");
const response = await api("/cashflow-cost");
if (!response.ok) {
throw new Error(`HTTP Error, Status: ${response.status}`);
}
Expand Down
14 changes: 6 additions & 8 deletions frontend/src/main-page/cash-flow/processCashflowDataEditSave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const saveRevenueEdits = async (updatedRevenue: CashflowRevenue) => {
try {
// Need to replace with acual endpoint
const response = await api("/revenue/save", {
method: "PUT",
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updatedRevenue),
});
Expand All @@ -96,11 +96,10 @@ export const saveRevenueEdits = async (updatedRevenue: CashflowRevenue) => {
}
};

export const saveCostEdits = async (updatedCost: CashflowCost) => {
export const saveCostEdits = async (updatedCost: CashflowCost, originalCostName: string) => {
try {
// Need to replace with acual endpoint
const response = await api("/cost/save", {
method: "PUT",
const response = await api(`/cashflow-cost/${originalCostName}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updatedCost),
});
Expand Down Expand Up @@ -169,10 +168,9 @@ export const deleteRevenue = async (revenueId: any) => {
}
};

export const deleteCost = async (costId: any) => {
// Need to replace with acual endpoint
export const deleteCost = async (costName: any) => {
try {
const response = await api(`/revenue/${costId}`, {
const response = await api(`/cashflow-cost/${costName}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Expand Down
Loading
Loading