@nyaruka/temba-components 0.17.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 (88) hide show
  1. package/CHANGELOG.md +9 -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 +131 -138
  15. package/out-tsc/src/contacts/ContactHistory.js.map +1 -1
  16. package/out-tsc/src/contacts/events.js +55 -44
  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 +1 -1
  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/tip/Tip.js +6 -0
  30. package/out-tsc/src/tip/Tip.js.map +1 -1
  31. package/out-tsc/src/vectoricon/VectorIcon.js +17 -5
  32. package/out-tsc/src/vectoricon/VectorIcon.js.map +1 -1
  33. package/out-tsc/test/temba-contact-history.test.js +2 -2
  34. package/out-tsc/test/temba-contact-history.test.js.map +1 -1
  35. package/package.json +1 -1
  36. package/screenshots/truth/contacts/history-expanded.png +0 -0
  37. package/screenshots/truth/contacts/history.png +0 -0
  38. package/screenshots/truth/list/items-selected.png +0 -0
  39. package/screenshots/truth/list/items-updated.png +0 -0
  40. package/screenshots/truth/list/items.png +0 -0
  41. package/screenshots/truth/modax/simple.png +0 -0
  42. package/screenshots/truth/options/block.png +0 -0
  43. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  44. package/screenshots/truth/select/disabled-selection.png +0 -0
  45. package/screenshots/truth/select/disabled.png +0 -0
  46. package/screenshots/truth/select/embedded.png +0 -0
  47. package/screenshots/truth/select/expression-selected.png +0 -0
  48. package/screenshots/truth/select/expressions.png +0 -0
  49. package/screenshots/truth/select/functions.png +0 -0
  50. package/screenshots/truth/select/local-options.png +0 -0
  51. package/screenshots/truth/select/remote-options.png +0 -0
  52. package/screenshots/truth/select/search-enabled.png +0 -0
  53. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  54. package/screenshots/truth/select/search-selected-focus.png +0 -0
  55. package/screenshots/truth/select/search-selected.png +0 -0
  56. package/screenshots/truth/select/search-with-selected.png +0 -0
  57. package/screenshots/truth/select/searching.png +0 -0
  58. package/screenshots/truth/select/selected-multi.png +0 -0
  59. package/screenshots/truth/select/selected-single.png +0 -0
  60. package/screenshots/truth/select/with-placeholder.png +0 -0
  61. package/screenshots/truth/select/without-placeholder.png +0 -0
  62. package/screenshots/truth/textinput/date-form.png +0 -0
  63. package/screenshots/truth/textinput/input-disabled.png +0 -0
  64. package/screenshots/truth/textinput/input-form.png +0 -0
  65. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  66. package/screenshots/truth/textinput/input-updated.png +0 -0
  67. package/screenshots/truth/textinput/input.png +0 -0
  68. package/screenshots/truth/textinput/textarea.png +0 -0
  69. package/screenshots/truth/tip/bottom.png +0 -0
  70. package/screenshots/truth/tip/left.png +0 -0
  71. package/screenshots/truth/tip/right.png +0 -0
  72. package/screenshots/truth/tip/top.png +0 -0
  73. package/src/button/Button.ts +1 -0
  74. package/src/contacts/ContactChat.ts +93 -33
  75. package/src/contacts/ContactDetails.ts +23 -23
  76. package/src/contacts/ContactHistory.ts +156 -159
  77. package/src/contacts/events.ts +59 -44
  78. package/src/interfaces.ts +1 -1
  79. package/src/list/ContactList.ts +1 -1
  80. package/src/list/TembaList.ts +13 -4
  81. package/src/list/TembaMenu.ts +7 -2
  82. package/src/loading/Loading.ts +8 -1
  83. package/src/options/Options.ts +14 -2
  84. package/src/tip/Tip.ts +6 -0
  85. package/src/vectoricon/VectorIcon.ts +17 -5
  86. package/test/temba-contact-history.test.ts +2 -2
  87. package/test-assets/style.css +4 -1
  88. package/dist/d59a9ae2.js +0 -1
@@ -6,6 +6,9 @@ import { TextInput } from '../textinput/TextInput';
6
6
  import { Completion } from '../completion/Completion';
7
7
  import { ContactHistory } from './ContactHistory';
8
8
  import { Modax } from '../dialog/Modax';
9
+ import { fetchContact } from './helpers';
10
+
11
+ const DEFAULT_REFRESH = 10000;
9
12
 
