Unverified Commit 6a163c20 authored by ksmirnov's avatar ksmirnov Committed by GitHub

Merge pull request #2 from AyaDigital/feature/DMVP-1039

DMVP-1039 - Appointments Global Settings Page
parents 8b76554a c3cf7143
REACT_APP_TOKEN_ENDPOINT=https://telehealth-token.aya-doc.com/token
REACT_APP_BACKEND_URL=https://telehealth-token.aya-doc.com
REACT_APP_BACKEND_DEV_URL=https://telehealth-token-dev.aya-doc.com
REACT_APP_SET_AUTH=keycloak
REACT_APP_KEYCLOAK_URL=https://auth-v3.aya-doc.com/auth
REACT_APP_BACKEND_API=https://api-v3.aya-doc.com/api
......
......@@ -18,31 +18,33 @@ import { ProfilesManagement } from 'pages/profilesCreation';
import { ProfilesEdition } from 'pages/profilesEdition';
import { Appeals } from 'pages/appeals';
import { Languages } from 'pages/languages';
import { Appointments } from 'pages/appointments';
function App() {
return (
<>
<Routes>
<Route path="/" element={<AppLayout />}>
<Route index element={Home} />
<Route path="profiles" element={<Practitioners />} />
<Route path="profile/create" element={<ProfilesManagement />} />
<Route path="profile/edit/:id" element={<ProfilesEdition />} />
<Route path="dictionaries/specialities" element={<Specialities />} />
<Route path="dictionaries/cities" element={<Cities />} />
<Route path="dictionaries/emergencies" element={<Emergencies />} />
<Route path="dictionaries/appeals" element={<Appeals />} />
<Route path="dictionaries/languages" element={<Languages />} />
<Route path="dictionaries/medical-degrees" element={<MedicalDegrees />} />
<Route path="insurance" element={<Insurances />} />
<Route path="clinics" element={<Clinics />} />
<Route path="settings" element={<Settings />} />
<Route path="clinics/:id" element={<ClinicPage />} />
<Route path="onboarding" element={<Onboarding />} />
<Route path="*" element={<Navigate to="/" />} />
</Route>
</Routes>
<Routes>
<Route path="/" element={<AppLayout />}>
<Route index element={Home} />
<Route path="profiles" element={<Practitioners />} />
<Route path="profile/create" element={<ProfilesManagement />} />
<Route path="profile/edit/:id" element={<ProfilesEdition />} />
<Route path="dictionaries/specialities" element={<Specialities />} />
<Route path="dictionaries/cities" element={<Cities />} />
<Route path="dictionaries/emergencies" element={<Emergencies />} />
<Route path="dictionaries/appeals" element={<Appeals />} />
<Route path="dictionaries/languages" element={<Languages />} />
<Route path="dictionaries/medical-degrees" element={<MedicalDegrees />} />
<Route path="insurance" element={<Insurances />} />
<Route path="appointments" element={<Appointments />} />
<Route path="clinics" element={<Clinics />} />
<Route path="settings" element={<Settings />} />
<Route path="clinics/:id" element={<ClinicPage />} />
<Route path="onboarding" element={<Onboarding />} />
<Route path="*" element={<Navigate to="/" />} />
</Route>
</Routes>
</>
);
}
......
......@@ -36,7 +36,6 @@ const MapBlock = ({
};
const AnyReactComponent = ({ content, action = () => {}, text = '', icon }) => {
console.log('icon', icon)
if (icon) {
return icon;
} else {
......
......@@ -49,4 +49,4 @@ export const {
useCreateAppealMutation,
useEditAppealMutation,
useDeleteAppealMutation
} = appealsApi;
\ No newline at end of file
} = appealsApi;
......@@ -63,4 +63,4 @@ export const {
useFetchStatesQuery,
useSearchStatesQuery,
useSearchCitiesByStateQuery
} = geographyApi;
\ No newline at end of file
} = geographyApi;
......@@ -16,6 +16,7 @@ const EDUCATION = '/api/education-train';
const PROFILES = '/api/profiles';
const PROFILES_ROLES = '/api/profiles/roles';
const PROFILE = '/api/profile';
const API_SETTING_ADDRESS = '/api/settings/address';
export const profilesApi = createApi({
reducerPath: 'profilesApi',
......@@ -127,6 +128,21 @@ export const profilesApi = createApi({
method: 'PUT',
body: data
}),
}),
updateProfileAddress: build.mutation({
query: ({id, data}) => ({
url: HEALTHAPP_URI + API_SETTING_ADDRESS + '/' + id,
method: 'PUT',
body: data
}),
invalidatesTags: (result) => ["Address"],
}),
getProfileAddress: build.query({
query: (id) => ({
url: HEALTHAPP_URI + API_SETTING_ADDRESS + '/' + id,
method: 'GET'
}),
providesTags: (result) => ["Address"],
})
}),
})
......@@ -144,5 +160,7 @@ export const {
useUploadProfileAvatarByUuidMutation,
useDestroyProfileAvatarByUuidMutation,
useGetProfileAvatarByUuidQuery,
useUpdateCredentialsMutation
useUpdateCredentialsMutation,
useUpdateProfileAddressMutation,
useGetProfileAddressQuery
} = profilesApi;
......@@ -14,6 +14,8 @@ import { degreesApi } from './api/degrees.api';
import { languagesApi } from './api/languages.api';
import { emergencyApi } from './api/emergency.api';
import { appealsApi } from './api/appeals.api';
import { appointmentsApi } from './api/appointments.api';
import { geocoderApi } from './api/geocoder.api';
import { authReducer } from './slice/auth.slice';
import { profileReducer } from './slice/profile.slice';
......@@ -33,6 +35,8 @@ export * from './api/degrees.api';
export * from './api/languages.api';
export * from './api/emergency.api';
export * from './api/appeals.api';
export * from './api/geocoder.api';
export * from './api/appointments.api';
export const store = configureStore({
reducer: {
......@@ -52,7 +56,9 @@ export const store = configureStore({
[degreesApi.reducerPath]: degreesApi.reducer,
[languagesApi.reducerPath]: languagesApi.reducer,
[emergencyApi.reducerPath]: emergencyApi.reducer,
[appealsApi.reducerPath]: appealsApi.reducer
[appealsApi.reducerPath]: appealsApi.reducer,
[geocoderApi.reducerPath]: geocoderApi.reducer,
[appointmentsApi.reducerPath]: appointmentsApi.reducer
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware()
......@@ -70,5 +76,7 @@ export const store = configureStore({
.concat(languagesApi.middleware)
.concat(emergencyApi.middleware)
.concat(appealsApi.middleware)
.concat(geocoderApi.middleware)
.concat(appointmentsApi.middleware)
.concat(practitionersApi.middleware),
});
......@@ -232,6 +232,23 @@ const SideBar = () => {
</li>
) : null
}
{
isAdmin ? (
<li>
<Button
sx={menu}
disableripple='true'
isDisabled={pathname.includes('appointments')}
onClick={() => navigate('/appointments')}
>
<div className="button-label">
<Settingsicon />
<div className="menu-item-title">Appointments</div>
</div>
</Button>
</li>
) : null
}
</ul>
</div>
</div>
......
import React, {useEffect, useState} from 'react';
import { useNavigate } from "react-router-dom";
import Pagination from 'rc-pagination';
import clone from 'lodash/clone';
import 'styles/assets/pagination.scss'
import { Button } from '@chakra-ui/react';
import {
useFetchAppointmentSettingsQuery,
usePatchAppointmentSettingsMutation
} from '../../_store';
import Loader from '../../_components/Loader';
import SuccessIcon from '../../images/Icons/successIcon';
import FailedIcon from '../../images/Icons/failedIcon';
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
Grid,
GridItem,
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
Flex
} from '@chakra-ui/react';
import {
Checkbox,
Input as TextField,
} from '@chakra-ui/react';
import './appointments.scss';
import { } from "react-router-dom";
const Appointments = () => {
const navigate = useNavigate();
const { isLoading, isSuccess, data = {}} = useFetchAppointmentSettingsQuery();
const [ patchSettings, {isLoading: isPatchLoading, isSuccess: isPatchSuccess}] = usePatchAppointmentSettingsMutation();
const [open, setOpen] = useState(false);
const [currentBeforeTimeout, setCurrentBeforeTimeout] = useState(0);
const [currentAfterTimeout, setCurrentAfterTimeout] = useState(0);
const [globalTimeoutAppointment, setGlobalTimeoutAppointment] = useState(false);
const handleOpen = () => {
setOpen(true);
}
useEffect(() => {
if (isSuccess) {
setCurrentBeforeTimeout(data.beforeTimeout);
setCurrentAfterTimeout(data.afterTimeout);
setGlobalTimeoutAppointment(data.globalAppointmentTimeout || false);
}
}, [isSuccess, data]);
useEffect(() => {
if (isPatchSuccess) {
setOpen(false);
}
}, [isPatchSuccess])
const handleClose = () => setOpen(false);
const onChange = ({ target: {value, checked} }) => {
setGlobalTimeoutAppointment(checked);
}
return (
<div className='appointments-layout'>
<Breadcrumb
fontWeight='medium'
fontSize='sm'
>
<BreadcrumbItem>
<BreadcrumbLink
color='gray.500'
onClick={() => {navigate('/')}}
>
Home
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbLink
href='#'
isCurrentPage
color='gray.500'
>
Appointments Global Settings
</BreadcrumbLink>
</BreadcrumbItem>
</Breadcrumb>
<div className='controls-block'>
<div>
<Button
sx={{
backgroundColor: 'rgba(44, 121, 206, 0.7)',
color: 'white'
}}
size='lg'
onClick={handleOpen}
>
Edit
</Button>
</div>
</div>
{
isLoading ? (
<Loader height='500px' width={'100%'} />
) : (
<>
<Grid
//gridTemplateRows={'100px'}
gridTemplateColumns={'50% 50%'}
//w='750px'
h='auto'
rowGap='5'
gap='1'
alignItems={'flex-start'}
color='blackAlpha.700'
fontWeight='bold'
>
<GridItem>
Before timeout
</GridItem>
<GridItem>
{currentBeforeTimeout}
</GridItem>
<GridItem>
After timeout
</GridItem>
<GridItem>
{currentAfterTimeout}
</GridItem>
<GridItem>
Is Global Settings Active
</GridItem>
<GridItem>
{
globalTimeoutAppointment ? <SuccessIcon /> : <FailedIcon />
}
</GridItem>
</Grid>
</>
)
}
<Modal
isOpen={open}
//isCentered
size='xl'
onClose={() => handleClose()}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<Flex justify="center">Appointment Global Settings</Flex>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<div className='edit-form'>
<Grid
gridTemplateColumns={'50% 50%'}
h='auto'
rowGap='5'
gap='1'
alignItems={'flex-start'}
color='blackAlpha.700'
fontWeight='bold'
>
<GridItem>
Before timeout
</GridItem>
<GridItem>
<TextField
value={currentBeforeTimeout}
onChange={({ target: {value} }) => {
setCurrentBeforeTimeout(value)
}}
/>
</GridItem>
<GridItem>
After timeout
</GridItem>
<GridItem>
<TextField
value={currentAfterTimeout}
onChange={({ target: {value} }) => {
setCurrentAfterTimeout(value)
}}
/>
</GridItem>
<GridItem>
Is Global Settings Active
</GridItem>
<GridItem>
<Checkbox
onChange={onChange}
isChecked={globalTimeoutAppointment}
/>
</GridItem>
<GridItem rowSpan={1} colSpan={1} textAlign={'center'}>
<Button
w='180px'
h='50px'
variant='outline'
colorScheme='teal'
size='xl'
onClick={() => {
setOpen(false)
}}
>
Close
</Button>
</GridItem>
<GridItem rowSpan={1} colSpan={1} textAlign={'center'}>
<Button
colorScheme='teal'
variant='outline'
w='180px'
h='50px'
size='xl'
isLoading={isPatchLoading}
onClick={() => {
const patchData = {
beforeTimeout: currentBeforeTimeout,
afterTimeout: currentAfterTimeout,
globalTimeoutAppointment
};
patchSettings(patchData);
}}
>
Save
</Button>
</GridItem>
</Grid>
</div>
</ModalBody>
</ModalContent>
</Modal>
</div>
)
};
export { Appointments };
@mixin flex-col {
display: flex;
flex-direction: column;
row-gap: 2px;
}
@mixin flex-row {
display: flex;
flex-direction: row;
column-gap: 10px;
justify-content: flex-start;
}
.appointments-layout {
.controls-block {
padding: 10px;
padding-bottom: 20px;
}
}
.edit-form {
padding: 20px 0px 20px 0px;
}
export * from './Appointments';
......@@ -71,7 +71,7 @@ const ProfilesEdition = () => {
<SpecialityData id={profileId} />
</TabPanel>
<TabPanel>
<AddressData />
<AddressData id={profileId} />
</TabPanel>
</TabPanels>
</Tabs>
......
import React, { useState, useEffect } from 'react';
import {
Input as TextField,
Button,
Grid,
GridItem,
Spinner
Spinner,
Avatar
} from '@chakra-ui/react';
import {
AutoComplete,
......@@ -12,21 +14,35 @@ import {
AutoCompleteList,
} from "@choc-ui/chakra-autocomplete";
import Map from '_components/map/map';
import {
useUpdateProfileAddressMutation,
useGetProfileAddressQuery,
useGetProfileAvatarByUuidQuery
} from '_store';
import usePlacesService from "react-google-autocomplete/lib/usePlacesAutocompleteService";
import useGeoLocation from 'hooks/useGeoLocation/useGeoLocation';
import useGeoLocation from 'hooks/useGeoLocation/useGeolocationApi';
import CloseIcon from 'images/Icons/closeIcon';
import '../../profilesEdition.scss';
const AddressData = ({
practitionerId = null,
onClose,
setError
id = null,
}) => {
const [name, setName] = useState('');
const {locations, latlng, setLatLng, setLocations} = useGeoLocation();
const [ updataProfileAddress, { isLoading: isAddressLoading, isSuccess: isAddressUpdated }] = useUpdateProfileAddressMutation();
const { isSuccess, data: address = {} } = useGetProfileAddressQuery(id);
const { isLoading: isAvatarLoading, isSuccess: isAvatarLoaded, data: avatarData = {} } = useGetProfileAvatarByUuidQuery(id);
const [currentlatlng, setCurrentLatLng] = useState();
const [addressLine, setAddressLine] = useState('');
const [avatar, setAvatar] = useState('');
const [currentProfileAddress, setCurrrentProfileAddress] = useState('');
useEffect(() => {
if (isAvatarLoaded) {
setAvatar(avatarData.fullUrl);
}
}, [isAvatarLoaded, avatarData]);
const {
placePredictions,
getPlacePredictions,
......@@ -40,6 +56,27 @@ const AddressData = ({
setLatLng({lat, lng});
}
useEffect(() => {
if (isSuccess) {
setCurrentLatLng({lat: address.lat, lng: address.long})
setAddressLine(address?.address_line);
setCurrrentProfileAddress(address?.address_line);
}
}, [isSuccess, address]);
useEffect(() => {
if (isAddressUpdated) {
setLocations([]);
}
}, [isAddressUpdated])
const handleUpdate = () => {
const patchData = {
"address_line": addressLine
}
updataProfileAddress({id, data: patchData})
}
const getAddressList = () => {
return (
<div className='locations-menu'>
......@@ -59,9 +96,9 @@ const AddressData = ({
href='#'
onClick={(event) => {
event.stopPropagation();
setAddressLine(item.formatted_address);
setAddressLine(item.formattedAddress);
}}>
{item.formatted_address}
{item.formattedAddress}
</a>
</div>;
})
......@@ -85,7 +122,7 @@ const AddressData = ({
/>
) : (
<Grid
gridTemplateRows={'repeat(2, ifr)'}
gridTemplateRows={'repeat(3, 1fr)'}
gridTemplateColumns={'250px 250px 250px'}
w='300px'
h='250px'
......@@ -97,7 +134,7 @@ const AddressData = ({
<GridItem h='50px' colSpan={1} className='label'>
Enter Addrtess Line
</GridItem>
<GridItem h='50px' colSpan={2}>
<GridItem colSpan={2}>
<AutoComplete
focusInputOnSelect
placeholder='Select option'
......@@ -129,7 +166,7 @@ const AddressData = ({
</AutoCompleteList>
</AutoComplete>
</GridItem>
<GridItem h='50px' colSpan={3} className='label'>
<GridItem colSpan={3} className='label'>
<Map
className='current-map'
zoom={8}
......@@ -137,14 +174,19 @@ const AddressData = ({
lat: currentlatlng?.lat,
lng: currentlatlng?.lng,
}}
onClick={handleMapClick}
//icon={}
markers={currentProfileAddress ?
[{
onClick={handleMapClick}
icon={
<Avatar
src={avatar}
size='lg'
/>
}
marker={currentProfileAddress ?
{
lat: currentlatlng?.lat,
lng: currentlatlng?.lng,
text: name,
}] : undefined}
text: '',
} : undefined}
overlay={locations?.length ? {
lat: latlng?.lat,
lng: latlng?.lng,
......@@ -153,6 +195,21 @@ const AddressData = ({
/>
</GridItem>
<GridItem h='50px' colSpan={3}>
<div className='control'>
<Button
w='30%'
h='50px'
variant='outline'
colorScheme='green'
size='xl'
onClick={handleUpdate}
>
Save and continue editing
</Button>
</div>
</GridItem>
</Grid>
)
}
......
......@@ -46,7 +46,6 @@ const BaseData = ({
useEffect(() => {
if (profile) {
console.log('profile.fullName', profile.fullName);
setEmail(profile.email);
setFirstName(profile.fullName);
setIsVerified(profile.verified);
......
......@@ -3,7 +3,6 @@ import React, { useState, useEffect } from 'react';
import {
Avatar,
Button,
Select,
Textarea,
Grid,
GridItem,
......
......@@ -29,7 +29,7 @@
}
& > div > div > .control {
@include flex-row();
justify-content: flex-end;
justify-content: space-around;
}
& > div > div > .items-cloud {
......@@ -71,6 +71,12 @@
}
}
.control {
display: flex;
justify-content: space-around;
padding: 20px;
}
.listbox {
width: '200px';
margin: 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment