@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.
- package/CHANGELOG.md +13 -0
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +1 -0
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/pt.js +1 -0
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +660 -562
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/display/Chat.ts +2 -2
- package/src/display/Dropdown.ts +11 -7
- package/src/display/Options.ts +27 -14
- package/src/display/TembaDate.ts +9 -2
- package/src/form/Compose.ts +37 -28
- package/src/interfaces.ts +13 -3
- package/src/list/BroadcastList.ts +3 -1
- package/src/list/ContactList.ts +113 -9
- package/src/list/ContentList.ts +351 -8
- package/src/list/ContentMenu.ts +12 -6
- package/src/list/TembaList.ts +99 -29
- package/src/list/TicketList.ts +87 -18
- package/src/live/ContactChat.ts +7 -1
- package/src/locales/es.ts +1 -0
- package/src/locales/fr.ts +1 -0
- package/src/locales/pt.ts +1 -0
- package/src/simulator/Simulator.ts +1 -0
- package/xliff/es.xlf +3 -0
- package/xliff/fr.xlf +3 -0
- package/xliff/pt.xlf +3 -0
package/src/list/TicketList.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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.
|
|
53
|
-
?
|
|
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
|
-
|
|
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
|
-
${
|
|
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.
|
|
105
|
-
contact.ticket.last_activity_on}
|
|
174
|
+
value=${contact.ticket.last_activity_on}
|
|
106
175
|
display="duration"
|
|
107
176
|
></temba-date>
|
|
108
177
|
</div>
|
package/src/live/ContactChat.ts
CHANGED
|
@@ -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`,
|
package/xliff/es.xlf
CHANGED
package/xliff/fr.xlf
CHANGED
package/xliff/pt.xlf
CHANGED