@nyaruka/temba-components 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/a525ddb7.js +1 -0
  3. package/dist/index.js +1 -1
  4. package/dist/sw.js +1 -1
  5. package/dist/sw.js.map +1 -1
  6. package/dist/templates/components-body.html +1 -1
  7. package/dist/templates/components-head.html +1 -1
  8. package/out-tsc/src/button/Button.js +1 -0
  9. package/out-tsc/src/button/Button.js.map +1 -1
  10. package/out-tsc/src/contacts/ContactChat.js +81 -33
  11. package/out-tsc/src/contacts/ContactChat.js.map +1 -1
  12. package/out-tsc/src/contacts/ContactDetails.js +22 -22
  13. package/out-tsc/src/contacts/ContactDetails.js.map +1 -1
  14. package/out-tsc/src/contacts/ContactHistory.js +132 -139
  15. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  16. package/out-tsc/src/contacts/events.js +110 -47
  17. package/out-tsc/src/contacts/events.js.map +1 -1
  18. package/out-tsc/src/interfaces.js.map +1 -1
  19. package/out-tsc/src/list/ContactList.js +32 -17
  20. package/out-tsc/src/list/ContactList.js.map +1 -1
  21. package/out-tsc/src/list/TembaList.js +10 -3
  22. package/out-tsc/src/list/TembaList.js.map +1 -1
  23. package/out-tsc/src/list/TembaMenu.js +7 -2
  24. package/out-tsc/src/list/TembaMenu.js.map +1 -1
  25. package/out-tsc/src/loading/Loading.js +9 -1
  26. package/out-tsc/src/loading/Loading.js.map +1 -1
  27. package/out-tsc/src/options/Options.js +14 -2
  28. package/out-tsc/src/options/Options.js.map +1 -1
  29. package/out-tsc/src/select/Select.js +23 -5
  30. package/out-tsc/src/select/Select.js.map +1 -1
  31. package/out-tsc/src/tip/Tip.js +6 -0
  32. package/out-tsc/src/tip/Tip.js.map +1 -1
  33. package/out-tsc/src/vectoricon/VectorIcon.js +17 -5
  34. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  35. package/out-tsc/test/temba-contact-history.test.js +2 -2
  36. package/out-tsc/test/temba-contact-history.test.js.map +1 -1
  37. package/package.json +1 -1
  38. package/screenshots/truth/contacts/history-expanded.png +0 -0
  39. package/screenshots/truth/contacts/history.png +0 -0
  40. package/screenshots/truth/list/items-selected.png +0 -0
  41. package/screenshots/truth/list/items-updated.png +0 -0
  42. package/screenshots/truth/list/items.png +0 -0
  43. package/screenshots/truth/modax/simple.png +0 -0
  44. package/screenshots/truth/options/block.png +0 -0
  45. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  46. package/screenshots/truth/select/disabled-selection.png +0 -0
  47. package/screenshots/truth/select/disabled.png +0 -0
  48. package/screenshots/truth/select/embedded.png +0 -0
  49. package/screenshots/truth/select/expression-selected.png +0 -0
  50. package/screenshots/truth/select/expressions.png +0 -0
  51. package/screenshots/truth/select/functions.png +0 -0
  52. package/screenshots/truth/select/local-options.png +0 -0
  53. package/screenshots/truth/select/remote-options.png +0 -0
  54. package/screenshots/truth/select/search-enabled.png +0 -0
  55. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  56. package/screenshots/truth/select/search-selected-focus.png +0 -0
  57. package/screenshots/truth/select/search-selected.png +0 -0
  58. package/screenshots/truth/select/search-with-selected.png +0 -0
  59. package/screenshots/truth/select/searching.png +0 -0
  60. package/screenshots/truth/select/selected-multi.png +0 -0
  61. package/screenshots/truth/select/selected-single.png +0 -0
  62. package/screenshots/truth/select/with-placeholder.png +0 -0
  63. package/screenshots/truth/select/without-placeholder.png +0 -0
  64. package/screenshots/truth/textinput/date-form.png +0 -0
  65. package/screenshots/truth/textinput/input-disabled.png +0 -0
  66. package/screenshots/truth/textinput/input-form.png +0 -0
  67. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  68. package/screenshots/truth/textinput/input-updated.png +0 -0
  69. package/screenshots/truth/textinput/input.png +0 -0
  70. package/screenshots/truth/textinput/textarea.png +0 -0
  71. package/screenshots/truth/tip/bottom.png +0 -0
  72. package/screenshots/truth/tip/left.png +0 -0
  73. package/screenshots/truth/tip/right.png +0 -0
  74. package/screenshots/truth/tip/top.png +0 -0
  75. package/src/button/Button.ts +1 -0
  76. package/src/contacts/ContactChat.ts +93 -33
  77. package/src/contacts/ContactDetails.ts +23 -23
  78. package/src/contacts/ContactHistory.ts +157 -160
  79. package/src/contacts/events.ts +117 -48
  80. package/src/interfaces.ts +3 -0
  81. package/src/list/ContactList.ts +39 -20
  82. package/src/list/TembaList.ts +13 -4
  83. package/src/list/TembaMenu.ts +7 -2
  84. package/src/loading/Loading.ts +8 -1
  85. package/src/options/Options.ts +14 -2
  86. package/src/select/Select.ts +28 -6
  87. package/src/tip/Tip.ts +6 -0
  88. package/src/vectoricon/VectorIcon.ts +17 -5
  89. package/test/temba-contact-history.test.ts +2 -2
  90. package/test-assets/style.css +4 -1
  91. package/dist/228cf25e.js +0 -1
