diff --git a/client/src/app/(overview)/url-store.ts b/client/src/app/(overview)/url-store.ts index 7f5f1281..7deef6d9 100644 --- a/client/src/app/(overview)/url-store.ts +++ b/client/src/app/(overview)/url-store.ts @@ -3,7 +3,6 @@ import { RESTORATION_ACTIVITY_SUBTYPE, } from "@shared/entities/activity.enum"; import { ECOSYSTEM } from "@shared/entities/ecosystem.enum"; -import { PROJECT_SCORE } from "@shared/entities/project-score.enum"; import { COST_TYPE_SELECTOR, PROJECT_PRICE_TYPE, @@ -37,18 +36,6 @@ export const filtersSchema = z.object({ [FILTER_KEYS[9]]: z.array(z.number()).length(2), }); -export const scorecardFiltersSchema = z.object({ - availabilityOfExperiencedLabor: z.nativeEnum(PROJECT_SCORE).optional(), - availabilityOfAlternatingFunding: z.nativeEnum(PROJECT_SCORE).optional(), - coastalProtectionBenefits: z.nativeEnum(PROJECT_SCORE).optional(), - biodiversityBenefit: z.nativeEnum(PROJECT_SCORE).optional(), - financialFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), - legalFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), - implementationFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), - socialFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), - securityRating: z.nativeEnum(PROJECT_SCORE).optional(), -}); - export const INITIAL_FILTERS_STATE: z.infer = { keyword: "", projectSizeFilter: PROJECT_SIZE_FILTER.MEDIUM, @@ -62,20 +49,6 @@ export const INITIAL_FILTERS_STATE: z.infer = { abatementPotentialRange: INITIAL_ABATEMENT_POTENTIAL_RANGE, }; -export const INITIAL_SCORECARD_FILTERS_STATE: z.infer< - typeof scorecardFiltersSchema -> = { - availabilityOfExperiencedLabor: undefined, - availabilityOfAlternatingFunding: undefined, - coastalProtectionBenefits: undefined, - biodiversityBenefit: undefined, - financialFeasibility: undefined, - legalFeasibility: undefined, - implementationFeasibility: undefined, - socialFeasibility: undefined, - securityRating: undefined, -}; - export function useGlobalFilters() { const [popup, setPopup] = useAtom(popupAtom); const [filters, setFilters] = useQueryState( @@ -95,27 +68,6 @@ export function useGlobalFilters() { return [filters, updateFilters] as const; } -export function useScorecardFilters() { - const [popup, setPopup] = useAtom(popupAtom); - const [filters, setFilters] = useQueryState( - "scorecardFilters", - parseAsJson(scorecardFiltersSchema.parse).withDefault( - INITIAL_SCORECARD_FILTERS_STATE, - ), - ); - - const updateFilters = async ( - updater: ( - prev: typeof INITIAL_SCORECARD_FILTERS_STATE, - ) => typeof INITIAL_SCORECARD_FILTERS_STATE, - ) => { - await setFilters(updater); - if (popup) setPopup(null); - }; - - return [filters, updateFilters] as const; -} - export function useTableView() { return useQueryState( "table", diff --git a/client/src/components/ui/score-card.tsx b/client/src/components/ui/score-card.tsx index 8ca158fe..fa469504 100644 --- a/client/src/components/ui/score-card.tsx +++ b/client/src/components/ui/score-card.tsx @@ -1,27 +1,31 @@ -export type Score = "high" | "medium" | "low"; +import { PROJECT_SCORE } from "@shared/entities/project-score.enum"; + +import { cn } from "@/lib/utils"; interface ScoreIndicatorProps { - score: Score; className?: string; children?: React.ReactNode; + bgColorClasses?: Record; } +export const DEFAULT_BG_CLASSES = { + high: "bg-high", + medium: "bg-medium", + low: "bg-low", +}; + export const ScoreIndicator = ({ - score, children, className = "", }: ScoreIndicatorProps) => { - const bgColorClass = { - high: "bg-high", - medium: "bg-medium", - low: "bg-low", - }[score]; - return (
- {children ? children : score} + {children}
); }; diff --git a/client/src/containers/overview/filters/index.tsx b/client/src/containers/overview/filters/index.tsx index 69574bfe..4ec22527 100644 --- a/client/src/containers/overview/filters/index.tsx +++ b/client/src/containers/overview/filters/index.tsx @@ -76,7 +76,7 @@ export default function ProjectsFilters() { setFiltersOpen((prev) => ({ ...prev, filtersOpen: false })); }; - const { queryKey } = queryKeys.projects.countries; + const { queryKey } = queryKeys.tables.countries; const { data: countryOptions } = client.projects.getProjectCountries.useQuery( queryKey, {}, diff --git a/client/src/containers/overview/project-details/index.tsx b/client/src/containers/overview/project-details/index.tsx index bd2f8e3b..54e5400b 100644 --- a/client/src/containers/overview/project-details/index.tsx +++ b/client/src/containers/overview/project-details/index.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; +import { PROJECT_SCORE } from "@shared/entities/project-score.enum"; import { useAtom } from "jotai"; import { ChevronUp, ChevronDown, NotebookPen } from "lucide-react"; @@ -8,6 +9,7 @@ import { formatCurrency, renderAbatementCurrency, } from "@/lib/format"; +import { cn } from "@/lib/utils"; import { projectDetailsAtom } from "@/app/(overview)/store"; @@ -27,7 +29,7 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; -import { ScoreIndicator } from "@/components/ui/score-card"; +import { ScoreIndicator, DEFAULT_BG_CLASSES } from "@/components/ui/score-card"; import { Sheet, SheetContent, @@ -380,9 +382,13 @@ export default function ProjectDetails() {
{item.name}
+ className={cn( + "w-1/3", + DEFAULT_BG_CLASSES[item.rating as PROJECT_SCORE], + )} + > + {item.rating} +
{projectData.scorecard.length !== index + 1 && (
diff --git a/client/src/containers/overview/table/utils.ts b/client/src/containers/overview/table/utils.ts index 817d34fe..7c351913 100644 --- a/client/src/containers/overview/table/utils.ts +++ b/client/src/containers/overview/table/utils.ts @@ -1,14 +1,10 @@ -import { ProjectScorecardView } from "@shared/entities/project-scorecard.view"; -import { Project } from "@shared/entities/projects.entity"; import { z } from "zod"; -import { - filtersSchema, - scorecardFiltersSchema, -} from "@/app/(overview)/url-store"; +import { filtersSchema } from "@/app/(overview)/url-store"; -export const NO_DATA: Project[] = []; -export const NO_SCORECARD_DATA: ProjectScorecardView[] = []; +import { scorecardFiltersSchema } from "./view/scorecard-prioritization/schema"; + +export const NO_DATA = []; const OMITTED_FIELDS = [ "keyword", @@ -20,7 +16,8 @@ const OMITTED_FIELDS = [ export const filtersToQueryParams = ( filters: z.infer, ) => { - return Object.keys(filters) + debugger; + const queryParams = Object.keys(filters) .filter((key) => !OMITTED_FIELDS.includes(key)) .reduce( (acc, key) => ({ @@ -31,4 +28,5 @@ export const filtersToQueryParams = ( }), {}, ); + return queryParams; }; diff --git a/client/src/containers/overview/table/view/key-costs/index.tsx b/client/src/containers/overview/table/view/key-costs/index.tsx index b359b744..2c5bc46d 100644 --- a/client/src/containers/overview/table/view/key-costs/index.tsx +++ b/client/src/containers/overview/table/view/key-costs/index.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons"; +import { projectsQuerySchema } from "@shared/contracts/projects.contract"; import { keepPreviousData } from "@tanstack/react-query"; import { flexRender, @@ -51,7 +52,7 @@ export function KeyCostsTable() { pageSize: Number.parseInt(PAGINATION_SIZE_OPTIONS[0]), }); - const queryKey = queryKeys.projects.all(tableView, { + const queryKey = queryKeys.tables.all(tableView, projectsQuerySchema, { ...filters, sorting, pagination, diff --git a/client/src/containers/overview/table/view/overview/columns.tsx b/client/src/containers/overview/table/view/overview/columns.tsx index 70b6902d..e11cb5cc 100644 --- a/client/src/containers/overview/table/view/overview/columns.tsx +++ b/client/src/containers/overview/table/view/overview/columns.tsx @@ -1,4 +1,5 @@ import { ProjectType } from "@shared/contracts/projects.contract"; +import { PROJECT_SCORE } from "@shared/entities/project-score.enum"; import { createColumnHelper } from "@tanstack/react-table"; import { z } from "zod"; @@ -6,7 +7,7 @@ import { formatNumber } from "@/lib/format"; import { filtersSchema } from "@/app/(overview)/url-store"; -import { ScoreIndicator } from "@/components/ui/score-card"; +import { DEFAULT_BG_CLASSES, ScoreIndicator } from "@/components/ui/score-card"; const columnHelper = createColumnHelper>(); @@ -23,7 +24,11 @@ export const columns = (filters: z.infer) => [ if (value === undefined) { return "-"; } - return ; + return ( + + {value} + + ); }, }), columnHelper.accessor( diff --git a/client/src/containers/overview/table/view/overview/index.tsx b/client/src/containers/overview/table/view/overview/index.tsx index 77ac5731..12daf0d1 100644 --- a/client/src/containers/overview/table/view/overview/index.tsx +++ b/client/src/containers/overview/table/view/overview/index.tsx @@ -60,7 +60,7 @@ export function OverviewTable() { pageSize: Number.parseInt(PAGINATION_SIZE_OPTIONS[0]), }); - const queryKey = queryKeys.projects.all(tableView, { + const queryKey = queryKeys.tables.all(tableView, projectsQuerySchema, { ...filters, sorting, pagination, diff --git a/client/src/containers/overview/table/view/scorecard-prioritization/columns.tsx b/client/src/containers/overview/table/view/scorecard-prioritization/columns.tsx index 8cf1b515..ddf0e834 100644 --- a/client/src/containers/overview/table/view/scorecard-prioritization/columns.tsx +++ b/client/src/containers/overview/table/view/scorecard-prioritization/columns.tsx @@ -1,8 +1,10 @@ +import { PROJECT_SCORE } from "@shared/entities/project-score.enum"; import { ProjectScorecardView } from "@shared/entities/project-scorecard.view"; import { createColumnHelper } from "@tanstack/react-table"; -import { Score } from "@/components/ui/score-card"; -import { ScoreIndicator } from "@/components/ui/score-card"; +import { cn } from "@/lib/utils"; + +import { ScoreIndicator, DEFAULT_BG_CLASSES } from "@/components/ui/score-card"; const columnHelper = createColumnHelper(); @@ -14,46 +16,145 @@ export const TABLE_COLUMNS = [ columnHelper.accessor("financialFeasibility", { enableSorting: true, header: () => Financial feasibility, - cell: (props) => , + cell: (props) => ( + + {props.row.original.financialFeasibility} + + ), }), columnHelper.accessor("legalFeasibility", { enableSorting: true, header: () => Legal Feasibility, - cell: (props) => , + cell: (props) => ( + + {props.row.original.legalFeasibility} + + ), }), columnHelper.accessor("implementationFeasibility", { enableSorting: true, header: () => Implementation feasibility, - cell: (props) => , + cell: (props) => ( + + {props.row.original.implementationFeasibility} + + ), }), columnHelper.accessor("socialFeasibility", { enableSorting: true, header: () => Social Feasibility, - cell: (props) => , + cell: (props) => ( + + {props.row.original.socialFeasibility} + + ), }), columnHelper.accessor("securityRating", { enableSorting: true, header: () => Security Rating, - cell: (props) => , + cell: (props) => ( + + {props.row.original.securityRating} + + ), }), columnHelper.accessor("availabilityOfExperiencedLabor", { enableSorting: true, header: () => Availability of experienced labor, - cell: (props) => , + cell: (props) => ( + + {props.row.original.availabilityOfExperiencedLabor} + + ), }), columnHelper.accessor("availabilityOfAlternatingFunding", { enableSorting: true, header: () => Availability of alternating funding, - cell: (props) => , + cell: (props) => ( + + {props.row.original.availabilityOfAlternatingFunding} + + ), }), columnHelper.accessor("coastalProtectionBenefits", { enableSorting: true, header: () => Coastal Protection benefit, - cell: (props) => , + cell: (props) => ( + + {props.row.original.coastalProtectionBenefits} + + ), }), columnHelper.accessor("biodiversityBenefit", { enableSorting: true, header: () => Biodiversity benefit, - cell: (props) => , + cell: (props) => ( + + {props.row.original.biodiversityBenefit} + + ), }), ]; diff --git a/client/src/containers/overview/table/view/scorecard-prioritization/index.tsx b/client/src/containers/overview/table/view/scorecard-prioritization/index.tsx index 8b6570a6..5c120ae1 100644 --- a/client/src/containers/overview/table/view/scorecard-prioritization/index.tsx +++ b/client/src/containers/overview/table/view/scorecard-prioritization/index.tsx @@ -12,17 +12,20 @@ import { SortingState, useReactTable, } from "@tanstack/react-table"; +import { useAtom } from "jotai"; import { ChevronsUpDownIcon } from "lucide-react"; import { client } from "@/lib/query-client"; import { queryKeys } from "@/lib/query-keys"; import { cn } from "@/lib/utils"; -import { useScorecardFilters, useTableView } from "@/app/(overview)/url-store"; +import { projectDetailsAtom } from "@/app/(overview)/store"; +import { useGlobalFilters, useTableView } from "@/app/(overview)/url-store"; +import ProjectDetails from "@/containers/overview/project-details"; import { filtersToQueryParams, - NO_SCORECARD_DATA, + NO_DATA, } from "@/containers/overview/table/utils"; import { TABLE_COLUMNS } from "@/containers/overview/table/view/scorecard-prioritization/columns"; @@ -38,9 +41,12 @@ import TablePagination, { PAGINATION_SIZE_OPTIONS, } from "@/components/ui/table-pagination"; +import { scorecardFiltersSchema } from "./schema"; + export function ScoredCardPrioritizationTable() { const [tableView] = useTableView(); - const [filters] = useScorecardFilters(); + const [filters] = useGlobalFilters(); + const [, setProjectDetails] = useAtom(projectDetailsAtom); const [sorting, setSorting] = useState([ { id: "projectName", @@ -51,8 +57,8 @@ export function ScoredCardPrioritizationTable() { pageIndex: 0, pageSize: Number.parseInt(PAGINATION_SIZE_OPTIONS[0]), }); - const queryKey = queryKeys.scorecardFilters.all(tableView, { - ...filters, + const queryKey = queryKeys.tables.all(tableView, scorecardFiltersSchema, { + filters, sorting, pagination, }).queryKey; @@ -61,8 +67,7 @@ export function ScoredCardPrioritizationTable() { queryKey, { query: { - ...filtersToQueryParams(filters), - filter: {}, + filter: filtersToQueryParams(filters), pageNumber: pagination.pageIndex + 1, pageSize: pagination.pageSize, }, @@ -78,7 +83,7 @@ export function ScoredCardPrioritizationTable() { ); const table = useReactTable({ - data: isSuccess ? data.data : NO_SCORECARD_DATA, + data: isSuccess ? data.data : (NO_DATA as ProjectScorecardView[]), columns: TABLE_COLUMNS, getCoreRowModel: getCoreRowModel(), manualPagination: true, @@ -141,9 +146,21 @@ export function ScoredCardPrioritizationTable() { { + setProjectDetails({ + isOpen: true, + projectName: row.original.projectName ?? "", + }); + }} > {row.getVisibleCells().map((cell) => ( - + {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} @@ -166,6 +183,7 @@ export function ScoredCardPrioritizationTable() { totalItems: data?.metadata?.totalItems ?? 0, }} /> + ); } diff --git a/client/src/containers/overview/table/view/scorecard-prioritization/schema.ts b/client/src/containers/overview/table/view/scorecard-prioritization/schema.ts new file mode 100644 index 00000000..0f596461 --- /dev/null +++ b/client/src/containers/overview/table/view/scorecard-prioritization/schema.ts @@ -0,0 +1,14 @@ +import { PROJECT_SCORE } from "@shared/entities/project-score.enum"; +import { z } from "zod"; + +export const scorecardFiltersSchema = z.object({ + availabilityOfExperiencedLabor: z.nativeEnum(PROJECT_SCORE).optional(), + availabilityOfAlternatingFunding: z.nativeEnum(PROJECT_SCORE).optional(), + coastalProtectionBenefits: z.nativeEnum(PROJECT_SCORE).optional(), + biodiversityBenefit: z.nativeEnum(PROJECT_SCORE).optional(), + financialFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), + legalFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), + implementationFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), + socialFeasibility: z.nativeEnum(PROJECT_SCORE).optional(), + securityRating: z.nativeEnum(PROJECT_SCORE).optional(), +}); diff --git a/client/src/lib/query-keys.ts b/client/src/lib/query-keys.ts index 8aee773b..3727a3d9 100644 --- a/client/src/lib/query-keys.ts +++ b/client/src/lib/query-keys.ts @@ -5,10 +5,7 @@ import { import { PaginationState, SortingState } from "@tanstack/react-table"; import { z } from "zod"; -import { - filtersSchema, - scorecardFiltersSchema, -} from "@/app/(overview)/url-store"; +import { filtersSchema } from "@/app/(overview)/url-store"; import { TABLE_VIEWS } from "@/containers/overview/table/toolbar/table-selector"; @@ -25,22 +22,11 @@ export const geometriesKeys = createQueryKeys("geometries", { all: (filters: z.infer) => [filters], }); -export const projectKeys = createQueryKeys("projects", { - all: ( - tableView: (typeof TABLE_VIEWS)[number], - filters?: z.infer & { - sorting?: SortingState; - pagination?: PaginationState; - }, - ) => ["all", tableView, filters], - id: (id: string) => [id], - countries: null, -}); - -export const scorecardFiltersKeys = createQueryKeys("scorecardFilters", { - all: ( +export const tableKeys = createQueryKeys("tables", { + all: ( tableView: (typeof TABLE_VIEWS)[number], - filters?: z.infer & { + schema: TFilters, + filters?: z.infer & { sorting?: SortingState; pagination?: PaginationState; }, @@ -53,6 +39,5 @@ export const queryKeys = mergeQueryKeys( authKeys, userKeys, geometriesKeys, - projectKeys, - scorecardFiltersKeys, + tableKeys, );