From f7ad0f7428ccaea69d539337f93a1f0a803b2550 Mon Sep 17 00:00:00 2001 From: isaacbello Date: Wed, 4 Sep 2024 12:03:12 +0100 Subject: [PATCH 01/98] Worked on the manual input validation --- .../backend/ManualnputValidationService.ts | 54 ++++-- .../activity-modal/activity-form-modal.tsx | 30 ++-- .../activity-modal/activity-modal-body.tsx | 5 +- app/src/components/MultiSelectInput.tsx | 167 ++++++++++++++++++ app/src/components/building-select-input.tsx | 24 ++- .../activity-value-form/use-activity-form.ts | 26 ++- 6 files changed, 276 insertions(+), 30 deletions(-) create mode 100644 app/src/components/MultiSelectInput.tsx diff --git a/app/src/backend/ManualnputValidationService.ts b/app/src/backend/ManualnputValidationService.ts index 6e9678924..2beab8611 100644 --- a/app/src/backend/ManualnputValidationService.ts +++ b/app/src/backend/ManualnputValidationService.ts @@ -159,12 +159,24 @@ export default class ManualInputValidationService { }) { let duplicateFields: string[] = []; - // using the LOWER function to make the comparison case insensitive - const conditions = uniqueBy.map((field) => - where(fn("lower", literal(`activity_data_jsonb->>'${field}'`)), { - [Op.eq]: activityValueParams.activityData?.[field].toLowerCase(), - }), - ); + const conditions = uniqueBy.map((field) => { + const value = activityValueParams.activityData?.[field]; + + if (Array.isArray(value)) { + // we don't need to do case-insesitive checks for the multi-select since the values are controlled + return where( + literal( + `activity_data_jsonb->'${field}' ?| array[${value.map((v) => `'${v}'`).join(",")}]`, + ), + true, + ); + } else { + // using the LOWER function to make the comparison case-insensitive + return where(fn("lower", literal(`activity_data_jsonb->>'${field}'`)), { + [Op.eq]: value.toLowerCase(), + }); + } + }); if (activityValueId) { conditions.push(where(col("id"), { [Op.ne]: activityValueId })); @@ -182,18 +194,28 @@ export default class ManualInputValidationService { }); if (existingRecord) { - duplicateFields = uniqueBy.filter( - (field) => - existingRecord.activityData?.[field].toLowerCase() === - activityValueParams.activityData?.[field].toLowerCase(), - ); + duplicateFields = uniqueBy.filter((field) => { + const existingValue = existingRecord.activityData?.[field]; + const newValue = activityValueParams.activityData?.[field]; - const errorBody = { - code: ManualInputValidationErrorCodes.UNIQUE_BY_CONFLICT, - targetFields: duplicateFields, - }; + if (Array.isArray(existingValue) && Array.isArray(newValue)) { + // For arrays, check for overlap without case sensitivity + return existingValue.some((item) => newValue.includes(item)); + } else { + // For single values (strings), do a case-insensitive comparison + return existingValue?.toLowerCase() === newValue?.toLowerCase(); + } + }); - throw new ManualInputValidationError(errorBody); + // If duplicate fields are found, throw an error + if (duplicateFields.length > 0) { + const errorBody = { + code: ManualInputValidationErrorCodes.UNIQUE_BY_CONFLICT, + targetFields: duplicateFields, + }; + + throw new ManualInputValidationError(errorBody); + } } } diff --git a/app/src/components/Modals/activity-modal/activity-form-modal.tsx b/app/src/components/Modals/activity-modal/activity-form-modal.tsx index 8e675fe71..32d354756 100644 --- a/app/src/components/Modals/activity-modal/activity-form-modal.tsx +++ b/app/src/components/Modals/activity-modal/activity-form-modal.tsx @@ -63,22 +63,30 @@ const AddActivityModal: FC = ({ targetActivityValue, resetSelectedActivityValue, }) => { - const { setError, setFocus, reset, handleSubmit, register, errors } = + const { fields, units } = useMemo(() => { + let fields: ExtraField[] = []; + let units = null; + if (methodology?.id.includes("direct-measure")) { + fields = methodology.fields; + } else { + fields = methodology?.fields[0]["extra-fields"]; + units = methodology?.fields[0].units; + } + + return { + fields, + units, + }; + }, [methodology]); + + const { setError, setFocus, reset, handleSubmit, register, errors, control } = useActivityForm({ targetActivityValue, selectedActivity, methodologyName: methodology?.id, + fields, }); - let fields: ExtraField[] = []; - let units = null; - if (methodology?.id.includes("direct-measure")) { - fields = methodology.fields; - } else { - fields = methodology?.fields[0]["extra-fields"]; - units = methodology?.fields[0].units; - } - const { handleManalInputValidationError } = useActivityValueValidation({ t, setError, @@ -270,6 +278,7 @@ const AddActivityModal: FC = ({ reset({ activity: generateDefaultActivityFormValues( selectedActivity as SuggestedActivity, + fields, ), }); }; @@ -307,6 +316,7 @@ const AddActivityModal: FC = ({ emissionsFactorTypes={emissionsFactorTypes} submit={submit} register={register} + control={control} fields={fields} units={units} methodology={methodology} diff --git a/app/src/components/Modals/activity-modal/activity-modal-body.tsx b/app/src/components/Modals/activity-modal/activity-modal-body.tsx index f2438c1f0..08ce232ef 100644 --- a/app/src/components/Modals/activity-modal/activity-modal-body.tsx +++ b/app/src/components/Modals/activity-modal/activity-modal-body.tsx @@ -19,7 +19,7 @@ import { useState } from "react"; import BuildingTypeSelectInput from "../../building-select-input"; import { InfoOutlineIcon, WarningIcon } from "@chakra-ui/icons"; import { TFunction } from "i18next"; -import { UseFormRegister } from "react-hook-form"; +import { Control, UseFormRegister } from "react-hook-form"; import type { DirectMeasureData, SubcategoryData, @@ -35,6 +35,7 @@ export type EmissionFactorTypes = { interface AddActivityModalBodyProps { t: TFunction; register: UseFormRegister; + control: Control; submit: () => void; fields: ExtraField[]; units: string[]; @@ -79,6 +80,7 @@ export type ExtraField = { const ActivityModalBody = ({ t, register, + control, submit, methodology, emissionsFactorTypes, @@ -121,6 +123,7 @@ const ActivityModalBody = ({ ; + t: TFunction; + multiselect?: boolean; + required?: boolean; + control: Control; +} + +const CustomMultiValue = (props: MultiValueProps) => { + const { data, removeProps } = props; + + return ( + + + {data.label} + + + + ); +}; + +const CustomOption = (props: OptionProps) => { + const { data, isSelected, innerRef, innerProps } = props; + + return ( + + + + {data.label} + + + ); +}; + +const customStyles = (error: boolean) => ({ + control: (provided: any, state: any) => ({ + ...provided, + borderRadius: "4px", + borderColor: + state.isHovered || state.isFocused + ? "#2351DC" + : error + ? "#F23D33" + : "#D7D8FB", + background: error ? "#FFEAEE" : "#fff", + boxShadow: "0px 1px 2px -1px #0000001A, 0px 1px 3px 0px #00001F1A", + minHeight: "48px", + fontSize: "16px", + "&:focus": { + borderColor: "#2351DC", + }, + }), + placeholder: (provided) => ({ + ...provided, + fontSize: "16px", + color: "#A0AEC0", + }), +}); + +const MultiSelectWithCheckbox = ({ + title, + options, + placeholder, + required, + activity, + errors, + t, + control, +}: MultiSelectInputProps) => { + const error = activity.split(".").reduce((acc, key) => acc?.[key], errors); + return ( + + + {t(title)} + + { + return ( + + {["by-sub-sector"].map((grouping) => ( + + ))} + + ; +}; + + +const EmissionsTable = ({ topEmissions }: { topEmissions: TopEmission[] }) => { + return ( + + + + + + + + + + + {(topEmissions || []).map((emission, index) => ( + + + + + + ))} + +
SubsectorTotal Emissions (CO2eq)% of Emissions
+ {emission.subsectorName} + {emission.sectorName} + {convertKgToTonnes(emission.co2eq)}{emission.percentage}%
+
+ ); +}; + + +const TopEmissionsWidget = ({ + t, + inventory + }: { + t: Function & TFunction<"translation", undefined>; + inventory?: InventoryResponse; +}) => { + + const { data: results, isLoading: isTopEmissionsResponseLoading } = + api.useGetResultsQuery(inventory!.inventoryId!); + + function getPercentagesForProgress(): SegmentedProgressValues[] { + // @ts-ignore + const grouped = (groupBy(results?.totalEmissions.bySector, e => e.sectorName)) as Record; + const out = Object.entries(grouped || {}).map(([name, [{ co2eq, percentage }]]) => { + return { + name, + value: co2eq, + percentage: percentage + } as SegmentedProgressValues; + }); + return out; + + } + + return ( + + + {isTopEmissionsResponseLoading + ?
+ : <> + + + + + } +
+
+ ); +}; + +export default TopEmissionsWidget; \ No newline at end of file diff --git a/app/src/app/[lng]/[inventory]/InventoryResultTab/index.tsx b/app/src/app/[lng]/[inventory]/InventoryResultTab/index.tsx new file mode 100644 index 000000000..a8e436b8b --- /dev/null +++ b/app/src/app/[lng]/[inventory]/InventoryResultTab/index.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useTranslation } from "@/i18n/client"; +import { InventoryProgressResponse, InventoryResponse } from "@/util/types"; +import { Box, HStack } from "@chakra-ui/react"; +import { TabHeader } from "@/app/[lng]/[inventory]/TabHeader"; +import EmissionsWidget from "@/app/[lng]/[inventory]/InventoryResultTab/EmissionsWidget"; +import TopEmissionsWidget from "@/app/[lng]/[inventory]/InventoryResultTab/TopEmissionsWidget"; + + +export default function InventoryResultTab({ + lng, + inventory, + isUserInfoLoading, + isInventoryProgressLoading, + inventoryProgress + }: { + lng: string; + inventory?: InventoryResponse; + isUserInfoLoading?: boolean; + isInventoryProgressLoading?: boolean; + inventoryProgress?: InventoryProgressResponse; +}) { + const { t } = useTranslation(lng, "dashboard"); + return ( + <> + {inventory && ( + + + + + + + + )} + + ); +} diff --git a/app/src/app/api/v0/inventory/[inventory]/results/route.ts b/app/src/app/api/v0/inventory/[inventory]/results/route.ts new file mode 100644 index 000000000..71b2ea1f7 --- /dev/null +++ b/app/src/app/api/v0/inventory/[inventory]/results/route.ts @@ -0,0 +1,39 @@ +import UserService from "@/backend/UserService"; +import { db } from "@/models"; +import { apiHandler } from "@/util/api"; +import { NextResponse } from "next/server"; +import { getEmissionResults } from "@/backend/ResultsService"; +import sumBy from "lodash/sumBy"; + + +export const GET = apiHandler(async (_req, { session, params: { inventory } }) => { + // ensure inventory belongs to user + await UserService.findUserInventory( + inventory, + session, + [ + { + model: db.models.InventoryValue, + as: "inventoryValues", + include: [ + { + model: db.models.DataSource, + attributes: ["datasourceId", "sourceType"], + as: "dataSource" + } + ] + } + ] + ); + + const { totalEmissionsBySector, topEmissionsBySubSector } = await getEmissionResults(inventory); + return NextResponse.json({ + data: { + totalEmissions: { + bySector: totalEmissionsBySector, + total: sumBy(totalEmissionsBySector, e => Number(e.co2eq)) + }, + topEmissions: { bySubSector: topEmissionsBySubSector } + } + }); +}); diff --git a/app/src/backend/ResultsService.ts b/app/src/backend/ResultsService.ts new file mode 100644 index 000000000..3a84531f0 --- /dev/null +++ b/app/src/backend/ResultsService.ts @@ -0,0 +1,76 @@ +import { db } from "@/models"; +import { QueryTypes } from "sequelize"; +import sumBy from "lodash/sumBy"; + +function calculatePercentage(co2eq: bigint, total: bigint): number { + if (total <= 0n) { + return 0; + } + const co2eqFloat = Number(co2eq); + const totalFloat = Number(total); + return Number(Number((co2eqFloat * 100 / totalFloat)).toFixed(0)); +} + +async function getTotalEmissionsWithPercentage(inventory: string) { + const rawQuery = ` + SELECT SUM(av.co2eq) AS co2eq, sector_name + FROM "ActivityValue" av + JOIN "InventoryValue" iv ON av.inventory_value_id = iv.id + JOIN "Sector" s ON iv.sector_id = s.sector_id + WHERE inventory_id = :inventoryId + GROUP BY sector_name + ORDER BY SUM(av.co2eq) DESC`; + + const totalEmissionsBySector: { + "co2eq": bigint, + "sector_name": string + }[] = await db.sequelize!.query(rawQuery, { + replacements: { inventoryId: inventory }, + type: QueryTypes.SELECT + }); + + const sumOfEmissions = BigInt(sumBy(totalEmissionsBySector, e => Number(e.co2eq))); + + return totalEmissionsBySector.map(({ co2eq, sector_name }) => ({ + sectorName: sector_name, + co2eq, + percentage: calculatePercentage(co2eq, sumOfEmissions) + })); +} + +function getTopEmissions(inventoryId: string) { + const rawQuery = ` + SELECT av.co2eq, sector_name, subsector_name + FROM "ActivityValue" av + JOIN "InventoryValue" iv ON av.inventory_value_id = iv.id + JOIN "Sector" s ON iv.sector_id = s.sector_id + JOIN "SubSector" ss ON iv.sub_sector_id = ss.subsector_id + WHERE inventory_id = :inventoryId + ORDER BY av.co2eq DESC + LIMIT 3; `; + + return db.sequelize!.query(rawQuery, { + replacements: { inventoryId }, + type: QueryTypes.SELECT + }); +} + +export async function getEmissionResults(inventoryId: string) { + const [totalEmissionsWithPercentage, topSubSectorEmissions] = await Promise.all([ + getTotalEmissionsWithPercentage(inventoryId), + getTopEmissions(inventoryId) + ]); + + // @ts-ignore + const topSubSectorEmissionsWithPercentage = topSubSectorEmissions.map(({ co2eq, sector_name, subsector_name }) => { + const sectorTotal = totalEmissionsWithPercentage.find(e => e.sectorName === sector_name)?.co2eq; + return { + subsectorName: subsector_name, + sectorName: sector_name, + co2eq, + percentage: sectorTotal ? calculatePercentage(co2eq, sectorTotal) : 0 + }; + }); + + return { totalEmissionsBySector: totalEmissionsWithPercentage, topEmissionsBySubSector: topSubSectorEmissionsWithPercentage }; +} \ No newline at end of file diff --git a/app/src/components/SegmentedProgress.tsx b/app/src/components/SegmentedProgress.tsx index 8b3d23ac6..7ee1297c0 100644 --- a/app/src/components/SegmentedProgress.tsx +++ b/app/src/components/SegmentedProgress.tsx @@ -1,36 +1,142 @@ -import { Box, useToken } from "@chakra-ui/react"; +import React, { useRef } from "react"; +import { + Badge, + Box, + Flex, + Table, + TableContainer, + Tbody, + Td, + Text, + Tfoot, + Th, + Tooltip, + Tr, + useToken, + VStack +} from "@chakra-ui/react"; +import { convertKgToTonnes } from "@/util/helpers"; + +export type SegmentedProgressValues = number | { name: string; percentage: number, value: bigint }; export function SegmentedProgress({ - values, - colors = ["interactive.connected", "interactive.tertiary"], - max = 1, - height = 4, -}: { - values: number[]; - colors?: string[]; - max?: number; - height?: number; + values, + colors = ["interactive.connected", "interactive.tertiary", "interactive.secondary"], + max = 1, + height = 4, + showLabels = false, + showHover = false, + t = (str: string) => str, + total + }: { + values: SegmentedProgressValues[], + colors?: string[], + max?: number, + height?: number, + showLabels?: boolean, + showHover?: boolean, + t?: (str: string) => string, + total?: bigint }) { const colorValues = useToken("colors", colors); - return ( - + typeof v === "number" ? { percentage: v, name: `Segment ${values.indexOf(v) + 1}`, value: (max) } : v + ); + const tooltipContent = ( + + + + {normalizedValues.map((value, index) => ( + + + + + + ))} + + + + + + + +
+ {t(value.name)} + + {value.percentage.toFixed(1)}% + + {convertKgToTonnes(value.value)} +
{t("TOTAL")}{convertKgToTonnes(total!)}
+
+ ); + + const progressBars = ( + - {values.map((value, i) => ( - - ))} - + + {normalizedValues.map((value, i) => ( + + ))} + + + ); + + if (!showLabels) { + return progressBars; + } + + return ( + + {progressBars} + + {normalizedValues.map((v, i) => ( + + + + {t(v.name)} + + + ))} + + ); } diff --git a/app/src/components/SegmentedProgressWithNames.tsx b/app/src/components/SegmentedProgressWithNames.tsx new file mode 100644 index 000000000..ef998fe13 --- /dev/null +++ b/app/src/components/SegmentedProgressWithNames.tsx @@ -0,0 +1,68 @@ +import { Badge, Box, Flex, useToken, VStack } from "@chakra-ui/react"; + +export function SegmentedProgressWithNames({ + values, + colors = ["interactive.connected", "interactive.tertiary", "interactive.secondary"], + max = 1, + height = 4, + t + }: { + values: { name: string, value: number }[]; + colors?: string[]; + max?: number; + height?: number; + t: Function; +}) { + const colorValues = useToken("colors", colors); + return ( + + + {values.map((value, i) => ( + + ))} + + + + {values.map((v, i) => + + + {t(v.name)} + + )} + + + ); +} diff --git a/app/src/i18n/locales/en/dashboard.json b/app/src/i18n/locales/en/dashboard.json index b973943cc..d959cc7b5 100644 --- a/app/src/i18n/locales/en/dashboard.json +++ b/app/src/i18n/locales/en/dashboard.json @@ -48,5 +48,7 @@ "total-emissions": "Total Emissions", "total-ghg-emissions-in-year": "Total GHG Emissions in {{year}}", "emissions-per-capita-in-year": "Emissions per capita in {{year}}", - "%-of-country's-emissions": "% of country's emissions" + "%-of-country's-emissions": "% of country's emissions", + "top-emissions": "Top Emissions", + "by-sub-sector": "BY SUB-SECTOR" } diff --git a/app/src/services/api.ts b/app/src/services/api.ts index 9c172dc05..32458d7c0 100644 --- a/app/src/services/api.ts +++ b/app/src/services/api.ts @@ -20,6 +20,7 @@ import type { EmissionsFactorResponse, UserInviteResponse, RequiredScopesResponse, + ResultsResponse } from "@/util/types"; import type { GeoJSON } from "geojson"; import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; @@ -64,6 +65,11 @@ export const api = createApi({ transformResponse: (response: { data: RequiredScopesResponse }) => response.data, }), + getResults: builder.query({ + query: (inventoryId: string) => `inventory/${inventoryId}/results`, + transformResponse: (response: { data: ResultsResponse} ) => + response.data, + }), getInventoryProgress: builder.query({ query: (inventoryId) => `inventory/${inventoryId}/progress`, transformResponse: (response: { data: InventoryProgressResponse }) => @@ -586,5 +592,6 @@ export const { useDeleteAllActivityValuesMutation, useDeleteActivityValueMutation, useGetInventoryValuesBySubsectorQuery, + useGetResultsQuery, } = api; export const { useGetOCCityQuery, useGetOCCityDataQuery } = openclimateAPI; diff --git a/app/src/util/helpers.ts b/app/src/util/helpers.ts index f59b69175..4ca7a714d 100644 --- a/app/src/util/helpers.ts +++ b/app/src/util/helpers.ts @@ -221,24 +221,24 @@ export const getInputMethodology = (methodologyId: string) => { } }; -export function convertKgToTonnes(valueInTonnes: number) { +export function convertKgToTonnes(valueInTonnes: number | bigint) { let result = ""; - - if (valueInTonnes >= 1e6) { + const tonnes = Number(valueInTonnes) + if (tonnes >= 1e6) { // Convert to megatonnes if the value is 1,000,000 tonnes or more - const megatonnes = (valueInTonnes / 1e6).toFixed(0); + const megatonnes = (tonnes / 1e6).toFixed(0); result = `${megatonnes} MtCO2`; - } else if (valueInTonnes >= 1e3) { + } else if (tonnes >= 1e3) { // Convert to kilotonnes if the value is 1,000 tonnes or more but less than 1,000,000 tonnes - const kilotonnes = (valueInTonnes / 1e3).toFixed(0); + const kilotonnes = (tonnes / 1e3).toFixed(0); result = `${kilotonnes} KtCO2`; - } else if (valueInTonnes < 1) { + } else if (tonnes < 1) { // Convert to kg if the value is less than 1 tonne - const kilograms = (valueInTonnes * 1e3).toFixed(0); + const kilograms = (tonnes * 1e3).toFixed(0); result = `${kilograms} kgCO2`; } else { // Return as tonnes if the value is less than 1,000 tonnes but more than or equal to 1 tonne - result = `${valueInTonnes} tCO2`; + result = `${tonnes} tCO2`; } return result; diff --git a/app/src/util/types.d.ts b/app/src/util/types.d.ts index 056e3da4d..39384908d 100644 --- a/app/src/util/types.d.ts +++ b/app/src/util/types.d.ts @@ -1,7 +1,4 @@ -import type { - DataSourceWithRelations, - InventoryValueData, -} from "@/app/[lng]/[inventory]/data/[step]/types"; +import type { DataSourceWithRelations, InventoryValueData } from "@/app/[lng]/[inventory]/data/[step]/types"; import type { ScopeAttributes } from "@/models/Scope"; import type { SectorAttributes } from "@/models/Sector"; import type { SubCategoryAttributes } from "@/models/SubCategory"; @@ -109,6 +106,7 @@ type fileContentValues = { size: number; fileType: string; }; + interface UserFileResponse { id: string; userId: string; @@ -137,3 +135,22 @@ interface UserInviteResponse { interface RequiredScopesResponse { requiredScopes: string[]; } + +interface TopEmission { + "co2eq": bigint; + "sectorName": string; + "subsectorName": string; + "percentage": number; +} + +interface ResultsResponse { + totalEmissions: { + "bySector": { + "sectorName": string, + "co2eq": bigint, + "percentage": number + } + "total": bigint + }, + topEmissions: { bySubSector: TopEmission[] }; +} \ No newline at end of file From 36d5eeab7d41897c40d5f312e4e29e7451870e54 Mon Sep 17 00:00:00 2001 From: isaacbello Date: Thu, 12 Sep 2024 11:24:47 +0100 Subject: [PATCH 76/98] Fix: Update Source Fields in activity value form --- .../activity-modal/activity-form-modal.tsx | 12 +- .../activity-modal/activity-modal-body.tsx | 231 +++++++++++------- .../activity-value-form/use-activity-form.ts | 4 +- .../use-activity-validation.tsx | 6 +- app/src/i18n/locales/en/data.json | 11 +- app/src/i18n/locales/es/data.json | 6 +- 6 files changed, 170 insertions(+), 100 deletions(-) diff --git a/app/src/components/Modals/activity-modal/activity-form-modal.tsx b/app/src/components/Modals/activity-modal/activity-form-modal.tsx index 32d354756..c1be4ddb7 100644 --- a/app/src/components/Modals/activity-modal/activity-form-modal.tsx +++ b/app/src/components/Modals/activity-modal/activity-form-modal.tsx @@ -4,7 +4,6 @@ import { api, useUpdateActivityValueMutation } from "@/services/api"; import { Box, Button, - CloseButton, Modal, ModalCloseButton, ModalContent, @@ -14,15 +13,14 @@ import { Text, useToast, } from "@chakra-ui/react"; -import { FC, useEffect, useMemo } from "react"; -import { SubmitHandler, useForm } from "react-hook-form"; -import { TFunction, use } from "i18next"; +import { FC, useMemo } from "react"; +import { SubmitHandler } from "react-hook-form"; +import { TFunction } from "i18next"; import { CheckCircleIcon } from "@chakra-ui/icons"; import { getInputMethodology } from "@/util/helpers"; import type { SuggestedActivity } from "@/util/form-schema"; import { getTranslationFromDict } from "@/i18n"; -import ActivityModalBody, { ExtraField } from "./activity-modal-body"; -import { Inputs } from "./activity-modal-body"; +import ActivityModalBody, { ExtraField, Inputs } from "./activity-modal-body"; import { ActivityValue } from "@/models/ActivityValue"; import { InventoryValue } from "@/models/InventoryValue"; import useActivityValueValidation from "@/hooks/activity-value-form/use-activity-validation"; @@ -205,7 +203,7 @@ const AddActivityModal: FC = ({ dataSource: { sourceType: "", dataQuality: activity.dataQuality, - notes: activity.sourceReference, + notes: activity.dataComments, }, gasValues: gasValues.map(({ gas, factor, unit, ...rest }) => ({ ...rest, diff --git a/app/src/components/Modals/activity-modal/activity-modal-body.tsx b/app/src/components/Modals/activity-modal/activity-modal-body.tsx index 8eb2de209..9afb46f44 100644 --- a/app/src/components/Modals/activity-modal/activity-modal-body.tsx +++ b/app/src/components/Modals/activity-modal/activity-modal-body.tsx @@ -3,10 +3,10 @@ import { FormControl, FormLabel, Grid, - HStack, Heading, - InputGroup, + HStack, Input, + InputGroup, InputRightAddon, ModalBody, NumberInput, @@ -54,7 +54,7 @@ export type Inputs = { N2OEmissionFactor: number; CH4EmissionFactor: number; dataQuality: string; - sourceReference: string; + dataComments: string; activityType: string; fuelType: string; totalFuelConsumption?: string | undefined; @@ -103,6 +103,14 @@ const ActivityModalBody = ({ } }; + const filteredfields = fields.filter((f) => { + return !(f.id.includes("-source") && f.type === "text"); + }); + + const sourceField = fields.find( + (f) => f.id.includes("-source") && f.type === "text", + ); + return (
@@ -115,7 +123,7 @@ const ActivityModalBody = ({ gap="24px" > {/* handle select, multi-select types, text */} - {fields.map((f) => { + {filteredfields.map((f) => { return ( <> {f.options && ( @@ -659,86 +667,143 @@ const ActivityModalBody = ({ )} - - {t("data-quality")} - - {errors.activity?.dataQuality ? ( - - - {t("data-quality-form-label")} - - ) : ( - "" - )} - - - {t("source-reference")} -