10
13
  export class ContactChat extends RapidElement {
11
14
  public static get styles() {
@@ -24,13 +27,16 @@ export class ContactChat extends RapidElement {
24
27
  overflow: hidden;
25
28
  }
26
29
 
30
+ .left-pane {
31
+ box-shadow: -13px 10px 7px 14px rgba(0, 0, 0, 0.15);
32
+ z-index: 100;
33
+ }
34
+
27
35
  .chat-wrapper {
28
36
  display: flex;
29
37
  flex-direction: column;
30
38
  height: 100%;
31
- background: #fff;
32
39
  overflow: hidden;
33
- border-radius: var(--curvature);
34
40
  }
35
41
 
36
42
  .chatbox {
@@ -79,11 +85,10 @@ export class ContactChat extends RapidElement {
79
85
 
80
86
  .toolbar {
81
87
  position: relative;
82
- width: 2em;
83
- background: #f2f2f2;
88
+ width: 2.5em;
89
+ background: #e6e6e6;
84
90
  transition: all 600ms ease-in;
85
91
  z-index: 10;
86
- box-shadow: -1px 0px 6px 1px rgba(0, 0, 0, 0.1);
87
92
  flex-shrink: 0;
88
93
  border-top-right-radius: var(--curvature);
89
94
  border-bottom-right-radius: var(--curvature);
@@ -100,7 +105,8 @@ export class ContactChat extends RapidElement {
100
105
  }
101
106
 
102
107
  .toolbar.closed {
103
- box-shadow: -1px 0px 1px 1px rgba(0, 0, 0, 0);
108
+ box-shadow: inset 10px 0px 10px -11px rgb(0 0 0 / 15%);
109
+ z-index: 1000;
104
110
  }
105
111
 
106
112
  temba-contact-details {
@@ -110,8 +116,6 @@ export class ContactChat extends RapidElement {
110
116
  transition: margin 600ms cubic-bezier(0.68, -0.55, 0.265, 1.05),
111
117
  opacity 600ms ease-in-out 200ms;
112
118
  z-index: 5;
113
- margin-right: -1.5em;
114
- border-top-right-radius: 6px;
115
119
  }
116
120
 
117
121
  temba-contact-details.hidden {
@@ -149,8 +153,11 @@ export class ContactChat extends RapidElement {
149
153
  `;
150
154
  }
151
155
 
152
- @property({ type: Object })
153
- contact: Contact = null;
156
+ @property({ type: String, attribute: 'contact' })
157
+ contactUUID: string;
158
+
159
+ @property({ type: String, attribute: 'ticket' })
160
+ ticketUUID: string;
154
161
 
155
162
  @property({ type: String })
156
163
  contactsEndpoint = '/api/v2/contacts.json';
@@ -164,9 +171,15 @@ export class ContactChat extends RapidElement {
164
171
  @property({ type: Boolean })
165
172
  showDetails = true;
166
173
 
174
+ @property({ type: Boolean })
175
+ monitor = false;
176
+
167
177
  @property({ type: Object })
168
178
  currentTicket: Ticket = null;
169
179
 
180
+ @property({ type: Object })
181
+ currentContact: Contact = null;
182
+
170
183
  @property({ type: Number })
171
184
  agent = -1;
172
185
 
@@ -175,6 +188,26 @@ export class ContactChat extends RapidElement {
175
188
  this.showDetails = getCookieBoolean(COOKIE_KEYS.TICKET_SHOW_DETAILS);
176
189
  }
177
190
 
191
+ refreshInterval = null;
192
+
193
+ public connectedCallback() {
194
+ super.connectedCallback();
195
+ if (this.monitor) {
196
+ this.refreshInterval = setInterval(() => {
197
+ if (this.currentTicket && this.currentTicket.closed_on) {
198
+ return;
199
+ }
200
+ this.refresh();
201
+ }, DEFAULT_REFRESH);
202
+ }
203
+ }
204
+
205
+ public disconnectedCallback() {
206
+ if (this.refreshInterval) {
207
+ clearInterval(this.refreshInterval);
208
+ }
209
+ }
210
+
178
211
  public getContactHistory(): ContactHistory {
179
212
  return this.shadowRoot.querySelector(
180
213
  'temba-contact-history'
@@ -194,13 +227,36 @@ export class ContactChat extends RapidElement {
194
227
  public updated(changedProperties: Map<string, any>) {
195
228
  super.updated(changedProperties);
196
229
 
230
+ /* if (changedProperties.has('currentTicket')) {
231
+ console.log('currentTicket', this.currentTicket);
232
+ }
233
+
234
+ if (changedProperties.has('currentContact')) {
235
+ console.log('currentContact', this.currentContact);
236
+ }
237
+
238
+ if (changedProperties.has('contactUUID')) {
239
+ console.log('contactUUID', this.contactUUID);
240
+ }*/
241
+
242
+ // we were provided a uuid, fetch our contact details
243
+ if (changedProperties.has('contactUUID')) {
244
+ fetchContact(this.contactsEndpoint + '?uuid=' + this.contactUUID).then(
245
+ contact => {
246
+ this.currentContact = contact;
247
+ }
248
+ );
249
+ }
250
+
197
251
  // if we don't have an endpoint infer one
198
- if (changedProperties.has('contact')) {
252
+ if (changedProperties.has('currentContact')) {
199
253
  // focus our completion on load
200
254
  const prevContact = changedProperties.get('contact');
201
255
  if (
202
256
  !prevContact ||
203
- (this.contact && this.contact.ticket.uuid !== prevContact.ticket.uuid)
257
+ (this.currentContact &&
258
+ this.currentContact.ticket &&
259
+ this.currentContact.ticket.uuid !== prevContact.ticket.uuid)
204
260
  ) {
205
261
  const completion = this.shadowRoot.querySelector(
206
262
  'temba-completion'
@@ -223,7 +279,7 @@ export class ContactChat extends RapidElement {
223
279
  }
224
280
 
225
281
  private handleReopen() {
226
- const uuid = this.contact.ticket.uuid;
282
+ const uuid = this.currentTicket.uuid;
227
283
  postJSON(`/api/v2/tickets.json?uuid=${uuid}`, {
228
284
  status: 'open',
229
285
  })
@@ -239,11 +295,16 @@ export class ContactChat extends RapidElement {
239
295
  }
240
296
 
241
297
  private handleSend() {
242
- postJSON(`/api/v2/broadcasts.json`, {
243
- contacts: [this.contact.uuid],
298
+ const payload = {
299
+ contacts: [this.currentContact.uuid],
244
300
  text: this.currentChat,
245
- ticket: this.currentTicket.uuid,
246
- })
301
+ };
302
+
303
+ if (this.currentTicket) {
304
+ payload['ticket'] = this.currentTicket.uuid;
305
+ }
306
+
307
+ postJSON(`/api/v2/broadcasts.json`, payload)
247
308
  .then(() => {
248
309
  this.currentChat = '';
249
310
  this.refresh(true);
@@ -264,26 +325,26 @@ export class ContactChat extends RapidElement {
264
325
  setCookie(COOKIE_KEYS.TICKET_SHOW_DETAILS, this.showDetails);
265
326
  }
266
327
 
267
- private handleCurrentTicketChanged(event: CustomEvent): void {
268
- this.currentTicket = event.detail.context;
269
- }
270
-
271
328
  public render(): TemplateResult {
272
329
  return html`
273
330
  <div style="display: flex; height: 100%;">
274
- <div style="flex-grow: 1; margin-right: 0em;">
331
+ <div style="flex-grow: 1; margin-right: 0em;" class="left-pane">
275
332
  <div class="chat-wrapper">
276
- ${this.contact
333
+ ${this.currentContact
277
334
  ? html` <temba-contact-history
278
- .uuid=${this.contact.uuid}
279
- .ticket=${this.contact.ticket.uuid}
280
- .endDate=${this.contact.ticket.closed_on}
335
+ .uuid=${this.currentContact.uuid}
336
+ .contact=${this.currentContact}
337
+ .ticket=${
338
+ this.currentTicket ? this.currentTicket.uuid : null
339
+ }
340
+ .endDate=${
341
+ this.currentTicket ? this.currentTicket.closed_on : null
342
+ }
281
343
  .agent=${this.agent}
282
- @temba-context-changed=${this.handleCurrentTicketChanged}
283
344
  ></temba-contact-history>
284
345
 
285
346
  ${
286
- this.contact.ticket.closed_on
347
+ this.currentTicket && this.currentTicket.closed_on
287
348
  ? html`<div class="closed-footer">
288
349
  <temba-button
289
350
  id="reopen-button"
@@ -320,20 +381,19 @@ export class ContactChat extends RapidElement {
320
381
  : null}
321
382
  </div>
322
383
  </div>
323
- ${this.contact
384
+ ${this.currentContact
324
385
  ? html`<temba-contact-details
325
386
  style="z-index: 10"
326
387
  class="${this.showDetails ? '' : 'hidden'}"
327
- .uuid="${this.contact.uuid}"
328
- .name="${this.contact.name}"
388
+ showGroups="true"
329
389
  .visible=${this.showDetails}
330
390
  .ticket=${this.currentTicket}
331
- endpoint="${this.contactsEndpoint}?uuid=${this.contact.uuid}"
391
+ .contact=${this.currentContact}
332
392
  ></temba-contact-details>`
333
393
  : null}
334
394
 
335
395
  <div class="toolbar ${this.showDetails ? '' : 'closed'}">
336
- ${this.contact
396
+ ${this.currentContact
337
397
  ? html`
338
398
  <temba-tip
339
399
  style="margin-top:5px"
@@ -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>