diff --git a/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx b/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx index b4a6a1d9d..dfa7d1134 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx +++ b/src/admin/components/ResourceTabs/MonitoredTab/components/DataCard.tsx @@ -378,7 +378,7 @@ const DataCard = ({ }: React.HTMLAttributes & { type?: EntityName; }) => { - const [tabActive, setTabActive] = useState(0); + const [tabActive, setTabActive] = useState(1); const [selected, setSelected] = useState(["1"]); const [selectedPolygonUuid, setSelectedPolygonUuid] = useState("0"); const basename = useBasename(); @@ -624,7 +624,7 @@ const DataCard = ({ - + diff --git a/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx b/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx index 7f315ba4c..06c6f42b8 100644 --- a/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx +++ b/src/admin/components/ResourceTabs/MonitoredTab/components/MonitoredCharts.tsx @@ -57,7 +57,7 @@ const RestorationMetrics = ({ tooltip={TOTAL_HECTARES_UNDER_RESTORATION_TOOLTIP} showTreesRestoredGraph={false} /> - + ); diff --git a/src/components/elements/Inputs/TreeSpeciesInput/RHFSeedingTableInput.tsx b/src/components/elements/Inputs/TreeSpeciesInput/RHFSeedingTableInput.tsx index 62b487e34..447eadfbe 100644 --- a/src/components/elements/Inputs/TreeSpeciesInput/RHFSeedingTableInput.tsx +++ b/src/components/elements/Inputs/TreeSpeciesInput/RHFSeedingTableInput.tsx @@ -32,6 +32,8 @@ const RHFSeedingTableInput = (props: PropsWithChildren) {...props} title={t("Tree Species")} buttonCaptionSuffix={t("Species")} + withPreviousCounts={true} + useTaxonomicBackbone={true} value={value ?? []} onChange={onChange} collection={collection} diff --git a/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.stories.tsx b/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.stories.tsx index 6e0df7760..f3fbe7609 100644 --- a/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.stories.tsx +++ b/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.stories.tsx @@ -27,6 +27,8 @@ export const Default: Story = { label: "Tree Species Grown", description: "List the tree species that you expect to restore on this project, across all sites. Please enter the scientific name for each tree species.", + withPreviousCounts: false, + useTaxonomicBackbone: true, required: true, value: [ { diff --git a/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.tsx b/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.tsx index 01ef90959..8677e0eba 100644 --- a/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.tsx +++ b/src/components/elements/Inputs/TreeSpeciesInput/TreeSpeciesInput.tsx @@ -14,6 +14,7 @@ import { EstablishmentEntityType, useEstablishmentTrees } from "@/connections/Es import { useEntityContext } from "@/context/entity.provider"; import { useModalContext } from "@/context/modal.provider"; import { useDebounce } from "@/hooks/useDebounce"; +import { useValueChanged } from "@/hooks/useValueChanged"; import { isReportModelName } from "@/types/common"; import { updateArrayState } from "@/utils/array"; @@ -29,6 +30,8 @@ export interface TreeSpeciesInputProps extends Omit title: string; buttonCaptionSuffix: string; withNumbers?: boolean; + withPreviousCounts: boolean; + useTaxonomicBackbone: boolean; value: TreeSpeciesValue[]; onChange: (value: any[]) => void; clearErrors: () => void; @@ -101,16 +104,31 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { const { entityUuid, entityName } = useEntityContext(); const isEntity = entityName != null && entityUuid != null; const isReport = isEntity && isReportModelName(entityName); - const handleBaseEntityTrees = isReport || (isEntity && ["sites", "nurseries"].includes(entityName)); + const handleBaseEntityTrees = + props.withPreviousCounts && (isReport || (isEntity && ["sites", "nurseries"].includes(entityName))); + const displayPreviousCounts = props.withPreviousCounts && isReport; const entity = (handleBaseEntityTrees ? entityName : undefined) as EstablishmentEntityType; const uuid = handleBaseEntityTrees ? entityUuid : undefined; - const [, { establishmentTrees, previousPlantingCounts }] = useEstablishmentTrees({ entity, uuid }); + const [establishmentLoaded, { establishmentTrees, previousPlantingCounts }] = useEstablishmentTrees({ entity, uuid }); + const shouldPrepopulate = value.length == 0 && Object.values(previousPlantingCounts ?? {}).length > 0; + useValueChanged(shouldPrepopulate, function () { + if (shouldPrepopulate) { + onChange( + Object.entries(previousPlantingCounts!).map(([name, previousCount]) => ({ + uuid: uuidv4(), + name, + taxon_id: previousCount.taxonId, + amount: 0 + })) + ); + } + }); const totalWithPrevious = useMemo( () => props.value.reduce( - (total, { name, amount }) => total + (amount ?? 0) + (previousPlantingCounts?.[name ?? ""] ?? 0), + (total, { name, amount }) => total + (amount ?? 0) + (previousPlantingCounts?.[name ?? ""]?.amount ?? 0), 0 ), [previousPlantingCounts, props.value] @@ -157,7 +175,7 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { handleCreate?.({ uuid: uuidv4(), name: valueAutoComplete, - taxon_id: taxonId, + taxon_id: props.useTaxonomicBackbone ? taxonId : undefined, amount: props.withNumbers ? 0 : undefined }); @@ -183,7 +201,7 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { handleUpdate({ ...editValue, name: valueAutoComplete, - taxon_id: findTaxonId(valueAutoComplete) + taxon_id: props.useTaxonomicBackbone ? taxonId : undefined }); setValueAutoComplete(""); @@ -202,6 +220,8 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { addValue(e); }; + if (!establishmentLoaded || shouldPrepopulate) return null; + return ( { value={valueAutoComplete} onChange={e => setValueAutoComplete(e.target.value)} onSearch={async search => { + if (!props.useTaxonomicBackbone) return []; + const result = await autocompleteSearch(search); setSearchResult(result); return result; @@ -289,7 +311,10 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => {
-
+
{props.title} @@ -297,7 +322,7 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { {props.value.length}
-
+
{isReport ? t("SPECIES PLANTED:") : t("TREES TO BE PLANTED:")} @@ -305,7 +330,7 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { {props.withNumbers ? props.value.reduce((total, v) => total + (v.amount || 0), 0).toLocaleString() : "0"}
- +
{t("TOTAL PLANTED TO DATE:")} @@ -368,7 +393,7 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { } >
- +
@@ -410,9 +435,9 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { containerClassName="" />
- + - {(previousPlantingCounts?.[value.name ?? ""] ?? 0).toLocaleString()} + {(previousPlantingCounts?.[value.name ?? ""]?.amount ?? 0).toLocaleString()}
@@ -426,11 +451,21 @@ const TreeSpeciesInput = (props: TreeSpeciesInputProps) => { autoCompleteRef.current?.focus(); }} /> - setDeleteIndex(value.uuid ?? null)} - /> + name === value.name) == null + } + > + setDeleteIndex(value.uuid ?? null)} + /> +
)} diff --git a/src/components/elements/Toggle/Toggle.tsx b/src/components/elements/Toggle/Toggle.tsx index 6e8da9ae2..873ae97f6 100644 --- a/src/components/elements/Toggle/Toggle.tsx +++ b/src/components/elements/Toggle/Toggle.tsx @@ -14,12 +14,19 @@ export interface ToggleProps { disabledIndexes?: number[]; variant?: ToggleVariants; onChangeActiveIndex?: (index: number) => void; + defaultActiveIndex?: number; } const Toggle = (props: ToggleProps) => { - const { items, disabledIndexes = [], variant = VARIANT_TOGGLE_PRIMARY, onChangeActiveIndex } = props; + const { + items, + disabledIndexes = [], + variant = VARIANT_TOGGLE_PRIMARY, + onChangeActiveIndex, + defaultActiveIndex = 0 + } = props; - const [activeIndex, setActiveIndex] = useState(0); + const [activeIndex, setActiveIndex] = useState(defaultActiveIndex); const [width, setWidth] = useState(0); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); diff --git a/src/components/extensive/WizardForm/WizardForm.stories.tsx b/src/components/extensive/WizardForm/WizardForm.stories.tsx index d351a3f2b..2a2de3ef9 100644 --- a/src/components/extensive/WizardForm/WizardForm.stories.tsx +++ b/src/components/extensive/WizardForm/WizardForm.stories.tsx @@ -187,6 +187,8 @@ const getSteps = (edit?: boolean): FormStepSchema[] => { description: "TRee species input description", fieldProps: { title: "Tree Species", + withPreviousCounts: false, + useTaxonomicBackbone: false, buttonCaptionSuffix: "Species", required: true }, @@ -204,6 +206,8 @@ const getSteps = (edit?: boolean): FormStepSchema[] => { fieldProps: { title: "Tree Species", + withPreviousCounts: false, + useTaxonomicBackbone: false, buttonCaptionSuffix: "Species", required: true, withNumbers: true diff --git a/src/generated/v3/entityService/entityServiceSchemas.ts b/src/generated/v3/entityService/entityServiceSchemas.ts index bb11f5e3f..9734a4f86 100644 --- a/src/generated/v3/entityService/entityServiceSchemas.ts +++ b/src/generated/v3/entityService/entityServiceSchemas.ts @@ -3,6 +3,17 @@ * * @version 1.0 */ +export type PreviousPlantingCountDto = { + /** + * Taxonomic ID for this tree species row + */ + taxonId: string | null; + /** + * Number of trees of this type that have been planted in all previous reports on this entity. + */ + amount: number; +}; + export type ScientificNameDto = { /** * The scientific name for this tree species @@ -20,9 +31,9 @@ export type EstablishmentsTreesDto = { /** * If the entity in this request is a report, the sum totals of previous planting by species. * - * @example {"Aster persaliens":256,"Cirsium carniolicum":1024} + * @example {"Aster persaliens":{"amount":256},"Cirsium carniolicum":{"taxonId":"wfo-0000130112","amount":1024}} */ previousPlantingCounts: { - [key: string]: number; + [key: string]: PreviousPlantingCountDto; } | null; }; diff --git a/src/helpers/customForms.ts b/src/helpers/customForms.ts index 229a11bf0..edc947569 100644 --- a/src/helpers/customForms.ts +++ b/src/helpers/customForms.ts @@ -732,7 +732,7 @@ const getFieldValidation = (question: FormQuestionRead, t: typeof useT, framewor const arrayItemShape = question.with_numbers ? yup.object({ name: yup.string().required(), - amount: yup.number().min(1).required() + amount: yup.number().min(0).required() }) : yup.object({ name: yup.string().required() diff --git a/src/pages/dashboard/charts/CustomBarJobsCreated.tsx b/src/pages/dashboard/charts/CustomBarJobsCreated.tsx index 4df4fe024..3446ea1f0 100644 --- a/src/pages/dashboard/charts/CustomBarJobsCreated.tsx +++ b/src/pages/dashboard/charts/CustomBarJobsCreated.tsx @@ -10,6 +10,9 @@ interface CustomBarProps { export const CustomBar: React.FC = ({ fill, x, y, width, height }) => { const radius = 5; + if (width === 0 || height === 0) { + return null; + } const path = ` M${x},${y + height} L${x},${y + radius} diff --git a/src/pages/dashboard/charts/CustomTooltip.tsx b/src/pages/dashboard/charts/CustomTooltip.tsx index 9cd396ee0..73313bcf7 100644 --- a/src/pages/dashboard/charts/CustomTooltip.tsx +++ b/src/pages/dashboard/charts/CustomTooltip.tsx @@ -2,13 +2,23 @@ import React from "react"; export const CustomTooltip: React.FC = ({ active, payload, label }) => { if (!active || !payload || !payload.length) return null; + const value = payload[0].value; + const total = payload[0].payload.total; + const percentage = (value / total) * 100; + + const formattedValue = value.toLocaleString("en-US", { + minimumFractionDigits: 1, + maximumFractionDigits: 1 + }); + + const formattedPercentage = percentage.toFixed(0); + return (

{label}

{payload.map((item: any, index: number) => (

- {item.name}: - {item.value.toLocaleString()} +

{`${formattedValue}ha (${formattedPercentage}%)`}

))}
diff --git a/src/pages/dashboard/charts/SimpleBarChart.tsx b/src/pages/dashboard/charts/SimpleBarChart.tsx index 4b83266f3..73a5a5423 100644 --- a/src/pages/dashboard/charts/SimpleBarChart.tsx +++ b/src/pages/dashboard/charts/SimpleBarChart.tsx @@ -17,12 +17,14 @@ type ResturationStrategy = { type FormattedData = { name: string; Value: number; + total: number; }; -const SimpleBarChart = ({ data }: { data: ResturationStrategy[] }) => { +const SimpleBarChart = ({ data, total }: { data: ResturationStrategy[]; total?: number }) => { const formattedData: FormattedData[] = data.map(item => ({ name: item.label.split(",").join(" + ").replace(/-/g, " "), - Value: item.value + Value: item.value, + total: total ?? 0 })); return ( diff --git a/src/pages/dashboard/components/GraphicIconDashboard.tsx b/src/pages/dashboard/components/GraphicIconDashboard.tsx index 942dac62e..0ead992d1 100644 --- a/src/pages/dashboard/components/GraphicIconDashboard.tsx +++ b/src/pages/dashboard/components/GraphicIconDashboard.tsx @@ -83,12 +83,12 @@ const GraphicIconDashboard = ({ data, maxValue }: { data: DashboardTableDataProp className="shadow-md fixed z-10 w-auto rounded border border-darkCustom bg-white p-2" style={{ left: `${tooltip.x}px`, - top: `${tooltip.y - 50}px`, + top: `${tooltip.y - 65}px`, transform: "translateX(-50%)" }} > - {`${t(tooltip.label)} `} - {t(tooltip.text)} +

{`${t(tooltip.label)} `}

+ {t(tooltip.text)}
)}
diff --git a/src/pages/organization/[id]/components/edit/getEditOrganisationSteps.ts b/src/pages/organization/[id]/components/edit/getEditOrganisationSteps.ts index d58972eb0..b14bd4804 100644 --- a/src/pages/organization/[id]/components/edit/getEditOrganisationSteps.ts +++ b/src/pages/organization/[id]/components/edit/getEditOrganisationSteps.ts @@ -599,6 +599,8 @@ export const getSteps = (t: typeof useT, uuid: string): FormStepSchema[] => { ), fieldProps: { title: t("Tree Species"), + withPreviousCounts: false, + useTaxonomicBackbone: true, buttonCaptionSuffix: t("Species"), withNumbers: false } diff --git a/src/pages/organization/[id]/project-pitch/create/[pitchUUID]/getCreatePitchSteps.ts b/src/pages/organization/[id]/project-pitch/create/[pitchUUID]/getCreatePitchSteps.ts index f98dcbe16..39c190d1e 100644 --- a/src/pages/organization/[id]/project-pitch/create/[pitchUUID]/getCreatePitchSteps.ts +++ b/src/pages/organization/[id]/project-pitch/create/[pitchUUID]/getCreatePitchSteps.ts @@ -332,6 +332,8 @@ export const getSteps = (t: typeof useT, uuid: string): FormStepSchema[] => [ ), fieldProps: { title: t("Tree Species"), + withPreviousCounts: false, + useTaxonomicBackbone: true, buttonCaptionSuffix: t("Species"), withNumbers: true } diff --git a/src/utils/dashboardUtils.ts b/src/utils/dashboardUtils.ts index 22632fce1..d3b339c61 100644 --- a/src/utils/dashboardUtils.ts +++ b/src/utils/dashboardUtils.ts @@ -482,7 +482,7 @@ export const parsePolygonsIndicatorDataForLandUse = ( return { label, value: adjustedValue as number, - valueText: `${Math.round(value as number)} ha ${percentage.toFixed(2)}%` + valueText: `${Math.round(value as number)}ha (${percentage.toFixed(0)}%)` }; }); @@ -524,12 +524,10 @@ export const parsePolygonsIndicatorDataForStrategies = (polygonsIndicator: Polyg } }); - return Object.entries(totals) - .filter(([_, value]) => value > 0) - .map(([label, value]) => ({ - label, - value: Number(value.toFixed(2)) - })); + return Object.entries(totals).map(([label, value]) => ({ + label, + value: Number(value.toFixed(2)) + })); }; export const parsePolygonsIndicatorDataForEcoRegion = (polygons: PolygonIndicator[]): ParsedResult => {