Unverified Commit 88d32284 authored by ksmirnov's avatar ksmirnov Committed by GitHub

Merge pull request #9 from AyaDigital/feature/DMVP-1050

DMVP-1050 - Hide Start Call Button Depending On Settings
parents d1e0d6e1 56e0a0de
......@@ -3,6 +3,7 @@ REACT_APP_BACKEND_URL=https://telehealth-token.aya-doc.com
REACT_APP_SET_AUTH=keycloak
REACT_APP_KEYCLOAK_URL=https://auth-v3.aya-doc.com/auth
REACT_APP_BASE_URL=https://api-v3.aya-doc.com
REACT_APP_BACKEND_DEV_URL=https://telehealth-token-dev.aya-doc.com
REACT_APP_KEYCLOAK_REALM=aya-realm
REACT_APP_KEYCLOAK_CLIENT=web-client
......@@ -10,4 +11,6 @@ REACT_APP_KEYCLOAK_SCOPE=email
REACT_APP_GOOGLE=AIzaSyAwito9ePyRNK_J7Nx5iWjz_BPNEhUmppA
REACT_APP_PORT=8083:80
\ No newline at end of file
REACT_APP_PORT=8083:80
PUPPETEER_SKIP_DOWNLOAD=true
\ No newline at end of file
......@@ -146,17 +146,28 @@
.controls-block {
display: flex;
flex-direction: column;
.controls-button-label {
& > .availability-block {
width: max-content;
display: flex;
flex-direction: row;
flex-direction: column;
row-gap: 0px;
font-family: Manrope;
font-size: 20px;
color: green;
}
& > div:nth-child(1) {
& > .button-block {
.controls-button-label {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 8px;
}
& > div:nth-child(1) {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 8px;
}
}
}
& > div {
......
import React, {useEffect, useState} from 'react';
import React, {useEffect, useState, useMemo} from 'react';
import { DispatchProp, connect } from 'react-redux';
import { useNavigate, useLocation } from 'react-router-dom';
import { styled } from '@material-ui/core/styles';
......@@ -12,11 +12,13 @@ import NonAttendingIcon from '../../images/Icons/nonAttendingIcon';
import VideoIcon from '../../images/Icons/videoIcon';
import Avatar from '../../images/appointmentAvatar.png';
import AppStateType from '../../redux/types';
import { AppointmentT, ParticipantT } from '../../types';
import { AppointmentT, ParticipantT, AppointmentSettingsT } from '../../types';
import Loader from '../../components/layout/Loader';
import { getAppointmentRequest, clearSelectedAppointment } from 'redux/modules/doctors/actions/doctors';
import { setNonAttendingPatientRequest } from 'redux/modules/patients/actions/patients';
import { cancelAppointmentRequest, clearCanceledStatus, clearErrorStatus } from 'redux/modules/schedule/actions/schedule';
import {
cancelAppointmentRequest, clearCanceledStatus, clearErrorStatus, getAppointmentsGlobalSettingsRequest
} from 'redux/modules/schedule/actions/schedule';
import { getProfileByTokenRequest } from 'redux/modules/profile/actions/profile';
import { setModalWindowOpen } from '../../redux/modules/layout/actions/modalActions';
import { CancelAppointmentModal } from './components/cancel'
......@@ -35,7 +37,9 @@ type DispatchProps = ReturnType<typeof mapDispatchToProps>;
type AppointmentProps = {
doctorId?: number,
isSettingsLoading: boolean,
selectedAppointment: AppointmentT | null,
settings: AppointmentSettingsT,
isLoading?: boolean,
appointmentCanceled?: boolean,
error: string,
......@@ -46,13 +50,16 @@ const Patients: React.FC<AppointmentProps> = ({
selectedAppointment: profile,
doctorId,
cancellError,
isSettingsLoading,
isLoading, getAppointmennt = () => {},
clearApointment = () => {},
getAppointmentsGlobalSettings = () => {},
cancelAppointment,
getProfile,
clearCanceled = () => {},
setNonAttendingPatient = () => {},
clearError = () => {},
settings,
appointmentCanceled
}) => {
const [isCancelWindowOpen, setIsCancelWindowOpen] = useState<boolean>(false);
......@@ -62,6 +69,8 @@ const Patients: React.FC<AppointmentProps> = ({
const [participant, setParticipant] = useState<ParticipantT>();
const [appointmentId, setAppointmentId] = useState<number>(0);
const [appointmentStatus, setAppointmentStatus] = useState<string>('');
const [availabilityMessage, setAvailabilityMessage] = useState<string>('');
const [isVideoCallAvailable, setIsVideoCallAvailable] = useState<boolean>(false);
const navigate = useNavigate();
const { state: id, pathname } = useLocation();
......@@ -108,6 +117,54 @@ const Patients: React.FC<AppointmentProps> = ({
}
}, [pathname, id])
useEffect(() => {
if (!settings?.globalTimeoutAppointment) {
setIsVideoCallAvailable(true)
} else if (appointmentDate && settings?.globalTimeoutAppointment) {
const appointmentDatePoint = moment(appointmentDate);
const afterTimeout = settings.afterTimeout;
const beforeTimeout = settings.beforeTimeout;
let message = '';
const interval = setInterval(() => {
const now = moment();
const diff = appointmentDatePoint.diff(now, 'minutes');
const callUnavailable = (diff < 0) && ((diff * -1) > afterTimeout) || diff > beforeTimeout;
if (callUnavailable) {
setIsVideoCallAvailable(false);
if((diff < 0) && ((diff * -1) > afterTimeout)) {
message =
`The call is available ${afterTimeout} minutes after the start of the meeting. (${handleMinutes(diff * -1)} passed)`;
clearInterval(interval)
} else {
if (diff > beforeTimeout) {
message =
`The call will be available ${beforeTimeout} minutes before the start of the meeting. (${handleMinutes(diff)} left)`;
}
}
setAvailabilityMessage(message);
} else {
if (!isVideoCallAvailable) {
setAvailabilityMessage('');
setIsVideoCallAvailable(true);
}
}
return () => clearInterval(interval);
}, 1000)
}
}, [settings, appointmentDate])
const handleMinutes = (totalMinutes: number) => {
const minutes = totalMinutes % 60;
const hours = Math.floor(totalMinutes / 60);
if (hours > 0) {
return `${hours}h${minutes > 0 ? ` ${minutes} m` : ''}`;
} else {
return `${totalMinutes} m`;
}
}
const handleVideoCall = () => {
window.open(`https://telehealth.aya-doc.com/room/${appointmentId}`, '_blank');
}
......@@ -116,6 +173,7 @@ const Patients: React.FC<AppointmentProps> = ({
if (getAppointmennt && appointmentId) {
getAppointmennt(appointmentId);
getProfile();
getAppointmentsGlobalSettings();
}
}, [appointmentId, getAppointmennt])
......@@ -164,7 +222,7 @@ const Patients: React.FC<AppointmentProps> = ({
</div>
<div>
{
isLoading || (profile === null) ? (
isSettingsLoading || isLoading || (profile === null) ? (
<Loader height='600px' />
) : (
<>
......@@ -218,18 +276,24 @@ const Patients: React.FC<AppointmentProps> = ({
<div className='controls-block'>
{
appointmentType === 'ONLINE' ? (
<div>
<RescheduleButton
variant="contained"
disableRipple={true}
onClick={() => handleVideoCall()}
>
<div className="controls-button-label">
<VideoIcon />
<div>Video call</div>
</div>
</RescheduleButton>
</div>
isVideoCallAvailable ? (
<div className='button-block'>
<RescheduleButton
variant="contained"
disableRipple={true}
onClick={() => handleVideoCall()}
>
<div className="controls-button-label">
<VideoIcon />
<div>Video call</div>
</div>
</RescheduleButton>
</div>
) : (
<div className='availability-block'>
<div>{availabilityMessage}</div>
</div>
)
) : null
}
<div>
......@@ -326,12 +390,15 @@ const mapStateToProps = (state: AppStateType) => ({
selectedAppointment: state.doctors.selectedAppointment,
appointmentCanceled: state.schedule.appointmentCanceled,
error: state.schedule.error,
cancellError: state.schedule.cancellError
cancellError: state.schedule.cancellError,
settings: state.schedule.settings,
isSettingsLoading: state.schedule.isSettingsLoading
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
getProfile: () => dispatch(getProfileByTokenRequest()),
getAppointmennt: (data: number) => dispatch(getAppointmentRequest(data)),
getAppointmentsGlobalSettings: () => dispatch(getAppointmentsGlobalSettingsRequest()),
clearApointment: () => dispatch(clearSelectedAppointment()),
setModalOpen: (data: boolean) => dispatch(setModalWindowOpen(data)),
cancelAppointment: (data: number) => dispatch(cancelAppointmentRequest(data)),
......
......@@ -40,6 +40,10 @@ export const GET_CANCELLED_APPOINTMENTS_REQUEST = 'GET_CANCELED_APPOINTMENTS_REQ
export const GET_CANCELLED_APPOINTMENTS_SUCCESS = 'GET_CANCELED_APPOINTMENTS_SUCCESS';
export const GET_CANCELLED_APPOINTMENTS_FAILURE = 'GET_CANCELED_APPOINTMENTS_FAILURE';
export const GET_APPOINTMENTS_GLOBAL_SETTINGS_REQUEST = 'GET_APPOINTMENTS_GLOBAL_SETTINGS_REQUEST';
export const GET_APPOINTMENTS_GLOBAL_SETTINGS_SUCCESS = 'GET_APPOINTMENTSS_GLOBAL_SETTINGS_SUCCESS';
export const GET_APPOINTMENTS_GLOBAL_SETTINGS_FAILURE = 'GET_APPOINTMENTSS_GLOBAL_SETTINGS_FAILURE';
export const UPDATE_SCHEDULE_STATUS = 'UPDATE_SCHEDULE_STATUS';
export const UPDATE_APPOINTMENT_STATUS = 'UPDATE_APPOINTMENT_STATUS';
export const CLEAR_CANCELED_STATUS = 'CLEAR_CANCELED_STATUS';
......@@ -50,6 +54,10 @@ export const getCountSlotsRequest = createAction(GET_COUNT_SLOTS_REQUEST);
export const getCountSlotsSuccess = createAction(GET_COUNT_SLOTS_SUCCESS);
export const getCountSlotsFailure = createAction(GET_COUNT_SLOTS_FAILURE);
export const getAppointmentsGlobalSettingsRequest = createAction(GET_APPOINTMENTS_GLOBAL_SETTINGS_REQUEST);
export const getAppointmentsGlobalSettingsSuccess = createAction(GET_APPOINTMENTS_GLOBAL_SETTINGS_SUCCESS);
export const getAppointmentsGlobalSettingsFailure = createAction(GET_APPOINTMENTS_GLOBAL_SETTINGS_FAILURE);
export const getCancelledAppointmentsRequest = createAction(GET_CANCELLED_APPOINTMENTS_REQUEST);
export const getCancelledAppointmentsSuccess = createAction(GET_CANCELLED_APPOINTMENTS_SUCCESS);
export const getCancelledAppointmentsFailure = createAction(GET_CANCELLED_APPOINTMENTS_FAILURE);
......
......@@ -30,7 +30,10 @@ import {
DELETE_SCHEDULE_FAILURE,
GET_CANCELLED_APPOINTMENTS_REQUEST,
GET_CANCELLED_APPOINTMENTS_SUCCESS,
GET_CANCELLED_APPOINTMENTS_FAILURE
GET_CANCELLED_APPOINTMENTS_FAILURE,
GET_APPOINTMENTS_GLOBAL_SETTINGS_REQUEST,
GET_APPOINTMENTS_GLOBAL_SETTINGS_SUCCESS,
GET_APPOINTMENTS_GLOBAL_SETTINGS_FAILURE
} from './actions/schedule';
export const INITIAL_STATE: ScheduleState | null = {
......@@ -46,11 +49,36 @@ export const INITIAL_STATE: ScheduleState | null = {
appointments: [],
cancelledAppointments: [],
error: '',
cancellError: ''
cancellError: '',
isSettingsLoading: false,
settings: {
beforeTimeout: 0,
afterTimeout: 0,
globalTimeoutAppointment :false
},
};
const ScheduleReducer = (state = INITIAL_STATE, { type, payload }: ScheduleActions<{data: any}>) => {
switch (type) {
case GET_APPOINTMENTS_GLOBAL_SETTINGS_REQUEST:
return {
...state,
isSettingsLoading: true,
error: ''
}
case GET_APPOINTMENTS_GLOBAL_SETTINGS_SUCCESS:
return {
...state,
isSettingsLoading: false,
settings: payload,
error: ''
}
case GET_APPOINTMENTS_GLOBAL_SETTINGS_FAILURE:
return {
...state,
isSettingsLoading: false,
error: payload
}
case DELETE_SCHEDULE_REQUEST:
return {
...state,
......
......@@ -2,7 +2,8 @@ import { type } from 'os';
import {
SlotsCountItemT as SlotTtype,
NewAppointmentT as NewAppointmentCreatedT,
SlotItemT
SlotItemT,
AppointmentSettingsT
} from '../../../types';
export interface ScheduleActions<T> {
type: string;
......@@ -19,9 +20,11 @@ export type ScheduleState = {
cancelledAppointments: any[],
error: string,
cancellError: string,
isSettingsLoading: boolean,
scheduleCreated: boolean,
appointmentCreated: boolean,
newAppointment: Partial<NewAppointmentCreatedT>,
slots: SlotsCountItemT[],
mySlots: SlotItemT[]
mySlots: SlotItemT[],
settings: AppointmentSettingsT,
}
......@@ -28,7 +28,10 @@ import {
deleteScheduleSuccess,
getCancelledAppointmentsRequest,
getCancelledAppointmentsSuccess,
getCancelledAppointmentsFailure
getCancelledAppointmentsFailure,
getAppointmentsGlobalSettingsRequest,
getAppointmentsGlobalSettingsSuccess,
getAppointmentsGlobalSettingsFailure
} from './actions/schedule';
import { getDoctorSlotsJwtRequest } from 'redux/modules/doctors/actions/doctors';
......@@ -44,6 +47,8 @@ const getTimeZone = () => {
return `${offsetOperator}${offsetHours}:${offsetMinutes}`
}
const globalSettingsBaseUrl = process.env.REACT_APP_BACKEND_URL;
const countSlotsUrl = 'api/count-slots-practitioners';
const schedulesUrl = 'api/schedules';
const appointmentUrl = 'api/appointments';
......@@ -51,6 +56,24 @@ const updateAppointmentUrl = 'api/appointments/reschedule';
const cancelAppointmentUrl = 'api/appointments/cancel';
const cuurrentSlotsUrl = 'api/slots/slots-appointments-practitioner';
const cancelledAppointmentsUrl = 'api/appointments/appointment-status-cancelled';
const globalSettingsUrl = 'settings/appointmentWindow';
function* getAppointmentsGlobalSettings() {
try {
let token = yield select((state) => state.auth.token);
const data = yield call(api.getData, token, globalSettingsUrl, globalSettingsBaseUrl);
if (data) {
yield put(getAppointmentsGlobalSettingsSuccess(data))
} else {
yield put(getAppointmentsGlobalSettingsFailure())
}
} catch (error) {
console.log('error', error);
yield put(getAppointmentsGlobalSettingsFailure(error));
}
}
function* getCountSlots({ payload }) {
try {
......@@ -250,5 +273,6 @@ export default function* root() {
yield takeEvery(getMySlotsRequest, getMySlots);
yield takeEvery(deleteScheduleRequest, deleteSchedule);
yield takeEvery(getCancelledAppointmentsRequest, getCancelledAppointments);
yield takeEvery(getAppointmentsGlobalSettingsRequest, getAppointmentsGlobalSettings);
}
\ No newline at end of file
......@@ -260,6 +260,12 @@ export type EmergencyContactItemT = {
type: EmergencyTypeT
}
export type AppointmentSettingsT = {
beforeTimeout: number,
afterTimeout: number,
globalTimeoutAppointment: boolean
}
export type EmergencyTypeDataT = {
scrollToken?: string | null,
scrollTime?: string,
......
......@@ -2,10 +2,10 @@ import axios from 'axios';
const BASE_URL = process.env.REACT_APP_BASE_URL;
export const getData = (token, url) => {
export const getData = (token, url, baseUrl = BASE_URL) => {
var requestOptions = {
method: 'GET',
url: `${BASE_URL}/${url}`,
url: `${baseUrl}/${url}`,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
......
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