Skip to content

Commit 7ee7dbe

Browse files
Hieu Lam - TMAnorbusan
authored andcommitted
feature-8684: Add option to tag attendees (#9155)
* feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Add option to tag attendees * feature-8684: Merge code development
1 parent 1f35f61 commit 7ee7dbe

File tree

25 files changed

+418
-12
lines changed

25 files changed

+418
-12
lines changed

app/components/forms/add-tag-form.hbs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<div class="d-flex align-center mb-4">
2+
<h2 class="header m-0">
3+
{{t 'Tags'}}
4+
</h2>
5+
</div>
6+
<div class="ui form">
7+
<div class="field">
8+
{{#each this.tagList as |tag index|}}
9+
<div class="{{if this.device.isMobile 'grouped'}} fields">
10+
<div class="{{unless this.device.isMobile 'three wide'}} field">
11+
<Input
12+
@type="text"
13+
@name="tag"
14+
@value={{tag.name}}
15+
placeholder={{t "Name" }}
16+
@readonly={{tag.isReadOnly}}
17+
required
18+
/>
19+
</div>
20+
<div class="{{unless this.device.isMobile 'four wide'}} field tag-form">
21+
<Widgets::Forms::ColorPicker @value={{tag.color}} @fontColor={{tag.fontColor}} required>
22+
{{#unless tag.isReadOnly}}
23+
<button class="ui icon red button remove-tag" type="button" {{action 'removeItem' tag}}>
24+
<i class="minus icon"></i>
25+
</button>
26+
{{/unless}}
27+
{{#if (eq index ( sub this.tagList.length 1 ) ) }}
28+
<button class="ui icon primary button add-tag" type="button" {{action 'addItem' 'tag' }}>
29+
<i class="plus icon"></i>
30+
</button>
31+
{{/if}}
32+
</Widgets::Forms::ColorPicker>
33+
</div>
34+
<div class="{{unless this.device.isMobile 'two wide'}} field">
35+
<input title="Preview" class="preview" value="{{if tag.name tag.name (t 'Preview Text')}}" readonly
36+
style={{css background-color=tag.color color=(text-color tag.color)}}>
37+
</div>
38+
</div>
39+
{{/each}}
40+
</div>
41+
<button type="submit" class="ui teal submit button update-changes mt-4" {{action 'submit'}}>
42+
{{t 'Submit'}}
43+
</button>
44+
<br>
45+
<p>{{t 'You need to hit "Submit" to save your changes.'}}</p>
46+
</div>

app/components/forms/add-tag-form.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import classic from 'ember-classic-decorator';
2+
import Component from '@ember/component';
3+
import { action, computed } from '@ember/object';
4+
import FormMixin from 'open-event-frontend/mixins/form';
5+
import { inject as service } from '@ember/service';
6+
import { tracked } from '@glimmer/tracking';
7+
8+
@classic
9+
export default class AddTagForm extends Component.extend(FormMixin) {
10+
@service errorHandler;
11+
@tracked tagsDeleted = [];
12+
13+
@computed('[email protected]', 'tagsDeleted.@each')
14+
get tagList() {
15+
return this.data.tags.filter(tag => !this.tagsDeleted.includes(tag));
16+
}
17+
18+
willDestroyElement() {
19+
const tagsNeedRemove = [];
20+
this.data.tags.forEach(tag => {
21+
if (!tag.id) {
22+
tagsNeedRemove.pushObject(tag);
23+
} else {
24+
if (tag?.changedAttributes()) {
25+
tag.rollbackAttributes();
26+
}
27+
}
28+
});
29+
if (tagsNeedRemove.length > 0) {
30+
this.data.tags.removeObjects(tagsNeedRemove);
31+
}
32+
this._super(...arguments);
33+
}
34+
35+
@action
36+
addItem() {
37+
this.data.tags.pushObject(this.store.createRecord('tag'));
38+
}
39+
40+
@action
41+
removeItem(tag) {
42+
if (tag.id) {
43+
this.tagsDeleted.pushObject(tag);
44+
} else {
45+
this.data.tags.removeObject(tag);
46+
}
47+
}
48+
49+
@action
50+
async submit() {
51+
try {
52+
this.data.tags.forEach(tag => {
53+
if (this.tagsDeleted.includes(tag)) {
54+
tag.deleteRecord();
55+
}
56+
tag.save();
57+
});
58+
this.notify.success(
59+
this.l10n.t('Your tag has been saved.'),
60+
{ id: 'tag_save' }
61+
);
62+
} catch (error) {
63+
this.notify.error(
64+
this.l10n.t('Tag failed.'),
65+
{ id: 'tag_save' }
66+
);
67+
}
68+
}
69+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import classic from 'ember-classic-decorator';
2+
import Component from '@ember/component';
3+
import { action } from '@ember/object';
4+
5+
@classic
6+
export default class SelectAll extends Component {
7+
8+
@action
9+
toggleSelectAll(value) {
10+
this.column.actions.toggleSelectAll(value);
11+
}
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import classic from 'ember-classic-decorator';
2+
import Component from '@ember/component';
3+
import { action } from '@ember/object';
4+
import $ from 'jquery';
5+
6+
@classic
7+
export default class AddTag extends Component {
8+
9+
@action
10+
onSelectTag(tag_id) {
11+
$(this.element).find('input').trigger('blur');
12+
this.selectTag(tag_id);
13+
}
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import classic from 'ember-classic-decorator';
2+
import Component from '@ember/component';
3+
4+
@classic
5+
export default class CellSelect extends Component {
6+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import classic from 'ember-classic-decorator';
2+
import Component from '@ember/component';
3+
import { computed, action } from '@ember/object';
4+
import { htmlSafe } from '@ember/string';
5+
6+
@classic
7+
export default class CellTag extends Component {
8+
didInsertElement() {
9+
this._super(...arguments);
10+
}
11+
12+
@action
13+
removeTag() {
14+
const attendee = this.props.row;
15+
if (attendee) {
16+
attendee.set('tagId', '');
17+
attendee.save();
18+
}
19+
}
20+
21+
@computed('record')
22+
get attendeeTagName() {
23+
const tag = this.props?.options?.tags?.find(tag => parseInt(tag.id) === this.record);
24+
if (tag) {
25+
return tag.name;
26+
}
27+
return '';
28+
}
29+
30+
@computed('record')
31+
get attendeeTagColor() {
32+
const tag = this.props?.options?.tags?.find(tag => parseInt(tag.id) === this.record);
33+
if (tag) {
34+
return htmlSafe('background-color:' + tag.color + '; display: inline-flex');
35+
}
36+
return '';
37+
}
38+
}

app/controllers/events/view/tags.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Controller from '@ember/controller';
2+
import { inject as service } from '@ember/service';
3+
4+
export default class extends Controller {
5+
@service errorHandler;
6+
}

app/controllers/events/view/tickets/attendees/list.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,32 @@ import Controller from '@ember/controller';
22
import { action } from '@ember/object';
33
import EmberTableControllerMixin from 'open-event-frontend/mixins/ember-table-controller';
44
import moment from 'moment-timezone';
5+
import { tracked } from '@glimmer/tracking';
56

67

78
export default class extends Controller.extend(EmberTableControllerMixin) {
89
sort_by = 'order.completed_at';
910
sort_dir = 'DSC';
11+
12+
@tracked
13+
selectAll = false
14+
1015
get columns() {
1116
return [
17+
{
18+
name : '',
19+
width : 50,
20+
valuePath : 'selected',
21+
cellComponent : 'ui-table/cell/events/view/tickets/attendees/cell-select',
22+
headerComponent : 'tables/headers/select-all',
23+
actions : {
24+
toggleSelectAll: this.toggleSelectAll.bind(this)
25+
},
26+
options: {
27+
tags: this.model.tags
28+
29+
}
30+
},
1231
{
1332
name : 'Order',
1433
width : 190,
@@ -23,6 +42,24 @@ export default class extends Controller.extend(EmberTableControllerMixin) {
2342
headerComponent : 'tables/headers/sort',
2443
isSortable : true
2544
},
45+
{
46+
name : 'Ticket Name',
47+
width : 80,
48+
valuePath : 'ticket.name',
49+
headerComponent : 'tables/headers/sort',
50+
isSortable : true
51+
},
52+
{
53+
name : 'Tags',
54+
width : 100,
55+
valuePath : 'tagId',
56+
cellComponent : 'ui-table/cell/events/view/tickets/attendees/cell-tag',
57+
headerComponent : 'tables/headers/sort',
58+
isSortable : true,
59+
options : {
60+
tags: this.model.tags
61+
}
62+
},
2663
{
2764
name : 'Date and Time',
2865
width : 120,
@@ -69,6 +106,36 @@ export default class extends Controller.extend(EmberTableControllerMixin) {
69106
];
70107
}
71108

109+
@action
110+
selectTag(tag) {
111+
this.selectedTag = tag;
112+
this.addTag();
113+
}
114+
115+
@action
116+
addTag() {
117+
if (!this.selectedTag) {return}
118+
this.model.attendees.data.forEach(attendee => {
119+
120+
if (attendee.selected) {
121+
attendee.set('tagId', parseInt(this.selectedTag));
122+
attendee.save();
123+
}
124+
});
125+
this.toggleSelectAll(false);
126+
this.selectedTag = null;
127+
}
128+
129+
@action
130+
toggleSelectAll(selected) {
131+
if (this.selectAll !== selected) {
132+
this.selectAll = selected;
133+
}
134+
this.model.attendees.data.forEach(data => {
135+
data.set('selected', selected);
136+
});
137+
}
138+
72139
@action
73140
toggleCheckIn(attendee_id, date, isCheckedInCurrently) {
74141
const attendee = this.store.peekRecord('attendee', attendee_id, { backgroundReload: false });

app/helpers/value-field-link.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { htmlSafe } from '@ember/string';
33

44
export function valueFieldLink(data) {
55
let field = '';
6-
if(data){
7-
field = data;
6+
if (data) {
7+
field = data;
88
}
99
return htmlSafe(field);
1010
}

app/models/attendee.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export default ModelBase.extend({
5050
is_consent_form_field_photo : attr('boolean', { defaultValue: false }),
5151
is_consent_form_field_email : attr('boolean', { defaultValue: false }),
5252
wiki_scholarship : attr('string'),
53+
tagId : attr('number'),
5354

5455
/**
5556
* Relationships

app/models/event.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ export default class Event extends ModelBase.extend(CustomPrimaryKeyMixin, {
171171

172172
accessCodes: hasMany('access-code'),
173173

174+
tags: hasMany('tag'),
175+
174176
/**
175177
* Computed properties
176178
*/

app/models/tag.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import attr from 'ember-data/attr';
2+
import ModelBase from 'open-event-frontend/models/base';
3+
import { belongsTo } from 'ember-data/relationships';
4+
5+
export default ModelBase.extend({
6+
/**
7+
* Attributes
8+
*/
9+
name : attr('string'),
10+
color : attr('string'),
11+
isReadOnly : attr('boolean', { defaultValue: false }),
12+
/**
13+
* Relationships
14+
*/
15+
event : belongsTo('event')
16+
});

app/router.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ Router.map(function() {
148148
this.route('edit', { path: '/:speaker_id/edit' });
149149
});
150150
this.route('documents');
151+
this.route('tags');
151152
this.route('chat');
152153
this.route('videoroom', { path: '/video' }, function() {
153154
this.route('list', { path: '/:status' });

app/routes/events/view/tags.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Route from '@ember/routing/route';
2+
3+
export default class extends Route {
4+
titleToken() {
5+
return this.l10n.t('Tag');
6+
}
7+
8+
addDefaultValue(tags) {
9+
tags.addObject(this.store.createRecord('tag', {
10+
name : 'Speakers',
11+
color : '#FF0000',
12+
isReadOnly : true
13+
}));
14+
tags.addObject(this.store.createRecord('tag', {
15+
name : 'Attendees',
16+
color : '#0000FF',
17+
isReadOnly : true
18+
}));
19+
tags.addObject(this.store.createRecord('tag', {
20+
name : 'VIPs',
21+
color : '#0000FF',
22+
isReadOnly : true
23+
}));
24+
tags.save();
25+
}
26+
27+
async model() {
28+
const event = this.modelFor('events.view');
29+
const tags = await event.query('tags', {});
30+
if (!tags || tags.length === 0) {
31+
this.addDefaultValue(tags);
32+
}
33+
return {
34+
event,
35+
tags
36+
};
37+
}
38+
}

0 commit comments

Comments
 (0)