Skip to content

Commit

Permalink
feature: made scorecard component reusable and added the scorecard fi…
Browse files Browse the repository at this point in the history
…eld to the overview table, added scorecard prioritization table in dashboard
  • Loading branch information
onehanddev committed Dec 6, 2024
1 parent 6ea26fc commit d7f1f70
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 107 deletions.
44 changes: 44 additions & 0 deletions client/src/app/(overview)/url-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
INITIAL_ABATEMENT_POTENTIAL_RANGE,
} from "@/containers/overview/filters/constants";
import { TABLE_VIEWS } from "@/containers/overview/table/toolbar/table-selector";
import { PROJECT_SCORE } from "@shared/entities/project-score.enum";

const SUB_ACTIVITIES = RESTORATION_ACTIVITY_SUBTYPE;

Expand All @@ -36,6 +37,18 @@ 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<typeof filtersSchema> = {
keyword: "",
projectSizeFilter: PROJECT_SIZE_FILTER.MEDIUM,
Expand All @@ -49,6 +62,18 @@ export const INITIAL_FILTERS_STATE: z.infer<typeof filtersSchema> = {
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(
Expand All @@ -68,6 +93,25 @@ 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",
Expand Down
21 changes: 21 additions & 0 deletions client/src/components/ui/score-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
interface ScoreIndicatorProps {
score: "high" | "medium" | "low";
className?: string;
children?: React.ReactNode;
}

export const ScoreIndicator = ({ score, children, className = "" }: ScoreIndicatorProps) => {
const bgColorClass = {
high: "bg-high",
medium: "bg-medium",
low: "bg-low",
}[score];

return (
<div
className={`flex h-10 items-center justify-center font-medium text-deep-ocean capitalize ${bgColorClass} ${className}`}
>
{children ? children : score}
</div>
);
};
40 changes: 10 additions & 30 deletions client/src/containers/overview/project-details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { ScoreIndicator } from "@/components/ui/score-card";
import {
Sheet,
SheetContent,
Expand Down Expand Up @@ -63,27 +64,6 @@ const CreateProjectDetails = () => (
</Dialog>
);

interface ScoreIndicatorProps {
score: "High" | "Medium" | "Low";
className?: string;
}

const ScoreIndicator = ({ score, className = "" }: ScoreIndicatorProps) => {
const bgColorClass = {
High: "bg-high",
Medium: "bg-medium",
Low: "bg-low",
}[score];

return (
<div
className={`flex h-10 items-center justify-center font-medium text-deep-ocean ${bgColorClass} ${className}`}
>
{score}
</div>
);
};

//////// Legend component ////////
const Legend = ({
name,
Expand Down Expand Up @@ -117,15 +97,15 @@ const projectData = {
totalRevenue: 40600000,
opExRevenue: 36500000,
abatement: 962991,
overallScore: "Medium",
overallScore: "medium",
scorecard: [
{ name: "Economic feasibility", rating: "High" },
{ name: "Legal feasibility", rating: "Low" },
{ name: "Implementation risk", rating: "High" },
{ name: "Social feasibility", rating: "Medium" },
{ name: "Security risk", rating: "Medium" },
{ name: "Value for money", rating: "Low" },
{ name: "Overall", rating: "Medium" },
{ name: "Economic feasibility", rating: "high" },
{ name: "Legal feasibility", rating: "low" },
{ name: "Implementation risk", rating: "high" },
{ name: "Social feasibility", rating: "medium" },
{ name: "Security risk", rating: "medium" },
{ name: "Value for money", rating: "low" },
{ name: "Overall", rating: "medium" },
],
costEstimates: [
{
Expand Down Expand Up @@ -401,7 +381,7 @@ export default function ProjectDetails() {
<div className="w-2/3 content-center py-2">{item.name}</div>
<ScoreIndicator
className="w-1/3"
score={item.rating as "High" | "Medium" | "Low"}
score={item.rating as "high" | "medium" | "low"}
/>
</div>
{projectData.scorecard.length !== index + 1 && (
Expand Down
17 changes: 12 additions & 5 deletions client/src/containers/overview/table/view/overview/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { z } from "zod";
import { formatNumber } from "@/lib/format";

import { filtersSchema } from "@/app/(overview)/url-store";
import { ScoreIndicator } from "@/components/ui/score-card";

const columnHelper = createColumnHelper<Partial<ProjectType>>();

Expand All @@ -13,11 +14,17 @@ export const columns = (filters: z.infer<typeof filtersSchema>) => [
enableSorting: true,
header: () => <span>Project Name</span>,
}),
// ! omitting until is available in the API
// columnHelper.accessor("scorecard", {
// enableSorting: true,
// header: () => <span>Scorecard rating</span>,
// }),
columnHelper.accessor("scoreCardRating", {
enableSorting: true,
header: () => <span>Scorecard rating</span>,
cell: (props) => {
const value = props.getValue();
if (value === undefined) {
return "-";
}
return <ScoreIndicator score={value} />;
},
}),
columnHelper.accessor(
filters.costRangeSelector === "npv" ? "costPerTCO2eNPV" : "costPerTCO2e",
{
Expand Down
20 changes: 14 additions & 6 deletions client/src/containers/overview/table/view/overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,21 @@ export function OverviewTable() {
}}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
<TableCell
key={cell.id}
className={cn({
"p-0": cell.column.id === "scoreCardRating",
})}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,96 @@
// import { ProjectType } from "@shared/contracts/projects.contract";
// import { createColumnHelper } from "@tanstack/react-table";
// const columnHelper = createColumnHelper<
// Partial<Project> & {
// // ! these types should be part of the Project entity eventually, we are adding them here to silent TS for now
// // financialFeasibility: number;
// // legalFeasibility: number;
// // implementationFeasibility: number;
// // socialFeasibility: number;
// // securityRating: number;
// // availabilityOfExperiencedLabor: number;
// // availabilityOfAlternatingFunding: number;
// // coastalProtectionBenefit: number;
// // biodiversityBenefit: number;
// }
// >();
import { createColumnHelper } from "@tanstack/react-table";
import { ScoreIndicator } from "@/components/ui/score-card";
import { ProjectScorecard } from "@shared/entities/project-scorecard.entity";

export const TABLE_COLUMNS = [];
const columnHelper = createColumnHelper<
Partial<ProjectScorecard> & {
financialFeasibility: number;
legalFeasibility: number;
implementationFeasibility: number;
socialFeasibility: number;
securityRating: number;
availabilityOfExperiencedLabor: number;
availabilityOfAlternatingFunding: number;
coastalProtectionBenefit: number;
biodiversityBenefit: number;
}
>();

// export const TABLE_COLUMNS = [
// columnHelper.accessor("projectName", {
// enableSorting: true,
// header: () => <span>Project Name</span>,
// }),
// columnHelper.accessor("financialFeasibility", {
// enableSorting: true,
// header: () => <span>Financial feasibility</span>,
// }),
// columnHelper.accessor("legalFeasibility", {
// enableSorting: true,
// header: () => <span>Legal Feasibility</span>,
// }),
// columnHelper.accessor("implementationFeasibility", {
// enableSorting: true,
// header: () => <span>Implementation feasibility</span>,
// }),
// columnHelper.accessor("socialFeasibility", {
// enableSorting: true,
// header: () => <span>Social Feasibility</span>,
// }),
// columnHelper.accessor("securityRating", {
// enableSorting: true,
// header: () => <span>Security Rating</span>,
// }),
// columnHelper.accessor("availabilityOfExperiencedLabor", {
// enableSorting: true,
// header: () => <span>Availability of experienced labor</span>,
// }),
// columnHelper.accessor("availabilityOfAlternatingFunding", {
// enableSorting: true,
// header: () => <span>Availability of alternating funding</span>,
// }),
// columnHelper.accessor("coastalProtectionBenefit", {
// enableSorting: true,
// header: () => <span>Coastal Protection benefit</span>,
// }),
// columnHelper.accessor("biodiversityBenefit", {
// enableSorting: true,
// header: () => <span>Biodiversity benefit</span>,
// }),
// ];
export const TABLE_COLUMNS = [
columnHelper.accessor("projectName", {
enableSorting: true,
header: () => <span>Project Name</span>,
}),
columnHelper.accessor("financialFeasibility", {
enableSorting: true,
header: () => <span>Financial feasibility</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("legalFeasibility", {
enableSorting: true,
header: () => <span>Legal Feasibility</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("implementationFeasibility", {
enableSorting: true,
header: () => <span>Implementation feasibility</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("socialFeasibility", {
enableSorting: true,
header: () => <span>Social Feasibility</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("securityRating", {
enableSorting: true,
header: () => <span>Security Rating</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("availabilityOfExperiencedLabor", {
enableSorting: true,
header: () => <span>Availability of experienced labor</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("availabilityOfAlternatingFunding", {
enableSorting: true,
header: () => <span>Availability of alternating funding</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("coastalProtectionBenefit", {
enableSorting: true,
header: () => <span>Coastal Protection benefit</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
columnHelper.accessor("biodiversityBenefit", {
enableSorting: true,
header: () => <span>Biodiversity benefit</span>,
cell: (props) => {
const value = props.getValue();
return <ScoreIndicator score={value as "high" | "medium" | "low"} />;
},
}),
];
Loading

0 comments on commit d7f1f70

Please sign in to comment.