Skip to content

Commit 94da57b

Browse files
committed
Fix appointment listing on group details page
This changes the listing from using opacity to represent reserved status, to using a badge. It also increases spacing a bit to make it easier to read. A few other changes were made to the code as well to reduce the number of eslint errors present. closes CNVS-33347 Test Plan: - Enable better scheduler - Go to an appointment group's detail page - The appointments listing should show the following: - Green reserved badge anytime an appointment group has someone reserved for that slot. - Gray available badge anytime no one is signed up for that slot - For slots with more than one signup allowed, if there is space left, "Available" should show as the last entry on the list. Change-Id: If32e68f4ada4a5d1ecdf353aa1134d968a532e28 Reviewed-on: https://gerrit.instructure.com/95494 Tested-by: Jenkins Reviewed-by: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Chris Ward <cward@instructure.com>
1 parent 50b9965 commit 94da57b

3 files changed

Lines changed: 145 additions & 49 deletions

File tree

app/jsx/calendar/scheduler/components/appointment_groups/AppointmentGroupList.jsx

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,51 @@ define([
77
'jquery',
88
'jquery.instructure_date_and_time'
99
], (React, classnames, I18n, FriendlyDatetime, natcompare, $) => {
10-
return class AppointmentGroupList extends React.Component {
11-
static propTypes = {
12-
appointmentGroup: React.PropTypes.object,
13-
}
10+
const renderAppointment = (appointment, participantList = '') => {
11+
const timeLabel = I18n.t('%{start_time} to %{end_time}', {
12+
start_time: $.timeString(appointment.start_at),
13+
end_time: $.timeString(appointment.end_at)
14+
})
1415

15-
renderAppointment (appointment, statusLabel) {
16-
const timeLabel = I18n.t('%{start_time} to %{end_time}', { start_time: $.timeString(appointment.start_at), end_time: $.timeString(appointment.end_at) })
17-
const label = statusLabel ? `${timeLabel} - ${statusLabel}` : timeLabel
16+
const isReserved = appointment.child_events && appointment.child_events.length > 0;
1817

19-
const reservedClass = classnames({
20-
AppointmentGroupList__unreserved: !appointment.reserved,
21-
AppointmentGroupList__reserved: appointment.reserved,
22-
})
18+
const badgeClasses = classnames({
19+
AppointmentGroupList__Badge: true,
20+
'AppointmentGroupList__Badge--reserved': isReserved,
21+
'AppointmentGroupList__Badge--unreserved': !isReserved
22+
})
2323

24-
return (
25-
<div key={appointment.id} className="AppointmentGroupList__Appointment">
26-
<div className={reservedClass}>
27-
<i className="icon-calendar-month" />
28-
<span className="pad-box-micro AppointmentGroupList__Appointment-label">{label}</span>
29-
</div>
24+
const rowClasses = classnames({
25+
AppointmentGroupList__Appointment: true,
26+
'AppointmentGroupList__Appointment--reserved': isReserved,
27+
'AppointmentGroupList__Appointment--unreserved': !isReserved
28+
})
29+
30+
const statusText = (isReserved) ?
31+
I18n.t('Reserved') :
32+
I18n.t('Available')
33+
34+
return (
35+
<div key={appointment.id} className={rowClasses}>
36+
<div>
37+
<i className="icon-calendar-month" />
38+
<span className="pad-box-micro AppointmentGroupList__Appointment-timeLabel">{timeLabel}</span>
39+
<span className={badgeClasses}>{statusText}</span>
40+
<span className="pad-box-micro AppointmentGroupList__Appointment-label">{participantList}</span>
3041
</div>
31-
)
42+
</div>
43+
)
44+
}
45+
46+
return class AppointmentGroupList extends React.Component {
47+
static propTypes = {
48+
appointmentGroup: React.PropTypes.object,
3249
}
3350

3451
renderAppointmentList () {
3552
const { appointmentGroup } = this.props
3653
return (appointmentGroup.appointments || []).map((appointment) => {
37-
let statusLabel = null
54+
let participantList = null
3855

3956
if (!appointment.reserved) {
4057
if (appointment.child_events.length) {
@@ -47,13 +64,11 @@ define([
4764
sorted.push(I18n.t('Available'))
4865
}
4966

50-
statusLabel = sorted.join('; ')
51-
} else {
52-
statusLabel = I18n.t('Available')
67+
participantList = sorted.join('; ')
5368
}
5469
}
5570

56-
return this.renderAppointment(appointment, statusLabel)
71+
return renderAppointment(appointment, participantList)
5772
})
5873
}
5974

app/stylesheets/pages/calendar/_appointment_group_edit.scss

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import "components/ic-badge";
2+
13
.EditPage {
24
.EditPage__Header {
35
border-bottom: 1px solid $ic-border-light;
@@ -43,8 +45,24 @@
4345
margin-left: 30px;
4446
}
4547

46-
.AppointmentGroupList__unreserved {
47-
opacity: 0.5;
48+
.AppointmentGroupList__Appointment-timeLabel {
49+
display: inline-block;
50+
width: 130px;
51+
}
52+
53+
.AppointmentGroupList__Badge--unreserved {
54+
@include ic-badge-maker(20px, $ic-color-medium-light, $ic-color-dark);
55+
border: 1px solid $ic-border-light;
56+
}
57+
58+
.AppointmentGroupList__Badge--reserved {
59+
@include ic-badge-maker(20px, $ic-color-success, $ic-color-light);
60+
border: 1px solid $ic-border-light;
61+
}
62+
63+
.AppointmentGroupList__Badge {
64+
min-width: 70px;
65+
margin-right: 10px;
4866
}
4967

5068
.ContextSelector__Dropdown {

spec/javascripts/jsx/calendar/scheduler/components/appointment_groups/AppointmentGroupListSpec.jsx

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,108 @@ define([
77
module('AppointmentGroupList')
88

99
test('renders the AppointmentGroupList component', () => {
10-
const appointmentGroup = {"appointments": [{"child_events": [{"user": {"sortable_name": "test"}}], "start_at": "2016-10-18T19:00:00Z", "end_at": "2016-10-18T110:00:00Z"}], "appointments_count": 1}
10+
const appointmentGroup = { appointments: [{ child_events: [{ user: { sortable_name: 'test' } }], start_at: '2016-10-18T19:00:00Z', end_at: '2016-10-18T110:00:00Z' }], appointments_count: 1 }
1111

12-
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup}/>)
12+
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup} />)
1313
const appointmentGroupList = TestUtils.findRenderedDOMComponentWithClass(component, 'AppointmentGroupList__List')
1414
ok(appointmentGroupList)
1515
})
1616

17-
test('renders unreserved with user', () => {
18-
const appointmentGroup = {"appointments": [{"child_events": [{"user": {"sortable_name": "test"}}], "start_at": "2016-10-18T19:00:00Z", "end_at": "2016-10-18T110:00:00Z"}], "appointments_count": 1}
19-
const expectedContent = "7pm to 12am - test; Available"
17+
test('renders renders reserved badge when someone is signed up in a slot', () => {
18+
const appointmentGroup = {
19+
appointments: [{
20+
child_events: [{
21+
user: { sortable_name: test }
22+
}],
23+
start_at: '2016-10-18T19:00:00Z',
24+
end_at: '2016-10-18T110:00:00Z'
25+
}, {
26+
child_events: [],
27+
start_at: '2016-10-18T16:00:00Z',
28+
end_at: '2016-10-18T17:00:00Z'
29+
}],
30+
appointments_count: 2
31+
}
2032

21-
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup}/>)
22-
const appointmentGroupUnreserved = TestUtils.findRenderedDOMComponentWithClass(component, 'AppointmentGroupList__unreserved')
23-
ok(expectedContent === appointmentGroupUnreserved.textContent)
33+
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup} />)
34+
const reservedBadge = TestUtils.scryRenderedDOMComponentsWithClass(component, 'AppointmentGroupList__Badge--reserved')[0]
35+
ok(reservedBadge)
2436
})
2537

26-
test('renders multiple unreserved with user', () => {
27-
const appointmentGroup = {"appointments": [{"child_events": [{"user": {"sortable_name": "test"}}], "start_at": "2016-10-18T19:00:00Z", "end_at": "2016-10-18T110:00:00Z"}, {"child_events": [{"user": {"sortable_name": "test"}}], "start_at": "2016-10-18T16:00:00Z", "end_at": "2016-10-18T17:00:00Z"}], "appointments_count": 2}
38+
test('renders available badge when no one is signed up', () => {
39+
const appointmentGroup = {
40+
appointments: [{
41+
child_events: [],
42+
start_at: '2016-10-18T19:00:00Z',
43+
end_at: '2016-10-18T110:00:00Z'
44+
}, {
45+
child_events: [],
46+
start_at: '2016-10-18T16:00:00Z',
47+
end_at: '2016-10-18T17:00:00Z'
48+
}],
49+
appointments_count: 2
50+
}
2851

29-
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup}/>)
30-
const appointmentGroupUnreserved = TestUtils.findRenderedDOMComponentWithClass(component, 'AppointmentGroupList__List')
31-
ok(appointmentGroupUnreserved.childElementCount === 2)
52+
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup} />)
53+
const availableBadge = TestUtils.scryRenderedDOMComponentsWithClass(component, 'AppointmentGroupList__Badge--unreserved')[0]
54+
ok(availableBadge)
3255
})
3356

34-
test('renders reserved with user', () => {
35-
const appointmentGroup = {"appointments": [{"child_events": [{"user": {"sortable_name": "test"}}], "start_at": "2016-10-18T19:00:00Z", "end_at": "2016-10-18T110:00:00Z", 'reserved': true}, {"child_events": [{"user": {"sortable_name": "test"}}], "start_at": "2016-10-18T16:00:00Z", "end_at": "2016-10-18T17:00:00Z"}], "appointments_count": 2}
57+
test('renders correct user names', () => {
58+
const appointmentGroup = {
59+
appointments: [{
60+
child_events: [{
61+
user: { sortable_name: 'test1' }
62+
}],
63+
start_at: '2016-10-18T19:00:00Z',
64+
end_at: '2016-10-18T110:00:00Z'
65+
}, {
66+
child_events: [{
67+
user: { sortable_name: 'test2' }
68+
}],
69+
start_at: '2016-10-18T16:00:00Z',
70+
end_at: '2016-10-18T17:00:00Z'
71+
}],
72+
appointments_count: 2,
73+
participants_per_appointment: 1
74+
}
3675

37-
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup}/>)
38-
const appointmentGroupReserved = TestUtils.findRenderedDOMComponentWithClass(component, 'AppointmentGroupList__reserved')
39-
ok(appointmentGroupReserved)
76+
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup} />)
77+
const appointmentGroupNames = TestUtils.scryRenderedDOMComponentsWithClass(component, 'AppointmentGroupList__Appointment-label')
78+
equal(appointmentGroupNames.length, 2)
79+
equal(appointmentGroupNames[0].textContent, 'test1')
80+
equal(appointmentGroupNames[1].textContent, 'test2')
4081
})
4182

42-
test('renders correct user names', () => {
43-
const appointmentGroup = {"appointments": [{"child_events": [{"user": {"sortable_name": "test1"}}], "start_at": "2016-10-18T19:00:00Z", "end_at": "2016-10-18T110:00:00Z"}, {"child_events": [{"user": {"sortable_name": "test2"}}], "start_at": "2016-10-18T16:00:00Z", "end_at": "2016-10-18T17:00:00Z"}], "appointments_count": 2, participants_per_appointment: 1}
83+
test('renders "Available" at the end of the user list if more appointments are available for the slot', () => {
84+
const appointmentGroup = {
85+
appointments: [{
86+
child_events: [{
87+
user: { sortable_name: 'test1' }
88+
}],
89+
start_at: '2016-10-18T19:00:00Z',
90+
end_at: '2016-10-18T110:00:00Z',
91+
child_events_count: 1
92+
}, {
93+
child_events: [{
94+
user: { sortable_name: 'test2' }
95+
}, {
96+
user: { sortable_name: 'test3' }
97+
}],
98+
start_at: '2016-10-18T16:00:00Z',
99+
end_at: '2016-10-18T17:00:00Z',
100+
child_events_count: 2
101+
}],
102+
appointments_count: 2,
103+
participants_per_appointment: 2
104+
}
44105

45-
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup}/>)
46-
const appointmentGroupNames = TestUtils.scryRenderedDOMComponentsWithClass(component, 'AppointmentGroupList__Appointment-label').map(nameComp => ReactDOM.findDOMNode(nameComp))
106+
const component = TestUtils.renderIntoDocument(<AppointmentGroupList appointmentGroup={appointmentGroup} />)
107+
const appointmentGroupNames = TestUtils.scryRenderedDOMComponentsWithClass(component, 'AppointmentGroupList__Appointment-label')
47108
equal(appointmentGroupNames.length, 2)
48-
equal(appointmentGroupNames[0].textContent.split(' - ')[1], 'test1')
49-
equal(appointmentGroupNames[1].textContent.split(' - ')[1], 'test2')
109+
equal(appointmentGroupNames[0].textContent, 'test1; Available')
110+
equal(appointmentGroupNames[1].textContent, 'test2; test3')
50111
})
112+
113+
51114
})

0 commit comments

Comments
 (0)