Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | 125x 6x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 138x 138x 138x 138x 138x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 157x 157x 157x 157x 157x 157x 157x 136x 136x 128x 157x 145x 145x 125x 125x 167x 167x 167x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 210x 210x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x 125x | import { html, TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { msg } from '@lit/localize';
import { TembaList } from './TembaList';
import { Contact } from '../interfaces';
import { Icon } from '../Icons';
export class TicketList extends TembaList {
@property({ type: String })
agent = '';
public getRefreshEndpoint() {
// open tickets sort above closed ones, so the newest activity can be
// anywhere in the list, not just the top. Skip anything unparseable so a
// single bad timestamp can't poison the cursor with NaN
const lastActivity = this.items.reduce((newest, item) => {
const activity = new Date(item.ticket.last_activity_on).getTime();
return isNaN(activity) ? newest : Math.max(newest, activity);
}, 0);
if (lastActivity > 0) {
const separator = this.endpoint.includes('?') ? '&' : '?';
return this.endpoint + separator + 'after=' + lastActivity * 1000;
}
return this.endpoint;
}
protected sanitizeResults(results: any[]): Promise<any[]> {
return new Promise<any[]>((resolve) => {
const contacts: Contact[] = results as Contact[];
this.store.resolveUsers(contacts, ['ticket.assignee']).then(() => {
resolve(contacts);
});
});
}
constructor() {
super();
this.valueKey = 'ticket.uuid';
// the refresh feed is served oldest first - the inherited reverseRefresh
// keeps mostRecentItem meaning the newest fetched item (compareItems
// re-sorts the merged list immediately anyway)
this.compareItems = (a: Contact, b: Contact): number => {
const aClosed = !!a.ticket.closed_on;
const bClosed = !!b.ticket.closed_on;
if (aClosed !== bClosed) {
return aClosed ? 1 : -1;
}
return (
new Date(b.ticket.last_activity_on).getTime() -
new Date(a.ticket.last_activity_on).getTime()
);
};
// a quiet label marks where the list crosses from open into closed
// tickets - the rows themselves then only need gentle muting
this.renderDivider = (
prev: Contact,
contact: Contact
): TemplateResult | null => {
if (!contact.ticket.closed_on || (prev && prev.ticket.closed_on)) {
return null;
}
const closed = msg('Closed');
return html`
<div
role="separator"
aria-label=${closed}
style="display:flex; align-items:center; gap:0.6em; padding:0.9em var(--pad) 0.2em var(--pad);"
>
<div
style="font-size:0.7em; font-weight:500; letter-spacing:0.08em; text-transform:uppercase; color:var(--text-4);"
>
${closed}
</div>
<div
style="flex-grow:1; height:1px; background:var(--color-widget-border);"
></div>
</div>
`;
};
this.renderOption = (
contact: Contact,
selected: boolean
): TemplateResult => {
// closed tickets are settled history - a compact single line that
// recedes behind the open work above it
if (contact.ticket.closed_on) {
return html`
<div
style="display:flex; align-items:baseline; margin-top:0.1em; margin-bottom:0.1em; ${selected
? ''
: 'color:var(--text-3);'}"
>
<div
style="flex:1; min-width:0; font-weight:400; line-height:1.6; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"
>
${contact.name}
</div>
<div style="font-size:0.8em; margin-left:0.75em;">
<temba-date
value=${contact.ticket.closed_on}
display="duration"
></temba-date>
</div>
</div>
`;
}
return html`
<div
style="align-items:center; margin-top: 0.1em; margin-bottom: 0.1em;"
>
<div
style="display:flex; align-items: flex-start;border:0px solid red;"
>
<div style="flex: 1; color:#333;">
<div
style="font-weight:400;line-height:1.6;padding-right:0.5em;display:-webkit-box;-webkit-box-orient: vertical; -webkit-line-clamp: 1;overflow: hidden;"
>
${contact.name}
</div>
${contact.last_msg
? html`
<div
style="font-size: 0.9em; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;"
>
${
contact.last_msg.direction === 'I'
? html`<div
style="border-radius:9999px; background:var(--color-primary-dark);width:6px;height:6px;display:inline-block;margin:0px 2px;margin-bottom:1px;"
></div>`
: null
}
${
contact.last_msg.text
? contact.last_msg.text
: contact.last_msg.attachments
? html`<div style="display:inline-block">
<div style="display:flex; margin-left:0.2em">
<temba-icon
name="${Icon.attachment}"
></temba-icon>
<div style="flex-grow:1;margin-left:0.2em">
Attachment
</div>
</div>
</div>`
: 'Unsupported Message'
}
</div></div>
`
: null}
</div>
<div
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"
>
<div>
${contact.ticket.assignee
? html`<temba-user
name=${contact.ticket.assignee.name}
email=${contact.ticket.assignee.email}
avatar=${contact.ticket.assignee.avatar}
scale="0.8"
></temba-user>`
: null}
</div>
</div>
</div>
<div style="font-size:0.8em;text-align:right;border:0px solid red;">
<temba-date
value=${contact.ticket.last_activity_on}
display="duration"
></temba-date>
</div>
</div>
`;
};
}
}
|