@nyaruka/temba-components 0.163.0 → 0.165.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 +27 -0
- package/DEV_DATA.md +11 -11
- package/README.md +4 -4
- package/TEST_OPTIMIZATION.md +8 -11
- package/dist/locales/es.js +4 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +4 -0
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/pt.js +4 -0
- package/dist/locales/pt.js.map +1 -1
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +5266 -3031
- package/dist/temba-components.js.map +1 -1
- package/orca/setup.sh +2 -2
- package/package.json +13 -18
- package/scripts/dev-data-sync.mjs +4 -4
- package/src/Icons.ts +3 -2
- package/src/display/Chat.ts +3 -2
- package/src/display/Lightbox.ts +57 -109
- package/src/display/ProgressBar.ts +15 -2
- package/src/display/Thumbnail.ts +34 -7
- package/src/form/Compose.ts +31 -1
- package/src/form/ContactSearch.ts +229 -126
- package/src/interfaces.ts +131 -2
- package/src/layout/Card.ts +336 -0
- package/src/layout/CardLayout.ts +425 -0
- package/src/layout/CardStack.ts +131 -0
- package/src/layout/Dialog.ts +33 -24
- package/src/layout/HeaderBar.ts +41 -0
- package/src/layout/PageHeader.ts +5 -6
- package/src/layout/Resizer.ts +20 -0
- package/src/list/BroadcastList.ts +912 -0
- package/src/list/CampaignList.ts +144 -0
- package/src/list/ContactList.ts +3 -1
- package/src/list/ContentList.ts +46 -10
- package/src/list/FieldList.ts +1057 -0
- package/src/list/FlowList.ts +31 -7
- package/src/list/MsgList.ts +18 -12
- package/src/list/SortableList.ts +52 -8
- package/src/list/TembaList.ts +8 -0
- package/src/list/TembaMenu.ts +5 -38
- package/src/list/TriggerList.ts +538 -0
- package/src/live/CampaignEvents.ts +1108 -0
- package/src/live/ContactChat.ts +7 -1
- package/src/live/ContactDetails.ts +20 -14
- package/src/live/ContactFieldEditor.ts +7 -3
- package/src/live/ContactFields.ts +1 -1
- package/src/live/ContactNameFetch.ts +5 -2
- package/src/live/ContactNotepad.ts +88 -2
- package/src/live/ContactStoreElement.ts +15 -3
- package/src/live/ContactTimeline.ts +28 -7
- package/src/locales/es.ts +4 -0
- package/src/locales/fr.ts +4 -0
- package/src/locales/pt.ts +4 -0
- package/src/utils.ts +19 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/packs/2-temba/star-filled.svg +3 -0
- package/static/svg/work/traced/star-filled.svg +1 -0
- package/static/svg/work/used/star-filled.svg +3 -0
- package/stress-test.js +3 -3
- package/temba-modules.ts +18 -0
- package/web-dev-server.config.mjs +54 -0
- package/web-test-runner.config.mjs +1 -1
- package/xliff/es.xlf +12 -0
- package/xliff/fr.xlf +12 -0
- package/xliff/pt.xlf +12 -0
|
@@ -226,12 +226,33 @@ export class ContactSearch extends FieldElement {
|
|
|
226
226
|
border: 1px solid var(--color-borders);
|
|
227
227
|
border-radius: var(--curvature);
|
|
228
228
|
}
|
|
229
|
+
|
|
230
|
+
.interrupt-confirm {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
margin-top: 0.5em;
|
|
234
|
+
margin-left: 0.6em;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
}
|
|
229
237
|
`;
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
@property({ type: Boolean })
|
|
233
241
|
in_a_flow: boolean;
|
|
234
242
|
|
|
243
|
+
// recipients are locked, e.g. starting a specific contact from their read page -
|
|
244
|
+
// no recipient editing, filters or query editing
|
|
245
|
+
@property({ type: Boolean })
|
|
246
|
+
fixed: boolean;
|
|
247
|
+
|
|
248
|
+
// name of the flow the fixed contact is currently in, if any
|
|
249
|
+
@property({ type: String, attribute: 'current_flow' })
|
|
250
|
+
currentFlow: string;
|
|
251
|
+
|
|
252
|
+
// whether the user has explicitly confirmed interrupting the current flow
|
|
253
|
+
@property({ type: Boolean })
|
|
254
|
+
interruptConfirmed = false;
|
|
255
|
+
|
|
235
256
|
@property({ type: Boolean })
|
|
236
257
|
started_previously: boolean;
|
|
237
258
|
|
|
@@ -310,6 +331,30 @@ export class ContactSearch extends FieldElement {
|
|
|
310
331
|
}
|
|
311
332
|
}
|
|
312
333
|
|
|
334
|
+
protected willUpdate(changedProperties: Map<string, any>) {
|
|
335
|
+
super.willUpdate(changedProperties);
|
|
336
|
+
|
|
337
|
+
// reconcile the in_a_flow exclusion; outside of fixed mode it is user-controlled
|
|
338
|
+
// via the filter checkbox, so we only ever clear it when the option goes away
|
|
339
|
+
if (
|
|
340
|
+
changedProperties.has('in_a_flow') ||
|
|
341
|
+
changedProperties.has('interruptConfirmed') ||
|
|
342
|
+
changedProperties.has('fixed') ||
|
|
343
|
+
changedProperties.has('currentFlow')
|
|
344
|
+
) {
|
|
345
|
+
if (!this.in_a_flow) {
|
|
346
|
+
delete this.exclusions['in_a_flow'];
|
|
347
|
+
} else if (this.fixed && this.currentFlow) {
|
|
348
|
+
// a fixed contact already in a flow stays excluded until interruption is confirmed
|
|
349
|
+
if (this.interruptConfirmed) {
|
|
350
|
+
delete this.exclusions['in_a_flow'];
|
|
351
|
+
} else {
|
|
352
|
+
this.exclusions['in_a_flow'] = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
313
358
|
public updated(changedProperties: Map<string, any>) {
|
|
314
359
|
super.updated(changedProperties);
|
|
315
360
|
|
|
@@ -317,12 +362,6 @@ export class ContactSearch extends FieldElement {
|
|
|
317
362
|
this.handleRecipientsChanged();
|
|
318
363
|
}
|
|
319
364
|
|
|
320
|
-
// if we remove the in_a_flow option, make sure it's not part of our exclusions
|
|
321
|
-
if (changedProperties.has('in_a_flow') && !this.in_a_flow) {
|
|
322
|
-
delete this.exclusions['in_a_flow'];
|
|
323
|
-
this.requestUpdate('exclusions');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
365
|
if (
|
|
327
366
|
(changedProperties.has('query') && this.advanced) ||
|
|
328
367
|
(changedProperties.has('refreshKey') && this.refreshKey !== '0')
|
|
@@ -455,6 +494,32 @@ export class ContactSearch extends FieldElement {
|
|
|
455
494
|
}
|
|
456
495
|
}
|
|
457
496
|
|
|
497
|
+
private async handleInterruptChanged(evt: any) {
|
|
498
|
+
const checkbox = evt.target as Checkbox;
|
|
499
|
+
this.interruptConfirmed = checkbox.checked;
|
|
500
|
+
|
|
501
|
+
// wait for willUpdate() to reconcile our exclusions
|
|
502
|
+
await this.updateComplete;
|
|
503
|
+
|
|
504
|
+
this.setValue({
|
|
505
|
+
advanced: this.advanced,
|
|
506
|
+
query: this.query,
|
|
507
|
+
exclusions: this.exclusions,
|
|
508
|
+
recipients: this.recipients
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// we already know whether our fixed contacts will be included so no need to re-run
|
|
512
|
+
// the search - just let the modal know, counting contacts only since a group entry
|
|
513
|
+
// doesn't tell us its membership size
|
|
514
|
+
const contactCount = this.recipients.filter(
|
|
515
|
+
(value: OmniOption) => value.type === 'contact'
|
|
516
|
+
).length;
|
|
517
|
+
this.fireCustomEvent(CustomEventType.ContentChanged, {
|
|
518
|
+
...(this.summary || {}),
|
|
519
|
+
total: this.interruptConfirmed ? contactCount : 0
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
458
523
|
private handleExclusionChanged(evt: any) {
|
|
459
524
|
if (evt.target.tagName === 'TEMBA-CHECKBOX') {
|
|
460
525
|
const ex = JSON.stringify(this.exclusions);
|
|
@@ -487,7 +552,7 @@ export class ContactSearch extends FieldElement {
|
|
|
487
552
|
|
|
488
553
|
public renderWidget(): TemplateResult {
|
|
489
554
|
let summary: TemplateResult;
|
|
490
|
-
if (this.summary) {
|
|
555
|
+
if (this.summary && !this.fixed) {
|
|
491
556
|
if (!this.summary.error) {
|
|
492
557
|
const count = this.summary.total || 0;
|
|
493
558
|
|
|
@@ -544,132 +609,170 @@ export class ContactSearch extends FieldElement {
|
|
|
544
609
|
)}`;
|
|
545
610
|
}
|
|
546
611
|
|
|
612
|
+
const interruptConfirm =
|
|
613
|
+
this.fixed && this.currentFlow && this.in_a_flow && !this.fetching
|
|
614
|
+
? html`<div
|
|
615
|
+
class="interrupt-confirm"
|
|
616
|
+
@click=${this.handleActivityLabelClicked}
|
|
617
|
+
>
|
|
618
|
+
<temba-checkbox
|
|
619
|
+
name="interrupt"
|
|
620
|
+
?checked=${this.interruptConfirmed}
|
|
621
|
+
@change=${this.handleInterruptChanged}
|
|
622
|
+
></temba-checkbox>
|
|
623
|
+
<div>
|
|
624
|
+
${this.flow && this.flow.name === this.currentFlow
|
|
625
|
+
? html`${msg("It's okay to restart")}
|
|
626
|
+
<b>${this.currentFlow}</b> ${msg('from the beginning')}`
|
|
627
|
+
: html`${msg("It's okay to interrupt")}
|
|
628
|
+
<b>${this.currentFlow}</b> ${msg('and start this one')}`}
|
|
629
|
+
</div>
|
|
630
|
+
</div>`
|
|
631
|
+
: null;
|
|
632
|
+
|
|
547
633
|
return html`
|
|
548
634
|
${
|
|
549
|
-
this.
|
|
550
|
-
?
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
635
|
+
this.fixed
|
|
636
|
+
? null
|
|
637
|
+
: this.advanced
|
|
638
|
+
? html`<div class="query">
|
|
639
|
+
<temba-textinput
|
|
640
|
+
.helpText=${this.helpText}
|
|
641
|
+
.widgetOnly=${this.widgetOnly}
|
|
642
|
+
.errors=${this.errors}
|
|
643
|
+
name=${this.name}
|
|
644
|
+
.inputRoot=${this}
|
|
645
|
+
@input=${this.handleQueryChange}
|
|
646
|
+
placeholder=${this.placeholder}
|
|
647
|
+
.value=${this.query}
|
|
648
|
+
textarea
|
|
649
|
+
autogrow
|
|
650
|
+
>
|
|
651
|
+
</temba-textinput>
|
|
652
|
+
</div>`
|
|
653
|
+
: html`<temba-omnibox
|
|
654
|
+
placeholder="Search for contacts or groups"
|
|
655
|
+
widget_only=""
|
|
656
|
+
groups=""
|
|
657
|
+
contacts=""
|
|
658
|
+
label="Recipients"
|
|
659
|
+
help_text="The contacts to send the message to."
|
|
660
|
+
.errors=${this.errors}
|
|
661
|
+
id="recipients"
|
|
662
|
+
name="recipients"
|
|
663
|
+
.values=${this.recipients}
|
|
664
|
+
endpoint="/contact/omnibox/?"
|
|
665
|
+
@change=${this.handleRecipientsChanged}
|
|
666
|
+
>
|
|
667
|
+
</temba-omnibox>
|
|
668
|
+
|
|
669
|
+
${this.not_seen_since_days ||
|
|
670
|
+
this.in_a_flow ||
|
|
671
|
+
this.started_previously
|
|
672
|
+
? html`
|
|
673
|
+
<div class="filters">
|
|
674
|
+
<div
|
|
675
|
+
style="display:flex;font-size:1em;margin-bottom:0.5em"
|
|
676
|
+
>
|
|
677
|
+
<temba-icon size="1" name="filter"></temba-icon>
|
|
678
|
+
<div style="margin-left:0.5em">
|
|
679
|
+
Only include contacts who...
|
|
680
|
+
</div>
|
|
592
681
|
</div>
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
@click=${this.handleActivityLabelClicked}
|
|
607
|
-
>
|
|
608
|
-
<temba-checkbox
|
|
609
|
-
style="display:inline;"
|
|
610
|
-
name="not_seen_since_days"
|
|
611
|
-
?checked=${notSeenSinceDays}
|
|
612
|
-
@change=${this.handleExclusionChanged}
|
|
682
|
+
${this.in_a_flow
|
|
683
|
+
? html`<temba-checkbox
|
|
684
|
+
name="in_a_flow"
|
|
685
|
+
label="${msg('Are not currently in a flow')}"
|
|
686
|
+
?checked=${this.exclusions['in_a_flow']}
|
|
687
|
+
@change=${this.handleExclusionChanged}
|
|
688
|
+
></temba-checkbox>`
|
|
689
|
+
: null}
|
|
690
|
+
${this.not_seen_since_days
|
|
691
|
+
? html`
|
|
692
|
+
<div
|
|
693
|
+
class="activity-select"
|
|
694
|
+
@click=${this.handleActivityLabelClicked}
|
|
613
695
|
>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
696
|
+
<temba-checkbox
|
|
697
|
+
style="display:inline;"
|
|
698
|
+
name="not_seen_since_days"
|
|
699
|
+
?checked=${notSeenSinceDays}
|
|
700
|
+
@change=${this.handleExclusionChanged}
|
|
701
|
+
>
|
|
702
|
+
</temba-checkbox>
|
|
703
|
+
|
|
704
|
+
<div>
|
|
705
|
+
${msg('Have sent a message in the last')}
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
<temba-select
|
|
709
|
+
style="margin-left:0.5em"
|
|
710
|
+
class="small-select"
|
|
711
|
+
@change=${this.handleActivityLevelChanged}
|
|
712
|
+
?disabled=${!notSeenSinceDays}
|
|
713
|
+
>
|
|
714
|
+
<temba-option
|
|
715
|
+
name="90 days"
|
|
716
|
+
value="90"
|
|
717
|
+
?selected=${notSeenSinceDays === 90}
|
|
718
|
+
></temba-option>
|
|
719
|
+
<temba-option
|
|
720
|
+
name="180 days"
|
|
721
|
+
value="180"
|
|
722
|
+
?selected=${notSeenSinceDays === 180}
|
|
723
|
+
></temba-option>
|
|
724
|
+
<temba-option
|
|
725
|
+
name="Year"
|
|
726
|
+
value="365"
|
|
727
|
+
?selected=${notSeenSinceDays === 365}
|
|
728
|
+
></temba-option>
|
|
729
|
+
</temba-select>
|
|
730
|
+
<div></div>
|
|
618
731
|
</div>
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
?selected=${notSeenSinceDays === 180}
|
|
635
|
-
></temba-option>
|
|
636
|
-
<temba-option
|
|
637
|
-
name="Year"
|
|
638
|
-
value="365"
|
|
639
|
-
?selected=${notSeenSinceDays === 365}
|
|
640
|
-
></temba-option>
|
|
641
|
-
</temba-select>
|
|
642
|
-
<div></div>
|
|
643
|
-
</div>
|
|
644
|
-
`
|
|
645
|
-
: null}
|
|
646
|
-
${this.started_previously
|
|
647
|
-
? html`<temba-checkbox
|
|
648
|
-
name="started_previously"
|
|
649
|
-
label="${msg(
|
|
650
|
-
'Have not started this flow in the last 90 days'
|
|
651
|
-
)}"
|
|
652
|
-
?checked=${this.exclusions['started_previously']}
|
|
653
|
-
@change=${this.handleExclusionChanged}
|
|
654
|
-
></temba-checkbox>`
|
|
655
|
-
: null}
|
|
656
|
-
</div>
|
|
657
|
-
`
|
|
658
|
-
: null} `
|
|
732
|
+
`
|
|
733
|
+
: null}
|
|
734
|
+
${this.started_previously
|
|
735
|
+
? html`<temba-checkbox
|
|
736
|
+
name="started_previously"
|
|
737
|
+
label="${msg(
|
|
738
|
+
'Have not started this flow in the last 90 days'
|
|
739
|
+
)}"
|
|
740
|
+
?checked=${this.exclusions['started_previously']}
|
|
741
|
+
@change=${this.handleExclusionChanged}
|
|
742
|
+
></temba-checkbox>`
|
|
743
|
+
: null}
|
|
744
|
+
</div>
|
|
745
|
+
`
|
|
746
|
+
: null} `
|
|
659
747
|
}
|
|
660
748
|
</div>
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
749
|
+
${interruptConfirm}
|
|
750
|
+
${
|
|
751
|
+
this.fixed
|
|
752
|
+
? html`<div
|
|
753
|
+
class="results ${getClasses({
|
|
754
|
+
fetching: this.fetching,
|
|
755
|
+
initialized: this.fetching,
|
|
756
|
+
empty: !this.fetching
|
|
757
|
+
})}"
|
|
758
|
+
>
|
|
759
|
+
<temba-loading units="6" size="8"></temba-loading>
|
|
760
|
+
</div>`
|
|
761
|
+
: html`<div
|
|
762
|
+
class="results ${getClasses({
|
|
763
|
+
fetching: this.fetching,
|
|
764
|
+
initialized: this.initialized || this.fetching,
|
|
765
|
+
empty:
|
|
766
|
+
((this.summary && this.summary.error) || !this.summary) &&
|
|
767
|
+
!this.fetching
|
|
768
|
+
})}"
|
|
769
|
+
>
|
|
770
|
+
<temba-loading units="6" size="8"></temba-loading>
|
|
771
|
+
<div class="summary ${this.expanded ? 'expanded' : ''}">
|
|
772
|
+
${summary}
|
|
773
|
+
</div>
|
|
774
|
+
</div>`
|
|
775
|
+
}
|
|
673
776
|
${blockers}
|
|
674
777
|
${
|
|
675
778
|
!blockers && this.summary && this.summary.warnings
|
package/src/interfaces.ts
CHANGED
|
@@ -54,6 +54,25 @@ export interface ScheduledEvent {
|
|
|
54
54
|
message?: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// one event definition in a campaign's schedule, offset from a date field
|
|
58
|
+
// on the contact - consumed by the temba-campaign-events component
|
|
59
|
+
export interface CampaignScheduleEvent {
|
|
60
|
+
uuid: string;
|
|
61
|
+
type: 'message' | 'flow';
|
|
62
|
+
status: 'ready' | 'scheduling';
|
|
63
|
+
offset: number;
|
|
64
|
+
unit: string;
|
|
65
|
+
offset_display: string;
|
|
66
|
+
delivery_hour_display?: string;
|
|
67
|
+
relative_to: { key: string; name: string; system?: boolean };
|
|
68
|
+
flow?: ObjectReference;
|
|
69
|
+
message?: string;
|
|
70
|
+
count: number;
|
|
71
|
+
edit_url: string;
|
|
72
|
+
delete_url: string;
|
|
73
|
+
fires_url: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
export interface NamedUser extends User {
|
|
58
77
|
name: string;
|
|
59
78
|
}
|
|
@@ -120,6 +139,111 @@ export interface Flow {
|
|
|
120
139
|
activity: number[];
|
|
121
140
|
}
|
|
122
141
|
|
|
142
|
+
/** A single row in the campaign CRUDL list
|
|
143
|
+
* (`campaigns/campaign_list.html`). */
|
|
144
|
+
export interface Campaign {
|
|
145
|
+
uuid: string;
|
|
146
|
+
name: string;
|
|
147
|
+
/** The contact group the campaign schedules against. */
|
|
148
|
+
group: ObjectReference;
|
|
149
|
+
/** Number of events (messages / flow starts) in the campaign. */
|
|
150
|
+
events: number;
|
|
151
|
+
/** Contacts currently in the campaign's group. */
|
|
152
|
+
contacts: number;
|
|
153
|
+
modified_on: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** A single row in the trigger CRUDL list
|
|
157
|
+
* (`triggers/trigger_list.html`): what starts the flow (type +
|
|
158
|
+
* per-type details), any channel / group filters, and the flow it
|
|
159
|
+
* starts. Triggers have no uuid — rows key off the numeric id. */
|
|
160
|
+
export interface Trigger {
|
|
161
|
+
id: number;
|
|
162
|
+
/** Trigger type slug — drives the leading row icon and the
|
|
163
|
+
* details cell (`keyword`, `catch_all`, `schedule`,
|
|
164
|
+
* `inbound_call`, `missed_call`, `new_conversation`, `referral`,
|
|
165
|
+
* `closed_ticket`, `opt_in`, `opt_out`). */
|
|
166
|
+
type: string;
|
|
167
|
+
/** The flow the trigger starts. */
|
|
168
|
+
flow: ObjectReference;
|
|
169
|
+
/** Channel the trigger is limited to, with its type icon. */
|
|
170
|
+
channel?: (ObjectReference & { icon?: string }) | null;
|
|
171
|
+
/** Groups the trigger is limited to. */
|
|
172
|
+
groups?: ObjectReference[];
|
|
173
|
+
/** Groups the trigger excludes. */
|
|
174
|
+
exclude_groups?: ObjectReference[];
|
|
175
|
+
/** Contacts a scheduled trigger starts directly. */
|
|
176
|
+
contacts?: ObjectReference[];
|
|
177
|
+
/** Keyword triggers only. */
|
|
178
|
+
keywords?: string[];
|
|
179
|
+
/** Keyword match type — `F` (starts with) or `O` (matches). */
|
|
180
|
+
match_type?: string | null;
|
|
181
|
+
/** Referral triggers only. */
|
|
182
|
+
referrer_id?: string | null;
|
|
183
|
+
/** Scheduled triggers only — `display` is the server-rendered
|
|
184
|
+
* human schedule ("each week on Monday"); `next_fire` is unset
|
|
185
|
+
* once the schedule is exhausted or paused. */
|
|
186
|
+
schedule?: {
|
|
187
|
+
repeat_period?: string;
|
|
188
|
+
display?: string;
|
|
189
|
+
next_fire?: string | null;
|
|
190
|
+
} | null;
|
|
191
|
+
priority?: number;
|
|
192
|
+
created_on?: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** A single row in the broadcast CRUDL lists — both the scheduled
|
|
196
|
+
* list (`msgs/broadcast_scheduled.html`) and the sent list
|
|
197
|
+
* (`msgs/broadcast_list.html`). Broadcasts key off the numeric id;
|
|
198
|
+
* content fields carry the base-language translation. */
|
|
199
|
+
export interface Broadcast {
|
|
200
|
+
id: number;
|
|
201
|
+
/** Server status slug — `pending`, `queued`, `started`,
|
|
202
|
+
* `completed`, `failed` or `interrupted`. Scheduled broadcasts
|
|
203
|
+
* that haven't fired yet sit at `pending`. */
|
|
204
|
+
status?: string;
|
|
205
|
+
/** Send progress for an in-flight broadcast — total recipients
|
|
206
|
+
* (-1 until the org's contact count resolves) and messages
|
|
207
|
+
* created so far. */
|
|
208
|
+
progress?: { total: number; started: number };
|
|
209
|
+
/** Base-language message text. */
|
|
210
|
+
text?: string;
|
|
211
|
+
/** Base-language attachments (`{content_type, url}` objects or
|
|
212
|
+
* `contentType:url` strings). */
|
|
213
|
+
attachments?: (string | Attachment)[];
|
|
214
|
+
/** Base-language quick replies. */
|
|
215
|
+
quick_replies?: string[];
|
|
216
|
+
/** The opt-in the broadcast requests, when it is one. */
|
|
217
|
+
optin?: ObjectReference | null;
|
|
218
|
+
/** The WhatsApp template the broadcast sends, when it uses one. */
|
|
219
|
+
template?: ObjectReference | null;
|
|
220
|
+
/** Recipient groups. */
|
|
221
|
+
groups?: ObjectReference[];
|
|
222
|
+
/** Recipient contacts. */
|
|
223
|
+
contacts?: ObjectReference[];
|
|
224
|
+
/** Raw recipient URNs (editors/admins only). */
|
|
225
|
+
urns?: string[];
|
|
226
|
+
/** Recipient contact query, for query-addressed broadcasts. */
|
|
227
|
+
query?: string | null;
|
|
228
|
+
/** Human-readable exclusion descriptions ("Skip inactive
|
|
229
|
+
* contacts", ...) rendered in the detail view. */
|
|
230
|
+
exclusions?: string[];
|
|
231
|
+
/** Scheduled broadcasts only — `display` is the server-rendered
|
|
232
|
+
* human repeat ("each week on Monday, Wednesday"); `next_fire` is
|
|
233
|
+
* null once the schedule is exhausted or paused. */
|
|
234
|
+
schedule?: {
|
|
235
|
+
repeat_period?: string;
|
|
236
|
+
display?: string;
|
|
237
|
+
next_fire?: string | null;
|
|
238
|
+
} | null;
|
|
239
|
+
/** Messages created by a sent broadcast. */
|
|
240
|
+
msg_count?: number;
|
|
241
|
+
created_on?: string;
|
|
242
|
+
/** Email of the user who created the broadcast. */
|
|
243
|
+
created_by?: string | null;
|
|
244
|
+
modified_on?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
123
247
|
export interface Msg {
|
|
124
248
|
/** Numeric id — present on persisted messages (the CRUDL list
|
|
125
249
|
* keys rows off it); absent on outbound drafts. */
|
|
@@ -137,7 +261,10 @@ export interface Msg {
|
|
|
137
261
|
/** Message type as exposed by the messages CRUDL endpoint
|
|
138
262
|
* (`text` / `optin` / …); mirrors `type` for that surface. */
|
|
139
263
|
msg_type?: string;
|
|
140
|
-
|
|
264
|
+
/** Attachments as exposed by the messages CRUDL endpoint, which
|
|
265
|
+
* serializes each as a `{content_type, url}` object; some other
|
|
266
|
+
* message surfaces carry them as `contentType:url` strings. */
|
|
267
|
+
attachments: (string | Attachment)[];
|
|
141
268
|
/** Labels applied to the message. */
|
|
142
269
|
labels?: ObjectReference[];
|
|
143
270
|
/** The flow the message was sent from, when there is one. */
|
|
@@ -192,6 +319,8 @@ export interface ContactGroup {
|
|
|
192
319
|
export interface URN {
|
|
193
320
|
scheme: string;
|
|
194
321
|
path: string;
|
|
322
|
+
display?: string | null;
|
|
323
|
+
channel?: ObjectReference | null;
|
|
195
324
|
}
|
|
196
325
|
|
|
197
326
|
export interface Group {
|
|
@@ -224,7 +353,7 @@ export interface Contact {
|
|
|
224
353
|
uuid: string;
|
|
225
354
|
stopped: boolean;
|
|
226
355
|
blocked: boolean;
|
|
227
|
-
urns:
|
|
356
|
+
urns: URN[];
|
|
228
357
|
language?: string;
|
|
229
358
|
fields: { [key: string]: string };
|
|
230
359
|
groups: Group[];
|