@nyaruka/temba-components 0.169.0 → 0.170.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.
@@ -1,5 +1,6 @@
1
1
  import { html, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
+ import { msg } from '@lit/localize';
3
4
  import { TembaList } from './TembaList';
4
5
  import { Contact } from '../interfaces';
5
6
  import { Icon } from '../Icons';
@@ -9,15 +10,17 @@ export class TicketList extends TembaList {
9
10
  agent = '';
10
11
 
11
12
  public getRefreshEndpoint() {
12
- if (this.items.length > 0) {
13
- const lastActivity = this.items[0].ticket.last_activity_on;
13
+ // open tickets sort above closed ones, so the newest activity can be
14
+ // anywhere in the list, not just the top. Skip anything unparseable so a
15
+ // single bad timestamp can't poison the cursor with NaN
16
+ const lastActivity = this.items.reduce((newest, item) => {
17
+ const activity = new Date(item.ticket.last_activity_on).getTime();
18
+ return isNaN(activity) ? newest : Math.max(newest, activity);
19
+ }, 0);
20
+
21
+ if (lastActivity > 0) {
14
22
  const separator = this.endpoint.includes('?') ? '&' : '?';
15
- return (
16
- this.endpoint +
17
- separator +
18
- 'after=' +
19
- new Date(lastActivity).getTime() * 1000
20
- );
23
+ return this.endpoint + separator + 'after=' + lastActivity * 1000;
21
24
  }
22
25
  return this.endpoint;
23
26
  }
@@ -35,10 +38,79 @@ export class TicketList extends TembaList {
35
38
  super();
36
39
 
37
40
  this.valueKey = 'ticket.uuid';
38
- this.renderOption = (contact: Contact): TemplateResult => {
41
+ // the refresh feed is served oldest first - the inherited reverseRefresh
42
+ // keeps mostRecentItem meaning the newest fetched item (compareItems
43
+ // re-sorts the merged list immediately anyway)
44
+
45
+ this.compareItems = (a: Contact, b: Contact): number => {
46
+ const aClosed = !!a.ticket.closed_on;
47
+ const bClosed = !!b.ticket.closed_on;
48
+ if (aClosed !== bClosed) {
49
+ return aClosed ? 1 : -1;
50
+ }
51
+ return (
52
+ new Date(b.ticket.last_activity_on).getTime() -
53
+ new Date(a.ticket.last_activity_on).getTime()
54
+ );
55
+ };
56
+ // a quiet label marks where the list crosses from open into closed
57
+ // tickets - the rows themselves then only need gentle muting
58
+ this.renderDivider = (
59
+ prev: Contact,
60
+ contact: Contact
61
+ ): TemplateResult | null => {
62
+ if (!contact.ticket.closed_on || (prev && prev.ticket.closed_on)) {
63
+ return null;
64
+ }
65
+ const closed = msg('Closed');
66
+ return html`
67
+ <div
68
+ role="separator"
69
+ aria-label=${closed}
70
+ style="display:flex; align-items:center; gap:0.6em; padding:0.9em var(--pad) 0.2em var(--pad);"
71
+ >
72
+ <div
73
+ style="font-size:0.7em; font-weight:500; letter-spacing:0.08em; text-transform:uppercase; color:var(--text-4);"
74
+ >
75
+ ${closed}
76
+ </div>
77
+ <div
78
+ style="flex-grow:1; height:1px; background:var(--color-widget-border);"
79
+ ></div>
80
+ </div>
81
+ `;
82
+ };
83
+ this.renderOption = (
84
+ contact: Contact,
85
+ selected: boolean
86
+ ): TemplateResult => {
87
+ // closed tickets are settled history - a compact single line that
88
+ // recedes behind the open work above it
89
+ if (contact.ticket.closed_on) {
90
+ return html`
91
+ <div
92
+ style="display:flex; align-items:baseline; margin-top:0.1em; margin-bottom:0.1em; ${selected
93
+ ? ''
94
+ : 'color:var(--text-3);'}"
95
+ >
96
+ <div
97
+ style="flex:1; min-width:0; font-weight:400; line-height:1.6; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"
98
+ >
99
+ ${contact.name}
100
+ </div>
101
+ <div style="font-size:0.8em; margin-left:0.75em;">
102
+ <temba-date
103
+ value=${contact.ticket.closed_on}
104
+ display="duration"
105
+ ></temba-date>
106
+ </div>
107
+ </div>
108
+ `;
109
+ }
110
+
39
111
  return html`
40
112
  <div
41
- style="align-items:center; margin-top: 0.1em; margin-bottom: 0.1em"
113
+ style="align-items:center; margin-top: 0.1em; margin-bottom: 0.1em;"
42
114
  >
43
115
  <div
44
116
  style="display:flex; align-items: flex-start;border:0px solid red;"
@@ -49,10 +121,8 @@ export class TicketList extends TembaList {
49
121
  >
50
122
  ${contact.name}
51
123
  </div>
52
- ${contact.ticket.closed_on
53
- ? null
54
- : contact.last_msg
55
- ? html`
124
+ ${contact.last_msg
125
+ ? html`
56
126
  <div
57
127
  style="font-size: 0.9em; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;"
58
128
  >
@@ -81,13 +151,13 @@ export class TicketList extends TembaList {
81
151
  }
82
152
  </div></div>
83
153
  `
84
- : null}
154
+ : null}
85
155
  </div>
86
156
  <div
87
157
  style="margin-right: -5px; margin-top: 0px;display:flex;flex-direction:column;align-items:flex-end;max-width:60px;min-width:30px;border:0px solid green;text-align:right"
88
158
  >
89
159
  <div>
90
- ${!contact.ticket.closed_on && contact.ticket.assignee
160
+ ${contact.ticket.assignee
91
161
  ? html`<temba-user
92
162
  name=${contact.ticket.assignee.name}
93
163
  email=${contact.ticket.assignee.email}
@@ -101,8 +171,7 @@ export class TicketList extends TembaList {
101
171
 
102
172
  <div style="font-size:0.8em;text-align:right;border:0px solid red;">
103
173
  <temba-date
104
- value=${contact.ticket.closed_on ||
105
- contact.ticket.last_activity_on}
174
+ value=${contact.ticket.last_activity_on}
106
175
  display="duration"
107
176
  ></temba-date>
108
177
  </div>
@@ -883,9 +883,15 @@ export class ContactChat extends ContactStoreElement {
883
883
  return null;
884
884
  }
885
885
 
886
+ // unparseable values and zero-value times (e.g. go's zero time for a
887
+ // contact that has never been seen) mean there's no last seen to show
888
+ const lastSeen = DateTime.fromISO(lastSeenOn);
889
+ if (!lastSeen.isValid || lastSeen.year <= 1) {
890
+ return null;
891
+ }
892
+
886
893
  // recent activity speaks for itself in the chat - only surface last
887
894
  // seen once the contact has been quiet for at least an hour
888
- const lastSeen = DateTime.fromISO(lastSeenOn);
889
895
  const minutes = DateTime.now().diff(lastSeen, 'minutes').minutes;
890
896
  if (minutes < 60) {
891
897
  return null;
package/src/locales/es.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  'sbc913d7dc0f33877': `to add`,
15
15
  's7722a91d3a512442': str`Last seen ${0}`,
16
16
  's22965bb9808befb0': `Interrupt`,
17
+ 's6dbbe2646b239ca5': `Closed`,
17
18
  's122d4de68bcfcdf4': `It's okay to restart`,
18
19
  's3eb2567092b4d7c1': `from the beginning`,
19
20
  's28f37776b3901438': `It's okay to interrupt`,
package/src/locales/fr.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  'sbc913d7dc0f33877': `to add`,
15
15
  's7722a91d3a512442': str`Last seen ${0}`,
16
16
  's22965bb9808befb0': `Interrupt`,
17
+ 's6dbbe2646b239ca5': `Closed`,
17
18
  's122d4de68bcfcdf4': `It's okay to restart`,
18
19
  's3eb2567092b4d7c1': `from the beginning`,
19
20
  's28f37776b3901438': `It's okay to interrupt`,
package/src/locales/pt.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  'sbc913d7dc0f33877': `to add`,
15
15
  's7722a91d3a512442': str`Last seen ${0}`,
16
16
  's22965bb9808befb0': `Interrupt`,
17
+ 's6dbbe2646b239ca5': `Closed`,
17
18
  's122d4de68bcfcdf4': `It's okay to restart`,
18
19
  's3eb2567092b4d7c1': `from the beginning`,
19
20
  's28f37776b3901438': `It's okay to interrupt`,
@@ -630,6 +630,7 @@ export class Simulator extends RapidElement {
630
630
  }
631
631
  .message-input input {
632
632
  flex: 1;
633
+ min-width: 0;
633
634
  border: 1px solid #c6c6c857;
634
635
  border-radius: 20px;
635
636
  padding: 8px 15px;
package/xliff/es.xlf CHANGED
@@ -42,6 +42,9 @@
42
42
  <trans-unit id="s7722a91d3a512442">
43
43
  <source>Last seen <x id="0" equiv-text="${lastSeen.duration}"/></source>
44
44
  </trans-unit>
45
+ <trans-unit id="s6dbbe2646b239ca5">
46
+ <source>Closed</source>
47
+ </trans-unit>
45
48
  </body>
46
49
  </file>
47
50
  </xliff>
package/xliff/fr.xlf CHANGED
@@ -41,6 +41,9 @@
41
41
  <trans-unit id="s7722a91d3a512442">
42
42
  <source>Last seen <x id="0" equiv-text="${lastSeen.duration}"/></source>
43
43
  </trans-unit>
44
+ <trans-unit id="s6dbbe2646b239ca5">
45
+ <source>Closed</source>
46
+ </trans-unit>
44
47
  </body>
45
48
  </file>
46
49
  </xliff>
package/xliff/pt.xlf CHANGED
@@ -41,6 +41,9 @@
41
41
  <trans-unit id="s7722a91d3a512442">
42
42
  <source>Last seen <x id="0" equiv-text="${lastSeen.duration}"/></source>
43
43
  </trans-unit>
44
+ <trans-unit id="s6dbbe2646b239ca5">
45
+ <source>Closed</source>
46
+ </trans-unit>
44
47
  </body>
45
48
  </file>
46
49
  </xliff>