diff --git a/frontend/src/custom/RingButton.tsx b/frontend/src/custom/RingButton.tsx index d419f2e..bc3d2f5 100644 --- a/frontend/src/custom/RingButton.tsx +++ b/frontend/src/custom/RingButton.tsx @@ -3,11 +3,13 @@ import "../styles/button.css"; /* Maintained with button.css */ export enum ButtonColorOption { - GREEN, - GRAY, - ORANGE, + GREEN = "#5AB911", + GRAY = "#D3D3D3", + ORANGE = "#F7A781", } + + /* API for components to setup a button with expected system colors */ interface RingButtonProps { /* The text to display for the button */ diff --git a/frontend/src/main-page/grants/filter-bar/FilterBar.tsx b/frontend/src/main-page/grants/filter-bar/FilterBar.tsx index 125650c..a61497c 100644 --- a/frontend/src/main-page/grants/filter-bar/FilterBar.tsx +++ b/frontend/src/main-page/grants/filter-bar/FilterBar.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { Link } from "react-router-dom"; import { Status, - statusToString, + stringToStatus, } from "../../../../../middle-layer/types/Status.ts"; import { updateFilter, @@ -38,7 +38,7 @@ const FilterBar: React.FC = observer(() => { ) { if (!linkTo) { e.preventDefault(); - updateFilter(statusToString(category)); + updateFilter(stringToStatus(category)); setSelected(category); } } diff --git a/frontend/src/main-page/grants/grant-list/GrantItem.tsx b/frontend/src/main-page/grants/grant-list/GrantItem.tsx index 38cd591..1f7e641 100644 --- a/frontend/src/main-page/grants/grant-list/GrantItem.tsx +++ b/frontend/src/main-page/grants/grant-list/GrantItem.tsx @@ -1,7 +1,5 @@ import React, { useEffect, useState } from "react"; import "../styles/GrantItem.css"; -import { GrantAttributes } from "../grant-details/GrantAttributes"; -import GrantDetails from "../grant-details/GrantDetails"; import StatusIndicator from "./StatusIndicator"; import { FaChevronDown, FaChevronRight } from "react-icons/fa"; import { Grant } from "../../../../../middle-layer/types/Grant"; @@ -9,6 +7,9 @@ import { DoesBcanQualifyText } from "../../../translations/general"; import RingButton, { ButtonColorOption } from "../../../custom/RingButton"; import { Status } from "../../../../../middle-layer/types/Status"; import { api } from "../../../api"; +import { MdOutlinePerson2 } from "react-icons/md"; +import Attachment from "../../../../../middle-layer/types/Attachment"; +import NewGrantModal from "../new-grant/NewGrantModal"; interface GrantItemProps { grant: Grant; @@ -19,6 +20,8 @@ const GrantItem: React.FC = ({ grant, defaultExpanded = false }) const [isExpanded, setIsExpanded] = useState(defaultExpanded); const [isEditing, setIsEditing] = useState(false); const [curGrant, setCurGrant] = useState(grant); + const [showNewGrantModal, setShowNewGrantModal] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); // Track whether each custom dropdown is open. const [qualifyDropdownOpen, setQualifyDropdownOpen] = useState(false); @@ -59,13 +62,121 @@ const GrantItem: React.FC = ({ grant, defaultExpanded = false }) setStatusDropdownOpen(false); }; + {/* The popup that appears on delete */} + const DeleteModal = ({ + isOpen, + onCloseDelete, + onConfirmDelete, + title = "Are you sure?", + message = "This action cannot be undone." + }: { + isOpen: boolean; + onCloseDelete: () => void; + onConfirmDelete: () => void; + title?: string; + message?: string; + }) => { + if (!isOpen) return null; + + return ( +
+
e.stopPropagation()} + > + {/* Icon */} +
+
+ + + +
+
+ + {/* Title */} +

+ {title} +

+ + {/* Message */} +

+ {message} +

+ + {/* Buttons */} +
+ + +
+
+
+ ); + }; + + function formatDate(isoString: string): string { + const date = new Date(isoString); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const year = date.getFullYear(); + return `${month}/${day}/${year}`; + } + + function formatCurrency(amount : number): string { + const formattedCurrency = new Intl.NumberFormat('en-US', {style: 'currency',currency: 'USD', + maximumFractionDigits:0}).format(amount); + return formattedCurrency; + } + return ( -
+
-
  • +
  • {isExpanded ? : } {curGrant.organization}
  • @@ -75,7 +186,7 @@ const GrantItem: React.FC = ({ grant, defaultExpanded = false }) : "No date"}
  • - {curGrant.amount ? "$" + curGrant.amount : ""} + {formatCurrency(curGrant.amount)}
  • {isEditing ? ( @@ -167,32 +278,366 @@ const GrantItem: React.FC = ({ grant, defaultExpanded = false }) )}
  • -
    + +
    {isExpanded && ( -
    -

    - Community Development Initiative Grant -

    -
    - - +
    + + {/*div for the two columns above description*/} +
    + + {/*Left column */} +
    + + {/*Organization name (only div in the first row) */} +
    + +
    {curGrant.organization}
    +
    + + {/*Col of gray labels + col of report deadliens (below org name) */} +
    + {/*Left column of gray labels */} +
    + + {/*Application date and grant start date row*/} +
    + {/*Application date*/} +
    + +
    + {formatDate(curGrant.application_deadline)} +
    +
    + {/*Grant Start Date */} +
    + +
    + {curGrant.grant_start_date} +
    +
    + + {/*End application date and grant start date row */} +
    + + {/*Estimated completion time row*/} +
    + +
    + {curGrant.estimated_completion_time + " hours"} +
    +
    + + + {/*End column of gray labels */} +
    + + {/*Report deadlines div*/} +
    + +
    + {/*Map each available report deadline to a div label + If no deadlines, add "No deadlines" text */} + {curGrant.report_deadlines && curGrant.report_deadlines.length > 0 ? ( + curGrant.report_deadlines.map((deadline: string, index: number) => ( +
    + {formatDate(deadline)} +
    + )) + ) : ( +
    No deadlines
    + )} +
    + {/*End report deadlines div*/} +
    + + {/* End row of gray labels (application date, grant start date, estimated completion time) to next of report deadline + report deadline */} +
    + + {/*Timeline and Amount row*/} +
    + {/*Timeline*/} +
    + +
    + {curGrant.timeline + " years"} +
    +
    + {/*Amount */} +
    + +
    + {formatCurrency(curGrant.amount)} +
    +
    + {/*End timeline and amount row */} +
    + + + + {/*End left column */}
    -
    -
    + + {/*Description*/} +
    + +
    + {curGrant.description} +
    +
    + + {/*bottom buttons */} +
    + <> + + + setShowDeleteModal(false)} + onConfirmDelete={() => { + setShowDeleteModal(false); + }} + /> + + +
    + + + + +
    +
    + + {/*End expanded div */} +
    )}
    + +
    + {showNewGrantModal && ( + setShowNewGrantModal(false)} + /> + )} +
    + +
    ); }; diff --git a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx index e9b168f..f267a2d 100644 --- a/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx +++ b/frontend/src/main-page/grants/new-grant/NewGrantModal.tsx @@ -6,9 +6,10 @@ import POCEntry from "./POCEntry"; import { MdOutlinePerson2 } from "react-icons/md"; import { Grant } from "../../../../../middle-layer/types/Grant"; import { TDateISO } from "../../../../../backend/src/utils/date"; -import { Status } from "../../../../../middle-layer/types/Status"; +import { Status, statusToString } from "../../../../../middle-layer/types/Status"; import { api } from "../../../api"; + /** Attachment type from your middle layer */ enum AttachmentType { SCOPE_DOCUMENT = 0, @@ -27,7 +28,13 @@ export interface POCEntryRef { getPOC: () => string; } -const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { +interface NewGrantModalProps { + grant?: Grant; + onClose: () => void; +} + + +const NewGrantModal: React.FC = ({ grant, onClose }) => { /* grantId: number; organization: string; @@ -248,6 +255,20 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { } }; + function formatDate(isoString: string): string { + const date = new Date(isoString); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + + function formatCurrency(amount : number): string { + const formattedCurrency = new Intl.NumberFormat('en-US', {style: 'currency',currency: 'USD', + maximumFractionDigits:0}).format(amount); + return formattedCurrency; + } + return (
    {/*Greyed out background */} @@ -264,7 +285,8 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { Organization Name + className="h-14 block w-full border rounded py-3 px-4 mb-3 leading-tight" id="grid-first-name" + type="text" placeholder = {grant? "" : "Type Here"} defaultValue={grant? grant.organization : ""}/>
    {/*Top left quadrant - from app date, start date, report deadlines, est completion time*/} @@ -278,45 +300,69 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { {/*Application date and input */}
    - +
    {/*Grant Start Date and input */}
    - +
    {/*Estimated completition time and input - need to make wider (length of application date and grant start date)*/}
    + className="h-14 appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500" + id="grid-city" + defaultValue={grant? grant.estimated_completion_time : ""}/>
    - {/*Right column*/} + {/*Report deadlines column*/}
    {/*Report deadlines label and grey box */} -
    +
    -
    - - +
    + {grant?.report_deadlines && grant.report_deadlines.length > 0 ? ( + grant.report_deadlines.map((deadline, index) => ( + + )) + ) : ( + + )} +
    + {/*End report deadline */}
    @@ -326,8 +372,10 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { - +
    {/*Amount label and input */} @@ -336,7 +384,9 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { Amount + className="h-14 appearance-none block w-full bg-gray-200 text-gray-700 border border-red-500 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" + id="grid-first-name" type="text" + placeholder={grant? "" : "Type Here"} defaultValue={grant? formatCurrency(grant.amount) : ""}/>
    @@ -356,9 +406,9 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
    + className="h-14 w-full text-gray-700 rounded" id="grid-city" placeholder="Name" defaultValue={grant?.bcan_poc? grant.bcan_poc.POC_name : ""} /> + className="h-14 w-full text-gray-700 rounded" id="grid-city" placeholder="Email" defaultValue={grant?.bcan_poc? grant.bcan_poc.POC_email : ""} />
    @@ -373,9 +423,9 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
    + className="h-14 w-full text-gray-700 rounded" id="grid-city" placeholder="Name" defaultValue={grant?.grantmaker_poc? grant.grantmaker_poc.POC_name : ""} /> + className="h-14 w-full text-gray-700 rounded" id="grid-city" placeholder="Email" defaultValue={grant?.grantmaker_poc? grant.grantmaker_poc.POC_email : ""} />
    @@ -390,9 +440,17 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { - + id="grid-first-name" + value={grant ? statusToString(grant.status) : ""} + > - - - - - + + + + + @@ -421,9 +481,9 @@ const NewGrantModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { - + className="h-48 block w-full text-gray-700 border rounded py-3 px-4 mb-3 leading-tight" id="grid-first-name" type="text" + placeholder={grant? "" : "Write description here" } defaultValue={grant? grant.description : ""}/>
    diff --git a/frontend/src/main-page/grants/styles/GrantItem.css b/frontend/src/main-page/grants/styles/GrantItem.css index 5108a93..481b068 100644 --- a/frontend/src/main-page/grants/styles/GrantItem.css +++ b/frontend/src/main-page/grants/styles/GrantItem.css @@ -86,6 +86,9 @@ transition: background-color 0.3s ease; } - +.gray-label { + color: black; + background-color: "#D3D3D3"; +} diff --git a/frontend/src/main-page/header/Header.tsx b/frontend/src/main-page/header/Header.tsx index 2478af5..120b94f 100644 --- a/frontend/src/main-page/header/Header.tsx +++ b/frontend/src/main-page/header/Header.tsx @@ -3,7 +3,7 @@ import "./styles/Header.css"; import logo from "../../images/bcan_logo.svg"; import { Status, - statusToString, + stringToStatus, } from "../../../../middle-layer/types/Status.ts"; import { updateFilter, @@ -40,7 +40,7 @@ const Header: React.FC = observer(() => { ) { if (!linkTo) { e.preventDefault(); - updateFilter(statusToString(category)); + updateFilter(stringToStatus(category)); } } diff --git a/frontend/src/styles/button.css b/frontend/src/styles/button.css index 5ecbdbb..f1a0f2e 100644 --- a/frontend/src/styles/button.css +++ b/frontend/src/styles/button.css @@ -48,7 +48,7 @@ } .gray-button { - background-color: #A8A3A3; + background-color: #D3D3D3; } .orange-button { diff --git a/middle-layer/types/Status.ts b/middle-layer/types/Status.ts index 44dbc44..62c2fec 100644 --- a/middle-layer/types/Status.ts +++ b/middle-layer/types/Status.ts @@ -5,7 +5,7 @@ * (3) Inactive: Grant earnings are used up */ export enum Status { - Potential= "Potential", + Potential = "Potential", Active = "Active", Inactive = "Inactive", Rejected = "Rejected", @@ -16,7 +16,7 @@ export enum Status { // 3) turn enums to string // string rep of status -export function statusToString(status: string): Status | null{ +export function stringToStatus(status: string): Status | null{ switch (status) { case 'All': return null; // no filter case 'Active': return Status.Active; @@ -28,6 +28,16 @@ export function statusToString(status: string): Status | null{ } } +export function statusToString(status : Status): string { + switch (status) { + case Status.Active : return 'Active'; + case Status.Inactive : return "Inactive"; + case Status.Potential : return "Potential"; + case Status.Rejected : return "Rejected"; + case Status.Pending : return "Pending"; + } +} + // color associated with status on UI, represented as a string export function getColorStatus(status: string) { switch (status) {