Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
doctors-web-app
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
8
Merge Requests
8
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Рогов Артем Владимирович
doctors-web-app
Commits
56e0a0de
Commit
56e0a0de
authored
Nov 14, 2023
by
konstantin-smirnov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
DMVP-1050 - Hide Start Call Button Depending On Settings
parent
d1e0d6e1
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
182 additions
and
32 deletions
+182
-32
.env
.env
+4
-1
appointmentPage.scss
src/pages/AppointmentPage/appointmentPage.scss
+18
-7
index.tsx
src/pages/AppointmentPage/index.tsx
+84
-17
schedule.js
src/redux/modules/schedule/actions/schedule.js
+8
-0
reducer.tsx
src/redux/modules/schedule/reducer.tsx
+30
-2
types.ts
src/redux/modules/schedule/types.ts
+5
-2
watcher.js
src/redux/modules/schedule/watcher.js
+25
-1
types.ts
src/types.ts
+6
-0
api.js
src/utils/api.js
+2
-2
No files found.
.env
View file @
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
src/pages/AppointmentPage/appointmentPage.scss
View file @
56e0a0de
...
...
@@ -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
{
...
...
src/pages/AppointmentPage/index.tsx
View file @
56e0a0de
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
)
?
(
is
SettingsLoading
||
is
Loading
||
(
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
)),
...
...
src/redux/modules/schedule/actions/schedule.js
View file @
56e0a0de
...
...
@@ -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
);
...
...
src/redux/modules/schedule/reducer.tsx
View file @
56e0a0de
...
...
@@ -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
,
...
...
src/redux/modules/schedule/types.ts
View file @
56e0a0de
...
...
@@ -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
,
}
src/redux/modules/schedule/watcher.js
View file @
56e0a0de
...
...
@@ -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
src/types.ts
View file @
56e0a0de
...
...
@@ -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
,
...
...
src/utils/api.js
View file @
56e0a0de
...
...
@@ -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'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment