From 975412d74a545e9c84af8f4d8a27891c908eac8d Mon Sep 17 00:00:00 2001 From: Henri Nieminen Date: Tue, 11 Jun 2024 15:35:25 +0300 Subject: [PATCH] fetch intended use choices from api & query with leases service_unit this change filters intended use choices based on service_unit adds and improves typing here and there --- src/api/callApiAsync.ts | 6 ++- src/api/types.ts | 4 ++ src/areaSearch/selectors.ts | 4 +- src/batchrun/selectors.ts | 2 +- .../form/FieldTypeIntendedUseSelect.tsx | 37 +++++++++++++++++++ src/components/form/FormField.tsx | 5 +++ src/contacts/requestsAsync.ts | 5 ++- src/contacts/types.ts | 3 ++ src/enums.tsx | 1 + .../leaseSections/summary/Summary.tsx | 6 +-- .../leaseSections/summary/SummaryEdit.tsx | 3 +- src/leases/helpers.ts | 23 ++++++++++-- src/leases/requestsAsync.ts | 18 +++++++++ src/leases/types.ts | 6 +++ src/serviceUnits/types.ts | 2 +- src/types.ts | 4 +- src/util/helpers.tsx | 6 +-- 17 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 src/components/form/FieldTypeIntendedUseSelect.tsx diff --git a/src/api/callApiAsync.ts b/src/api/callApiAsync.ts index b41ea3d44..685947309 100644 --- a/src/api/callApiAsync.ts +++ b/src/api/callApiAsync.ts @@ -1,9 +1,11 @@ import { store } from "root/startApp"; import { getApiToken } from "auth/selectors"; import { UI_ACCEPT_LANGUAGE_VALUE } from "api/constants"; +import type { ApiSyncResponse } from "./types"; +import type { ApiResponse } from "types"; -const callApiAsync = async (request: Request): Promise> => { - const apiToken = await getApiToken(store.getState()); +const callApiAsync = async (request: Request): Promise> => { + const apiToken = getApiToken(store.getState()); if (apiToken) { request.headers.set('Authorization', `Bearer ${apiToken}`); diff --git a/src/api/types.ts b/src/api/types.ts index 4cb28724a..d89d59f8a 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -4,4 +4,8 @@ export type ReceiveErrorAction = Action; export type ClearErrorAction = Action; export type ApiState = { error: ApiError; +}; +export type ApiSyncResponse = { + response: Response, + bodyAsJson: T }; \ No newline at end of file diff --git a/src/areaSearch/selectors.ts b/src/areaSearch/selectors.ts index 1494d9364..7643a233c 100644 --- a/src/areaSearch/selectors.ts +++ b/src/areaSearch/selectors.ts @@ -7,8 +7,8 @@ export const getIsFetchingAttributes: Selector = (state: RootStat export const getListAttributes: Selector = (state: RootState): Attributes => state.areaSearch.listAttributes; export const getListMethods: Selector = (state: RootState): Methods => state.areaSearch.listMethods; export const getIsFetchingListAttributes: Selector = (state: RootState): boolean => state.areaSearch.isFetchingListAttributes; -export const getAreaSearchList: Selector = (state: RootState): ApiResponse => state.areaSearch.areaSearchList; -export const getAreaSearchListByBBox: Selector = (state: RootState): ApiResponse => state.areaSearch.areaSearchListByBBox; +export const getAreaSearchList: Selector = (state: RootState): ApiResponse => state.areaSearch.areaSearchList; +export const getAreaSearchListByBBox: Selector = (state: RootState): ApiResponse => state.areaSearch.areaSearchListByBBox; export const getIsFetchingAreaSearchList: Selector = (state: RootState): boolean => state.areaSearch.isFetchingAreaSearchList; export const getIsFetchingAreaSearchListByBBox: Selector = (state: RootState): boolean => state.areaSearch.isFetchingAreaSearchListByBBox; export const getCurrentAreaSearch: Selector, void> = (state: RootState): Record => state.areaSearch.currentAreaSearch; diff --git a/src/batchrun/selectors.ts b/src/batchrun/selectors.ts index 43316cc72..181b46eec 100644 --- a/src/batchrun/selectors.ts +++ b/src/batchrun/selectors.ts @@ -10,7 +10,7 @@ export const getIsFetchingJobRunLogEntryAttributes: Selector = (s export const getJobRunLogEntryAttributes: Selector = (state: RootState): Attributes => state.batchrun.jobRunLogEntryAttributes; export const getJobRunLogEntryMethods: Selector = (state: RootState): Methods => state.batchrun.jobRunLogEntryMethods; export const getIsFetchingJobRunLogEntriesByRun: Selector = (state: RootState, run: number): boolean => state.batchrun.isFetchingJobRunLogEntriesByRun[run]; -export const getJobRunLogEntriesByRun: Selector = (state: RootState, run: number): ApiResponse => state.batchrun.jobRunLogEntriesByRun[run]; +export const getJobRunLogEntriesByRun: Selector = (state: RootState, run: number): ApiResponse => state.batchrun.jobRunLogEntriesByRun[run]; export const getIsFetchingScheduledJobAttributes: Selector = (state: RootState): boolean => state.batchrun.isFetchingScheduledJobAttributes; export const getScheduledJobAttributes: Selector = (state: RootState): Attributes => state.batchrun.scheduledJobAttributes; export const getScheduledJobMethods: Selector = (state: RootState): Methods => state.batchrun.scheduledJobMethods; diff --git a/src/components/form/FieldTypeIntendedUseSelect.tsx b/src/components/form/FieldTypeIntendedUseSelect.tsx new file mode 100644 index 000000000..312c5ee08 --- /dev/null +++ b/src/components/form/FieldTypeIntendedUseSelect.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import debounce from "lodash/debounce"; +import AsyncSelect from "components/form/AsyncSelect"; +import { addEmptyOption, sortStringByKeyAsc } from "util/helpers"; +import { getContentIntendedUse } from "leases/helpers"; +import { fetchIntendedUses } from "leases/requestsAsync"; +import type { ServiceUnit } from "serviceUnits/types"; +type Props = { + disabled?: boolean; + displayError: boolean; + input: Record; + isDirty: boolean; + onChange: (...args: Array) => any; + placeholder?: string; + serviceUnit: ServiceUnit; +}; +const FieldTypeIntendedUseSelect = ({ + disabled, + displayError, + input, + isDirty, + onChange, + placeholder, + serviceUnit +}: Props): React.ReactNode => { + const getIntendedUses = debounce(async (inputValue: string, callback: (...args: Array) => any) => { + const intendedUses = await fetchIntendedUses({ + search: inputValue, + limit: 20, + service_unit: serviceUnit?.id || "" + }); + callback(addEmptyOption(intendedUses.map((intendedUse) => getContentIntendedUse(intendedUse)).sort((a, b) => sortStringByKeyAsc(a, b, 'label')))); + }, 500); + return ; +}; + +export default FieldTypeIntendedUseSelect; \ No newline at end of file diff --git a/src/components/form/FormField.tsx b/src/components/form/FormField.tsx index c6ae7c34a..3ce86ffba 100644 --- a/src/components/form/FormField.tsx +++ b/src/components/form/FormField.tsx @@ -13,6 +13,7 @@ import FieldTypeCheckboxDateTime from "components/form/FieldTypeCheckboxDateTime import FieldTypeContactSelect from "components/form/FieldTypeContactSelect"; import FieldTypeDatePicker from "components/form/FieldTypeDatePicker"; import FieldTypeDecimal from "components/form/FieldTypeDecimal"; +import FieldTypeIntendedUseSelect from "components/form/FieldTypeIntendedUseSelect"; import FieldTypeLeaseSelect from "components/form/FieldTypeLeaseSelect"; import FieldTypeLessorSelect from "components/form/FieldTypeLessorSelect"; import FieldTypeMultiSelect from "components/form/FieldTypeMultiSelect"; @@ -47,6 +48,7 @@ const FieldTypes = { [FieldTypeOptions.DECIMAL]: FieldTypeDecimal, [FieldTypeOptions.FIELD]: FieldTypeSelect, [FieldTypeOptions.INTEGER]: FieldTypeBasic, + [FieldTypeOptions.INTENDED_USE]: FieldTypeIntendedUseSelect, [FieldTypeOptions.LEASE]: FieldTypeLeaseSelect, [FieldTypeOptions.LESSOR]: FieldTypeLessorSelect, [FieldTypeOptions.MULTISELECT]: FieldTypeMultiSelect, @@ -187,6 +189,9 @@ const FormFieldInput = ({ case FieldTypeOptions.USER: return getUserFullName(value); + case FieldTypeOptions.INTENDED_USE: + return value ? value.label : '-'; + default: console.error(`Field type ${type} is not implemented`); return 'NOT IMPLEMENTED'; diff --git a/src/contacts/requestsAsync.ts b/src/contacts/requestsAsync.ts index e0c10c1af..174772570 100644 --- a/src/contacts/requestsAsync.ts +++ b/src/contacts/requestsAsync.ts @@ -1,5 +1,6 @@ import createUrl from "api/createUrl"; import callApiAsync from "api/callApiAsync"; +import { ContactExistsResponse } from "./types"; export const fetchContacts = async (query?: Record) => { const { response: { @@ -17,13 +18,13 @@ export const fetchContacts = async (query?: Record) => { return []; } }; -export const contactExists = async (identifier: string) => { +export const contactExists = async (identifier: string): Promise> => { const { response: { status }, bodyAsJson - } = await callApiAsync(new Request(createUrl(`contact_exists/?identifier=${identifier}`))); + } = await callApiAsync(new Request(createUrl(`contact_exists/?identifier=${identifier}`))); switch (status) { case 200: diff --git a/src/contacts/types.ts b/src/contacts/types.ts index a2df889f3..e5b3abe2f 100644 --- a/src/contacts/types.ts +++ b/src/contacts/types.ts @@ -14,6 +14,9 @@ export type ContactState = { list: ContactList; methods: Methods; }; +export type ContactExistsResponse = { + exists: boolean; +} export type Contact = Record; export type ContactId = number; export type ContactList = any; diff --git a/src/enums.tsx b/src/enums.tsx index 324b5bbe4..eea0b7dff 100644 --- a/src/enums.tsx +++ b/src/enums.tsx @@ -423,6 +423,7 @@ export const FieldTypes = { DECIMAL: 'decimal', FIELD: 'field', INTEGER: 'integer', + INTENDED_USE: 'intended_use', LEASE: 'lease', LESSOR: 'lessor', MULTISELECT: 'multiselect', diff --git a/src/leases/components/leaseSections/summary/Summary.tsx b/src/leases/components/leaseSections/summary/Summary.tsx index 20010dd41..9a28eaee9 100644 --- a/src/leases/components/leaseSections/summary/Summary.tsx +++ b/src/leases/components/leaseSections/summary/Summary.tsx @@ -44,7 +44,6 @@ type State = { currentLease: Lease; financingOptions: Array>; hitasOptions: Array>; - intendedUseOptions: Array>; managementOptions: Array>; noticePeriodOptions: Array>; regulationOptions: Array>; @@ -64,7 +63,6 @@ class Summary extends PureComponent { currentLease: {}, financingOptions: [], hitasOptions: [], - intendedUseOptions: [], lessorOptions: [], managementOptions: [], noticePeriodOptions: [], @@ -86,7 +84,6 @@ class Summary extends PureComponent { newState.classificationOptions = getFieldOptions(props.attributes, LeaseFieldPaths.CLASSIFICATION); newState.financingOptions = getFieldOptions(props.attributes, LeaseFieldPaths.FINANCING); newState.hitasOptions = getFieldOptions(props.attributes, LeaseFieldPaths.HITAS); - newState.intendedUseOptions = getFieldOptions(props.attributes, LeaseFieldPaths.INTENDED_USE); newState.managementOptions = getFieldOptions(props.attributes, LeaseFieldPaths.MANAGEMENT); newState.noticePeriodOptions = getFieldOptions(props.attributes, LeaseFieldPaths.NOTICE_PERIOD); newState.regulationOptions = getFieldOptions(props.attributes, LeaseFieldPaths.REGULATION); @@ -140,7 +137,6 @@ class Summary extends PureComponent { classificationOptions, financingOptions, hitasOptions, - intendedUseOptions, managementOptions, noticePeriodOptions, regulationOptions, @@ -239,7 +235,7 @@ class Summary extends PureComponent { {LeaseFieldTitles.INTENDED_USE} - {getLabelOfOption(intendedUseOptions, summary.intended_use) || '-'} + {(summary.intended_use && summary.intended_use.name) || '-'} diff --git a/src/leases/components/leaseSections/summary/SummaryEdit.tsx b/src/leases/components/leaseSections/summary/SummaryEdit.tsx index 35b49a846..d77a27660 100644 --- a/src/leases/components/leaseSections/summary/SummaryEdit.tsx +++ b/src/leases/components/leaseSections/summary/SummaryEdit.tsx @@ -208,8 +208,9 @@ class SummaryEdit extends PureComponent { + }} serviceUnit={currentLease.service_unit} enableUiDataEdit uiDataKey={getUiDataLeaseKey(LeaseFieldPaths.INTENDED_USE)} /> diff --git a/src/leases/helpers.ts b/src/leases/helpers.ts index 8c6261f1e..b298371dd 100644 --- a/src/leases/helpers.ts +++ b/src/leases/helpers.ts @@ -18,7 +18,7 @@ import { addEmptyOption, convertStrToDecimalNumber, fixedLengthNumber, formatDat import { getCoordinatesOfGeometry } from "util/map"; import { getIsEditMode } from "./selectors"; import { removeSessionStorageItem } from "util/storage"; -import type { Lease } from "./types"; +import type { Lease, IntendedUse } from "./types"; import type { CommentList } from "comments/types"; import type { Attributes, LeafletFeature, LeafletGeoJson } from "types"; import type { RootState } from "root/types"; @@ -212,6 +212,23 @@ export const getContentLeaseInfo = (lease: Record): Record | null => { + if (!intendedUse) return null; + return { + id: intendedUse.id, + value: intendedUse.id, + label: intendedUse.name, + name: intendedUse.name, + service_unit: intendedUse.service_unit, + }; +} + + /** * Get content lease address * @param {Object} lease @@ -269,7 +286,7 @@ export const getContentLeaseSummary = (lease: Record): Record, form end_date: formValues.end_date, financing: formValues.financing, hitas: formValues.hitas, - intended_use: formValues.intended_use, + intended_use: get(formValues, 'intended_use.value'), intended_use_note: formValues.intended_use_note, internal_order: formValues.internal_order, is_subject_to_vat: formValues.is_subject_to_vat, diff --git a/src/leases/requestsAsync.ts b/src/leases/requestsAsync.ts index f216e7f5f..ee1ce66c3 100644 --- a/src/leases/requestsAsync.ts +++ b/src/leases/requestsAsync.ts @@ -1,5 +1,6 @@ import createUrl from "api/createUrl"; import callApiAsync from "api/callApiAsync"; +import type { IntendedUse } from "./types"; export const fetchLeases = async (query?: Record) => { const { response: { @@ -84,4 +85,21 @@ export const fetchDecisions = async (query?: Record) => { console.error('Failed to fetch decisions'); return []; } +}; +export const fetchIntendedUses = async (query?: Record): Promise> => { + const { + response: { + status + }, + bodyAsJson + } = await callApiAsync(new Request(createUrl('intended_use/', query))); + + switch (status) { + case 200: + return bodyAsJson.results; + + default: + console.error('Failed to fetch intended uses'); + return []; + } }; \ No newline at end of file diff --git a/src/leases/types.ts b/src/leases/types.ts index 22d80bbf7..de8d893e0 100644 --- a/src/leases/types.ts +++ b/src/leases/types.ts @@ -1,4 +1,5 @@ import type { Action, ApiResponse, Attributes, Methods } from "../types"; +import type { ServiceUnit } from "serviceUnits/types"; export type LeaseState = { attributes: Attributes; byId: Record; @@ -37,6 +38,11 @@ export type SendEmailPayload = { recipients: Array; text: string; }; +export type IntendedUse = { + id: number; + name: string; + service_unit: ServiceUnit["id"]; +}; export type FetchAttributesAction = Action; export type ReceiveAttributesAction = Action; export type ReceiveMethodsAction = Action; diff --git a/src/serviceUnits/types.ts b/src/serviceUnits/types.ts index 92c615d4c..bfdc8ea13 100644 --- a/src/serviceUnits/types.ts +++ b/src/serviceUnits/types.ts @@ -1,5 +1,5 @@ import type { Action } from "../types"; -export type ServiceUnit = Record; +export type ServiceUnit = { id: number, name: string }; export type ServiceUnits = Array>; export type ServiceUnitState = { isFetching: boolean; diff --git a/src/types.ts b/src/types.ts index 9c18b24b6..50d9e89c3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,11 +8,11 @@ export type Selector = (state: RootState, props: Props) => Value; export type Attributes = Record | null | undefined; export type Reports = Record | null | undefined; export type Methods = Record | null | undefined; -export type ApiResponse = ({ +export type ApiResponse = ({ count: number; next: string | null | undefined; previous: string | null | undefined; - results: Array>; + results: Array; } | null | undefined) | null; type Coordinate = Array; export type LeafletFeatureGeometry = { diff --git a/src/util/helpers.tsx b/src/util/helpers.tsx index f25e8c4d4..c4f1b0e30 100644 --- a/src/util/helpers.tsx +++ b/src/util/helpers.tsx @@ -651,7 +651,7 @@ export const createPTPPlotDivisionUrl = (plotDivisionId: string): string => `${P * @param {Object} response * @returns {number} */ -export const getApiResponseCount = (response: ApiResponse): number => get(response, 'count', 0); +export const getApiResponseCount = (response: ApiResponse): number => get(response, 'count', 0); /** * Get maximum number of pages from api response @@ -659,7 +659,7 @@ export const getApiResponseCount = (response: ApiResponse): number => get(respon * @param {number} size * @returns {number} */ -export const getApiResponseMaxPage = (response: ApiResponse, size: number): number => { +export const getApiResponseMaxPage = (response: ApiResponse, size: number): number => { const count = getApiResponseCount(response); return Math.ceil(count / size); }; @@ -669,7 +669,7 @@ export const getApiResponseMaxPage = (response: ApiResponse, size: number): numb * @param {Object} response * @returns {Object[]} */ -export const getApiResponseResults = (response: ApiResponse): Array> => get(response, 'results', []); +export const getApiResponseResults = (response: ApiResponse): Array> => get(response, 'results', []); /** * Get React component by dom id