Skip to content
Merged
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
137 changes: 137 additions & 0 deletions frontend/src/main-page/grants/grant-details/CostBenefitAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { useState } from 'react';
import { Grant } from '../../../../../middle-layer/types/Grant';
import '../styles/CostBenefitAnalysis.css';

interface CostBenefitAnalysisProps {
grant: Grant;
}

export const CostBenefitAnalysis: React.FC<CostBenefitAnalysisProps> = ({ grant }) => {
const [hourlyRate, setHourlyRate] = useState<string>('');
const [timePerReport, setTimePerReport] = useState<string>('');
const [netBenefit, setNetBenefit] = useState<number | null>(null);

const calculateNetBenefit = () => {
console.log('Called calculate')
console.log('hourlyRate state:', hourlyRate)
console.log('timePerReport state:', timePerReport)
const rate = parseFloat(hourlyRate);
const timeReport = parseFloat(timePerReport);

console.log('Parsed rate:', rate)
console.log('Parsed timeReport:', timeReport)

// Validation
if (isNaN(rate) || isNaN(timeReport) || rate <= 0 || timeReport <= 0) {
alert('Please enter valid positive numbers for hourly rate and time per report.');
return;
}

const reportCount = grant.report_deadlines?.length ?? 0;
const grantAmount = grant.amount;
const estimatedTime = grant.estimated_completion_time | 5;

console.log('Grant values - Amount:', grantAmount, 'EstTime:', estimatedTime, 'ReportCount:', reportCount);

// Formula: NetBenefit = GrantAmount - ((EstimatedCompletionTime + ReportCount * TimePerReport) * StaffHourlyRate)
const result = grantAmount - ((estimatedTime + reportCount * timeReport) * rate);

console.log('Final result:', result);

setNetBenefit(result);
};

const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2
}).format(amount);
};

