Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fetch intended use choices from api & query with leases service_unit #489

Merged
merged 1 commit into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/api/callApiAsync.ts
Original file line number Diff line number Diff line change
@@ -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<Record<string, any>> => {
const apiToken = await getApiToken(store.getState());
const callApiAsync = async <T = ApiResponse>(request: Request): Promise<ApiSyncResponse<T>> => {
const apiToken = getApiToken(store.getState());

if (apiToken) {
request.headers.set('Authorization', `Bearer ${apiToken}`);
Expand Down
4 changes: 4 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ export type ReceiveErrorAction = Action<string, ApiError>;
export type ClearErrorAction = Action<string, void>;
export type ApiState = {
error: ApiError;
};
export type ApiSyncResponse<T> = {
response: Response,
bodyAsJson: T
};
4 changes: 2 additions & 2 deletions src/areaSearch/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export const getIsFetchingAttributes: Selector<boolean, void> = (state: RootStat
export const getListAttributes: Selector<Attributes, void> = (state: RootState): Attributes => state.areaSearch.listAttributes;
export const getListMethods: Selector<Methods, void> = (state: RootState): Methods => state.areaSearch.listMethods;
export const getIsFetchingListAttributes: Selector<boolean, void> = (state: RootState): boolean => state.areaSearch.isFetchingListAttributes;
export const getAreaSearchList: Selector<ApiResponse, void> = (state: RootState): ApiResponse => state.areaSearch.areaSearchList;
export const getAreaSearchListByBBox: Selector<ApiResponse, void> = (state: RootState): ApiResponse => state.areaSearch.areaSearchListByBBox;
export const getAreaSearchList: Selector<ApiResponse, void> = <T>(state: RootState): ApiResponse<T> => state.areaSearch.areaSearchList;
export const getAreaSearchListByBBox: Selector<ApiResponse, void> = <T>(state: RootState): ApiResponse<T> => state.areaSearch.areaSearchListByBBox;
export const getIsFetchingAreaSearchList: Selector<boolean, void> = (state: RootState): boolean => state.areaSearch.isFetchingAreaSearchList;
export const getIsFetchingAreaSearchListByBBox: Selector<boolean, void> = (state: RootState): boolean => state.areaSearch.isFetchingAreaSearchListByBBox;
export const getCurrentAreaSearch: Selector<Record<string, any>, void> = (state: RootState): Record<string, any> => state.areaSearch.currentAreaSearch;
Expand Down
2 changes: 1 addition & 1 deletion src/batchrun/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const getIsFetchingJobRunLogEntryAttributes: Selector<boolean, void> = (s
export const getJobRunLogEntryAttributes: Selector<Attributes, void> = (state: RootState): Attributes => state.batchrun.jobRunLogEntryAttributes;
export const getJobRunLogEntryMethods: Selector<Methods, void> = (state: RootState): Methods => state.batchrun.jobRunLogEntryMethods;
export const getIsFetchingJobRunLogEntriesByRun: Selector<boolean, number> = (state: RootState, run: number): boolean => state.batchrun.isFetchingJobRunLogEntriesByRun[run];
export const getJobRunLogEntriesByRun: Selector<ApiResponse, number> = (state: RootState, run: number): ApiResponse => state.batchrun.jobRunLogEntriesByRun[run];
export const getJobRunLogEntriesByRun: Selector<ApiResponse, number> = <T>(state: RootState, run: number): ApiResponse<T> => state.batchrun.jobRunLogEntriesByRun[run];
export const getIsFetchingScheduledJobAttributes: Selector<boolean, void> = (state: RootState): boolean => state.batchrun.isFetchingScheduledJobAttributes;
export const getScheduledJobAttributes: Selector<Attributes, void> = (state: RootState): Attributes => state.batchrun.scheduledJobAttributes;
export const getScheduledJobMethods: Selector<Methods, void> = (state: RootState): Methods => state.batchrun.scheduledJobMethods;
Expand Down
37 changes: 37 additions & 0 deletions src/components/form/FieldTypeIntendedUseSelect.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>;
isDirty: boolean;
onChange: (...args: Array<any>) => 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>) => 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 <AsyncSelect disabled={disabled} displayError={displayError} getOptions={getIntendedUses} input={input} isDirty={isDirty} onChange={onChange} placeholder={placeholder} />;
};

export default FieldTypeIntendedUseSelect;
5 changes: 5 additions & 0 deletions src/components/form/FormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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';
Expand Down
5 changes: 3 additions & 2 deletions src/contacts/requestsAsync.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import createUrl from "api/createUrl";
import callApiAsync from "api/callApiAsync";
import { ContactExistsResponse } from "./types";
export const fetchContacts = async (query?: Record<string, any>) => {
const {
response: {
Expand All @@ -17,13 +18,13 @@ export const fetchContacts = async (query?: Record<string, any>) => {
return [];
}
};
export const contactExists = async (identifier: string) => {
export const contactExists = async (identifier: string): Promise<boolean | Array<any>> => {
const {
response: {
status
},
bodyAsJson
} = await callApiAsync(new Request(createUrl(`contact_exists/?identifier=${identifier}`)));
} = await callApiAsync<ContactExistsResponse>(new Request(createUrl(`contact_exists/?identifier=${identifier}`)));

switch (status) {
case 200:
Expand Down
3 changes: 3 additions & 0 deletions src/contacts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export type ContactState = {
list: ContactList;
methods: Methods;
};
export type ContactExistsResponse = {
exists: boolean;
}
export type Contact = Record<string, any>;
export type ContactId = number;
export type ContactList = any;
Expand Down
1 change: 1 addition & 0 deletions src/enums.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ export const FieldTypes = {
DECIMAL: 'decimal',
FIELD: 'field',
INTEGER: 'integer',
INTENDED_USE: 'intended_use',
LEASE: 'lease',
LESSOR: 'lessor',
MULTISELECT: 'multiselect',
Expand Down
6 changes: 1 addition & 5 deletions src/leases/components/leaseSections/summary/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ type State = {
currentLease: Lease;
financingOptions: Array<Record<string, any>>;
hitasOptions: Array<Record<string, any>>;
intendedUseOptions: Array<Record<string, any>>;
managementOptions: Array<Record<string, any>>;
noticePeriodOptions: Array<Record<string, any>>;
regulationOptions: Array<Record<string, any>>;
Expand All @@ -64,7 +63,6 @@ class Summary extends PureComponent<Props, State> {
currentLease: {},
financingOptions: [],
hitasOptions: [],
intendedUseOptions: [],
lessorOptions: [],
managementOptions: [],
noticePeriodOptions: [],
Expand All @@ -86,7 +84,6 @@ class Summary extends PureComponent<Props, State> {
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);
Expand Down Expand Up @@ -140,7 +137,6 @@ class Summary extends PureComponent<Props, State> {
classificationOptions,
financingOptions,
hitasOptions,
intendedUseOptions,
managementOptions,
noticePeriodOptions,
regulationOptions,
Expand Down Expand Up @@ -239,7 +235,7 @@ class Summary extends PureComponent<Props, State> {
<FormTextTitle uiDataKey={getUiDataLeaseKey(LeaseFieldPaths.INTENDED_USE)}>
{LeaseFieldTitles.INTENDED_USE}
</FormTextTitle>
<FormText>{getLabelOfOption(intendedUseOptions, summary.intended_use) || '-'}</FormText>
<FormText>{(summary.intended_use && summary.intended_use.name) || '-'}</FormText>
</Authorization>
</Column>
<Column small={12} medium={6} large={8}>
Expand Down
3 changes: 2 additions & 1 deletion src/leases/components/leaseSections/summary/SummaryEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ class SummaryEdit extends PureComponent<Props, State> {
<Column small={12} medium={6} large={4}>
<Authorization allow={isFieldAllowedToRead(attributes, LeaseFieldPaths.INTENDED_USE)}>
<FormField disableTouched={isSaveClicked} fieldAttributes={getFieldAttributes(attributes, LeaseFieldPaths.INTENDED_USE)} name='intended_use' overrideValues={{
fieldType: FieldTypes.INTENDED_USE,
label: LeaseFieldTitles.INTENDED_USE
}} enableUiDataEdit uiDataKey={getUiDataLeaseKey(LeaseFieldPaths.INTENDED_USE)} />
}} serviceUnit={currentLease.service_unit} enableUiDataEdit uiDataKey={getUiDataLeaseKey(LeaseFieldPaths.INTENDED_USE)} />
</Authorization>
</Column>
<Column small={12} medium={6} large={8}>
Expand Down
23 changes: 20 additions & 3 deletions src/leases/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -212,6 +212,23 @@ export const getContentLeaseInfo = (lease: Record<string, any>): Record<string,
};
};

/**
* Get contenr for intended use
* @param {Object} intendedUse
* @returns {Object}
*/
export const getContentIntendedUse = (intendedUse: IntendedUse): Record<string, any> | 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
Expand Down Expand Up @@ -269,7 +286,7 @@ export const getContentLeaseSummary = (lease: Record<string, any>): Record<strin
financing: lease.financing,
hitas: lease.hitas,
infill_development_compensations: getContentLeaseInfillDevelopmentCompensations(lease),
intended_use: lease.intended_use,
intended_use: getContentIntendedUse(lease.intended_use),
intended_use_note: lease.intended_use_note,
internal_order: lease.internal_order,
is_subject_to_vat: lease.is_subject_to_vat,
Expand Down Expand Up @@ -1974,7 +1991,7 @@ export const addSummaryFormValuesToPayload = (payload: Record<string, any>, 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,
Expand Down
18 changes: 18 additions & 0 deletions src/leases/requestsAsync.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>) => {
const {
response: {
Expand Down Expand Up @@ -84,4 +85,21 @@ export const fetchDecisions = async (query?: Record<string, any>) => {
console.error('Failed to fetch decisions');
return [];
}
};
export const fetchIntendedUses = async (query?: Record<string, any>): Promise<Array<IntendedUse>> => {
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 [];
}
};
6 changes: 6 additions & 0 deletions src/leases/types.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>;
Expand Down Expand Up @@ -37,6 +38,11 @@ export type SendEmailPayload = {
recipients: Array<number>;
text: string;
};
export type IntendedUse = {
id: number;
name: string;
service_unit: ServiceUnit["id"];
};
export type FetchAttributesAction = Action<string, void>;
export type ReceiveAttributesAction = Action<string, Attributes>;
export type ReceiveMethodsAction = Action<string, Methods>;
Expand Down
2 changes: 1 addition & 1 deletion src/serviceUnits/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Action } from "../types";
export type ServiceUnit = Record<string, any>;
export type ServiceUnit = { id: number, name: string };
export type ServiceUnits = Array<Record<string, any>>;
export type ServiceUnitState = {
isFetching: boolean;
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export type Selector<Value, Props> = (state: RootState, props: Props) => Value;
export type Attributes = Record<string, any> | null | undefined;
export type Reports = Record<string, any> | null | undefined;
export type Methods = Record<string, any> | null | undefined;
export type ApiResponse = ({
export type ApiResponse<T = any> = ({
count: number;
next: string | null | undefined;
previous: string | null | undefined;
results: Array<Record<string, any>>;
results: Array<T>;
} | null | undefined) | null;
type Coordinate = Array<number>;
export type LeafletFeatureGeometry = {
Expand Down
6 changes: 3 additions & 3 deletions src/util/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -651,15 +651,15 @@ 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 = <T,>(response: ApiResponse<T>): number => get(response, 'count', 0);

/**
* Get maximum number of pages from api response
* @param {Object} response
* @param {number} size
* @returns {number}
*/
export const getApiResponseMaxPage = (response: ApiResponse, size: number): number => {
export const getApiResponseMaxPage = <T,>(response: ApiResponse<T>, size: number): number => {
const count = getApiResponseCount(response);
return Math.ceil(count / size);
};
Expand All @@ -669,7 +669,7 @@ export const getApiResponseMaxPage = (response: ApiResponse, size: number): numb
* @param {Object} response
* @returns {Object[]}
*/
export const getApiResponseResults = (response: ApiResponse): Array<Record<string, any>> => get(response, 'results', []);
export const getApiResponseResults = <T,>(response: ApiResponse<T>): Array<Record<string, any>> => get(response, 'results', []);

/**
* Get React component by dom id
Expand Down