feature/create-place #8

Merged
behnam merged 5 commits from feature/create-place into develop 2023-05-30 13:07:57 +00:00
13 changed files with 307 additions and 100 deletions
Showing only changes of commit 84e2a186b9 - Show all commits

View File

@ -7,7 +7,7 @@ const getPlacesRO = (placesResponse: GetPlacesResponse): GetPlacesRO => {
id: placeResponse._id,
placeType: placeResponse.place_type,
name: placeResponse.name,
parentId: placeResponse.place_type,
parentId: placeResponse.parent_id,
availableServices: placeResponse.available_services,
createdAt: placeResponse.createdAt,
updatedAt: placeResponse.updatedAt,

View File

@ -8,7 +8,8 @@ interface IInputBox<ValueType> extends IInputWrapper {
state: {
value: { value: ValueType; label: string };
options: { value: ValueType; label: string }[];
setValue: (newValue: ValueType) => void;
setValue: (newValue: ValueType, label: string) => void;
isLoading?: boolean;
};
}
@ -20,10 +21,13 @@ export default function InputBox<ValueType>(props: IInputBox<ValueType>) {
<Select
classNamePrefix='select'
defaultValue={value}
onChange={(newValue) => newValue && newValue.value && setValue(newValue.value)}
value={value}
isLoading={state.isLoading}
onChange={(newValue) => newValue && newValue.value && setValue(newValue.value, newValue.label)}
isSearchable
name={name}
options={options}
className='text-txt-medium'
/>
</InputWrapper>
);

View File

@ -13,6 +13,7 @@ export const staticMessages = {
title: 'title',
status: 'Status',
placeType: 'Place Type',
placeParentId: 'Parent id',
address: 'Address',
qrCode: 'qrCode',
createUser: 'Create user',

View File

@ -0,0 +1,79 @@
/* eslint-disable consistent-return */
/* eslint-disable no-underscore-dangle */
import { QrPlace } from '~/business-logic/core/places/common/entity/placeEntity';
import getPlaces from '~/business-logic/core/places/get-places';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import getPlacesAdapter from '~/driven/adapters/get-places-adapter/getPlacesAdapter';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { apiUrls } from '~/driven/utils/configs/appConfig';
import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator';
import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers';
import PlacesModel from '~/business-logic/core/places/common/model/placesModel';
import placesListModel from '../../places-list/model/placesListModel';
type QrCodeResponse = {
one_time: false;
place_id: string;
user_id: string;
_id: string;
}[];
const QrCodeRO = (response: QrCodeResponse): QrPlace[] => {
return response.map((qrCode) => ({
id: qrCode._id,
oneTime: qrCode.one_time,
placeId: qrCode.place_id,
userId: qrCode.user_id,
}));
};
const prepareTheLogicForModel = () => {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const { user } = userData;
const gettingPlacesDrivenAdapter = getPlacesAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth);
const { url } = gettingPlacesDrivenAdapter;
const getingPlacesLogic = getPlaces(gettingPlacesDrivenAdapter);
return { getingPlacesLogic, url };
};
const useGetPlaceList = () => {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const getQrCodes = async () => {
try {
// url
const apiUrl = apiUrls.core.getQrs;
// options
const apiOptions: HttpOptionsType = {
url: apiUrl,
};
// request
const userToken = {
accessToken: userData.user?.adminUserData.accessToken || null,
refreshToken: userData.user?.adminUserData.refreshToken || null,
};
const httpProvider = new HTTPPovider(userToken, accessTokenUpdateHandler, notLoginAuth);
const response = await httpProvider.request<QrCodeResponse>(apiOptions);
const qrCodeList = QrCodeRO(response);
return qrCodeList;
// update the query
} catch (error) {
console.log(error);
}
};
const { getingPlacesLogic, url } = prepareTheLogicForModel();
const getPlacesWithQrLogic = async () => {
const placesList = await getingPlacesLogic();
const qrCodesList = await getQrCodes();
placesList.setQrFor(qrCodesList || []);
return placesList;
};
const placesModel = async () => await placesListModel(getPlacesWithQrLogic);
const useGetPlacesList = prepareStateManagementForVM<PlacesModel>(url, placesModel);
return useGetPlacesList;
};
export default useGetPlaceList;

View File

@ -1,8 +1,22 @@
import React from 'react';
import CreatePlaceView from '../view/CreatePlaceView';
import useCreatePlaceVm from '../viewmodel/createPlaceVM';
import createPlaceModel from '../model/createPlaceModel';
export default function CreatePlace() {
const { formStateData, handleNameSetState } = useCreatePlaceVm();
return <CreatePlaceView formStateData={formStateData} handleNameSetState={handleNameSetState} />;
const { inputOptions } = createPlaceModel();
const { formStateData, inputStateHandlers, selectBoxOptions } = useCreatePlaceVm({
getInputOptions: inputOptions,
});
return (
<CreatePlaceView
inputOptions={{
parentId: selectBoxOptions.parentIdsOptions,
placeType: selectBoxOptions.placeTypesOptions,
isParentIdLoading: selectBoxOptions.isParentIdLoading,
}}
formStateData={formStateData}
inputStateHandlers={inputStateHandlers}
/>
);
}

View File

@ -0,0 +1,14 @@
import { CreatePlaceType } from './protocols';
import getValidOptionsOfSelectBox from './getValidOptionsOfSelectBox';
const createPlaceModel = (): CreatePlaceType => {
const { getPlacesTypeOptionsOfSelectBox, getValidParentIdsOptionsOfSelectBox } = getValidOptionsOfSelectBox;
return {
inputOptions: {
getPlaceType: getPlacesTypeOptionsOfSelectBox,
getParentIds: getValidParentIdsOptionsOfSelectBox,
},
};
};
export default createPlaceModel;

View File

@ -0,0 +1,66 @@
import Places from '~/business-logic/core/places/common/entity/placeEntity';
import PlacesModel from '~/business-logic/core/places/common/model/placesModel';
import { staticMessages } from '~/driven/utils/constants/staticMessages';
import { placeTypes } from '../view/protocol';
export const placeTypeTuple: ReadonlyArray<`${placeTypes}`> = [
placeTypes.continent,
placeTypes.country,
placeTypes.state,
placeTypes.region,
placeTypes.city,
placeTypes.district,
placeTypes.street,
placeTypes.building,
placeTypes.section,
placeTypes.floor,
placeTypes.flat,
];
const makeOptionsOfSelectBoxForParentIds = (places: Places[], currentPlaceType: `${placeTypes}`) => {
const currentPlaceTypeIndex = placeTypeTuple.indexOf(currentPlaceType);
if (currentPlaceTypeIndex === -1) throw new Error();
const placesValidToShow = places.reduce((prevVal, currentVal) => {
const placeListItemIndex = placeTypeTuple.indexOf(currentVal.placeType as `${placeTypes}`);
if (!currentVal.parentId) return prevVal;
if (placeListItemIndex === -1) return prevVal;
if (placeListItemIndex > currentPlaceTypeIndex) return prevVal;
const label = `${currentVal.placeType} ${currentVal.name}`;
return [...prevVal, { value: currentVal.parentId, label }];
}, <{ value: string; label: string }[]>[]);
return placesValidToShow;
};
const getValidParentIdsOptionsOfSelectBox = (
currentPlaceType: `${placeTypes}`,
placesList: PlacesModel,
): { value: string; label: string }[] => {
try {
if (!placesList) throw new Error();
const places = placesList.getData();
if (!places.length) throw new Error();
const placesOptionsValidToShow = makeOptionsOfSelectBoxForParentIds(places, currentPlaceType);
return placesOptionsValidToShow;
} catch (_error) {
return [];
}
};
const getPlacesTypeOptionsOfSelectBox = () => {
return Object.keys(placeTypes).map((placeTypeKey) => {
const key = placeTypeKey as keyof typeof placeTypes;
return { value: placeTypes[key], label: staticMessages.placeTypes[placeTypes[key]] };
});
};
export default {
getValidParentIdsOptionsOfSelectBox,
getPlacesTypeOptionsOfSelectBox,
};

View File

@ -0,0 +1,18 @@
import PlacesModel from '~/business-logic/core/places/common/model/placesModel';
import { placeTypes } from '../view/protocol';
export type CreatePlaceType = {
inputOptions: {
getPlaceType: () => {
value: placeTypes;
label: string;
}[];
getParentIds: (
currentPlaceType: `${placeTypes}`,
placesList: PlacesModel,
) => {
value: string;
label: string;
}[];
};
};

View File

@ -2,17 +2,13 @@ import React from 'react';
import SimpleInput from '~/driven/utils/components/inputs/simple-input/SimpleInput';
import InputBox from '~/driven/utils/components/inputs/select-box/InputBox';
import { staticMessages } from '~/driven/utils/constants/staticMessages';
import { ICreatePlaceProps, placeTypes } from './protocol';
import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton';
import { ICreatePlaceProps } from './protocol';
export default function CreatePlaceView(props: ICreatePlaceProps) {
const { formStateData, handleNameSetState } = props;
const { formState, setFormState } = formStateData;
const { formStateData, inputStateHandlers, inputOptions } = props;
const { formState } = formStateData;
const PlacesOptions = Object.keys(placeTypes).map((placeTypeKey) => {
const key = placeTypeKey as keyof typeof placeTypes;
return { value: placeTypes[key], label: staticMessages.placeTypes[placeTypes[key]] };
});
return (
<form className='p-4 py-6 flex flex-wrap w-full gap-4'>
<SimpleInput
@ -22,20 +18,34 @@ export default function CreatePlaceView(props: ICreatePlaceProps) {
}}
stateHanlder={{
state: formState.placeName,
setState: handleNameSetState,
setState: inputStateHandlers.name,
}}
className='mb-4 w-[48%] px-2'
/>
<InputBox
name={staticMessages.global.placeType}
state={{
value: { value: placeTypes.continent, label: staticMessages.placeTypes[placeTypes.continent] },
setValue: (newValue: `${placeTypes}`) => setFormState((prev) => ({ ...prev, placeTypes: newValue })),
options: PlacesOptions,
value: { value: formState.placeTypes, label: staticMessages.placeTypes[formState.placeTypes] },
setValue: inputStateHandlers.placeType,
options: inputOptions.placeType,
}}
title={staticMessages.global.placeType}
className='mb-4 w-[48%] px-2 border-1 border-txt-medium'
className='mb-4 w-[48%] px-2 border-1 border-txt-medium text-txt-medium'
/>
<InputBox
name={staticMessages.global.placeParentId}
state={{
value: { value: formState.parentId.value, label: formState.parentId.label },
setValue: inputStateHandlers.parentId,
options: inputOptions.parentId,
isLoading: inputOptions.isParentIdLoading,
}}
title={staticMessages.global.placeParentId}
className='mb-4 w-[48%] px-2 border-1 border-txt-medium text-txt-medium'
/>
<div className='w-full'>
<PrimaryButton onClick={() => null} title={staticMessages.global.submit} />
</div>
</form>
);
}

