All files / src/list TicketList.ts

100% Statements 182/182
100% Branches 21/21
100% Functions 3/3
100% Lines 182/182

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 183125x 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>
      `;
    };
  }
}