Skip to content

Commit 3aa05e5

Browse files
committed
basic appointment booking working
1 parent 703eb1e commit 3aa05e5

File tree

7 files changed

+153
-44
lines changed

7 files changed

+153
-44
lines changed

src/components/appointments/Appointments.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class Appointments extends Component {
132132
got_data={Boolean(this.state.appointments)}
133133
root={this.props.root}
134134
config={this.props.config}
135+
update_apts={this.update}
135136
history={props.history}/>
136137
)}/>
137138
</div>

src/components/appointments/AptModal.js

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
11
import React, { Component } from 'react'
2-
import {IfElse, Markdown, DetailGrid, Detail} from '../shared/Tools'
2+
import {IfElse, Markdown, DetailGrid, Detail, Tick} from '../shared/Tools'
33
import Modal from '../shared/Modal'
44

5+
const LS_KEY = '_tcs_user_data_'
6+
57
class AptModal extends Component {
68
constructor (props) {
79
super(props)
810
this.apt_id = parseInt(this.props.id, 10)
911
this.login = this.login.bind(this)
1012
this.process_message = this.process_message.bind(this)
1113
this.update_session = this.update_session.bind(this)
14+
this.check_client = this.check_client.bind(this)
1215
this.signout = this.signout.bind(this)
1316
this.book = this.book.bind(this)
17+
this.get_srs = this.get_srs.bind(this)
1418
this.state = {
19+
apt: props.appointments && props.appointments.find(a => a.id === this.apt_id),
1520
signature: null,
1621
sso_data: null,
1722
display_data: null,
1823
appointment_attendees: null,
1924
new_student: null,
25+
booking_allowed: false,
26+
extra_attendees: 0,
2027
}
2128
}
2229

2330
componentDidMount () {
2431
window.addEventListener('message', this.process_message, false)
25-
this.update_session(window.sessionStorage.__tcs_user_data)
32+
this.update_session(window.sessionStorage[LS_KEY])
33+
}
34+
35+
componentWillReceiveProps (nextProps) {
36+
const new_apt = nextProps.appointments && nextProps.appointments.find(a => a.id === this.apt_id)
37+
if (this.state.apt && new_apt && this.state.apt.attendees_count !== new_apt.attendees_count) {
38+
// clear temporary extra_attendees
39+
this.setState({extra_attendees: 0})
40+
}
41+
this.setState({apt: new_apt})
2642
}
2743

2844
login () {
@@ -34,11 +50,11 @@ class AptModal extends Component {
3450
const success = this.update_session(event.data)
3551
if (success) {
3652
event.source.close()
37-
window.sessionStorage.__tcs_user_data = event.data
53+
window.sessionStorage[LS_KEY] = event.data
3854
}
3955
}
4056

41-
async update_session (raw_data) {
57+
update_session (raw_data) {
4258
let data
4359
try {
4460
data = JSON.parse(raw_data)
@@ -47,33 +63,51 @@ class AptModal extends Component {
4763
}
4864
data.display_data = JSON.parse(data.sso_data)
4965
this.setState(data)
66+
this.check_client(data)
67+
return true
68+
}
5069

70+
async check_client (data) {
71+
data = data || this.state
5172
const args = {signature: data.signature, sso_data: data.sso_data}
52-
this.props.root.requests.get('check-client', args, {set_app_state: false}).then(data => {
53-
this.setState({appointment_attendees: data.appointment_attendees})
54-
}).catch(e => {
73+
try {
74+
const r = await this.props.root.requests.get('check-client', args, {set_app_state: false})
75+
this.setState({appointment_attendees: r.appointment_attendees[this.apt_id] || [], booking_allowed: true})
76+
} catch (e) {
5577
if (e.xhr.status === 403) {
56-
console.log('got error in update_sesion:', e)
5778
this.signout()
5879
} else {
5980
this.props.root.setState({error: e.msg})
6081
}
61-
})
62-
return true
82+
}
6383
}
6484

6585
async book (student_id) {
66-
console.log('book:', student_id)
86+
this.setState({booking_allowed: false})
6787
const data = {appointment: this.apt_id}
6888
if (student_id) {
6989
data.student_id = student_id
7090
} else {
7191
data.student_name = this.state.new_student
72-
this.setState({new_student: null})
7392
}
7493
const url = `book-appointment?signature=${this.state.signature}&sso_data=${encodeURIComponent(this.state.sso_data)}`
75-
const r = await this.props.root.requests.post(url, data)
76-
console.log(r)
94+
await this.props.root.requests.post(url, data)
95+
this.props.update_apts()
96+
if (!student_id) {
97+
const display_data = Object.assign({}, this.state.display_data)
98+
student_id = 999999999
99+
display_data.srs[student_id] = data.student_name
100+
this.setState({new_student: null, display_data})
101+
window.sessionStorage.removeItem(LS_KEY) // force new login when opening appointment to update students
102+
}
103+
const appointment_attendees = this.state.appointment_attendees.slice()
104+
appointment_attendees.push(student_id)
105+
this.setState({
106+
booking_allowed: true,
107+
extra_attendees: this.state.extra_attendees + 1,
108+
appointment_attendees
109+
})
110+
setTimeout(() => this.props.update_apts(), 5000)
77111
}
78112

79113
signout () {
@@ -82,20 +116,32 @@ class AptModal extends Component {
82116
sso_data: null,
83117
display_data: null,
84118
appointment_attendees: null,
119+
new_student: null,
120+
booking_allowed: false,
121+
})
122+
window.sessionStorage.removeItem(LS_KEY)
123+
}
124+
125+
get_srs () {
126+
return this.state.display_data && Object.entries(this.state.display_data.srs).map(([k, name]) => {
127+
const sr_id = parseInt(k, 10)
128+
return {
129+
id: sr_id,
130+
name: name,
131+
already_on_apt: this.state.appointment_attendees && this.state.appointment_attendees.includes(sr_id)
132+
}
85133
})
86-
window.sessionStorage.removeItem('__tcs_user_data')
87134
}
88135

89136
render () {
90-
console.log(this.state)
91137
if (!this.props.got_data) {
92138
return (
93139
<Modal history={this.props.history} title=''>
94140
<p>Loading...</p>
95141
</Modal>
96142
)
97143
}
98-
const apt = this.props.appointments.find(a => a.id === this.apt_id)
144+
const apt = this.state.apt
99145
if (!apt) {
100146
return (
101147
<Modal history={this.props.history} title="Appointment not Found">
@@ -109,6 +155,9 @@ class AptModal extends Component {
109155
{apt.topic}
110156
</span>
111157
)
158+
const srs = this.get_srs()
159+
const spaces_available = apt.attendees_max - apt.attendees_count - this.state.extra_attendees
160+
const booking_allowed = this.state.booking_allowed && spaces_available > 0
112161
return (
113162
<Modal history={this.props.history} title={title} last_url={this.props.last_url} flex={false}>
114163
<div className="tcs-modal-flex">
@@ -123,7 +172,7 @@ class AptModal extends Component {
123172
<Detail label="Job">
124173
{apt.service_name}
125174
</Detail>
126-
{apt.attendees_max && <Detail label="Spaces Available">{apt.attendees_max - apt.attendees_count}</Detail>}
175+
{apt.attendees_max && <Detail label="Spaces Available">{spaces_available}</Detail>}
127176
<Detail label="Start" className="tcs-new-line">{this.props.config.format_datetime(apt.start)}</Detail>
128177
<Detail label="Finish">{this.props.config.format_datetime(apt.finish)}</Detail>
129178
{apt.location && <Detail label="Location">{apt.location}</Detail>}
@@ -145,24 +194,42 @@ class AptModal extends Component {
145194
<div className="tcs-book">
146195
<IfElse v={this.state.display_data}>
147196
<div>
148-
{Object.entries(this.state.display_data.srs).map(([k, v]) => (
149-
<div key={k}>
150-
{v}
197+
{srs && (
198+
<div className="tcs-book-existing">
199+
<div>Add your existing Students to the lesson</div>
200+
{srs.map(({id, name, already_on_apt}) => (
201+
<div key={id} className="tcs-book-item">
202+
<div className="tcs-existing-name">
203+
{name}
204+
</div>
205+
{already_on_apt ? (
206+
<div className="tcs-already-added">
207+
Added <Tick/>
208+
</div>
209+
) : (
210+
<button className="tcs-button tcs-add-button"
211+
onClick={() => this.book(id)}
212+
disabled={!booking_allowed || already_on_apt}>
213+
{this.props.root.get_text('add_to_lesson')}
214+
</button>
215+
)}
216+
</div>
217+
))}
151218
</div>
152-
))}
219+
)}
153220
<div className="tcs-book-new">
154-
<div>Add a new Student to the lesson:</div>
155-
<div>
221+
<div>Add a new Student to the lesson</div>
222+
<div className="tcs-book-item">
156223
<input type="text"
157224
className="tcs-default-input"
158225
placeholder="Student Name"
159226
required={true}
160227
maxLength={255}
161228
value={this.state.new_student || ''}
162229
onChange={e => this.setState({new_student: e.target.value})}/>
163-
<button className="tcs-button"
230+
<button className="tcs-button tcs-add-button"
164231
onClick={() => this.book(null)}
165-
disabled={!this.state.new_student}>
232+
disabled={!booking_allowed || !this.state.new_student}>
166233
{this.props.root.get_text('add_to_lesson')}
167234
</button>
168235
</div>

src/components/shared/Tools.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ export const Location = () => (
4646
</svg>
4747
)
4848

49+
// https://github.com/encharm/Font-Awesome-SVG-PNG/blob/master/black/svg/check.svg
50+
export const Tick = () => (
51+
<svg className="tcs-svg" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
52+
<path d="M1671 566q0 40-28 68l-724 724-136 136q-28 28-68
53+
28t-68-28l-136-136-362-362q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 295 656-657q28-28 68-28t68
54+
28l136 136q28 28 28 68z"/>
55+
</svg>
56+
)
57+
4958
export const If = ({v, children}) => (v ? children : <div/>)
5059

5160
export const IfElse = ({v, children}) => {

src/conf.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ $text-color: #444;
2525
$dft-border-color: #CCC;
2626
// details labels and others
2727
$label-colour: #666;
28+
// border colour used for inputs
29+
$input-border-colour: #aaa;

src/styles/appointments.scss

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,27 +71,54 @@ $circle-size: 22px;
7171
cursor: pointer;
7272
}
7373

74-
.tcs-book {
75-
padding-top: 10px;
76-
}
77-
7874
.tcs-signin {
7975
margin: 0 auto;
8076
display: block;
8177
}
8278

83-
$new-student-height: 38px;
84-
$new-student-border-radius: 5px;
85-
.tcs-book-new {
86-
.tcs-default-input {
87-
width: calc(100% - 200px);
88-
height: $new-student-height;
89-
border-radius: $new-student-border-radius 0 0 $new-student-border-radius;
90-
border-right: none;
79+
$student-height: 38px;
80+
$student-border-radius: 5px;
81+
$student-border-colour: #D8D8D8;
82+
.tcs-book {
83+
padding-top: 10px;
84+
.tcs-book-existing {
85+
margin-bottom: 15px;
9186
}
92-
.tcs-button {
87+
.tcs-book-item {
88+
margin-top: 5px;
89+
display: flex;
90+
justify-content: space-between;
91+
padding: 7px 0;
92+
border-bottom: 1px solid $student-border-colour;
93+
&:nth-child(2) {
94+
border-top: 1px solid $student-border-colour;
95+
}
96+
}
97+
.tcs-existing-name {
98+
padding: 8px 6px;
99+
}
100+
.tcs-existing-name, .tcs-default-input {
101+
width: calc(100% - 220px);
102+
height: $student-height;
103+
}
104+
.tcs-already-added {
105+
border-radius: $student-border-radius;
106+
}
107+
.tcs-add-button, .tcs-already-added {
93108
width: 200px;
94-
height: $new-student-height;
95-
border-radius: 0 $new-student-border-radius $new-student-border-radius 0;
109+
height: $student-height;
110+
}
111+
.tcs-already-added {
112+
background: #12a012;
113+
color: white;
114+
text-align: center;
115+
padding: 8px 12px;
116+
.tcs-svg {
117+
fill: white;
118+
margin-left: 2px;
119+
transform: translateY(3px);
120+
width: 18px;
121+
height: 18px;
122+
}
96123
}
97124
}

src/styles/input.scss

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
@import '../conf';
22

3-
$border-colour: #aaa;
43
$border-highlight-colour: #66afe9;
54
$border-radius: 5px;
65

@@ -12,7 +11,7 @@ $border-radius: 5px;
1211
margin: 0;
1312
height: inherit;
1413
border-radius: $border-radius;
15-
border: 1px solid $border-colour;
14+
border: 1px solid $input-border-colour;
1615
font-family: inherit;
1716
outline: none;
1817
background-color: white;
@@ -54,7 +53,7 @@ $border-radius: 5px;
5453
width: 50%;
5554
&.tcs-date {
5655
border-radius: $border-radius 0 0 $border-radius;
57-
border-right: 1px solid lighten($border-colour, 20%);
56+
border-right: 1px solid lighten($input-border-colour, 20%);
5857
}
5958
&.tcs-time {
6059
border-radius: 0 $border-radius $border-radius 0;

src/styles/shared.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
text-align: right !important;
55
}
66

7+
.tcs-text-center {
8+
text-align: center !important;
9+
}
10+
711
.tcs-button {
812
display: inline-block;
913
background-color: $button-colour;

0 commit comments

Comments
 (0)