diff --git a/README.md b/README.md index bad2392ad..949846b1f 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ When adding a new **service** app to the v3 API: * Follow directions below for all namespaces and resources in the new service When adding a new **namespace** to the V3 API: -* In `geneated/v3/utils.ts`, add namespace -> service URL mapping to `V3_NAMESPACES` +* In `generated/v3/utils.ts`, add namespace -> service URL mapping to `V3_NAMESPACES` When adding a new **resource** to the v3 API: * The resource needs to be specified in shape of the redux API store. In `apiSlice.ts`, add the new diff --git a/src/admin/apiProvider/dataProviders/projectDataProvider.ts b/src/admin/apiProvider/dataProviders/projectDataProvider.ts index 0dc5c800d..93ed0395b 100644 --- a/src/admin/apiProvider/dataProviders/projectDataProvider.ts +++ b/src/admin/apiProvider/dataProviders/projectDataProvider.ts @@ -1,50 +1,36 @@ -import { DataProvider } from "react-admin"; +import { DataProvider, HttpError } from "react-admin"; +import { loadFullProject, loadProjectIndex } from "@/connections/Entity"; import { DeleteV2AdminProjectsUUIDError, fetchDeleteV2AdminProjectsUUID, - fetchGetV2AdminProjects, fetchGetV2AdminProjectsMulti, - fetchGetV2ENTITYUUID, - GetV2AdminProjectsError, - GetV2AdminProjectsMultiError, - GetV2ENTITYUUIDError + GetV2AdminProjectsMultiError } from "@/generated/apiComponents"; import { getFormattedErrorForRA } from "../utils/error"; -import { apiListResponseToRAListResult, raListParamsToQueryParams } from "../utils/listing"; - -const projectSortableList = ["name", "organisation_name", "planting_start_date"]; +import { entitiesListResult, raConnectionProps } from "../utils/listing"; // @ts-ignore export const projectDataProvider: DataProvider = { + // @ts-expect-error until we can get the whole DataProvider on Project DTOs async getList(_, params) { - try { - const response = await fetchGetV2AdminProjects({ - queryParams: raListParamsToQueryParams(params, projectSortableList) - }); - - return apiListResponseToRAListResult(response); - } catch (err) { - throw getFormattedErrorForRA(err as GetV2AdminProjectsError); + const connection = await loadProjectIndex(raConnectionProps(params)); + if (connection.fetchFailure != null) { + throw new HttpError(connection.fetchFailure.message, connection.fetchFailure.statusCode); } + + return entitiesListResult(connection); }, - // @ts-ignore + // @ts-expect-error until we can get the whole DataProvider on Project DTOs async getOne(_, params) { - try { - const response = await fetchGetV2ENTITYUUID({ - pathParams: { - entity: "projects", - uuid: params.id - } - }); - - // @ts-ignore - return { data: { ...response.data, id: response.data.uuid } }; - } catch (err) { - throw getFormattedErrorForRA(err as GetV2ENTITYUUIDError); + const { entity: project, fetchFailure } = await loadFullProject({ uuid: params.id }); + if (fetchFailure != null) { + throw new HttpError(fetchFailure.message, fetchFailure.statusCode); } + + return { data: { ...project, id: project!.uuid } }; }, // @ts-ignore diff --git a/src/admin/apiProvider/utils/listing.ts b/src/admin/apiProvider/utils/listing.ts index b20909e1d..aae446165 100644 --- a/src/admin/apiProvider/utils/listing.ts +++ b/src/admin/apiProvider/utils/listing.ts @@ -1,5 +1,7 @@ import { GetListParams, GetListResult } from "react-admin"; +import { EntityIndexConnection, EntityIndexConnectionProps, EntityLightDto } from "@/connections/Entity"; + interface ListQueryParams extends Record { search?: string; filter?: string; @@ -16,6 +18,21 @@ const getFilterKey = (original: string, replace?: { key: string; replaceWith: st return replace.replaceWith; }; +export const raConnectionProps = (params: GetListParams) => { + const queryParams: EntityIndexConnectionProps = { + pageSize: params.pagination.perPage, + pageNumber: params.pagination.page, + filter: params.filter + }; + + if (params.sort.field != null) { + queryParams.sortField = params.sort.field; + queryParams.sortDirection = (params.sort.order as "ASC" | "DESC") ?? "ASC"; + } + + return queryParams; +}; + export const raListParamsToQueryParams = ( params: GetListParams, sortableList?: string[], @@ -61,6 +78,11 @@ interface ApiListResponse { meta?: any; } +export const entitiesListResult = ({ entities, indexTotal }: EntityIndexConnection) => ({ + data: entities?.map(entity => ({ ...entity, id: entity.uuid })), + total: indexTotal +}); + export const apiListResponseToRAListResult = (response: ApiListResponse): GetListResult => { return { data: response?.data?.map(item => ({ ...item, id: item.uuid })) || [], diff --git a/src/admin/components/Actions/ShowActions.tsx b/src/admin/components/Actions/ShowActions.tsx index 298a241d4..e3243ed55 100644 --- a/src/admin/components/Actions/ShowActions.tsx +++ b/src/admin/components/Actions/ShowActions.tsx @@ -64,7 +64,10 @@ const ShowActions = ({ className="!text-sm !font-semibold !capitalize lg:!text-base wide:!text-md" onClick={() => toggleTestStatus(record)} > - + )} {canEdit && hasDelete && ( diff --git a/src/admin/components/Dialogs/StatusChangeModal.tsx b/src/admin/components/Dialogs/StatusChangeModal.tsx index dd7c2c3be..6443b34ed 100644 --- a/src/admin/components/Dialogs/StatusChangeModal.tsx +++ b/src/admin/components/Dialogs/StatusChangeModal.tsx @@ -23,6 +23,7 @@ import { usePostV2AdminENTITYUUIDReminder, usePutV2AdminENTITYUUIDSTATUS } from "@/generated/apiComponents"; +import ApiSlice, { RESOURCES, ResourceType } from "@/store/apiSlice"; import { optionToChoices } from "@/utils/options"; interface StatusChangeModalProps extends DialogProps { @@ -44,24 +45,24 @@ const StatusChangeModal = ({ handleClose, status, ...dialogProps }: StatusChange const { openNotification } = useNotificationContext(); const t = useT(); - const resourceName = (() => { + const [resourceName, v3Resource] = useMemo(() => { switch (resource as keyof typeof modules) { case "project": - return "projects"; + return ["projects", "projects"]; case "site": - return "sites"; + return ["sites", "sites"]; case "nursery": - return "nurseries"; + return ["nurseries", "nurseries"]; case "projectReport": - return "project-reports"; + return ["project-reports", "projectReports"]; case "siteReport": - return "site-reports"; + return ["site-reports", "siteReports"]; case "nurseryReport": - return "nursery-reports"; + return ["nursery-reports", "nurseryReports"]; default: - return resource; + return [resource, resource]; } - })(); + }, [resource]); const dialogTitle = (() => { let name; @@ -124,6 +125,12 @@ const StatusChangeModal = ({ handleClose, status, ...dialogProps }: StatusChange const { mutateAsync, isLoading } = usePutV2AdminENTITYUUIDSTATUS({ onSuccess: () => { + const type = v3Resource as ResourceType; + if (RESOURCES.includes(type)) { + // Temporary until the entity update goes through v3. Then the prune isn't needed, and the + // refetch() will pull the updated resource from the store without an API request. + ApiSlice.pruneCache(type, [record.id]); + } refetch(); } }); diff --git a/src/admin/components/Fields/FrameworkField.tsx b/src/admin/components/Fields/FrameworkField.tsx index 0086a9733..47a904eef 100644 --- a/src/admin/components/Fields/FrameworkField.tsx +++ b/src/admin/components/Fields/FrameworkField.tsx @@ -1,17 +1,16 @@ -import { FC } from "react"; import { FunctionField } from "react-admin"; import { useFrameworkChoices } from "@/constants/options/frameworks"; -const FrameworkField: FC = () => { +const FrameworkField = ({ prop = "framework_key" }: { prop?: string }) => { const frameworkChoices = useFrameworkChoices(); return ( - frameworkChoices.find((framework: any) => framework.id === record?.framework_key)?.name || record?.framework_key + frameworkChoices.find((framework: any) => framework.id === record?.[prop])?.name || record?.[prop] } sortable={false} /> diff --git a/src/admin/components/Fields/ReadableStatusField.tsx b/src/admin/components/Fields/ReadableStatusField.tsx new file mode 100644 index 000000000..943f9b185 --- /dev/null +++ b/src/admin/components/Fields/ReadableStatusField.tsx @@ -0,0 +1,9 @@ +import { FunctionField } from "react-admin"; + +import { STATUS_MAP } from "@/components/elements/Status/constants/statusMap"; + +const ReadableStatusField = ({ prop }: { prop: string }) => ( + (record[prop] == null ? null : STATUS_MAP[record[prop]])} /> +); + +export default ReadableStatusField; diff --git a/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogProjectStatus.tsx b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogProjectStatus.tsx index 57684ba9c..0b31d016d 100644 --- a/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogProjectStatus.tsx +++ b/src/admin/components/ResourceTabs/AuditLogTab/components/SiteAuditLogProjectStatus.tsx @@ -3,11 +3,12 @@ import { When } from "react-if"; import Text from "@/components/elements/Text/Text"; import { AuditStatusResponse, ProjectLiteRead } from "@/generated/apiSchemas"; +import { ProjectLightDto } from "@/generated/v3/entityService/entityServiceSchemas"; import AuditLogTable from "./AuditLogTable"; export interface SiteAuditLogProjectStatusProps { - record?: ProjectLiteRead | null; + record?: ProjectLiteRead | ProjectLightDto | null; auditLogData?: { data: AuditStatusResponse[] }; auditData?: { entity: string; entity_uuid: string }; refresh?: () => void; @@ -31,7 +32,7 @@ const SiteAuditLogProjectStatus: FC = ({ - History and Discussion for {record && record?.name} + History and Discussion for {record?.name} {auditLogData && } diff --git a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/HighLevelMetrics.tsx b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/HighLevelMetrics.tsx index 67c16f531..21ec029c0 100644 --- a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/HighLevelMetrics.tsx +++ b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/HighLevelMetrics.tsx @@ -22,35 +22,35 @@ const HighLevelMetics: FC = () => { - + - + - + - + - + - + - + diff --git a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/ProjectOverview.tsx b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/ProjectOverview.tsx index 8b801a62e..2b0215a7a 100644 --- a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/ProjectOverview.tsx +++ b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/ProjectOverview.tsx @@ -1,10 +1,11 @@ import { Check } from "@mui/icons-material"; import { Box, Button, Card, Divider, Grid, Stack, Typography } from "@mui/material"; import { FC, useState } from "react"; -import { BooleanField, Labeled, TextField, useShowContext } from "react-admin"; +import { Labeled, TextField, useShowContext } from "react-admin"; import StatusChangeModal from "@/admin/components/Dialogs/StatusChangeModal"; import FrameworkField from "@/admin/components/Fields/FrameworkField"; +import ReadableStatusField from "@/admin/components/Fields/ReadableStatusField"; const ProjectOverview: FC = () => { const [statusModal, setStatusModal] = useState<"approve" | "moreinfo" | undefined>(); @@ -21,31 +22,25 @@ const ProjectOverview: FC = () => { - + - - - - - - - + - + - + diff --git a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx index 896d1e261..0da9e0f9e 100644 --- a/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx +++ b/src/admin/components/ResourceTabs/InformationTab/components/ProjectInformationAside/QuickActions.tsx @@ -66,7 +66,7 @@ const QuickActions: FC = () => { - + diff --git a/src/components/extensive/Tables/SitesTable.tsx b/src/components/extensive/Tables/SitesTable.tsx index a7755328d..bceaafcf7 100644 --- a/src/components/extensive/Tables/SitesTable.tsx +++ b/src/components/extensive/Tables/SitesTable.tsx @@ -16,13 +16,14 @@ import { useDeleteV2SitesUUID, useGetV2ProjectsUUIDSites } from "@/generated/apiComponents"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; import { getEntityDetailPageLink } from "@/helpers/entity"; import { useDate } from "@/hooks/useDate"; import { ModalId } from "../Modal/ModalConst"; interface SitesTableProps { - project: any; + project: ProjectFullDto; hasAddButton?: boolean; onFetch?: (data: GetV2ProjectsUUIDSitesResponse) => void; } @@ -170,7 +171,7 @@ const SitesTable = ({ project, hasAddButton = true, onFetch }: SitesTableProps) name: t("Add Site"), hide: !hasAddButton, as: Link, - href: `/entity/sites/create/${project.framework_uuid}?parent_name=projects&parent_uuid=${project.uuid}` + href: `/entity/sites/create/${project.frameworkUuid}?parent_name=projects&parent_uuid=${project.uuid}` } ]} > diff --git a/src/components/extensive/Tables/TreeSpeciesTablePD.tsx b/src/components/extensive/Tables/TreeSpeciesTablePD.tsx index e25b0f58b..ba118fa08 100644 --- a/src/components/extensive/Tables/TreeSpeciesTablePD.tsx +++ b/src/components/extensive/Tables/TreeSpeciesTablePD.tsx @@ -5,7 +5,7 @@ import Table from "@/components/elements/Table/Table"; import { VARIANT_TABLE_TREE_SPECIES } from "@/components/elements/Table/TableVariants"; import Text from "@/components/elements/Text/Text"; import ToolTip from "@/components/elements/Tooltip/Tooltip"; -import { Framework } from "@/context/framework.provider"; +import { Framework, useFrameworkContext } from "@/context/framework.provider"; import { useGetV2SeedingsENTITYUUID, useGetV2TreeSpeciesEntityUUID } from "@/generated/apiComponents"; import Icon, { IconNames } from "../Icon/Icon"; @@ -17,7 +17,6 @@ export type ModelNameType = | "treeCountSite" | "treeCount/Goal" | "speciesCount/Goal" - | "saplingsCount" | "seedCount/Goal" | "saplingsCount" | "noGoal"; @@ -25,7 +24,6 @@ export type ModelNameType = export interface TreeSpeciesTablePDProps { modelUUID: string; modelName: string; - framework?: string; setTotalCount?: React.Dispatch>; setTotalSpecies?: React.Dispatch>; setTotalNonTree?: React.Dispatch>; @@ -59,7 +57,6 @@ export interface TreeSpeciesTableRowData { const TreeSpeciesTablePD = ({ modelUUID, modelName, - framework, setTotalCount, setTotalSpecies, setTotalNonTree, @@ -104,6 +101,8 @@ const TreeSpeciesTablePD = ({ } ); + const { framework } = useFrameworkContext(); + const getCollectionType = (collection: string) => { let result = "tree"; if (collection === "non-tree") { diff --git a/src/connections/Entity.ts b/src/connections/Entity.ts new file mode 100644 index 000000000..a26b6a16a --- /dev/null +++ b/src/connections/Entity.ts @@ -0,0 +1,194 @@ +import { createSelector } from "reselect"; + +import { + entityGet, + EntityGetPathParams, + entityIndex, + EntityIndexQueryParams +} from "@/generated/v3/entityService/entityServiceComponents"; +import { entityGetFetchFailed, entityIndexFetchFailed } from "@/generated/v3/entityService/entityServicePredicates"; +import { + ProjectFullDto, + ProjectLightDto, + SiteFullDto, + SiteLightDto +} from "@/generated/v3/entityService/entityServiceSchemas"; +import { getStableQuery } from "@/generated/v3/utils"; +import ApiSlice, { ApiDataStore, PendingErrorState, StoreResourceMap } from "@/store/apiSlice"; +import { EntityName } from "@/types/common"; +import { Connection } from "@/types/connection"; +import { connectionHook, connectionLoader } from "@/utils/connectionShortcuts"; +import { selectorCache } from "@/utils/selectorCache"; + +export type EntityFullDto = ProjectFullDto | SiteFullDto; +export type EntityLightDto = ProjectLightDto | SiteLightDto; +export type EntityDtoType = EntityFullDto | EntityLightDto; + +type EntityConnection = { + entity?: T; + fetchFailure?: PendingErrorState | null; + refetch: () => void; +}; + +type EntityConnectionProps = { + uuid: string; +}; + +export type EntityIndexConnection = { + entities?: T[]; + indexTotal?: number; + fetchFailure?: PendingErrorState | null; + refetch: () => void; +}; + +type EntityIndexFilterKey = keyof Omit< + EntityIndexQueryParams, + "page[size]" | "page[number]" | "sort[field]" | "sort[direction]" +>; +export type EntityIndexConnectionProps = { + pageSize?: number; + pageNumber?: number; + sortField?: string; + sortDirection?: "ASC" | "DESC"; + filter?: Record; +}; + +export type SupportedEntity = EntityGetPathParams["entity"]; + +const entitySelector = + (entityName: SupportedEntity) => + (store: ApiDataStore) => + store[entityName] as StoreResourceMap; + +const pageMetaSelector = (entityName: SupportedEntity, props: EntityIndexConnectionProps) => (store: ApiDataStore) => { + const { queryParams } = entityIndexParams(entityName, props); + delete queryParams["page[number]"]; + const query = getStableQuery(queryParams); + return store.meta.indices[entityName][query]?.[props.pageNumber ?? 0]; +}; + +const entityGetParams = (entity: SupportedEntity, uuid: string) => ({ pathParams: { entity, uuid } }); +const entityIndexQuery = (props?: EntityIndexConnectionProps) => { + const queryParams = { "page[number]": props?.pageNumber, "page[size]": props?.pageSize } as EntityIndexQueryParams; + if (props?.sortField != null) { + queryParams["sort[field]"] = props.sortField; + queryParams["sort[direction]"] = props.sortDirection ?? "ASC"; + } + if (props?.filter != null) { + for (const [key, value] of Object.entries(props.filter)) { + queryParams[key as EntityIndexFilterKey] = value; + } + } + return queryParams; +}; +const entityIndexParams = (entity: SupportedEntity, props?: EntityIndexConnectionProps) => ({ + pathParams: { entity }, + queryParams: entityIndexQuery(props) +}); + +const entityIsLoaded = + (requireFullEntity: boolean) => + ({ entity, fetchFailure }: EntityConnection, { uuid }: EntityConnectionProps) => { + if (uuid == null || fetchFailure != null) return true; + if (entity == null) return false; + return !requireFullEntity || !entity.lightResource; + }; + +const createGetEntityConnection = ( + entityName: SupportedEntity, + requireFullEntity: boolean +): Connection, EntityConnectionProps> => ({ + load: (connection, props) => { + if (!entityIsLoaded(requireFullEntity)(connection, props)) entityGet(entityGetParams(entityName, props.uuid)); + }, + + isLoaded: entityIsLoaded(requireFullEntity), + + selector: selectorCache( + ({ uuid }) => uuid, + ({ uuid }) => + createSelector( + [entitySelector(entityName), entityGetFetchFailed(entityGetParams(entityName, uuid))], + (entities, failure) => ({ + entity: entities[uuid]?.attributes as T, + fetchFailure: failure ?? undefined, + refetch: () => { + if (uuid != null) ApiSlice.pruneCache(entityName, [uuid]); + } + }) + ) + ) +}); + +const indexIsLoaded = ({ entities, fetchFailure }: EntityIndexConnection) => + entities != null || fetchFailure != null; + +const indexCacheKey = (props: EntityIndexConnectionProps) => getStableQuery(entityIndexQuery(props)); + +const createEntityIndexConnection = ( + entityName: SupportedEntity +): Connection, EntityIndexConnectionProps> => ({ + load: (connection, props) => { + if (!indexIsLoaded(connection)) entityIndex(entityIndexParams(entityName, props)); + }, + + isLoaded: indexIsLoaded, + + selector: selectorCache( + props => indexCacheKey(props), + props => + createSelector( + [ + pageMetaSelector(entityName, props), + entitySelector(entityName), + entityIndexFetchFailed(entityIndexParams(entityName, props)) + ], + (pageMeta, entitiesStore, fetchFailure) => { + // For now, we don't have filter support, so all search queries should be "" + const refetch = () => ApiSlice.pruneIndex(entityName, ""); + if (pageMeta == null) return { refetch, fetchFailure }; + + const entities = [] as T[]; + for (const id of pageMeta.ids) { + // If we're missing any of the entities we're supposed to have, return nothing so the + // index endpoint is queried again. + if (entitiesStore[id] == null) return { refetch, fetchFailure }; + entities.push(entitiesStore[id].attributes as T); + } + + return { entities, indexTotal: pageMeta.meta.total, refetch, fetchFailure }; + } + ) + ) +}); + +export const entityIsSupported = (entity: EntityName): entity is SupportedEntity => + SUPPORTED_ENTITIES.includes(entity as SupportedEntity); + +export const pruneEntityCache = (entity: EntityName, uuid: string) => { + // TEMPORARY check while we transition all entities to v3. Once that's done, SupportedEntity and + // EntityName will be equivalent and this prune call will be valid for all entities. At that time, + // this function may no longer be needed as well. + if (entityIsSupported(entity)) { + ApiSlice.pruneCache(entity, [uuid]); + } +}; + +// TEMPORARY while we transition all entities to v3. When adding a new entity to this connection, +// please update this array. +const SUPPORTED_ENTITIES: SupportedEntity[] = ["projects"]; + +// The "light" version of entity connections will return the full DTO if it's what's cached in the store. However, +// the type of the entity will use the Light DTO. For the "full" version of the entity connection, if the version that's +// currently cached is the "light" version, it will issue a request to the server to get the full version. +const fullProjectConnection = createGetEntityConnection("projects", true); +export const loadFullProject = connectionLoader(fullProjectConnection); +export const useFullProject = connectionHook(fullProjectConnection); +const lightProjectConnection = createGetEntityConnection("projects", false); +export const loadLightProject = connectionLoader(lightProjectConnection); +export const useLightProject = connectionHook(lightProjectConnection); + +// For indexes, we only support the light dto +const indexProjectConnection = createEntityIndexConnection("projects"); +export const loadProjectIndex = connectionLoader(indexProjectConnection); +export const useProjectIndex = connectionHook(indexProjectConnection); diff --git a/src/context/framework.provider.tsx b/src/context/framework.provider.tsx index ee4c19af0..3000a52d2 100644 --- a/src/context/framework.provider.tsx +++ b/src/context/framework.provider.tsx @@ -22,7 +22,7 @@ export const FrameworkContext = createContext({ framework: Framework.UNDEFINED }); -type FrameworkProviderProps = { children: ReactNode; frameworkKey?: string }; +type FrameworkProviderProps = { children: ReactNode; frameworkKey?: string | null }; const FrameworkProvider = ({ children, frameworkKey }: FrameworkProviderProps) => { const framework = useMemo( @@ -68,7 +68,7 @@ export function withFrameworkShow(WrappedComponent: ComponentType) { export function RecordFrameworkProvider({ children }: { children: ReactNode }) { const { record } = useShowContext(); - return {children}; + return {children}; } export default FrameworkProvider; diff --git a/src/generated/apiComponents.ts b/src/generated/apiComponents.ts index 8db47e4f9..376068f5b 100644 --- a/src/generated/apiComponents.ts +++ b/src/generated/apiComponents.ts @@ -2392,524 +2392,6 @@ export const useDeleteV2ProjectsUUIDEMAILRemovePartner = ( ); }; -export type GetV2MyProjectsError = Fetcher.ErrorWrapper; - -export type GetV2MyProjectsResponse = { - id?: string; - uuid?: string; - is_test?: boolean; - status?: string; - name?: string; - organisation?: { - uuid?: string; - type?: string; - private?: boolean; - name?: string; - phone?: string; - currency?: string; - states?: string[]; - loan_status_types?: string[]; - land_systems?: string[]; - fund_utilisation?: string[]; - detailed_intervention_types?: string[]; - account_number_1?: string; - account_number_2?: string; - approach_of_marginalized_communities?: string; - community_engagement_numbers_marginalized?: string; - founding_date?: string; - description?: string; - leadership_team?: string; - countries?: string[]; - languages?: string[]; - project_pitches?: { - id?: string; - uuid?: string; - status?: string; - readable_status?: string; - organisation_id?: string; - funding_programmes?: { - id?: number; - uuid?: string; - name?: string; - description?: string; - read_more_url?: string; - organisation_types?: string[]; - location?: string; - status?: string; - }; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - project_name?: string; - how_discovered?: string; - project_objectives?: string; - project_country?: string[]; - project_county_district?: string; - restoration_intervention_types?: string[]; - land_systems?: string[]; - tree_restoration_practices?: string[]; - total_hectares?: number; - project_budget?: number; - total_trees?: number; - capacity_building_needs?: string[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - restoration_photos?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - proof_of_land_tenure_mou?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - detailed_project_budget?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - expected_active_restoration_start_date?: string; - expected_active_restoration_end_date?: string; - description_of_project_timeline?: string; - proj_partner_info?: string; - land_tenure_proj_area?: string[]; - landholder_comm_engage?: string; - proj_success_risks?: string; - monitor_eval_plan?: string; - proj_boundary?: string; - sustainable_dev_goals?: string[]; - proj_area_description?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_sites?: number; - environmental_goals?: string; - main_degradation_causes?: string; - seedlings_source?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_nurseries?: number; - curr_land_degradation?: string; - proj_impact_socieconom?: string; - proj_impact_foodsec?: string; - proj_impact_watersec?: string; - proj_impact_jobtypes?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - num_jobs_created?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_men?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_18to35?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_older35?: number; - proj_beneficiaries?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_small?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_large?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_youth?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_classes?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_tribes?: number; - monitoring_evaluation_plan?: string; - main_causes_of_degradation?: string; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }[]; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - web_url?: string; - facebook_url?: string; - instagram_url?: string; - linkedin_url?: string; - twitter_url?: string; - hq_street_1?: string; - hq_street_2?: string; - hq_city?: string; - hq_state?: string; - hq_zipcode?: string; - hq_country?: string; - fin_start_month?: number; - /** - * @format float - */ - fin_budget_3year?: number; - /** - * @format float - */ - fin_budget_2year?: number; - /** - * @format float - */ - fin_budget_1year?: number; - /** - * @format float - */ - fin_budget_current_year?: number; - /** - * @format float - */ - ha_restored_total?: number; - /** - * @format float - */ - ha_restored_3year?: number; - relevant_experience_years?: number; - trees_grown_total?: number; - trees_grown_3year?: number; - tree_care_approach?: string; - ft_permanent_employees?: number; - pt_permanent_employees?: number; - temp_employees?: number; - female_employees?: number; - male_employees?: number; - young_employees?: number; - additional_funding_details?: string; - community_experience?: string; - total_engaged_community_members_3yr?: number; - percent_engaged_women_3yr?: number; - percent_engaged_men_3yr?: number; - percent_engaged_under_35_3yr?: number; - percent_engaged_over_35_3yr?: number; - percent_engaged_smallholder_3yr?: number; - total_trees_grown?: number; - avg_tree_survival_rate?: number; - tree_maintenance_aftercare_approach?: string; - restored_areas_description?: string; - monitoring_evaluation_experience?: string; - funding_history?: string; - engagement_farmers?: string[]; - engagement_women?: string[]; - engagement_youth?: string[]; - engagement_non_youth?: string[]; - tree_restoration_practices?: string[]; - business_model?: string; - subtype?: string; - organisation_revenue_this_year?: number; - shapefiles?: { - uuid?: string; - shapefileable_type?: string; - shapefileable_id?: number; - geojson?: string; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - bank_statements?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - previous_annual_reports?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - logo?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - reference?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_2year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_last_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_this_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_next_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - legal_registration?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - /** - * this is a list of key value pairs eg slug: name - */ - tags?: string[]; - }; - planting_start_date?: string; - framework_key?: string; - framework_uuid?: string; - has_monitoring_data?: boolean; -}[]; - -export type GetV2MyProjectsVariables = ApiContext["fetcherOptions"]; - -export const fetchGetV2MyProjects = (variables: GetV2MyProjectsVariables, signal?: AbortSignal) => - apiFetch({ - url: "/v2/my/projects", - method: "get", - ...variables, - signal - }); - -export const useGetV2MyProjects = ( - variables: GetV2MyProjectsVariables, - options?: Omit< - reactQuery.UseQueryOptions, - "queryKey" | "queryFn" - > -) => { - const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options); - return reactQuery.useQuery( - queryKeyFn({ path: "/v2/my/projects", operationId: "getV2MyProjects", variables }), - ({ signal }) => fetchGetV2MyProjects({ ...fetcherOptions, ...variables }, signal), - { - ...options, - ...queryOptions - } - ); -}; - export type GetV2MyActionsError = Fetcher.ErrorWrapper; export type GetV2MyActionsResponse = { @@ -16826,1919 +16308,6 @@ export const useGetV2AdminProjectPitches = ; - -export type GetV2AdminProjectsResponse = { - data?: { - id?: string; - uuid?: string; - is_test?: boolean; - status?: string; - name?: string; - organisation?: { - uuid?: string; - type?: string; - private?: boolean; - name?: string; - phone?: string; - currency?: string; - states?: string[]; - loan_status_types?: string[]; - land_systems?: string[]; - fund_utilisation?: string[]; - detailed_intervention_types?: string[]; - account_number_1?: string; - account_number_2?: string; - approach_of_marginalized_communities?: string; - community_engagement_numbers_marginalized?: string; - founding_date?: string; - description?: string; - leadership_team?: string; - countries?: string[]; - languages?: string[]; - project_pitches?: { - id?: string; - uuid?: string; - status?: string; - readable_status?: string; - organisation_id?: string; - funding_programmes?: { - id?: number; - uuid?: string; - name?: string; - description?: string; - read_more_url?: string; - organisation_types?: string[]; - location?: string; - status?: string; - }; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - project_name?: string; - how_discovered?: string; - project_objectives?: string; - project_country?: string[]; - project_county_district?: string; - restoration_intervention_types?: string[]; - land_systems?: string[]; - tree_restoration_practices?: string[]; - total_hectares?: number; - project_budget?: number; - total_trees?: number; - capacity_building_needs?: string[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - restoration_photos?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - proof_of_land_tenure_mou?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - detailed_project_budget?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - expected_active_restoration_start_date?: string; - expected_active_restoration_end_date?: string; - description_of_project_timeline?: string; - proj_partner_info?: string; - land_tenure_proj_area?: string[]; - landholder_comm_engage?: string; - proj_success_risks?: string; - monitor_eval_plan?: string; - proj_boundary?: string; - sustainable_dev_goals?: string[]; - proj_area_description?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_sites?: number; - environmental_goals?: string; - main_degradation_causes?: string; - seedlings_source?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_nurseries?: number; - curr_land_degradation?: string; - proj_impact_socieconom?: string; - proj_impact_foodsec?: string; - proj_impact_watersec?: string; - proj_impact_jobtypes?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - num_jobs_created?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_men?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_18to35?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_older35?: number; - proj_beneficiaries?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_small?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_large?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_youth?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_classes?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_tribes?: number; - monitoring_evaluation_plan?: string; - main_causes_of_degradation?: string; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }[]; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - web_url?: string; - facebook_url?: string; - instagram_url?: string; - linkedin_url?: string; - twitter_url?: string; - hq_street_1?: string; - hq_street_2?: string; - hq_city?: string; - hq_state?: string; - hq_zipcode?: string; - hq_country?: string; - fin_start_month?: number; - /** - * @format float - */ - fin_budget_3year?: number; - /** - * @format float - */ - fin_budget_2year?: number; - /** - * @format float - */ - fin_budget_1year?: number; - /** - * @format float - */ - fin_budget_current_year?: number; - /** - * @format float - */ - ha_restored_total?: number; - /** - * @format float - */ - ha_restored_3year?: number; - relevant_experience_years?: number; - trees_grown_total?: number; - trees_grown_3year?: number; - tree_care_approach?: string; - ft_permanent_employees?: number; - pt_permanent_employees?: number; - temp_employees?: number; - female_employees?: number; - male_employees?: number; - young_employees?: number; - additional_funding_details?: string; - community_experience?: string; - total_engaged_community_members_3yr?: number; - percent_engaged_women_3yr?: number; - percent_engaged_men_3yr?: number; - percent_engaged_under_35_3yr?: number; - percent_engaged_over_35_3yr?: number; - percent_engaged_smallholder_3yr?: number; - total_trees_grown?: number; - avg_tree_survival_rate?: number; - tree_maintenance_aftercare_approach?: string; - restored_areas_description?: string; - monitoring_evaluation_experience?: string; - funding_history?: string; - engagement_farmers?: string[]; - engagement_women?: string[]; - engagement_youth?: string[]; - engagement_non_youth?: string[]; - tree_restoration_practices?: string[]; - business_model?: string; - subtype?: string; - organisation_revenue_this_year?: number; - shapefiles?: { - uuid?: string; - shapefileable_type?: string; - shapefileable_id?: number; - geojson?: string; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - bank_statements?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - previous_annual_reports?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - logo?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - reference?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_2year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_last_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_this_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_next_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - legal_registration?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - /** - * this is a list of key value pairs eg slug: name - */ - tags?: string[]; - }; - planting_start_date?: string; - framework_key?: string; - framework_uuid?: string; - has_monitoring_data?: boolean; - }[]; - links?: { - first?: string; - last?: string; - prev?: string; - next?: string; - }; - meta?: { - current_page?: number; - from?: number; - last_page?: number; - next?: number; - }; -}; - -export type GetV2AdminProjectsVariables = { - queryParams?: GetV2AdminProjectsQueryParams; -} & ApiContext["fetcherOptions"]; - -export const fetchGetV2AdminProjects = (variables: GetV2AdminProjectsVariables, signal?: AbortSignal) => - apiFetch({ - url: "/v2/admin/projects", - method: "get", - ...variables, - signal - }); - -export const useGetV2AdminProjects = ( - variables: GetV2AdminProjectsVariables, - options?: Omit< - reactQuery.UseQueryOptions, - "queryKey" | "queryFn" - > -) => { - const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options); - return reactQuery.useQuery( - queryKeyFn({ path: "/v2/admin/projects", operationId: "getV2AdminProjects", variables }), - ({ signal }) => fetchGetV2AdminProjects({ ...fetcherOptions, ...variables }, signal), - { - ...options, - ...queryOptions - } - ); -}; - -export type GetV2ProjectsUUIDPathParams = { - uuid: string; -}; - -export type GetV2ProjectsUUIDError = Fetcher.ErrorWrapper; - -export type GetV2ProjectsUUIDResponse = { - data?: { - id?: string; - uuid?: string; - is_test?: boolean; - status?: string; - organisation?: { - uuid?: string; - type?: string; - private?: boolean; - name?: string; - phone?: string; - currency?: string; - states?: string[]; - loan_status_types?: string[]; - land_systems?: string[]; - fund_utilisation?: string[]; - detailed_intervention_types?: string[]; - account_number_1?: string; - account_number_2?: string; - approach_of_marginalized_communities?: string; - community_engagement_numbers_marginalized?: string; - founding_date?: string; - description?: string; - leadership_team?: string; - countries?: string[]; - languages?: string[]; - project_pitches?: { - id?: string; - uuid?: string; - status?: string; - readable_status?: string; - organisation_id?: string; - funding_programmes?: { - id?: number; - uuid?: string; - name?: string; - description?: string; - read_more_url?: string; - organisation_types?: string[]; - location?: string; - status?: string; - }; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - project_name?: string; - how_discovered?: string; - project_objectives?: string; - project_country?: string[]; - project_county_district?: string; - restoration_intervention_types?: string[]; - land_systems?: string[]; - tree_restoration_practices?: string[]; - total_hectares?: number; - project_budget?: number; - total_trees?: number; - capacity_building_needs?: string[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - restoration_photos?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - proof_of_land_tenure_mou?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - detailed_project_budget?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - expected_active_restoration_start_date?: string; - expected_active_restoration_end_date?: string; - description_of_project_timeline?: string; - proj_partner_info?: string; - land_tenure_proj_area?: string[]; - landholder_comm_engage?: string; - proj_success_risks?: string; - monitor_eval_plan?: string; - proj_boundary?: string; - sustainable_dev_goals?: string[]; - proj_area_description?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_sites?: number; - environmental_goals?: string; - main_degradation_causes?: string; - seedlings_source?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_nurseries?: number; - curr_land_degradation?: string; - proj_impact_socieconom?: string; - proj_impact_foodsec?: string; - proj_impact_watersec?: string; - proj_impact_jobtypes?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - num_jobs_created?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_men?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_18to35?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_older35?: number; - proj_beneficiaries?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_small?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_large?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_youth?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_classes?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_tribes?: number; - monitoring_evaluation_plan?: string; - main_causes_of_degradation?: string; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }[]; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - web_url?: string; - facebook_url?: string; - instagram_url?: string; - linkedin_url?: string; - twitter_url?: string; - hq_street_1?: string; - hq_street_2?: string; - hq_city?: string; - hq_state?: string; - hq_zipcode?: string; - hq_country?: string; - fin_start_month?: number; - /** - * @format float - */ - fin_budget_3year?: number; - /** - * @format float - */ - fin_budget_2year?: number; - /** - * @format float - */ - fin_budget_1year?: number; - /** - * @format float - */ - fin_budget_current_year?: number; - /** - * @format float - */ - ha_restored_total?: number; - /** - * @format float - */ - ha_restored_3year?: number; - relevant_experience_years?: number; - trees_grown_total?: number; - trees_grown_3year?: number; - tree_care_approach?: string; - ft_permanent_employees?: number; - pt_permanent_employees?: number; - temp_employees?: number; - female_employees?: number; - male_employees?: number; - young_employees?: number; - additional_funding_details?: string; - community_experience?: string; - total_engaged_community_members_3yr?: number; - percent_engaged_women_3yr?: number; - percent_engaged_men_3yr?: number; - percent_engaged_under_35_3yr?: number; - percent_engaged_over_35_3yr?: number; - percent_engaged_smallholder_3yr?: number; - total_trees_grown?: number; - avg_tree_survival_rate?: number; - tree_maintenance_aftercare_approach?: string; - restored_areas_description?: string; - monitoring_evaluation_experience?: string; - funding_history?: string; - engagement_farmers?: string[]; - engagement_women?: string[]; - engagement_youth?: string[]; - engagement_non_youth?: string[]; - tree_restoration_practices?: string[]; - business_model?: string; - subtype?: string; - organisation_revenue_this_year?: number; - shapefiles?: { - uuid?: string; - shapefileable_type?: string; - shapefileable_id?: number; - geojson?: string; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - bank_statements?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - previous_annual_reports?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - logo?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - reference?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_2year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_last_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_this_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_next_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - legal_registration?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - /** - * this is a list of key value pairs eg slug: name - */ - tags?: string[]; - }; - application?: { - uuid?: string; - form_submissions?: { - id?: string; - uuid?: string; - name?: string; - form?: { - id?: number; - uuid?: string; - type?: string; - version?: number; - title?: string; - subtitle?: string; - description?: string; - framework_key?: string; - duration?: string; - deadline_at?: string; - documentation?: string; - documentation_label?: string; - submission_message?: string; - published?: boolean; - stage_id?: string; - options_other?: boolean; - form_sections?: { - order?: number; - form_id?: number; - form_questions?: { - id?: number; - uuid?: string; - form_section_id?: number; - label?: string; - validation?: string[]; - parent_id?: string; - linked_field_key?: string; - children?: Record[]; - multichoice?: boolean; - order?: number; - options?: { - id?: number; - uuid?: string; - form_question_id?: number; - label?: string; - order?: number; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - table_headers?: { - id?: number; - uuid?: string; - form_question_id?: number; - label?: string; - order?: number; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - additional_text?: string; - additional_url?: string; - show_on_parent_condition?: boolean; - input_type?: - | "date" - | "text" - | "long-text" - | "select" - | "checkboxes" - | "radio" - | "number" - | "image" - | "file" - | "conditional"; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - /** - * this is a list of key value pairs eg. slug: name - */ - tags?: string[]; - updated_by?: number; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }; - stage?: { - uuid?: string; - name?: string; - status?: string; - readable_status?: string; - }; - answers?: string; - status?: string; - readable_status?: string; - audits?: { - id?: number; - event?: string; - user_id?: number; - user_uuid?: string; - old_values?: Record; - new_values?: Record; - created_at?: string; - updated_at?: string; - }[]; - /** - * this is a list of key value pairs eg slug: name - */ - tags?: string[]; - project_pitch_uuid?: string; - updated_by?: string; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }[]; - current_submission?: { - id?: string; - uuid?: string; - name?: string; - form?: { - id?: number; - uuid?: string; - type?: string; - version?: number; - title?: string; - subtitle?: string; - description?: string; - framework_key?: string; - duration?: string; - deadline_at?: string; - documentation?: string; - documentation_label?: string; - submission_message?: string; - published?: boolean; - stage_id?: string; - options_other?: boolean; - form_sections?: { - order?: number; - form_id?: number; - form_questions?: { - id?: number; - uuid?: string; - form_section_id?: number; - label?: string; - validation?: string[]; - parent_id?: string; - linked_field_key?: string; - children?: Record[]; - multichoice?: boolean; - order?: number; - options?: { - id?: number; - uuid?: string; - form_question_id?: number; - label?: string; - order?: number; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - table_headers?: { - id?: number; - uuid?: string; - form_question_id?: number; - label?: string; - order?: number; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - additional_text?: string; - additional_url?: string; - show_on_parent_condition?: boolean; - input_type?: - | "date" - | "text" - | "long-text" - | "select" - | "checkboxes" - | "radio" - | "number" - | "image" - | "file" - | "conditional"; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - /** - * this is a list of key value pairs eg. slug: name - */ - tags?: string[]; - updated_by?: number; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }; - stage?: { - uuid?: string; - name?: string; - status?: string; - readable_status?: string; - }; - answers?: string; - status?: string; - readable_status?: string; - audits?: { - id?: number; - event?: string; - user_id?: number; - user_uuid?: string; - old_values?: Record; - new_values?: Record; - created_at?: string; - updated_at?: string; - }[]; - /** - * this is a list of key value pairs eg slug: name - */ - tags?: string[]; - project_pitch_uuid?: string; - updated_by?: string; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }; - funding_programme?: { - id?: number; - uuid?: string; - name?: string; - description?: string; - location?: string; - read_more_url?: string; - framework_key?: string; - status?: string; - organisation_types?: string[]; - stages?: { - id?: number; - uuid?: string; - status?: string; - deadline_at?: string; - readable_status?: string; - funding_programme_id?: number; - name?: string; - order?: number; - forms?: { - id?: number; - uuid?: string; - type?: string; - version?: number; - title?: string; - subtitle?: string; - description?: string; - framework_key?: string; - duration?: string; - deadline_at?: string; - documentation?: string; - documentation_label?: string; - submission_message?: string; - published?: boolean; - stage_id?: string; - options_other?: boolean; - form_sections?: { - order?: number; - form_id?: number; - form_questions?: { - id?: number; - uuid?: string; - form_section_id?: number; - label?: string; - validation?: string[]; - parent_id?: string; - linked_field_key?: string; - children?: Record[]; - multichoice?: boolean; - order?: number; - options?: { - id?: number; - uuid?: string; - form_question_id?: number; - label?: string; - order?: number; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - table_headers?: { - id?: number; - uuid?: string; - form_question_id?: number; - label?: string; - order?: number; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - additional_text?: string; - additional_url?: string; - show_on_parent_condition?: boolean; - input_type?: - | "date" - | "text" - | "long-text" - | "select" - | "checkboxes" - | "radio" - | "number" - | "image" - | "file" - | "conditional"; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - /** - * this is a list of key value pairs eg. slug: name - */ - tags?: string[]; - updated_by?: number; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }[]; - organisations?: { - uuid?: string; - name?: string; - }[]; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }; - organisation?: { - uuid?: string; - status?: string; - readable_status?: string; - type?: string; - is_test?: boolean; - private?: boolean; - name?: string; - phone?: string; - founding_date?: string; - description?: string; - countries?: string[]; - languages?: string[]; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - project_pitches?: { - id?: string; - uuid?: string; - status?: string; - readable_status?: string; - organisation_id?: string; - funding_programmes?: { - id?: number; - uuid?: string; - name?: string; - description?: string; - read_more_url?: string; - organisation_types?: string[]; - location?: string; - status?: string; - }; - tree_species?: { - uuid?: string; - name?: string; - amount?: number; - type?: string; - collection?: string; - }[]; - project_name?: string; - how_discovered?: string; - project_objectives?: string; - project_country?: string[]; - project_county_district?: string; - restoration_intervention_types?: string[]; - land_systems?: string[]; - tree_restoration_practices?: string[]; - total_hectares?: number; - project_budget?: number; - total_trees?: number; - capacity_building_needs?: string[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - restoration_photos?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - proof_of_land_tenure_mou?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - detailed_project_budget?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - expected_active_restoration_start_date?: string; - expected_active_restoration_end_date?: string; - description_of_project_timeline?: string; - proj_partner_info?: string; - land_tenure_proj_area?: string[]; - landholder_comm_engage?: string; - proj_success_risks?: string; - monitor_eval_plan?: string; - proj_boundary?: string; - sustainable_dev_goals?: string[]; - proj_area_description?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_sites?: number; - environmental_goals?: string; - main_degradation_causes?: string; - seedlings_source?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - proposed_num_nurseries?: number; - curr_land_degradation?: string; - proj_impact_socieconom?: string; - proj_impact_foodsec?: string; - proj_impact_watersec?: string; - proj_impact_jobtypes?: string; - /** - * @minimum 0 - * @maximum 4294967295 - */ - num_jobs_created?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_men?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_18to35?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_employees_older35?: number; - proj_beneficiaries?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_women?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_small?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_large?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_youth?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_classes?: number; - /** - * @minimum 0 - * @maximum 100 - */ - pct_beneficiaries_scheduled_tribes?: number; - monitoring_evaluation_plan?: string; - main_causes_of_degradation?: string; - deleted_at?: string; - created_at?: string; - updated_at?: string; - }[]; - web_url?: string; - facebook_url?: string; - instagram_url?: string; - linkedin_url?: string; - twitter_url?: string; - hq_street_1?: string; - hq_street_2?: string; - hq_city?: string; - hq_state?: string; - hq_zipcode?: string; - hq_country?: string; - fin_start_month?: number; - /** - * @format float - */ - fin_budget_3year?: number; - /** - * @format float - */ - fin_budget_2year?: number; - /** - * @format float - */ - fin_budget_1year?: number; - /** - * @format float - */ - fin_budget_current_year?: number; - /** - * @format float - */ - ha_restored_total?: number; - /** - * @format float - */ - ha_restored_3year?: number; - relevant_experience_years?: number; - trees_grown_total?: number; - trees_grown_3year?: number; - tree_care_approach?: string; - ft_permanent_employees?: number; - pt_permanent_employees?: number; - temp_employees?: number; - female_employees?: number; - male_employees?: number; - young_employees?: number; - additional_funding_details?: string; - community_experience?: string; - total_engaged_community_members_3yr?: number; - percent_engaged_women_3yr?: number; - percent_engaged_men_3yr?: number; - percent_engaged_under_35_3yr?: number; - percent_engaged_over_35_3yr?: number; - percent_engaged_smallholder_3yr?: number; - total_trees_grown?: number; - avg_tree_survival_rate?: number; - tree_maintenance_aftercare_approach?: string; - restored_areas_description?: string; - monitoring_evaluation_experience?: string; - funding_history?: string; - shapefiles?: { - uuid?: string; - shapefileable_type?: string; - shapefileable_id?: number; - geojson?: string; - created_at?: string; - updated_at?: string; - deleted_at?: string; - }[]; - bank_statements?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - previous_annual_reports?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - logo?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - cover?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }; - reference?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - additional?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_2year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_last_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_this_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - op_budget_next_year?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - legal_registration?: { - uuid?: string; - url?: string; - thumb_url?: string; - collection_name?: string; - title?: string; - file_name?: string; - mime_type?: string; - size?: number; - lat?: number; - lng?: number; - is_public?: boolean; - is_cover?: boolean; - created_at?: string; - }[]; - /** - * this is a list of key value pairs eg. slug: name - */ - tags?: string[]; - created_at?: string; - updated_at?: string; - }; - /** - * @format date-time - */ - created_at?: string; - /** - * @format date-time - */ - updated_at?: string; - }; - planting_start_date?: string; - framework_key?: string; - framework_uuid?: string; - has_monitoring_data?: boolean; - }[]; -}; - -export type GetV2ProjectsUUIDVariables = { - pathParams: GetV2ProjectsUUIDPathParams; -} & ApiContext["fetcherOptions"]; - -export const fetchGetV2ProjectsUUID = (variables: GetV2ProjectsUUIDVariables, signal?: AbortSignal) => - apiFetch({ - url: "/v2/projects/{uuid}", - method: "get", - ...variables, - signal - }); - -export const useGetV2ProjectsUUID = ( - variables: GetV2ProjectsUUIDVariables, - options?: Omit< - reactQuery.UseQueryOptions, - "queryKey" | "queryFn" - > -) => { - const { fetcherOptions, queryOptions, queryKeyFn } = useApiContext(options); - return reactQuery.useQuery( - queryKeyFn({ path: "/v2/projects/{UUID}", operationId: "getV2ProjectsUUID", variables }), - ({ signal }) => fetchGetV2ProjectsUUID({ ...fetcherOptions, ...variables }, signal), - { - ...options, - ...queryOptions - } - ); -}; - export type DeleteV2ProjectsUUIDPathParams = { uuid: string; }; @@ -39472,11 +37041,6 @@ export type QueryOperation = operationId: "getV2ProjectsUUIDPartners"; variables: GetV2ProjectsUUIDPartnersVariables; } - | { - path: "/v2/my/projects"; - operationId: "getV2MyProjects"; - variables: GetV2MyProjectsVariables; - } | { path: "/v2/my/actions"; operationId: "getV2MyActions"; @@ -39687,16 +37251,6 @@ export type QueryOperation = operationId: "getV2AdminProjectPitches"; variables: GetV2AdminProjectPitchesVariables; } - | { - path: "/v2/admin/projects"; - operationId: "getV2AdminProjects"; - variables: GetV2AdminProjectsVariables; - } - | { - path: "/v2/projects/{UUID}"; - operationId: "getV2ProjectsUUID"; - variables: GetV2ProjectsUUIDVariables; - } | { path: "/v2/projects/{UUID}/sites"; operationId: "getV2ProjectsUUIDSites"; diff --git a/src/generated/v3/entityService/entityServiceComponents.ts b/src/generated/v3/entityService/entityServiceComponents.ts index 554d7e750..233f4b870 100644 --- a/src/generated/v3/entityService/entityServiceComponents.ts +++ b/src/generated/v3/entityService/entityServiceComponents.ts @@ -7,6 +7,218 @@ import type * as Fetcher from "./entityServiceFetcher"; import { entityServiceFetch } from "./entityServiceFetcher"; import type * as Schemas from "./entityServiceSchemas"; +export type EntityIndexPathParams = { + /** + * Entity type to retrieve + */ + entity: "projects" | "sites"; +}; + +export type EntityIndexQueryParams = { + /** + * The size of page being requested + * + * @minimum 1 + * @maximum 100 + * @default 100 + */ + ["page[size]"]?: number; + /** + * The page number to return. If neither page[after] nor page[number] is provided, the first page is returned. If page[number] is provided, page[size] is required. + */ + ["page[number]"]?: number; + ["sort[field]"]?: string; + /** + * @default ASC + */ + ["sort[direction]"]?: "ASC" | "DESC"; + search?: string; + country?: string; + status?: string; + updateRequestStatus?: string; +}; + +export type EntityIndexError = Fetcher.ErrorWrapper<{ + status: 400; + payload: { + /** + * @example 400 + */ + statusCode: number; + /** + * @example Bad Request + */ + message: string; + }; +}>; + +export type EntityIndexVariables = { + pathParams: EntityIndexPathParams; + queryParams?: EntityIndexQueryParams; +}; + +export const entityIndex = (variables: EntityIndexVariables, signal?: AbortSignal) => + entityServiceFetch< + | { + meta?: { + /** + * @example projects + */ + type?: string; + page?: { + /** + * The total number of records available. + * + * @example 42 + */ + total?: number; + /** + * The current page number. + */ + number?: number; + }; + }; + data?: { + /** + * @example projects + */ + type?: string; + /** + * @format uuid + */ + id?: string; + attributes?: Schemas.ProjectLightDto; + }[]; + } + | { + meta?: { + /** + * @example sites + */ + type?: string; + page?: { + /** + * The total number of records available. + * + * @example 42 + */ + total?: number; + /** + * The current page number. + */ + number?: number; + }; + }; + data?: { + /** + * @example sites + */ + type?: string; + /** + * @format uuid + */ + id?: string; + attributes?: Schemas.SiteLightDto; + }[]; + }, + EntityIndexError, + undefined, + {}, + EntityIndexQueryParams, + EntityIndexPathParams + >({ url: "/entities/v3/{entity}", method: "get", ...variables, signal }); + +export type EntityGetPathParams = { + /** + * Entity type to retrieve + */ + entity: "projects" | "sites"; + /** + * Entity UUID for resource to retrieve + */ + uuid: string; +}; + +export type EntityGetError = Fetcher.ErrorWrapper< + | { + status: 401; + payload: { + /** + * @example 401 + */ + statusCode: number; + /** + * @example Unauthorized + */ + message: string; + }; + } + | { + status: 404; + payload: { + /** + * @example 404 + */ + statusCode: number; + /** + * @example Not Found + */ + message: string; + }; + } +>; + +export type EntityGetVariables = { + pathParams: EntityGetPathParams; +}; + +export const entityGet = (variables: EntityGetVariables, signal?: AbortSignal) => + entityServiceFetch< + | { + meta?: { + /** + * @example projects + */ + type?: string; + }; + data?: { + /** + * @example projects + */ + type?: string; + /** + * @format uuid + */ + id?: string; + attributes?: Schemas.ProjectFullDto; + }; + } + | { + meta?: { + /** + * @example sites + */ + type?: string; + }; + data?: { + /** + * @example sites + */ + type?: string; + /** + * @format uuid + */ + id?: string; + attributes?: Schemas.SiteFullDto; + }; + }, + EntityGetError, + undefined, + {}, + {}, + EntityGetPathParams + >({ url: "/entities/v3/{entity}/{uuid}", method: "get", ...variables, signal }); + export type TreeScientificNamesSearchQueryParams = { search: string; }; @@ -14,6 +226,12 @@ export type TreeScientificNamesSearchQueryParams = { export type TreeScientificNamesSearchError = Fetcher.ErrorWrapper; export type TreeScientificNamesSearchResponse = { + meta?: { + /** + * @example treeSpeciesScientificNames + */ + type?: string; + }; data?: { /** * @example treeSpeciesScientificNames @@ -82,6 +300,12 @@ export type EstablishmentTreesFindError = Fetcher.ErrorWrapper< >; export type EstablishmentTreesFindResponse = { + meta?: { + /** + * @example establishmentTrees + */ + type?: string; + }; data?: { /** * @example establishmentTrees diff --git a/src/generated/v3/entityService/entityServicePredicates.ts b/src/generated/v3/entityService/entityServicePredicates.ts index 76e307ecb..c6c6ab979 100644 --- a/src/generated/v3/entityService/entityServicePredicates.ts +++ b/src/generated/v3/entityService/entityServicePredicates.ts @@ -1,12 +1,39 @@ import { isFetching, fetchFailed } from "../utils"; import { ApiDataStore } from "@/store/apiSlice"; import { + EntityIndexPathParams, + EntityIndexQueryParams, + EntityIndexVariables, + EntityGetPathParams, + EntityGetVariables, TreeScientificNamesSearchQueryParams, TreeScientificNamesSearchVariables, EstablishmentTreesFindPathParams, EstablishmentTreesFindVariables } from "./entityServiceComponents"; +export const entityIndexIsFetching = (variables: Omit) => (store: ApiDataStore) => + isFetching({ + store, + url: "/entities/v3/{entity}", + method: "get", + ...variables + }); + +export const entityIndexFetchFailed = (variables: Omit) => (store: ApiDataStore) => + fetchFailed({ + store, + url: "/entities/v3/{entity}", + method: "get", + ...variables + }); + +export const entityGetIsFetching = (variables: Omit) => (store: ApiDataStore) => + isFetching<{}, EntityGetPathParams>({ store, url: "/entities/v3/{entity}/{uuid}", method: "get", ...variables }); + +export const entityGetFetchFailed = (variables: Omit) => (store: ApiDataStore) => + fetchFailed<{}, EntityGetPathParams>({ store, url: "/entities/v3/{entity}/{uuid}", method: "get", ...variables }); + export const treeScientificNamesSearchIsFetching = (variables: Omit) => (store: ApiDataStore) => isFetching({ diff --git a/src/generated/v3/entityService/entityServiceSchemas.ts b/src/generated/v3/entityService/entityServiceSchemas.ts index 4e7a9f4fc..56fa06ec1 100644 --- a/src/generated/v3/entityService/entityServiceSchemas.ts +++ b/src/generated/v3/entityService/entityServiceSchemas.ts @@ -3,6 +3,265 @@ * * @version 1.0 */ +export type ANRDto = { + /** + * Site name + */ + name: string; + treeCount: number; +}; + +export type ProjectApplicationDto = { + uuid: string; + fundingProgrammeName: string; + projectPitchUuid: string; +}; + +export type MediaDto = { + uuid: string; + collectionName: string; + url: string; + thumbUrl: string; + name: string; + fileName: string; + mimeType: string | null; + size: number; + lat: number | null; + lng: number | null; + isPublic: boolean; + isCover: boolean; + /** + * @format date-time + */ + createdAt: string; + description: string | null; + photographer: string | null; +}; + +export type ProjectLightDto = { + /** + * Indicates if this resource has the full resource definition. + */ + lightResource: boolean; + uuid: string; + /** + * Framework key for this project + */ + frameworkKey: string | null; + /** + * Framework UUID. Will be removed after the FE is refactored to not use these IDs + * + * @deprecated true + */ + frameworkUuid: string | null; + /** + * The associated organisation name + */ + organisationName: string | null; + /** + * Entity status for this project + */ + status: "started" | "awaiting-approval" | "approved" | "needs-more-information" | null; + /** + * Update request status for this project + */ + updateRequestStatus: "draft" | "awaiting-approval" | "approved" | "needs-more-information" | null; + name: string | null; + /** + * @format date-time + */ + plantingStartDate: string | null; + /** + * @format date-time + */ + createdAt: string; + /** + * @format date-time + */ + updatedAt: string; +}; + +export type SiteLightDto = { + /** + * Indicates if this resource has the full resource definition. + */ + lightResource: boolean; + uuid: string; + /** + * Framework key for this project + */ + frameworkKey: Record | null; + /** + * Framework UUID. Will be removed after the FE is refactored to not use these IDs + * + * @deprecated true + */ + frameworkUuid: string | null; + /** + * Entity status for this project + */ + status: "started" | "awaiting-approval" | "approved" | "needs-more-information" | "restoration-in-progress" | null; + /** + * Update request status for this project + */ + updateRequestStatus: "draft" | "awaiting-approval" | "approved" | "needs-more-information" | null; + name: string | null; + /** + * @format date-time + */ + createdAt: string; + /** + * @format date-time + */ + updatedAt: string; +}; + +export type ProjectFullDto = { + /** + * Indicates if this resource has the full resource definition. + */ + lightResource: boolean; + uuid: string; + /** + * Framework key for this project + */ + frameworkKey: string | null; + /** + * Framework UUID. Will be removed after the FE is refactored to not use these IDs + * + * @deprecated true + */ + frameworkUuid: string | null; + /** + * The associated organisation name + */ + organisationName: string | null; + /** + * Entity status for this project + */ + status: "started" | "awaiting-approval" | "approved" | "needs-more-information" | null; + /** + * Update request status for this project + */ + updateRequestStatus: "draft" | "awaiting-approval" | "approved" | "needs-more-information" | null; + name: string | null; + /** + * @format date-time + */ + plantingStartDate: string | null; + /** + * @format date-time + */ + createdAt: string; + /** + * @format date-time + */ + updatedAt: string; + /** + * True for projects that are test data and do not represent actual planting on the ground. + */ + isTest: boolean; + feedback: string | null; + feedbackFields: string[] | null; + continent: string | null; + country: string | null; + states: string[] | null; + projectCountyDistrict: string | null; + /** + * @format date-time + */ + plantingEndDate: string | null; + budget: number | null; + history: string | null; + objectives: string | null; + environmentalGoals: string | null; + socioeconomicGoals: string | null; + sdgsImpacted: string | null; + totalHectaresRestoredGoal: number | null; + totalHectaresRestoredSum: number; + treesGrownGoal: number | null; + survivalRate: number | null; + landUseTypes: string[] | null; + restorationStrategy: string[] | null; + treesPlantedCount: number; + seedsPlantedCount: number; + regeneratedTreesCount: number; + workdayCount: number; + selfReportedWorkdayCount: number; + combinedWorkdayCount: number; + totalJobsCreated: number; + totalSites: number; + totalNurseries: number; + totalProjectReports: number; + totalOverdueReports: number; + descriptionOfProjectTimeline: string | null; + sitingStrategyDescription: string | null; + sitingStrategy: string | null; + landholderCommEngage: string | null; + projPartnerInfo: string | null; + seedlingsSource: string | null; + landTenureProjectArea: string[] | null; + projImpactBiodiv: string | null; + projImpactFoodsec: string | null; + proposedGovPartners: string | null; + treesRestoredPpc: number; + detailedInterventionTypes: string[] | null; + /** + * The list of tree counts regenerating naturally by site name + */ + assistedNaturalRegenerationList: ANRDto[]; + goalTreesRestoredAnr: number | null; + directSeedingSurvivalRate: number | null; + application: ProjectApplicationDto; + media: MediaDto[]; + socioeconomicBenefits: MediaDto[]; + file: MediaDto[]; + otherAdditionalDocuments: MediaDto[]; + photos: MediaDto[]; + documentFiles: MediaDto[]; + programmeSubmission: MediaDto[]; + detailedProjectBudget: MediaDto; + proofOfLandTenureMou: MediaDto[]; +}; + +export type SiteFullDto = { + /** + * Indicates that this resource has the full resource definition. + * + * @example false + */ + lightResource: boolean; + uuid: string; + /** + * Framework key for this project + */ + frameworkKey: Record | null; + /** + * Framework UUID. Will be removed after the FE is refactored to not use these IDs + * + * @deprecated true + */ + frameworkUuid: string | null; + /** + * Entity status for this project + */ + status: "started" | "awaiting-approval" | "approved" | "needs-more-information" | "restoration-in-progress" | null; + /** + * Update request status for this project + */ + updateRequestStatus: "draft" | "awaiting-approval" | "approved" | "needs-more-information" | null; + name: string | null; + /** + * @format date-time + */ + createdAt: string; + /** + * @format date-time + */ + updatedAt: string; + totalSiteReports: number; +}; + export type PreviousPlantingCountDto = { /** * Taxonomic ID for this tree species row diff --git a/src/generated/v3/jobService/jobServiceComponents.ts b/src/generated/v3/jobService/jobServiceComponents.ts index cff82641b..189bd58b8 100644 --- a/src/generated/v3/jobService/jobServiceComponents.ts +++ b/src/generated/v3/jobService/jobServiceComponents.ts @@ -22,6 +22,12 @@ export type ListDelayedJobsError = Fetcher.ErrorWrapper<{ }>; export type ListDelayedJobsResponse = { + meta?: { + /** + * @example delayedJobs + */ + type?: string; + }; data?: { /** * @example delayedJobs @@ -79,6 +85,12 @@ export type DelayedJobsFindError = Fetcher.ErrorWrapper< >; export type DelayedJobsFindResponse = { + meta?: { + /** + * @example delayedJobs + */ + type?: string; + }; data?: { /** * @example delayedJobs @@ -150,6 +162,12 @@ export type BulkUpdateJobsError = Fetcher.ErrorWrapper< >; export type BulkUpdateJobsResponse = { + meta?: { + /** + * @example delayedJobs + */ + type?: string; + }; data?: { /** * @example delayedJobs diff --git a/src/generated/v3/userService/userServiceComponents.ts b/src/generated/v3/userService/userServiceComponents.ts index 038808932..3ee9cb46a 100644 --- a/src/generated/v3/userService/userServiceComponents.ts +++ b/src/generated/v3/userService/userServiceComponents.ts @@ -22,6 +22,12 @@ export type AuthLoginError = Fetcher.ErrorWrapper<{ }>; export type AuthLoginResponse = { + meta?: { + /** + * @example logins + */ + type?: string; + }; data?: { /** * @example logins @@ -89,6 +95,12 @@ export type UsersFindError = Fetcher.ErrorWrapper< >; export type UsersFindResponse = { + meta?: { + /** + * @example users + */ + type?: string; + }; data?: { /** * @example users @@ -193,6 +205,12 @@ export type UserUpdateError = Fetcher.ErrorWrapper< >; export type UserUpdateResponse = { + meta?: { + /** + * @example users + */ + type?: string; + }; data?: { /** * @example users @@ -263,6 +281,12 @@ export type RequestPasswordResetError = Fetcher.ErrorWrapper<{ }>; export type RequestPasswordResetResponse = { + meta?: { + /** + * @example passwordResets + */ + type?: string; + }; data?: { /** * @example passwordResets @@ -310,6 +334,12 @@ export type ResetPasswordError = Fetcher.ErrorWrapper<{ }>; export type ResetPasswordResponse = { + meta?: { + /** + * @example passwordResets + */ + type?: string; + }; data?: { /** * @example passwordResets diff --git a/src/generated/v3/utils.ts b/src/generated/v3/utils.ts index 13b8c86a0..de6d8a9bb 100644 --- a/src/generated/v3/utils.ts +++ b/src/generated/v3/utils.ts @@ -15,9 +15,10 @@ type SelectorOptions = { const V3_NAMESPACES: Record = { auth: userServiceUrl, - users: userServiceUrl, + entities: entityServiceUrl, jobs: jobServiceUrl, - trees: entityServiceUrl + trees: entityServiceUrl, + users: userServiceUrl } as const; const getBaseUrl = (url: string) => { @@ -31,18 +32,28 @@ const getBaseUrl = (url: string) => { return baseUrl; }; +export const getStableQuery = (queryParams: Record) => { + // URLSearchParams will gleefully stringify undefined to "undefined" if you leave the key in place. + // For our implementation, we never want to send the string "null" or "undefined" to the server in + // the query, so delete any keys that have such a value. + for (const key of Object.keys(queryParams)) { + if (queryParams[key] == null) delete queryParams[key]; + } + const searchParams = new URLSearchParams(queryParams as Record); + // Make sure the output string always ends up in the same order because we need the URL string + // that is generated from a set of query / path params to be consistent even if the order of the + // params in the source object changes. + searchParams.sort(); + return searchParams.toString(); +}; + export const resolveUrl = ( url: string, queryParams: Record = {}, pathParams: Record = {} ) => { - const searchParams = new URLSearchParams(queryParams); - // Make sure the output string always ends up in the same order because we need the URL string - // that is generated from a set of query / path params to be consistent even if the order of the - // params in the source object changes. - searchParams.sort(); - let query = searchParams.toString(); - if (query) query = `?${query}`; + let query = getStableQuery(queryParams); + if (query.length > 0) query = `?${query}`; return `${getBaseUrl(url)}${url.replace(/\{\w*}/g, key => pathParams[key.slice(1, -1)]) + query}`; }; diff --git a/src/hooks/AuditStatus/useAuditLogActions.ts b/src/hooks/AuditStatus/useAuditLogActions.ts index f4fd66575..d1fabc890 100644 --- a/src/hooks/AuditStatus/useAuditLogActions.ts +++ b/src/hooks/AuditStatus/useAuditLogActions.ts @@ -138,7 +138,7 @@ const useAuditLogActions = ({ fetchCriteriaValidation(); fetchCheckPolygons(); } - }, [entityType, isPolygon, isSite, isSiteProject, record, selected, verifyEntity]); + }, [entityType, isPolygon, isSite, isSiteProject, record?.uuid, selected, verifyEntity]); const isValidCriteriaData = (criteriaData: any) => { if (!criteriaData?.criteria_list?.length) { diff --git a/src/hooks/useDate.ts b/src/hooks/useDate.ts index f2503de8f..7cd5757c6 100644 --- a/src/hooks/useDate.ts +++ b/src/hooks/useDate.ts @@ -19,7 +19,7 @@ export const useDate = () => { * @returns string */ const format = useCallback( - (date?: string | number | Date, format = "dd/MM/yyyy") => { + (date?: string | number | Date | null, format = "dd/MM/yyyy") => { if (!date) return ""; return _format(new Date(date), format, { locale: Locales[formattedLocale] ?? Locales.enUS }); diff --git a/src/hooks/useFormUpdate.ts b/src/hooks/useFormUpdate.ts index 87df51a1d..2b3289547 100644 --- a/src/hooks/useFormUpdate.ts +++ b/src/hooks/useFormUpdate.ts @@ -2,6 +2,7 @@ import { isEqual } from "lodash"; import { useEffect, useReducer } from "react"; import { v4 as uuidv4 } from "uuid"; +import { pruneEntityCache } from "@/connections/Entity"; import { PutV2FormsENTITYUUIDRequestBody, PutV2FormsENTITYUUIDResponse, @@ -72,6 +73,9 @@ export const useFormUpdate = (entityName: EntityName, entityUUID: string) => { const updateEntity = (body: PutV2FormsENTITYUUIDResponse) => { dispatch({ type: "addUpdate", body }); + // When an entity is updated via form, we want to forget the cached copy we might have from v3 + // so it gets re-fetched when a component needs it. + pruneEntityCache(entityName, entityUUID); }; return { updateEntity, error, isSuccess, isUpdating }; diff --git a/src/hooks/useGetOptions.ts b/src/hooks/useGetOptions.ts index 284a14fb8..a8a513703 100644 --- a/src/hooks/useGetOptions.ts +++ b/src/hooks/useGetOptions.ts @@ -9,7 +9,7 @@ import { toArray } from "@/utils/array"; * @param keys option keys * @returns Option[] */ -export const useGetOptions = (keys: string[]): Option[] => { +export const useGetOptions = (keys?: string[] | null): Option[] => { const { locale } = useRouter(); const _keys = toArray(keys); diff --git a/src/pages/entity/[entityName]/edit/[uuid]/EditEntityForm.tsx b/src/pages/entity/[entityName]/edit/[uuid]/EditEntityForm.tsx index 248cd25b9..005d9a88e 100644 --- a/src/pages/entity/[entityName]/edit/[uuid]/EditEntityForm.tsx +++ b/src/pages/entity/[entityName]/edit/[uuid]/EditEntityForm.tsx @@ -3,6 +3,7 @@ import { useRouter } from "next/router"; import { useMemo } from "react"; import WizardForm from "@/components/extensive/WizardForm"; +import { pruneEntityCache } from "@/connections/Entity"; import EntityProvider from "@/context/entity.provider"; import { useFrameworkContext } from "@/context/framework.provider"; import { GetV2FormsENTITYUUIDResponse, usePutV2FormsENTITYUUIDSubmit } from "@/generated/apiComponents"; @@ -34,6 +35,10 @@ const EditEntityForm = ({ entityName, entityUUID, entity, formData }: EditEntity const { updateEntity, error, isSuccess, isUpdating } = useFormUpdate(entityName, entityUUID); const { mutate: submitEntity, isLoading: isSubmitting } = usePutV2FormsENTITYUUIDSubmit({ onSuccess() { + // When an entity is submitted via form, we want to forget the cached copy we might have from + // v3 so it gets re-fetched when a component needs it. + pruneEntityCache(entityName, entityUUID); + if (mode === "edit" || mode?.includes("provide-feedback")) { router.push(getEntityDetailPageLink(entityName, entityUUID)); } else { diff --git a/src/pages/my-projects/index.page.tsx b/src/pages/my-projects/index.page.tsx index 02fbc5ac8..61bfbb229 100644 --- a/src/pages/my-projects/index.page.tsx +++ b/src/pages/my-projects/index.page.tsx @@ -2,7 +2,7 @@ import { useT } from "@transifex/react"; import Head from "next/head"; import Link from "next/link"; import { Fragment } from "react"; -import { Else, If, Then, When } from "react-if"; +import { Else, If, Then } from "react-if"; import Button from "@/components/elements/Button/Button"; import ProjectCard from "@/components/elements/Cards/ProjectCard/ProjectCard"; @@ -15,17 +15,17 @@ import PageFooter from "@/components/extensive/PageElements/Footer/PageFooter"; import PageHeader from "@/components/extensive/PageElements/Header/PageHeader"; import PageSection from "@/components/extensive/PageElements/Section/PageSection"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; +import { useProjectIndex } from "@/connections/Entity"; import { useMyOrg } from "@/connections/Organisation"; import { ToastType, useToastContext } from "@/context/toast.provider"; -import { GetV2MyProjectsResponse, useDeleteV2ProjectsUUID, useGetV2MyProjects } from "@/generated/apiComponents"; +import { useDeleteV2ProjectsUUID } from "@/generated/apiComponents"; const MyProjectsPage = () => { const t = useT(); const [, { organisation }] = useMyOrg(); const { openToast } = useToastContext(); - const { data: projectsData, isLoading, refetch } = useGetV2MyProjects<{ data: GetV2MyProjectsResponse }>({}); - const projects = projectsData?.data || []; + const [isLoaded, { entities: projects, refetch }] = useProjectIndex(); const { mutate: deleteProject } = useDeleteV2ProjectsUUID({ onSuccess() { @@ -37,61 +37,59 @@ const MyProjectsPage = () => { } }); + const hasProjects = projects != null && projects.length > 0; return ( <> {t("My Projects")} - 0}> + {hasProjects && ( - + )} - - 0}> - - - - {t("Projects I’m monitoring ({count})", { count: projects?.length })} - - ( - { - deleteProject({ pathParams: { uuid } }); - }} - /> - )} - /> - - - - - - - - + + {hasProjects ? ( + + + {t("Projects I’m monitoring ({count})", { count: projects.length })} + + ( + { + deleteProject({ pathParams: { uuid } }); + }} + /> + )} + /> + + ) : ( + + + + )} diff --git a/src/pages/organization/[id]/components/projects/ProjectsTabContent.tsx b/src/pages/organization/[id]/components/projects/ProjectsTabContent.tsx index 05152dedd..40048c7b5 100644 --- a/src/pages/organization/[id]/components/projects/ProjectsTabContent.tsx +++ b/src/pages/organization/[id]/components/projects/ProjectsTabContent.tsx @@ -11,18 +11,18 @@ import Text from "@/components/elements/Text/Text"; import { getActionCardStatusMapper } from "@/components/extensive/ActionTracker/ActionTrackerCard"; import { IconNames } from "@/components/extensive/Icon/Icon"; import Container from "@/components/generic/Layout/Container"; +import { useProjectIndex } from "@/connections/Entity"; import { getCountriesOptions } from "@/constants/options/countries"; -import { GetV2MyProjectsResponse, useGetV2MyProjects } from "@/generated/apiComponents"; +import { ProjectLightDto } from "@/generated/v3/entityService/entityServiceSchemas"; import { formatOptionsList } from "@/utils/options"; const ProjectsTabContent = () => { const t = useT(); - - const { data: projectsData } = useGetV2MyProjects<{ data: GetV2MyProjectsResponse }>({}); + const [, { entities: projects }] = useProjectIndex(); return ( - 0}> + 0}>
@@ -33,7 +33,7 @@ const ProjectsTabContent = () => {
- + variant={VARIANT_TABLE_BORDER_ALL} columns={[ { header: t("Title"), accessorKey: "name" }, @@ -76,7 +76,7 @@ const ProjectsTabContent = () => { ) } ]} - data={projectsData?.data || []} + data={projects ?? []} initialTableState={{ pagination: { pageSize: 5 } }} />
diff --git a/src/pages/project/[uuid]/index.page.tsx b/src/pages/project/[uuid]/index.page.tsx index e31052a73..c2e73e810 100644 --- a/src/pages/project/[uuid]/index.page.tsx +++ b/src/pages/project/[uuid]/index.page.tsx @@ -7,10 +7,11 @@ import PageBreadcrumbs from "@/components/extensive/PageElements/Breadcrumbs/Pag import PageFooter from "@/components/extensive/PageElements/Footer/PageFooter"; import Loader from "@/components/generic/Loading/Loader"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; +import { useFullProject } from "@/connections/Entity"; import FrameworkProvider, { Framework } from "@/context/framework.provider"; import { useLoading } from "@/context/loaderAdmin.provider"; import { MapAreaProvider } from "@/context/mapArea.provider"; -import { useGetV2ProjectsUUID } from "@/generated/apiComponents"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; import ProjectHeader from "@/pages/project/[uuid]/components/ProjectHeader"; import StatusBar from "@/pages/project/[uuid]/components/StatusBar"; import ProjectDetailTab from "@/pages/project/[uuid]/tabs/Details"; @@ -29,77 +30,83 @@ const ButtonStates = { POLYGON: 2 }; -const ProjectDetailPage = () => { +const ProjectContent = ({ project, refetch }: { project: ProjectFullDto; refetch: () => void }) => { const t = useT(); + return ( + <> + + {t("Project")} + + + + + }, + { key: "details", title: t("Details"), body: }, + { + key: "gallery", + title: t("Gallery"), + body: ( + + ) + }, + { key: "goals", title: t("Progress & Goals"), body: }, + { key: "sites", title: t("Sites"), body: }, + { + key: "nurseries", + title: t("Nurseries"), + body: , + hide: [Framework.PPC] + }, + { + key: "reporting-tasks", + title: t("Reporting Tasks"), + body: + }, + { + key: "audit-log", + title: t("Audit Log"), + body: + } + ]} + containerClassName="max-w-[82vw] px-10 xl:px-0 w-full" + /> + + + ); +}; + +const ProjectDetailPage = () => { const router = useRouter(); const { loading } = useLoading(); const projectUUID = router.query.uuid as string; - const { data, isLoading, refetch } = useGetV2ProjectsUUID({ - pathParams: { uuid: projectUUID } - }); - - const project = (data?.data || {}) as any; + const [isLoaded, { entity: project, refetch }] = useFullProject({ uuid: projectUUID }); return ( - - - {loading && ( -
- -
- )} - - - {t("Project")} - - - - - }, - { key: "details", title: t("Details"), body: }, - { - key: "gallery", - title: t("Gallery"), - body: ( - - ) - }, - { key: "goals", title: t("Progress & Goals"), body: }, - { key: "sites", title: t("Sites"), body: }, - { - key: "nurseries", - title: t("Nurseries"), - body: , - hide: [Framework.PPC] - }, - { - key: "reporting-tasks", - title: t("Reporting Tasks"), - body: - }, - { - key: "audit-log", - title: t("Audit Log"), - body: - } - ]} - containerClassName="max-w-[82vw] px-10 xl:px-0 w-full" - /> - - -
-
+ (!isLoaded || project != null) && ( + + + {loading && ( +
+ +
+ )} + + {project && } + +
+
+ ) ); }; diff --git a/src/pages/project/[uuid]/reporting-task/[reportingTaskUUID].page.tsx b/src/pages/project/[uuid]/reporting-task/[reportingTaskUUID].page.tsx index b69a2a05c..0bec0b6f0 100644 --- a/src/pages/project/[uuid]/reporting-task/[reportingTaskUUID].page.tsx +++ b/src/pages/project/[uuid]/reporting-task/[reportingTaskUUID].page.tsx @@ -21,11 +21,11 @@ import PageSection from "@/components/extensive/PageElements/Section/PageSection import { CompletionStatusMapping } from "@/components/extensive/Tables/ReportingTasksTable"; import WelcomeTour from "@/components/extensive/WelcomeTour/WelcomeTour"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; +import { useFullProject } from "@/connections/Entity"; import FrameworkProvider from "@/context/framework.provider"; import { useModalContext } from "@/context/modal.provider"; import { GetV2TasksUUIDReportsResponse, - useGetV2ProjectsUUID, useGetV2TasksUUID, useGetV2TasksUUIDReports, usePutV2ENTITYUUIDNothingToReport @@ -89,10 +89,7 @@ const ReportingTaskPage = () => { const reportingTask = reportingTaskData?.data as any; const { data: reportsData, isLoading } = useGetV2TasksUUIDReports({ pathParams: { uuid: reportingTaskUUID } }); - const { data: projectData } = useGetV2ProjectsUUID({ - pathParams: { uuid: projectUUID } - }); - const project = (projectData?.data ?? {}) as any; + const [projectLoaded, { entity: project }] = useFullProject({ uuid: projectUUID }); const { mutate: submitNothingToReport } = usePutV2ENTITYUUIDNothingToReport({ onSuccess: result => { @@ -252,65 +249,67 @@ const ReportingTaskPage = () => { ]; return ( - - - - - - - - - - - - -
setFilters(state.filters)} - hasPagination={true} - resetOnDataChange={false} - initialTableState={{ pagination: { pageSize: 15 } }} - columnFilters={[ - { - type: "dropDown", - accessorKey: "type", - label: t("Report type"), - options: [ - { - title: t("Site"), - value: "site-report" - }, - { - title: t("Nursery"), - value: "nursery-report" - } - ], - hide: project.framework_key === "ppc" - }, - { - type: "dropDown", - accessorKey: "completion_status", - label: t("Report Status"), - options: Object.entries(CompletionStatusMapping(t)).map(([value, status]: any) => ({ - title: status.statusText, - value - })) - } - ]} - /> - - - setTourEnabled(true)} - onFinish={() => setTourEnabled(false)} - /> - - - + projectLoaded && ( + + + + + + + +
+ + + + +
setFilters(state.filters)} + hasPagination={true} + resetOnDataChange={false} + initialTableState={{ pagination: { pageSize: 15 } }} + columnFilters={[ + { + type: "dropDown", + accessorKey: "type", + label: t("Report type"), + options: [ + { + title: t("Site"), + value: "site-report" + }, + { + title: t("Nursery"), + value: "nursery-report" + } + ], + hide: project?.frameworkKey === "ppc" + }, + { + type: "dropDown", + accessorKey: "completion_status", + label: t("Report Status"), + options: Object.entries(CompletionStatusMapping(t)).map(([value, status]: any) => ({ + title: status.statusText, + value + })) + } + ]} + /> + + + setTourEnabled(true)} + onFinish={() => setTourEnabled(false)} + /> + + + + ) ); }; diff --git a/src/pages/project/[uuid]/tabs/AuditLog.tsx b/src/pages/project/[uuid]/tabs/AuditLog.tsx index 372184981..1abd2b36d 100644 --- a/src/pages/project/[uuid]/tabs/AuditLog.tsx +++ b/src/pages/project/[uuid]/tabs/AuditLog.tsx @@ -11,11 +11,12 @@ import PageCard from "@/components/extensive/PageElements/Card/PageCard"; import PageColumn from "@/components/extensive/PageElements/Column/PageColumn"; import PageRow from "@/components/extensive/PageElements/Row/PageRow"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; import useAuditLogActions from "@/hooks/AuditStatus/useAuditLogActions"; import { useValueChanged } from "@/hooks/useValueChanged"; interface ReportingTasksProps { - project: any; + project: ProjectFullDto; label?: string; refresh?: () => void; enableChangeStatus?: number; diff --git a/src/pages/project/[uuid]/tabs/Details.tsx b/src/pages/project/[uuid]/tabs/Details.tsx index d6ae93f67..905fed8b8 100644 --- a/src/pages/project/[uuid]/tabs/Details.tsx +++ b/src/pages/project/[uuid]/tabs/Details.tsx @@ -1,6 +1,6 @@ +import { Paper } from "@mui/material"; import { useT } from "@transifex/react"; import Link from "next/link"; -import { Else, If, Then, When } from "react-if"; import Button from "@/components/elements/Button/Button"; import UserProfileCard from "@/components/elements/Cards/UserProfileCard/UserProfileCard"; @@ -8,7 +8,6 @@ import ButtonField from "@/components/elements/Field/ButtonField"; import LongTextField from "@/components/elements/Field/LongTextField"; import SelectImageListField from "@/components/elements/Field/SelectImageListField"; import TextField from "@/components/elements/Field/TextField"; -import Paper from "@/components/elements/Paper/Paper"; import List from "@/components/extensive/List/List"; import { ModalId } from "@/components/extensive/Modal/ModalConst"; import PageBody from "@/components/extensive/PageElements/Body/PageBody"; @@ -19,33 +18,34 @@ import { getCountriesOptions } from "@/constants/options/countries"; import { getLandTenureOptions } from "@/constants/options/landTenure"; import { getRestorationStrategyOptions } from "@/constants/options/restorationStrategy"; import { ContextCondition } from "@/context/ContextCondition"; -import { ALL_TF, Framework } from "@/context/framework.provider"; +import { ALL_TF, Framework, useFrameworkContext } from "@/context/framework.provider"; import { useModalContext } from "@/context/modal.provider"; import { GetV2ProjectsUUIDPartnersResponse, useGetV2ProjectsUUIDPartners } from "@/generated/apiComponents"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; import { useDate } from "@/hooks/useDate"; import { useGetOptions } from "@/hooks/useGetOptions"; import InviteMonitoringPartnerModal from "@/pages/project/[uuid]/components/InviteMonitoringPartnerModal"; import { formatOptionsList } from "@/utils/options"; interface ProjectDetailsTabProps { - project: any; + project: ProjectFullDto; } const ProjectDetailTab = ({ project }: ProjectDetailsTabProps) => { const t = useT(); const { format } = useDate(); const { openModal } = useModalContext(); + const { framework } = useFrameworkContext(); const restorationOptions = getRestorationStrategyOptions(t); const filterRestorationStrategyOptions = restorationOptions - .filter(option => project.restoration_strategy?.includes(option.value)) + .filter(option => project.restorationStrategy?.includes(option.value as string)) .map(option => option.value.toString()); - const landUseTypesOptions = useGetOptions(project.land_use_types); - const sdgsImpactedOptions = useGetOptions(project.sdgs_impacted); + const landUseTypesOptions = useGetOptions(project.landUseTypes); const restorationStrategyOptions = useGetOptions(filterRestorationStrategyOptions); - const detailedInterventionTypeOptions = useGetOptions(project.detailed_intervention_types); + const detailedInterventionTypeOptions = useGetOptions(project.detailedInterventionTypes); const { data: partners, refetch } = useGetV2ProjectsUUIDPartners<{ data: GetV2ProjectsUUIDPartnersResponse }>({ pathParams: { uuid: project.uuid } @@ -58,173 +58,149 @@ const ProjectDetailTab = ({ project }: ProjectDetailsTabProps) => { ); }; + const downloadButtons: JSX.Element[] = []; + if (framework === Framework.PPC) { + project.file.forEach(({ url, fileName }) => { + downloadButtons.push( + + ); + }); + project.otherAdditionalDocuments.forEach(({ url, fileName }) => { + downloadButtons.push( + + ); + }); + } else if (framework === Framework.TF_LANDSCAPES) { + project.proofOfLandTenureMou.forEach(({ url, fileName }) => { + downloadButtons.push( + + ); + }); + } + return ( - - {project.description_of_project_timeline} - + {project.descriptionOfProjectTimeline} {project.history} {detailedInterventionTypeOptions.map(({ title }) => title).join(", ")} - {format(project.planting_start_date)} + {format(project.plantingStartDate)} - {format(project.planting_end_date)} + {format(project.plantingEndDate)} {project.objectives} - {project.environmental_goals} - {project.socioeconomic_goals} + {project.environmentalGoals} + {project.socioeconomicGoals} - {project.landholder_comm_engage} + {project.landholderCommEngage} - {project.proj_partner_info} + {project.projPartnerInfo} - {project.proj_impact_biodiv} - {project.proj_impact_foodsec} + {project.projImpactBiodiv} + {project.projImpactFoodsec} - {project.proposed_gov_partners} - - {project.seedlings_source} - {project.siting_strategy} - - {project.siting_strategy_description} - + {project.proposedGovPartners} + {project.sdgsImpacted} + {project.seedlingsSource} + {project.sitingStrategy} + {project.sitingStrategyDescription} - - - - - + + + + + - +
- - -

{t("Files not found")}

-
- - - - {project.file?.map((document: any, index: any) => ( - - ))} - {project.other_additional_documents?.map((document: any, index: any) => ( - - ))} - - - {project?.proof_of_land_tenure_mou?.map((document: any, index: any) => ( - - ))} - - - -
+ {downloadButtons.length === 0 ?

{t("Files not found")}

: <>{downloadButtons}}
- - - - - - - - - - + {project.application && ( + <> + + + + + + + + )} 0 ? `(${partners?.data.length})` : ""}`} headerChildren={ diff --git a/src/pages/project/[uuid]/tabs/GoalsAndProgress.tsx b/src/pages/project/[uuid]/tabs/GoalsAndProgress.tsx index 989f420a9..27c9ddb96 100644 --- a/src/pages/project/[uuid]/tabs/GoalsAndProgress.tsx +++ b/src/pages/project/[uuid]/tabs/GoalsAndProgress.tsx @@ -17,11 +17,12 @@ import { TEXT_TYPES } from "@/constants/dashboardConsts"; import { ContextCondition } from "@/context/ContextCondition"; import { ALL_TF, Framework } from "@/context/framework.provider"; import { useGetV2EntityUUIDAggregateReports } from "@/generated/apiComponents"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; import GoalsAndProgressEntityTab from "@/pages/site/[uuid]/components/GoalsAndProgressEntityTab"; import { getNewRestorationGoalDataForChart } from "@/utils/dashboardUtils"; interface GoalsAndProgressProps { - project: any; + project: ProjectFullDto; } interface NaturalRegenerationItem { @@ -44,265 +45,6 @@ export const LABEL_LEGEND = [ } ]; -export const dataTreeCount = [ - { - name: ["Species scientific name", "tree"], - treeCount: "45,000" - }, - { - name: ["Species scientific name", "Native species"], - treeCount: "45,000" - }, - { - name: ["Species scientific name", "tree"], - treeCount: "10,350" - }, - { - name: ["Species scientific name", "tree"], - treeCount: "7,500" - }, - { - name: ["Non-scientific name", "non-scientific"], - treeCount: "4,040" - }, - { - name: ["Species scientific name", "tree"], - treeCount: "3,200" - }, - { - name: ["Species scientific name", "new"], - treeCount: "3,000" - }, - { - name: ["Species scientific name", "tree"], - treeCount: "0" - } -]; - -export const dataSeedCount = [ - { - name: ["Species scientific name", "tree"], - seedCount: "45,000" - }, - { - name: ["Species scientific name", "Native species"], - seedCount: "45,000" - }, - { - name: ["Species scientific name", "tree"], - seedCount: "10,350" - }, - { - name: ["Species scientific name", "tree"], - seedCount: "7,500" - } -]; -export const dataNonTreeCount = [ - { - name: ["Species scientific name", "tree"], - nonTreeCount: "45,000" - }, - { - name: ["Species scientific name", "Native species"], - nonTreeCount: "45,000" - }, - { - name: ["Species scientific name", "tree"], - nonTreeCount: "10,350" - }, - { - name: ["Species scientific name", "tree"], - nonTreeCount: "7,500" - } -]; - -export const dataTreeCountSite = [ - { - name: "Site Name", - treeCount: "2,500" - }, - { - name: "Site Name", - treeCount: "1,850" - }, - { - name: "Site Name", - treeCount: "1,000" - }, - { - name: "Site Name", - treeCount: "960" - }, - { - name: "Site Name", - treeCount: "620" - }, - { - name: "Site Name", - treeCount: "450" - }, - { - name: "Site Name", - treeCount: "300" - } -]; - -export const dataTreeCountGoal = [ - { - name: ["Species scientific name", "tree"], - treeCountGoal: ["45,0000", "90,000"] - }, - { - name: ["Species scientific name", "Native species"], - treeCountGoal: ["35,350", "70,000"] - }, - { - name: ["Species scientific name", "tree"], - treeCountGoal: ["10,350", "35,000"] - }, - { - name: ["Species scientific name", "tree"], - treeCountGoal: ["7,500", "21,000"] - }, - { - name: ["Non-scientific name", "tree"], - treeCountGoal: ["4,040", "15,300"] - }, - { - name: ["Species scientific name", "tree"], - treeCountGoal: ["3,200", "8,000"] - }, - { - name: ["Species scientific name", "new"], - treeCountGoal: ["3,000", "5,000"] - }, - { - name: ["Species scientific name", "tree"], - treeCountGoal: ["1,000", "4,500"] - }, - { - name: ["Species scientific name", "tree"], - treeCountGoal: ["0", "3,000"] - } -]; - -export const dataSpeciesCountGoal = [ - { - name: ["Species scientific name", "tree"], - speciesCountGoal: ["45,0000", "90,000"] - }, - { - name: ["Species scientific name", "Native species"], - speciesCountGoal: ["35,350", "70,000"] - }, - { - name: ["Species scientific name", "tree"], - speciesCountGoal: ["10,350", "35,000"] - }, - { - name: ["Species scientific name", "tree"], - speciesCountGoal: ["7,500", "21,000"] - }, - { - name: ["Non-scientific name", "tree"], - speciesCountGoal: ["4,040", "15,300"] - }, - { - name: ["Species scientific name", "tree"], - speciesCountGoal: ["3,200", "8,000"] - }, - { - name: ["Species scientific name", "new"], - speciesCountGoal: ["3,000", "5,000"] - }, - { - name: ["Species scientific name", "tree"], - speciesCountGoal: ["1,000", "4,500"] - }, - { - name: ["Species scientific name", "tree"], - speciesCountGoal: ["0", "3,000"] - } -]; - -export const dataSeedCountGoal = [ - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["45,0000", "90,000"] - }, - { - name: ["Species scientific name", "Native species"], - seedCountGoal: ["35,350", "70,000"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["10,350", "35,000"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["7,500", "21,000"] - }, - { - name: ["Non-scientific name", "tree"], - seedCountGoal: ["4,040", "15,300"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["3,200", "8,000"] - }, - { - name: ["Species scientific name", "new"], - seedCountGoal: ["3,000", "5,000"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["1,000", "4,500"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["0", "3,000"] - } -]; - -export const dataSeedCountGoalSiteReport = [ - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["45,0000", "90,000"] - }, - { - name: ["Species scientific name", "Native species", "approved"], - seedCountGoal: ["35,350", "70,000"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["10,350", "35,000"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["7,500", "21,000"] - }, - { - name: ["Non-scientific name", "tree", "approved"], - seedCountGoal: ["4,040", "15,300"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["3,200", "8,000"] - }, - { - name: ["Species scientific name", "new"], - seedCountGoal: ["3,000", "5,000"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["1,000", "4,500"] - }, - { - name: ["Species scientific name", "tree"], - seedCountGoal: ["0", "3,000"] - } -]; - const getProgressData = (totalValue: number, progressValue: number) => { return [ { name: "Total", value: totalValue, color: "#13487A" }, @@ -330,7 +72,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { } }); - const formatNaturalGenerationData = project.assisted_natural_regeneration_list + const formatNaturalGenerationData = project.assistedNaturalRegenerationList .sort((a: NaturalRegenerationItem, b: NaturalRegenerationItem) => b.treeCount - a.treeCount) .map((item: NaturalRegenerationItem) => { return { @@ -339,7 +81,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { }; }); - const isTerrafund = ALL_TF.includes(project.framework_key as Framework); + const isTerrafund = ALL_TF.includes(project.frameworkKey as Framework); return ( @@ -350,7 +92,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => {
@@ -365,7 +107,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { variantLabel: "text-14", classNameLabel: " text-neutral-650 uppercase !w-auto", classNameLabelValue: "!justify-start ml-2 !text-2xl", - value: project.trees_planted_count + value: project.treesPlantedCount }, { iconName: IconNames.SURVIVAL_RATE, @@ -373,7 +115,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { variantLabel: "text-14", classNameLabel: " text-neutral-650 uppercase !w-auto", classNameLabelValue: "!justify-start ml-2 !text-2xl", - value: project.survival_rate ? `${project.survival_rate}%` : "N/A" + value: project.survivalRate ? `${project.survivalRate}%` : "N/A" }, { iconName: IconNames.LEAF_PLANTED_CIRCLE, @@ -396,7 +138,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => {
@@ -408,9 +150,9 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { />
- {project.trees_planted_count.toLocaleString()} + {project.treesPlantedCount.toLocaleString()} - of {project.trees_grown_goal.toLocaleString()} + of {(project.treesGrownGoal ?? 0).toLocaleString()}
@@ -464,7 +206,6 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { { { { variantLabel: "text-14", classNameLabel: " text-neutral-650 uppercase !w-auto", classNameLabelValue: "!justify-start ml-2 !text-2xl", - value: project.seeds_planted_count + value: project.seedsPlantedCount }, { iconName: IconNames.SURVIVAL_RATE, @@ -522,7 +261,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { variantLabel: "text-14", classNameLabel: " text-neutral-650 uppercase !w-auto", classNameLabelValue: "!justify-start ml-2 !text-2xl", - value: project.direct_seeding_survival_rate ? `${project.direct_seeding_survival_rate}%` : "N/A" + value: project.directSeedingSurvivalRate != null ? `${project.directSeedingSurvivalRate}%` : "N/A" }, { iconName: IconNames.LEAF_PLANTED_CIRCLE, @@ -566,12 +305,6 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => {
-
- -
{ />
- {project.seeds_planted_count.toLocaleString()} - - of {project.seeds_grown_goal ? project.seeds_grown_goal.toLocaleString() : "0"} - + {project.seedsPlantedCount.toLocaleString()}
{ {
@@ -655,9 +381,9 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { />
- {project.regenerated_trees_count.toLocaleString()} + {project.regeneratedTreesCount.toLocaleString()} - of {project.goal_trees_restored_anr?.toLocaleString()} + of {project.goalTreesRestoredAnr?.toLocaleString()}
@@ -674,7 +400,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => { variantLabel: "text-14", classNameLabel: " text-neutral-650 uppercase !w-auto", classNameLabelValue: "!justify-start ml-2 !text-2xl", - value: project.regenerated_trees_count.toLocaleString() + value: project.regeneratedTreesCount.toLocaleString() } ]} /> @@ -704,10 +430,7 @@ const GoalsAndProgressTab = ({ project }: GoalsAndProgressProps) => {
diff --git a/src/pages/project/[uuid]/tabs/Overview.tsx b/src/pages/project/[uuid]/tabs/Overview.tsx index a978180d5..e5ede3c04 100644 --- a/src/pages/project/[uuid]/tabs/Overview.tsx +++ b/src/pages/project/[uuid]/tabs/Overview.tsx @@ -1,6 +1,5 @@ import { useT } from "@transifex/react"; import Link from "next/link"; -import { useRouter } from "next/router"; import Button from "@/components/elements/Button/Button"; import ItemMonitoringCards from "@/components/elements/Cards/ItemMonitoringCard/ItemMonitoringCards"; @@ -12,15 +11,15 @@ import PageBody from "@/components/extensive/PageElements/Body/PageBody"; import PageCard from "@/components/extensive/PageElements/Card/PageCard"; import PageColumn from "@/components/extensive/PageElements/Column/PageColumn"; import PageRow from "@/components/extensive/PageElements/Row/PageRow"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; import GoalsAndProgressEntityTab from "@/pages/site/[uuid]/components/GoalsAndProgressEntityTab"; interface ProjectOverviewTabProps { - project: any; + project: ProjectFullDto; } const ProjectOverviewTab = ({ project }: ProjectOverviewTabProps) => { const t = useT(); - const router = useRouter(); return ( @@ -32,7 +31,7 @@ const ProjectOverviewTab = ({ project }: ProjectOverviewTabProps) => { as={Link} variant="secondary" className="m-auto" - href={`/project/${router.query.uuid}?tab=goals`} + href={`/project/${project.uuid}?tab=goals`} shallow > {t("View all")} @@ -50,7 +49,7 @@ const ProjectOverviewTab = ({ project }: ProjectOverviewTabProps) => { diff --git a/src/pages/project/[uuid]/tabs/ProjectNurseries.tsx b/src/pages/project/[uuid]/tabs/ProjectNurseries.tsx index 0654e605b..9c73d1fad 100644 --- a/src/pages/project/[uuid]/tabs/ProjectNurseries.tsx +++ b/src/pages/project/[uuid]/tabs/ProjectNurseries.tsx @@ -11,9 +11,10 @@ import PageRow from "@/components/extensive/PageElements/Row/PageRow"; import NurseriesTable from "@/components/extensive/Tables/NurseriesTable"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; import { useGetV2ProjectsUUIDNurseries } from "@/generated/apiComponents"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; interface ProjectNurseriesTabProps { - project: any; + project: ProjectFullDto; } const ProjectNurseriesTab = ({ project }: ProjectNurseriesTabProps) => { @@ -45,7 +46,7 @@ const ProjectNurseriesTab = ({ project }: ProjectNurseriesTabProps) => { )} ctaProps={{ as: Link, - href: `/entity/nurseries/create/${project.framework_uuid}?parent_name=projects&parent_uuid=${project.uuid}`, + href: `/entity/nurseries/create/${project.frameworkUuid}?parent_name=projects&parent_uuid=${project.uuid}`, children: "Add Nursery" }} /> diff --git a/src/pages/project/[uuid]/tabs/ProjectSites.tsx b/src/pages/project/[uuid]/tabs/ProjectSites.tsx index 17f34b003..afbfdb650 100644 --- a/src/pages/project/[uuid]/tabs/ProjectSites.tsx +++ b/src/pages/project/[uuid]/tabs/ProjectSites.tsx @@ -11,9 +11,10 @@ import PageRow from "@/components/extensive/PageElements/Row/PageRow"; import SitesTable from "@/components/extensive/Tables/SitesTable"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; import { useGetV2ProjectsUUIDSites } from "@/generated/apiComponents"; +import { ProjectFullDto } from "@/generated/v3/entityService/entityServiceSchemas"; interface ProjectNurseriesTabProps { - project: any; + project: ProjectFullDto; } const ProjectSitesTab = ({ project }: ProjectNurseriesTabProps) => { @@ -48,7 +49,7 @@ const ProjectSitesTab = ({ project }: ProjectNurseriesTabProps) => { )} ctaProps={{ as: Link, - href: `/entity/sites/create/${project.framework_uuid}?parent_name=projects&parent_uuid=${project.uuid}`, + href: `/entity/sites/create/${project.frameworkUuid}?parent_name=projects&parent_uuid=${project.uuid}`, children: "Add Site" }} /> diff --git a/src/pages/reports/project-report/[uuid].tsx b/src/pages/reports/project-report/[uuid].tsx index aba53e25f..978ffc1e5 100644 --- a/src/pages/reports/project-report/[uuid].tsx +++ b/src/pages/reports/project-report/[uuid].tsx @@ -4,9 +4,10 @@ import { useRouter } from "next/router"; import SecondaryTabs from "@/components/elements/Tabs/Secondary/SecondaryTabs"; import LoadingContainer from "@/components/generic/Loading/LoadingContainer"; +import { useFullProject } from "@/connections/Entity"; import { ContextCondition } from "@/context/ContextCondition"; import FrameworkProvider, { Framework } from "@/context/framework.provider"; -import { useGetV2ENTITYUUID, useGetV2ProjectsUUID, useGetV2TasksUUID } from "@/generated/apiComponents"; +import { useGetV2ENTITYUUID, useGetV2TasksUUID } from "@/generated/apiComponents"; import StatusBar from "@/pages/project/[uuid]/components/StatusBar"; import GalleryTab from "@/pages/project/[uuid]/tabs/Gallery"; import ProjectReportBreadcrumbs from "@/pages/reports/project-report/components/ProjectReportBreadcrumbs"; @@ -32,14 +33,7 @@ const ProjectReportDetailPage = () => { } ); - const { data: project } = useGetV2ProjectsUUID( - { - pathParams: { uuid: data?.data?.project?.uuid } - }, - { - enabled: !!data?.data?.project?.uuid - } - ); + const [, { entity: project }] = useFullProject({ uuid: data?.data?.project?.uuid }); const { data: reportingTaskData } = useGetV2TasksUUID( { @@ -78,7 +72,6 @@ const ProjectReportDetailPage = () => { modelName="project-reports" modelUUID={report.uuid} modelTitle={t("Report")} - // @ts-ignore incorrect docs entityData={project} emptyStateContent={t( "Your gallery is currently empty. Add images by using the 'Edit' button on this report." diff --git a/src/pages/reports/site-report/[uuid].page.tsx b/src/pages/reports/site-report/[uuid].page.tsx index d1d714053..1ac51041f 100644 --- a/src/pages/reports/site-report/[uuid].page.tsx +++ b/src/pages/reports/site-report/[uuid].page.tsx @@ -174,7 +174,6 @@ const SiteReportDetailPage = () => { modelName="site-report" modelUUID={siteReportUUID} collection="tree-planted" - framework={siteReport.framework_key} visibleRows={8} galleryType={"treeSpeciesPD"} /> @@ -202,7 +201,6 @@ const SiteReportDetailPage = () => { modelName="site-report" modelUUID={siteReportUUID} collection="tree-planted" - framework={siteReport.framework_key} visibleRows={8} galleryType={"treeSpeciesPD"} /> @@ -230,7 +228,6 @@ const SiteReportDetailPage = () => { modelName="site-report" modelUUID={siteReportUUID} collection="seeding" - framework={siteReport.framework_key} visibleRows={8} galleryType={"treeSpeciesPD"} /> @@ -258,7 +255,6 @@ const SiteReportDetailPage = () => { modelName="site-report" modelUUID={siteReportUUID} collection="non-tree" - framework={siteReport.framework_key} visibleRows={8} galleryType={"treeSpeciesPD"} /> @@ -286,7 +282,6 @@ const SiteReportDetailPage = () => { modelName="site-report" modelUUID={siteReportUUID} collection="replanting" - framework={siteReport.framework_key} visibleRows={8} galleryType={"treeSpeciesPD"} /> diff --git a/src/pages/site/[uuid]/components/GoalsAndProgressEntityTab.tsx b/src/pages/site/[uuid]/components/GoalsAndProgressEntityTab.tsx index 44a2f224f..0a3d8b982 100644 --- a/src/pages/site/[uuid]/components/GoalsAndProgressEntityTab.tsx +++ b/src/pages/site/[uuid]/components/GoalsAndProgressEntityTab.tsx @@ -47,21 +47,23 @@ const ProgressDataCard = (values: ProgressDataCardItem) => { const GoalsAndProgressEntityTab = ({ entity, project = false }: GoalsAndProgressEntityTabProps) => { const t = useT(); - const totaTreesRestoredCount = - entity?.trees_planted_count + entity?.approved_regenerated_trees_count + entity?.seeds_planted_count; + const totalTreesRestoredCount = + (entity?.trees_planted_count ?? entity?.treesPlantedCount) + + (entity?.approved_regenerated_trees_count ?? entity?.regeneratedTreesCount) + + (entity?.seeds_planted_count ?? entity?.seedsPlantedCount); const keyAttribute = project ? "project" : "site"; const attribMapping: { [key: string]: any } = { project: { - total_jobs_created: entity.total_jobs_created, - jobs_created_goal: entity.jobs_created_goal, + total_jobs_created: entity.totalJobsCreated, + jobs_created_goal: entity.jobsCreatedGoal, total_hectares_restored_sum: - project && entity.framework_key == Framework.PPC - ? Math.round(entity.total_hectares_restored_sum) - : entity.total_hectares_restored_sum, - total_hectares_restored_goal: entity.total_hectares_restored_goal, - trees_restored_count: entity.trees_restored_count, - trees_grown_goal: entity.trees_grown_goal, - workday_count: entity.framework_key == Framework.PPC ? entity.combined_workday_count : entity.workday_count + project && entity.frameworkKey == Framework.PPC + ? Math.round(entity.totalHectaresRestoredSum) + : entity.totalHectaresRestoredSum, + total_hectares_restored_goal: entity.totalHectaresRestoredGoal, + trees_restored_count: totalTreesRestoredCount, + trees_grown_goal: entity.treesGrownGoal, + workday_count: entity.frameworkKey == Framework.PPC ? entity.combinedWorkdayCount : entity.workdayCount }, site: { total_jobs_created: null, @@ -218,7 +220,8 @@ const GoalsAndProgressEntityTab = ({ entity, project = false }: GoalsAndProgress /> ] }; - const framework = ALL_TF.includes(entity.framework_key as Framework) ? "terrafund" : entity.framework_key; + const frameworkKey = (entity.framework_key ?? entity.frameworkKey) as Framework; + const framework = ALL_TF.includes(frameworkKey) ? "terrafund" : frameworkKey; return (
{chartsDataMapping[framework as keyof ChartsData]?.map((chart, index) => ( @@ -226,8 +229,8 @@ const GoalsAndProgressEntityTab = ({ entity, project = false }: GoalsAndProgress ))} { ; }; +export type ApiFilteredIndexCache = { + ids: string[]; + meta: Required["page"]; +}; + +// This one is a map of resource -> queryString -> page number -> list of ids from that page. +export type ApiIndexStore = { + [key in ResourceType]: Record>; +}; + type AttributeValue = string | number | boolean; type Attributes = { [key: string]: AttributeValue | Attributes; @@ -57,7 +74,7 @@ export type StoreResource = { relationships?: Relationships; }; -type StoreResourceMap = Record>; +export type StoreResourceMap = Record>; // The list of potential resource types. IMPORTANT: When a new resource type is integrated, it must // be added to this list. @@ -66,29 +83,52 @@ export const RESOURCES = [ "establishmentTrees", "logins", "organisations", - "users", - "passwordResets" + "passwordResets", + "projects", + "sites", + "users" ] as const; +// The store for entities may contain either light DTOs or full DTOs depending on where the +// data came from. This type allows us to specify that the shape of the objects in the store +// conform to the light DTO and all full DTO members are optional. The connections that use +// this section of the store should explicitly cast their member object to either the light +// or full version depending on what the connection is expected to produce. See Entity.ts connection +// for more. +type EntityType = LightDto & Partial>; + type ApiResources = { delayedJobs: StoreResourceMap; establishmentTrees: StoreResourceMap; logins: StoreResourceMap; - passwordResets: StoreResourceMap; organisations: StoreResourceMap; + passwordResets: StoreResourceMap; + projects: StoreResourceMap>; + sites: StoreResourceMap>; users: StoreResourceMap; }; +export type ResourceType = (typeof RESOURCES)[number]; + export type JsonApiResource = { - type: (typeof RESOURCES)[number]; + type: ResourceType; id: string; attributes: Attributes; relationships?: { [key: string]: { data: Relationship | Relationship[] } }; }; +export type ResponseMeta = { + resourceType: ResourceType; + page?: { + number: number; + total: number; + }; +}; + export type JsonApiResponse = { data: JsonApiResource[] | JsonApiResource; included?: JsonApiResource[]; + meta: ResponseMeta; }; export type ApiDataStore = ApiResources & { @@ -96,6 +136,12 @@ export type ApiDataStore = ApiResources & { /** Stores the state of in-flight and failed requests */ pending: ApiPendingStore; + /** + * Stores the IDs and metadata that were returned for paginated (and often filtered and/or + * sorted) index queries. + **/ + indices: ApiIndexStore; + /** Is snatched and stored by middleware when a users/me request completes. */ meUserId?: string; }; @@ -122,7 +168,9 @@ export const INITIAL_STATE = { pending: METHODS.reduce((acc: Partial, method) => { acc[method] = {}; return acc; - }, {}) as ApiPendingStore + }, {}) as ApiPendingStore, + + indices: RESOURCES.reduce((acc, resource) => ({ ...acc, [resource]: {} }), {} as Partial) } } as ApiDataStore; @@ -130,13 +178,24 @@ type ApiFetchStartingProps = { url: string; method: Method; }; + type ApiFetchFailedProps = ApiFetchStartingProps & { error: PendingErrorState; }; + type ApiFetchSucceededProps = ApiFetchStartingProps & { response: JsonApiResponse; }; +// This may get more sophisticated in the future, but for now this is good enough +type PruneCacheProps = { + resource: ResourceType; + // If ids and searchQuery are null, the whole cache for this resource is removed. + ids?: string[]; + // If searchQuery is specified, the search index meta cache is removed for this query. + searchQuery?: string; +}; + const clearApiCache = (state: WritableDraft) => { for (const resource of RESOURCES) { state[resource] = {}; @@ -152,6 +211,9 @@ const clearApiCache = (state: WritableDraft) => { const isLogin = ({ url, method }: { url: string; method: Method }) => url.endsWith("auth/v3/logins") && method === "POST"; +const isPaginatedResponse = ({ method, response }: { method: string; response: JsonApiResponse }) => + method === "GET" && response.meta?.page != null; + export const apiSlice = createSlice({ name: "api", @@ -162,12 +224,34 @@ export const apiSlice = createSlice({ const { url, method } = action.payload; state.meta.pending[method][url] = true; }, + apiFetchFailed: (state, action: PayloadAction) => { const { url, method, error } = action.payload; state.meta.pending[method][url] = error; }, + apiFetchSucceeded: (state, action: PayloadAction) => { const { url, method, response } = action.payload; + // All response objects from the v3 api conform to JsonApiResponse + let { data, included, meta } = response; + if (!isArray(data)) data = [data]; + + if (isPaginatedResponse(action.payload)) { + const search = new URL(url).searchParams; + const pageNumber = Number(search.get("page[number]") ?? 0); + search.delete("page[number]"); + search.sort(); + const searchQuery = search.toString(); + + let cache = state.meta.indices[meta.resourceType][searchQuery]; + if (cache == null) cache = state.meta.indices[meta.resourceType][searchQuery] = {}; + + cache[pageNumber ?? 0] = { + ids: data.map(({ id }) => id), + meta: action.payload.response.meta!.page! + }; + } + if (isLogin(action.payload)) { // After a successful login, clear the entire cache; we want all mounted components to // re-fetch their data with the new login credentials. @@ -176,9 +260,6 @@ export const apiSlice = createSlice({ delete state.meta.pending[method][url]; } - // All response objects from the v3 api conform to JsonApiResponse - let { data, included } = response; - if (!isArray(data)) data = [data]; if (included != null) { // For the purposes of this reducer, data and included are the same: they both get merged // into the data cache. @@ -189,14 +270,36 @@ export const apiSlice = createSlice({ // there isn't a way to enforce that with TS against this dynamic data structure, so we // use the dreaded any. const { type, id, attributes, relationships: responseRelationships } = resource; - const storeResource: StoreResource = { attributes }; - if (responseRelationships != null) { - storeResource.relationships = {}; - for (const [key, { data }] of Object.entries(responseRelationships)) { - storeResource.relationships[key] = Array.isArray(data) ? data : [data]; + let useResponseResource = true; + + const cached = state[type][id] as StoreResource; + if (cached != null) { + const { updatedAt: cachedUpdatedAt, lightResource: cachedLightResource } = cached.attributes; + const { updatedAt: responseUpdatedAt, lightResource: responseLightResource } = attributes; + if ( + cachedUpdatedAt != null && + responseUpdatedAt != null && + responseLightResource === true && + cachedLightResource === false + ) { + // if the cached value in the store is a full resource and the resource in the response + // is a light resource, we only want to replace what's in the store if the updatedAt + // stamp on the response resource is newer. + useResponseResource = + compareDesc(new Date(responseUpdatedAt as string), new Date(cachedUpdatedAt as string)) > 0; } } - state[type][id] = storeResource; + + if (useResponseResource) { + const storeResource: StoreResource = { attributes }; + if (responseRelationships != null) { + storeResource.relationships = {}; + for (const [key, { data }] of Object.entries(responseRelationships)) { + storeResource.relationships[key] = Array.isArray(data) ? data : [data]; + } + } + state[type][id] = storeResource; + } } if (url.endsWith("users/v3/users/me") && method === "GET") { @@ -204,6 +307,24 @@ export const apiSlice = createSlice({ } }, + pruneCache: (state, action: PayloadAction) => { + const { resource, ids, searchQuery } = action.payload; + if (ids == null && searchQuery == null) { + state[resource] = {}; + return; + } + + if (ids != null) { + for (const id of ids) { + delete state[resource][id]; + } + } + + if (searchQuery != null) { + delete state.meta.indices[resource][searchQuery]; + } + }, + clearApiCache }, @@ -265,6 +386,14 @@ export default class ApiSlice { this.redux.dispatch(apiSlice.actions.apiFetchSucceeded(props)); } + static pruneCache(resource: ResourceType, ids?: string[]) { + this.redux.dispatch(apiSlice.actions.pruneCache({ resource, ids })); + } + + static pruneIndex(resource: ResourceType, searchQuery: string) { + this.redux.dispatch(apiSlice.actions.pruneCache({ resource, searchQuery })); + } + static clearApiCache() { this.redux.dispatch(apiSlice.actions.clearApiCache()); this._queryClient?.getQueryCache()?.clear();