return (
<div className="cost-benefit-analysis">
<label className="text-lg flex block tracking-wide text-gray-700 font-semibold text-left">
Cost Benefit Analysis
</label>

<div
className="cost-benefit-container p-1 rounded-lg h-full flex flex-col"
style={{
backgroundColor: 'white'
}}
>
{/* Hourly Rate Input */}
<div className="mb-3">
<label className="block text-left text-gray-700 text-sm mb-1">
Hourly rate
</label>
<input
type="number"
placeholder="Enter rate"
value={hourlyRate}
onChange={(e) => setHourlyRate(e.target.value)}
className="w-full h-[42px] px-3 py-4 border border-gray-400 rounded-md"
style={{ backgroundColor: '#F2EBE4' }}
/>
</div>

{/* Time Per Report Input */}
<div className="mb-3">
<label className="block text-left text-gray-700 text-sm mb-1">
Time per report (in hours)
</label>
<input
type="number"
placeholder="Enter time"
value={timePerReport}
onChange={(e) => {
console.log('Time per report changed to:', e.target.value);
setTimePerReport(e.target.value);
}}
className="w-full h-[42px] px-3 py-4 border border-gray-400 rounded-md"
style={{ backgroundColor: '#F2EBE4' }}
/>
</div>

{/* Calculate Button */}
<button
onClick={calculateNetBenefit}
className="w-full py-1 px-4 rounded-md mb-7"
style={{
backgroundColor: '#F58D5C',
color: 'black',
borderStyle: 'solid',
borderColor: 'black',
borderWidth: '1px'
}}
>
calculate
</button>

{/* Analysis Button - Shows the net benefit result */}
<div className="flex justify-end items-center gap-2">
<span className="text-sm font-semibold"> Net Benefit:</span>
<div
onClick={calculateNetBenefit}
className="w-1/2 py-2 px-4 rounded-md"
style={{
backgroundColor: '#F2EBE4',
color: netBenefit !== null ? 'black' : 'gray',
borderStyle: 'solid',
borderColor: 'black',
borderWidth: '1px',
overflow: 'auto',
textAlign: 'center',
whiteSpace: 'nowrap',
scrollbarWidth: 'none',
msOverflowStyle: 'none'
}}
>
{netBenefit !== null ? formatCurrency(netBenefit) : 'Analysis'}
</div>
</div>
</div>
</div>
);
};
68 changes: 39 additions & 29 deletions frontend/src/main-page/grants/grant-list/GrantItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { api } from "../../../api";
import { MdOutlinePerson2 } from "react-icons/md";
import Attachment from "../../../../../middle-layer/types/Attachment";
import NewGrantModal from "../new-grant/NewGrantModal";
import { CostBenefitAnalysis } from "../grant-details/CostBenefitAnalysis";
import ActionConfirmation from "../../../custom/ActionConfirmation";
import { observer } from "mobx-react-lite";
import { fetchGrants } from "../filter-bar/processGrantData";
Expand Down Expand Up @@ -331,7 +332,7 @@ const GrantItem: React.FC<GrantItemProps> = observer(

{/*Report deadlines div*/}
<div className="w-1/2 h-full pl-5">
<label className="flex block tracking-wide text-gray-700 font-bold mb-2 sm:text-sm lg:text-base">
<label className="flex block tracking-wide text-gray-700 font-bold mb-2 text-left sm:text-sm lg:text-base">
Report Deadlines
</label>
<div
Expand Down Expand Up @@ -574,18 +575,19 @@ const GrantItem: React.FC<GrantItemProps> = observer(
borderStyle: "solid",
borderColor: "black",
borderWidth: "1px",
height:"42px"
height: "42px",
}}
className="items-center truncate overflow-x-scroll overflow-hidden text-left justify-center w-full rounded-md p-2 mb-2 bg-tan"
>
<a
href={attachment.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline truncate"
>
{attachment.attachment_name || "Untitled"}
</a>
href={attachment.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline truncate"
>
{attachment.attachment_name ||
"Untitled"}
</a>
</div>
)}
</div>
Expand All @@ -607,29 +609,37 @@ const GrantItem: React.FC<GrantItemProps> = observer(
{/*End two main left right columns */}
</div>

{/*Description*/}
<div className="w-full mb-3">
<label
className="text-lg flex block tracking-wide text-gray-700 font-semibold mb-2"
htmlFor="grid-city"
>
Description
</label>
<div
style={{
color: "black",
borderStyle: "solid",
borderColor: "black",
borderWidth: "1px",
}}
className=" h-32 bg-tan flex w-full rounded-md p-5 overflow-auto"
>
{curGrant.description}
</div>
{/*Cost Benefit Analysis and Description Row*/}
<div className="flex w-full mb-3 space-x-4 items-stretch">
{/* Cost Benefit Analysis */}
<div className="w-1/3 mr-2">
<CostBenefitAnalysis grant={curGrant} />
</div>

{/*Description */}
<div className="w-2/3">
<label
className="text-lg flex block tracking-wide text-gray-700 font-semibold mb-2"
htmlFor="grid-city"
>
Description
</label>
<div
style={{
color: "black",
borderStyle: "solid",
borderColor: "black",
borderWidth: "1px",
}}
className="h-64 bg-tan flex w-full rounded-md p-5 overflow-auto"
>
{curGrant.description}
</div>
</div>
</div>

{/*bottom buttons */}
<div className="flex justify-between items-center w-full mt-6 mb-6">
<div className="flex justify-between items-center w-full mt-6">
<>
<button
style={{
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/main-page/grants/styles/CostBenefitAnalysis.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.cost-benefit-analysis {
width: 100%;
}

.cost-benefit-container {
min-height: 200px;
}

.cost-benefit-analysis input[type="number"] {
appearance: textfield;
-moz-appearance: textfield;
}

.cost-benefit-analysis input[type="number"]::-webkit-outer-spin-button,
.cost-benefit-analysis input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

.cost-benefit-analysis button:hover {
opacity: 0.9;
transition: opacity 0.2s;
}

.cost-benefit-analysis button::-webkit-scrollbar {
display: none; /* Chrome/Safari/Opera */
}
2 changes: 2 additions & 0 deletions frontend/src/styles/notification.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
max-height: 200px;
overflow-y: auto;
margin-top: 10px;
scrollbar-width: none;
-ms-overflow-style: none;
}


Expand Down