@@ -10,18 +10,16 @@ export class ContactDetails extends RapidElement {
10
10
  static get styles() {
11
11
  return css`
12
12
  :host {
13
- box-shadow: inset 14px 0 7px -14px rgba(0, 0, 0, 0.15);
14
13
  background: #f9f9f9;
15
14
  display: block;
16
15
  height: 100%;
17
16
  position: relative;
18
- border-bottom-right-radius: var(--curvature);
19
17
  overflow: hidden;
18
+ -webkit-mask-image: -webkit-radial-gradient(white, black);
20
19
  }
21
20
 
22
21
  .wrapper {
23
- padding-right: 3.5em;
24
- padding-left: 1em;
22
+ padding: 0em 1em;
25
23
  }
26
24
 
27
25
  a {
@@ -33,9 +31,9 @@ export class ContactDetails extends RapidElement {
33
31
  }
34
32
 
35
33
  .contact > .name {
36
- font-size: 18px;
34
+ font-size: 1.2em;
37
35
  font-weight: 400;
38
- padding: 0.75em;
36
+ padding: 0.5em 0.75em;
39
37
  padding-right: 1em;
40
38
  }
41
39
 
@@ -82,12 +80,9 @@ export class ContactDetails extends RapidElement {
82
80
  }
83
81
 
84
82
  .fields-wrapper {
85
- margin-top: 1em;
86
83
  background: #fff;
87
- border-radius: var(--curvature);
88
84
  overflow: hidden;
89
- box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
90
- 0 1px 2px 0 rgba(0, 0, 0, 0.06);
85
+ margin: 0em -1em;
91
86
  }
92
87
 
93
88
  .body-wrapper {
@@ -102,9 +97,7 @@ export class ContactDetails extends RapidElement {
102
97
  .fields {
103
98
  padding: 1em;
104
99
  max-height: 200px;
105
- border-radius: var(--curvature);
106
100
  overflow-y: auto;
107
- -webkit-mask-image: -webkit-radial-gradient(white, black);
108
101
  }
109
102
 
110
103
  .field {
@@ -142,7 +135,7 @@ export class ContactDetails extends RapidElement {
142
135
  @property({ type: String })
143
136
  uuid: string;
144
137
 
145
- @property({ attribute: false, type: Object })
138
+ @property({ type: Object })
146
139
  contact: Contact;
147
140
 
148
141
  @property({ attribute: false })
@@ -172,14 +165,10 @@ export class ContactDetails extends RapidElement {
172
165
 
173
166
  public updated(changes: Map<string, any>) {
174
167
  super.updated(changes);
175
- if (changes.has('endpoint')) {
176
- this.flow = null;
177
- this.expandFields = false;
178
168
 
169
+ if (changes.has('contact')) {
179
170
  const store: Store = document.querySelector('temba-store');
180
-
181
- fetchContact(this.endpoint).then((contact: Contact) => {
182
- this.contact = contact;
171
+ if (this.contact && this.contact.fields) {
183
172
  this.fields = Object.keys(this.contact.fields).filter((key: string) => {
184
173
  const hasField = !!this.contact.fields[key];
185
174
  return hasField && store.getContactField(key).pinned;
@@ -198,6 +187,14 @@ export class ContactDetails extends RapidElement {
198
187
  }
199
188
  return a.name.localeCompare(b.name);
200
189
  });
190
+ }
191
+ }
192
+
193
+ if (changes.has('endpoint')) {
194
+ this.flow = null;
195
+ this.expandFields = false;
196
+ fetchContact(this.endpoint).then((contact: Contact) => {
197
+ this.contact = contact;
201
198
  });
202
199
  }
203
200
  }
@@ -237,9 +234,9 @@ export class ContactDetails extends RapidElement {
237
234
 
238
235
  if (this.contact) {
239
236
  return html`<div class="contact">
240
- <div class="name">${this.name || this.contact.name}</div>
241
- <div class="wrapper">
242
- ${this.showGroups
237
+ <div class="name">
238
+ ${this.name || this.contact.name}
239
+ ${this.showGroups && !this.ticket
243
240
  ? html`<div>
244
241
  ${this.contact.groups.map((group: Group) => {
245
242
  return html`<a
@@ -254,6 +251,8 @@ export class ContactDetails extends RapidElement {
254
251
  })}
255
252
  </div>`
256
253
  : html``}
254
+ </div>
255
+ <div class="wrapper">
257
256
  ${body
258
257
  ? html`<div class="body-wrapper">
259
258
  <div class="body">${body}</div>
@@ -306,13 +305,14 @@ export class ContactDetails extends RapidElement {
306
305
  : null}
307
306
 
308
307
  <div class="actions">
309
- ${this.showGroups
308
+ ${this.showGroups && !this.ticket
310
309
  ? html`
311
310
  <div class="start-flow">
312
311
  <temba-select
313
312
  endpoint="/api/v2/flows.json?archived=false"
314
313
  placeholder="Start Flow"
315
314
  flavor="small"
315
+ searchable="true"
316
316
  .values=${this.flow ? [this.flow] : []}
317
317
  @temba-selection=${this.handleFlowChanged}
318
318
  ></temba-select>
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable @typescript-eslint/camelcase */
2
2
  import { css, property } from 'lit-element';
3
3
  import { html, TemplateResult } from 'lit-html';
4
- import { CustomEventType, Ticket } from '../interfaces';
4
+ import { Contact, CustomEventType, Ticket } from '../interfaces';
5
5
  import { RapidElement } from '../RapidElement';
6
6
  import { Asset, getAssets, getClasses, postJSON, throttle } from '../utils';
7
- import ResizeObserver from 'resize-observer-polyfill';
7
+
8
8
  import {
9
9
  AirtimeTransferredEvent,
10
10
  CampaignFiredEvent,
@@ -56,9 +56,39 @@ import {
56
56
  SCROLL_THRESHOLD,
57
57
  } from './helpers';
58
58
 
59
+ // when images load, make sure we are on the bottom of the scroll window if necessary
60
+ export const loadHandler = function (event) {
61
+ const target = event.target as HTMLElement;
62
+ const events = this.host.getEventsPane();
63
+ if (target.tagName == 'IMG') {
64
+ if (!this.host.showMessageAlert) {
65
+ if (
66
+ events.scrollTop > target.offsetTop - 1000 &&
67
+ target.offsetTop > events.scrollHeight - 500
68
+ ) {
69
+ this.host.scrollToBottom();
70
+ }
71
+ }
72
+ }
73
+ };
74
+
59
75
  export class ContactHistory extends RapidElement {
60
76
  public httpComplete: Promise<void | ContactHistoryPage>;
61
77
 
78
+ public constructor() {
79
+ super();
80
+ }
81
+
82
+ connectedCallback() {
83
+ super.connectedCallback();
84
+ this.shadowRoot.addEventListener('load', loadHandler, true);
85
+ }
86
+
87
+ disconnectedCallback() {
88
+ super.disconnectedCallback();
89
+ this.shadowRoot.removeEventListener('load', loadHandler, true);
90
+ }
91
+
62
92
  private getStickyId = (event: ContactEvent) => {
63
93
  if (event.type === Events.TICKET_OPENED) {
64
94
  const ticket = this.getTicketForEvent(event as TicketEvent);
@@ -95,6 +125,7 @@ export class ContactHistory extends RapidElement {
95
125
  flex-grow: 1;
96
126
  border-top-left-radius: var(--curvature);
97
127
  padding-top: 1em;
128
+ background: #fff;
98
129
  }
99
130
 
100
131
  temba-loading {
@@ -135,15 +166,27 @@ export class ContactHistory extends RapidElement {
135
166
  pointer: cursor;
136
167
  }
137
168
 
169
+ .scroll-title {
170
+ display: flex;
171
+ flex-direction: column;
172
+ z-index: 2;
173
+ border-top-left-radius: var(--curvature);
174
+ overflow: hidden;
175
+ box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.15);
176
+ background: rgb(240, 240, 240);
177
+ padding: 1em 1.2em;
178
+ font-size: 1.2em;
179
+ font-weight: 400;
180
+ }
181
+
138
182
  .sticky-bin {
139
183
  display: flex;
140
184
  flex-direction: column;
141
- position: fixed;
142
185
  z-index: 2;
143
186
  border-top-left-radius: var(--curvature);
144
187
  overflow: hidden;
145
- background: rgba(240, 240, 240, 0.95);
146
188
  box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.15);
189
+ background: rgb(240, 240, 240);
147
190
  }
148
191
 
149
192
  .sticky-bin temba-icon {
@@ -183,6 +226,9 @@ export class ContactHistory extends RapidElement {
183
226
  `;
184
227
  }
185
228
 
229
+ @property({ type: Object })
230
+ contact: Contact;
231
+
186
232
  @property({ type: String })
187
233
  uuid: string;
188
234
 
@@ -222,9 +268,6 @@ export class ContactHistory extends RapidElement {
222
268
  @property({ type: Array })
223
269
  tickets: Ticket[] = null;
224
270
 
225
- @property({ type: Object })
226
- currentTicket: Ticket = null;
227
-
228
271
  ticketEvents: { [uuid: string]: TicketEvent } = {};
229
272
 
230
273
  nextBefore: number;
@@ -236,35 +279,23 @@ export class ContactHistory extends RapidElement {
236
279
 
237
280
  public firstUpdated(changedProperties: Map<string, any>) {
238
281
  super.firstUpdated(changedProperties);
239
-
240
282
  this.handleClose = this.handleClose.bind(this);
241
-
242
- const stickyBin = this.getDiv('.sticky-bin');
243
- const resizer = new ResizeObserver(entries => {
244
- for (const entry of entries) {
245
- const eventContainer = entry.contentRect;
246
- stickyBin.style.width =
247
- eventContainer.width + eventContainer.left - 16 + 'px';
248
- }
249
- });
250
- resizer.observe(this);
251
283
  }
252
284
 
253
285
  public updated(changedProperties: Map<string, any>) {
254
286
  super.updated(changedProperties);
255
287
 
256
- // fire an event when our current ticket changes
257
- if (changedProperties.has('currentTicket')) {
258
- this.fireCustomEvent(CustomEventType.ContextChanged, {
259
- context: this.currentTicket,
260
- });
261
- }
262
-
263
288
  // fire an event if we get a new event
264
289
  if (changedProperties.has('mostRecentEvent')) {
265
290
  this.fireCustomEvent(CustomEventType.Refreshed);
266
291
  }
267
292
 
293
+ if (changedProperties.has('endDate')) {
294
+ if (this.refreshTimeout && this.endDate) {
295
+ window.clearTimeout(this.refreshTimeout);
296
+ }
297
+ }
298
+
268
299
  // if we don't have an endpoint infer one
269
300
  if (changedProperties.has('uuid')) {
270
301
  if (this.uuid == null) {
@@ -294,7 +325,8 @@ export class ContactHistory extends RapidElement {
294
325
  if (
295
326
  changedProperties.has('refreshing') &&
296
327
  this.refreshing &&
297
- this.endpoint
328
+ this.endpoint &&
329
+ !this.endDate
298
330
  ) {
299
331
  const after = (this.getLastEventTime() - 1) * 1000;
300
332
  let forceOpen = false;
@@ -488,30 +520,20 @@ export class ContactHistory extends RapidElement {
488
520
  stickyBin.removeChild(child);
489
521
  }
490
522
  }
491
- if (!this.currentTicket) {
492
- this.updateCurrentTicket();
493
- }
494
- }
495
- }
496
-
497
- private updateCurrentTicket() {
498
- const openTickets = (this.tickets || []).filter(
499
- ticket => ticket && ticket.status === 'open'
500
- );
501
- if (openTickets.length > 0) {
502
- this.currentTicket = openTickets[openTickets.length - 1];
503
523
  }
504
524
  }
505
525
 
506
526
  private refreshTickets() {
507
- let url = `/api/v2/tickets.json?contact=${this.uuid}`;
508
527
  if (this.ticket) {
509
- url = `${url}&ticket=${this.ticket}`;
510
- }
528
+ let url = `/api/v2/tickets.json?contact=${this.uuid}`;
529
+ if (this.ticket) {
530
+ url = `${url}&ticket=${this.ticket}`;
531
+ }
511
532
 
512
- getAssets(url).then((tickets: Ticket[]) => {
513
- this.tickets = tickets.reverse();
514
- });
533
+ getAssets(url).then((tickets: Ticket[]) => {
534
+ this.tickets = tickets.reverse();
535
+ });
536
+ }
515
537
  }
516
538
 
517
539
  public getEventsPane() {
@@ -568,6 +590,10 @@ export class ContactHistory extends RapidElement {
568
590
  }
569
591
 
570
592
  private scheduleRefresh(wait = -1) {
593
+ if (this.endDate) {
594
+ return;
595
+ }
596
+
571
597
  let refreshWait = wait;
572
598
 
573
599
  if (wait === -1) {
@@ -596,11 +622,12 @@ export class ContactHistory extends RapidElement {
596
622
  private reset() {
597
623
  // clear out our sticky bin which we manipulated manually
598
624
  const stickyBin = this.getDiv('.sticky-bin');
599
- while (stickyBin.childElementCount > 0) {
600
- stickyBin.removeChild(stickyBin.firstElementChild);
625
+ if (stickyBin) {
626
+ while (stickyBin.childElementCount > 0) {
627
+ stickyBin.removeChild(stickyBin.firstElementChild);
628
+ }
601
629
  }
602
630
 
603
- this.currentTicket = null;
604
631
  this.endpoint = null;
605
632
  this.tickets = null;
606
633
  this.ticketEvents = {};
@@ -657,43 +684,12 @@ export class ContactHistory extends RapidElement {
657
684
  sticky.classList.add('pinned');
658
685
  (sticky as any).eventElement = eventElement;
659
686
  stickyBin.appendChild(eventElement);
660
- const uuid = eventElement.getAttribute('data-sticky-id');
661
- const ticket = this.getTicket(uuid);
662
- if (ticket) {
663
- if (
664
- !this.currentTicket ||
665
- this.currentTicket.uuid !== ticket.uuid
666
- ) {
667
- this.currentTicket = ticket;
668
- }
669
- }
670
687
  }
671
688
  } else {
672
689
  const eventElement = (sticky as any).eventElement;
673
690
  if (scrollBoundary < sticky.offsetTop + sticky.offsetHeight) {
674
691
  sticky.appendChild(eventElement);
675
692
  sticky.classList.remove('pinned');
676
-
677
- const uuid = eventElement.getAttribute('data-sticky-id');
678
- let previousTicket: Ticket = null;
679
- for (const ticket of this.tickets) {
680
- if (ticket.uuid === uuid) {
681
- break;
682
- }
683
- previousTicket = ticket;
684
- }
685
-
686
- if (
687
- previousTicket &&
688
- (!this.currentTicket ||
689
- this.currentTicket.uuid !== previousTicket.uuid)
690
- ) {
691
- if (previousTicket.status === 'open') {
692
- this.currentTicket = previousTicket;
693
- } else {
694
- this.currentTicket = null;
695
- }
696
- }
697
693
  }
698
694
  }
699
695
  });
@@ -765,7 +761,7 @@ export class ContactHistory extends RapidElement {
765
761
  if (activeTicket && ticket && ticket.status === 'open') {
766
762
  closeHandler = this.handleClose;
767
763
  }
768
- return renderTicketOpened(ticketEvent, closeHandler);
764
+ return renderTicketOpened(ticketEvent, closeHandler, !this.ticket);
769
765
  }
770
766
  case Events.TICKET_NOTE_ADDED:
771
767
  return renderNoteCreated(event as TicketEvent, this.agent);
@@ -773,10 +769,14 @@ export class ContactHistory extends RapidElement {
773
769
  case Events.TICKET_ASSIGNED:
774
770
  return renderTicketAssigned(event as TicketEvent);
775
771
  case Events.TICKET_REOPENED: {
776
- return renderTicketAction(event as TicketEvent, 'reopened');
772
+ return renderTicketAction(
773
+ event as TicketEvent,
774
+ 'reopened',
775
+ !this.ticket
776
+ );
777
777
  }
778
778
  case Events.TICKET_CLOSED:
779
- return renderTicketAction(event as TicketEvent, 'closed');
779
+ return renderTicketAction(event as TicketEvent, 'closed', !this.ticket);
780
780
 
781
781
  case Events.ERROR:
782
782
  case Events.FAILURE:
@@ -816,7 +816,6 @@ export class ContactHistory extends RapidElement {
816
816
  this.fireCustomEvent(CustomEventType.ContentChanged, {
817
817
  ticket: { uuid, status: 'closed' },
818
818
  });
819
- this.updateCurrentTicket();
820
819
  })
821
820
  .catch((response: any) => {
822
821
  console.error(response);
@@ -824,24 +823,22 @@ export class ContactHistory extends RapidElement {
824
823
  }
825
824
 
826
825
  public checkForAgentAssignmentEvent(agent: number) {
827
- if (this.currentTicket) {
828
- this.httpComplete = getAssets(
829
- `/api/v2/tickets.json?ticket=${this.currentTicket.uuid}`
830
- ).then((assets: Asset[]) => {
831
- if (assets.length === 1) {
832
- const ticket = assets[0] as Ticket;
833
- if (ticket.assignee && ticket.assignee.id === agent) {
834
- this.fireCustomEvent(CustomEventType.ContentChanged, {
835
- ticket: { uuid: this.currentTicket.uuid, assigned: 'self' },
836
- });
837
- } else {
838
- this.fireCustomEvent(CustomEventType.ContentChanged, {
839
- ticket: { uuid: this.currentTicket.uuid, assigned: 'other' },
840
- });
841
- }
826
+ this.httpComplete = getAssets(
827
+ `/api/v2/tickets.json?ticket=${this.ticket}`
828
+ ).then((assets: Asset[]) => {
829
+ if (assets.length === 1) {
830
+ const ticket = assets[0] as Ticket;
831
+ if (ticket.assignee && ticket.assignee.id === agent) {
832
+ this.fireCustomEvent(CustomEventType.ContentChanged, {
833
+ ticket: { uuid: this.ticket, assigned: 'self' },
834
+ });
835
+ } else {
836
+ this.fireCustomEvent(CustomEventType.ContentChanged, {
837
+ ticket: { uuid: this.ticket, assigned: 'other' },
838
+ });
842
839
  }
843
- });
844
- }
840
+ }
841
+ });
845
842
  }
846
843
 
847
844
  public getEventHandlers() {
@@ -864,7 +861,9 @@ export class ContactHistory extends RapidElement {
864
861
 
865
862
  const renderedEvent = html`
866
863
  <div
867
- class="event ${event.type} ${isSticky ? 'has-sticky' : ''}"
864
+ class="${this.ticket
865
+ ? 'active-ticket'
866
+ : ''} event ${event.type} ${isSticky ? 'has-sticky' : ''}"
868
867
  data-sticky-id="${stickyId}"
869
868
  >
870
869
  ${this.renderEvent(event)}
@@ -891,7 +890,7 @@ export class ContactHistory extends RapidElement {
891
890
  type: Events.TICKET_OPENED,
892
891
  ticket: {
893
892
  uuid: ticket.uuid,
894
- subject: ticket.subject,
893
+ topic: ticket.topic,
895
894
  body: ticket.body,
896
895
  ticketer: ticket.ticketer,
897
896
  },
@@ -901,7 +900,8 @@ export class ContactHistory extends RapidElement {
901
900
 
902
901
  const renderedEvent = renderTicketOpened(
903
902
  ticketOpenedEvent,
904
- this.handleClose
903
+ this.handleClose,
904
+ !this.ticket
905
905
  );
906
906
  return html`<div class="event ticket_opened">
907
907
  ${renderedEvent}
@@ -912,68 +912,65 @@ export class ContactHistory extends RapidElement {
912
912
  : null;
913
913
 
914
914
  return html`
915
- <div class="sticky-bin">${unfetchedTickets}</div>
915
+ ${this.ticket
916
+ ? html`<div class="sticky-bin">${unfetchedTickets}</div>`
917
+ : this.contact
918
+ ? html`<div class="scroll-title">${this.contact.name}</div>`
919
+ : null}
916
920
  ${this.fetching
917
921
  ? html`<temba-loading units="5" size="10"></temba-loading>`
918
922
  : html`<div style="height:0em"></div>`}
919
923
  <div class="events" @scroll=${this.handleScroll}>
920
- ${this.tickets
921
- ? this.eventGroups.map((eventGroup: EventGroup, index: number) => {
922
- const grouping = getEventGroupType(
923
- eventGroup.events[0],
924
- this.ticket
925
- );
926
- const groupIndex = this.eventGroups.length - index - 1;
927
-
928
- const classes = getClasses({
929
- grouping: true,
930
- [grouping]: true,
931
- expanded: eventGroup.open,
932
- closing: eventGroup.closing,
933
- });
934
-
935
- return html`<div class="${classes}">
936
- ${grouping === 'verbose'
937
- ? html`<div
938
- class="event-count"
939
- @click=${this.handleEventGroupShow}
940
- data-group-index="${groupIndex}"
941
- >
942
- ${eventGroup.events.length}
943
- ${eventGroup.events.length === 1
944
- ? html`event`
945
- : html`events`}
946
- </div>`
947
- : null}
948
- ${grouping === 'verbose'
949
- ? html`
950
- <temba-icon
951
- @click=${this.handleEventGroupHide}
952
- data-group-index="${groupIndex}"
953
- class="grouping-close-button"
954
- name="x"
955
- clickable
956
- ></temba-icon>
957
- `
958
- : null}
959
- ${eventGroup.events.map((event: ContactEvent) => {
960
- if (
961
- event.type === Events.TICKET_ASSIGNED &&
962
- (event as TicketEvent).note
963
- ) {
964
- const noteEvent = { ...event };
965
- noteEvent.type = Events.TICKET_NOTE_ADDED;
966
-
967
- return html`${this.renderEventContainer(
968
- noteEvent
969
- )}${this.renderEventContainer(event)}`;
970
- } else {
971
- return this.renderEventContainer(event);
972
- }
973
- })}
974
- </div>`;
975
- })
976
- : null}
924
+ ${this.eventGroups.map((eventGroup: EventGroup, index: number) => {
925
+ const grouping = getEventGroupType(eventGroup.events[0], this.ticket);
926
+ const groupIndex = this.eventGroups.length - index - 1;
927
+
928
+ const classes = getClasses({
929
+ grouping: true,
930
+ [grouping]: true,
931
+ expanded: eventGroup.open,
932
+ closing: eventGroup.closing,
933
+ });
934
+
935
+ return html`<div class="${classes}">
936
+ ${grouping === 'verbose'
937
+ ? html`<div
938
+ class="event-count"
939
+ @click=${this.handleEventGroupShow}
940
+ data-group-index="${groupIndex}"
941
+ >
942
+ ${eventGroup.events.length}
943
+ ${eventGroup.events.length === 1 ? html`event` : html`events`}
944
+ </div>`
945
+ : null}
946
+ ${grouping === 'verbose'
947
+ ? html`
948
+ <temba-icon
949
+ @click=${this.handleEventGroupHide}
950
+ data-group-index="${groupIndex}"
951
+ class="grouping-close-button"
952
+ name="x"
953
+ clickable
954
+ ></temba-icon>
955
+ `
956
+ : null}
957
+ ${eventGroup.events.map((event: ContactEvent) => {
958
+ if (
959
+ event.type === Events.TICKET_ASSIGNED &&
960
+ (event as TicketEvent).note
961
+ ) {
962
+ const noteEvent = { ...event };
963
+ noteEvent.type = Events.TICKET_NOTE_ADDED;
964
+
965
+ return html`${this.renderEventContainer(
966
+ noteEvent
967
+ )}${this.renderEventContainer(event)}`;
968
+ } else {
969
+ return this.renderEventContainer(event);
970
+ }
971
+ })}
972
+ </div>`;
973
+ })}
977
974
  </div>
978
975
 
979
976
  <div class="new-messages-container">