View File

@ -15,7 +15,7 @@ export enum placeTypes {
export type PlaceFormState = {
placeName: string;
placeTypes: `${placeTypes}`;
parentId: string;
parentId: { value: string; label: string };
};
export interface ICreatePlaceProps {
@ -23,5 +23,20 @@ export interface ICreatePlaceProps {
formState: PlaceFormState;
setFormState: React.Dispatch<React.SetStateAction<PlaceFormState>>;
};
handleNameSetState: (_name: string, newValue: string) => void;
inputStateHandlers: {
name: (_name: string, newValue: string) => void;
placeType: (newValue: `${placeTypes}`) => void;
parentId: (newValue: string, label: string) => void;
};
inputOptions: {
placeType: {
value: placeTypes;
label: string;
}[];
parentId: {
value: string;
label: string;
}[];
isParentIdLoading: boolean;
};
}

View File

@ -1,24 +1,65 @@
import { useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import useGetPlaceList from '../../common/hooks/useGetPlaceList';
import { PlaceFormState, placeTypes } from '../view/protocol';
import { ICreatePlaceVm, initialPlaceFormState } from './protocols';
const initialPlaceFormState: PlaceFormState = {
placeTypes: placeTypes.continent,
placeName: '',
parentId: '',
};
const useCreatePlaceVm = () => {
const [formState, setFormState] = useState<PlaceFormState>(initialPlaceFormState);
const onChangeInputStateHandlers = (setFormState: React.Dispatch<React.SetStateAction<PlaceFormState>>) => {
const handleNameSetState = (_name: string, newValue: string) =>
setFormState((prev) => ({ ...prev, placeName: newValue }));
const handlePlaceTypeSetState = (newValue: `${placeTypes}`) =>
setFormState((prev) => ({ ...prev, placeTypes: newValue }));
const handleParentIdSetState = (newValue: string, label: string) =>
setFormState((prev) => ({ ...prev, parentId: { value: newValue, label } }));
return {
handleNameSetState,
handlePlaceTypeSetState,
handleParentIdSetState,
};
};
const resetParentIdOnChangePlaceType = (setFormState: React.Dispatch<React.SetStateAction<PlaceFormState>>) => {
setFormState((prev) => ({ ...prev, parentId: { label: '', value: '' } }));
};
const useCreatePlaceVm = (dependencies: ICreatePlaceVm) => {
const { getInputOptions } = dependencies;
const { getParentIds, getPlaceType } = getInputOptions;
const [formState, setFormState] = useState<PlaceFormState>(initialPlaceFormState);
const { handleNameSetState, handleParentIdSetState, handlePlaceTypeSetState } = useMemo(
() => onChangeInputStateHandlers(setFormState),
[],
);
useEffect(() => resetParentIdOnChangePlaceType(setFormState), [formState.placeTypes]);
const getPlaceList = useCallback(() => useGetPlaceList()(), []);
const places = getPlaceList();
const placeTypesOptions = useMemo(() => getPlaceType(), []);
const parentIdsOptions = useMemo(() => {
if (!places.data) return [];
return getParentIds(formState.placeTypes, places.data);
}, [formState.placeTypes, places]);
return {
formStateData: {
formState,
setFormState,
},
handleNameSetState,
inputStateHandlers: {
name: handleNameSetState,
parentId: handleParentIdSetState,
placeType: handlePlaceTypeSetState,
},
selectBoxOptions: {
placeTypesOptions,
parentIdsOptions,
isParentIdLoading: places.isLoading,
},
};
};

View File

@ -0,0 +1,12 @@
import { CreatePlaceType } from '../model/protocols';
import { PlaceFormState, placeTypes } from '../view/protocol';
export const initialPlaceFormState: PlaceFormState = {
placeTypes: placeTypes.continent,
placeName: '',
parentId: { value: '', label: '' },
};
export interface ICreatePlaceVm {
getInputOptions: CreatePlaceType['inputOptions'];
}

View File

@ -1,44 +1,9 @@
/* eslint-disable consistent-return */
/* eslint-disable no-underscore-dangle */
import React from 'react';
import getPlaces from '~/business-logic/core/places/get-places';
import getPlacesAdapter from '~/driven/adapters/get-places-adapter/getPlacesAdapter';
import PlacesModel from '~/business-logic/core/places/common/model/placesModel';
import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers';
import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import { apiUrls } from '~/driven/utils/configs/appConfig';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { QrPlace } from '~/business-logic/core/places/common/entity/placeEntity';
import PlacesListView from '../view/PlacesListView';
import usePlacesListVM from '../viewmodel/placesListVM';
import placesListModel from '../model/placesListModel';
type QrCodeResponse = {
one_time: false;
place_id: string;
user_id: string;
_id: string;
}[];
const QrCodeRO = (response: QrCodeResponse): QrPlace[] => {
return response.map((qrCode) => ({
id: qrCode._id,
oneTime: qrCode.one_time,
placeId: qrCode.place_id,
userId: qrCode.user_id,
}));
};
const prepareTheLogicForModel = () => {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const { user } = userData;
const gettingPlacesDrivenAdapter = getPlacesAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth);
const { url } = gettingPlacesDrivenAdapter;
const getingPlacesLogic = getPlaces(gettingPlacesDrivenAdapter);
return { getingPlacesLogic, url };
};
import useGetPlaceList from '../../common/hooks/useGetPlaceList';
export interface IPlacesListProps {
selectedRowId: string;
@ -47,40 +12,8 @@ export interface IPlacesListProps {
export default function PlacessList(props: IPlacesListProps) {
const { selectedRowId, setSelectedRowId } = props;
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const getQrCodes = async () => {
try {
// url
const apiUrl = apiUrls.core.getQrs;
// options
const apiOptions: HttpOptionsType = {
url: apiUrl,
};
// request
const userToken = {
accessToken: userData.user?.adminUserData.accessToken || null,
refreshToken: userData.user?.adminUserData.refreshToken || null,
};
const httpProvider = new HTTPPovider(userToken, accessTokenUpdateHandler, notLoginAuth);
const response = await httpProvider.request<QrCodeResponse>(apiOptions);
const qrCodeList = QrCodeRO(response);
return qrCodeList;
// update the query
} catch (error) {
console.log(error);
}
};
const { getingPlacesLogic, url } = prepareTheLogicForModel();
const getPlacesWithQrLogic = async () => {
const placesList = await getingPlacesLogic();
const qrCodesList = await getQrCodes();
placesList.setQrFor(qrCodesList || []);
return placesList;
};
const placesModel = async () => await placesListModel(getPlacesWithQrLogic);
const useGetPlacesList = prepareStateManagementForVM<PlacesModel>(url, placesModel);
const useGetPlacesList = useGetPlaceList();
const { placesData } = usePlacesListVM({
useGetPlacesList,
});