@aglyn/plugins-email 1.0.0-beta.218 → 1.0.0-beta.220
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/package.json +18 -18
- package/src/lib/components/campaign-topic-options-widget.js +2 -2
- package/src/lib/components/campaign-topic-options-widget.js.map +1 -1
- package/src/lib/components/campaign-topic-select.js +3 -3
- package/src/lib/components/campaign-topic-select.js.map +1 -1
- package/src/lib/components/consent-group-review.js +2 -2
- package/src/lib/components/consent-group-review.js.map +1 -1
- package/src/lib/components/dynamic-list-rule-fields.d.ts +20 -4
- package/src/lib/components/dynamic-list-rule-fields.js +537 -439
- package/src/lib/components/dynamic-list-rule-fields.js.map +1 -1
- package/src/lib/components/email-topic-detail.d.ts +1 -1
- package/src/lib/components/email-topic-detail.js +5 -5
- package/src/lib/components/email-topic-detail.js.map +1 -1
- package/src/lib/components/email-topics-card.d.ts +1 -1
- package/src/lib/components/email-topics-card.js +6 -5
- package/src/lib/components/email-topics-card.js.map +1 -1
- package/src/lib/components/emails-console-sections.js +3 -2
- package/src/lib/components/emails-console-sections.js.map +1 -1
- package/src/lib/components/list-import-drawer.js +2 -2
- package/src/lib/components/list-import-drawer.js.map +1 -1
- package/src/lib/components/use-org-email-topics.d.ts +4 -4
- package/src/lib/components/use-org-email-topics.js +6 -5
- package/src/lib/components/use-org-email-topics.js.map +1 -1
- package/src/lib/list-import.d.ts +149 -0
- package/src/lib/list-import.js +324 -0
- package/src/lib/list-import.js.map +1 -0
- package/src/lib/model/email-record-routes.d.ts +22 -0
- package/src/lib/model/email-record-routes.js +75 -0
- package/src/lib/model/email-record-routes.js.map +1 -0
- package/src/lib/model/email-topic-catalog.d.ts +36 -0
- package/src/lib/model/email-topic-catalog.js +36 -0
- package/src/lib/model/email-topic-catalog.js.map +1 -0
- package/src/lib/model/index.d.ts +0 -7
- package/src/lib/model/index.js +0 -6
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/template-provenance.d.ts +1 -1
- package/src/lib/model/template-provenance.js +1 -1
- package/src/lib/model/template-provenance.js.map +1 -1
- package/src/lib/plugin.js +5 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server-console.js +6 -2
- package/src/lib/server-console.js.map +1 -1
- package/src/lib/server-email-drafts.js +2 -2
- package/src/lib/server-email-drafts.js.map +1 -1
- package/src/lib/server-list-gate.js +35 -10
- package/src/lib/server-list-gate.js.map +1 -1
- package/src/lib/server-list-import.js +4 -3
- package/src/lib/server-list-import.js.map +1 -1
- package/src/lib/server-topic-index.d.ts +52 -0
- package/src/lib/server-topic-index.js +92 -0
- package/src/lib/server-topic-index.js.map +1 -0
- package/src/lib/server.js +11 -11
- package/src/lib/server.js.map +1 -1
- package/src/lib/unsubscribe-link.d.ts +1 -1
- package/src/lib/unsubscribe-link.js +2 -2
- package/src/lib/unsubscribe-link.js.map +1 -1
|
@@ -58,9 +58,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
58
58
|
* nobody has opened.
|
|
59
59
|
*/ import { CONTACT_LIFECYCLE_STAGES, CONTACT_LIFECYCLE_STAGE_LABELS, CONTACT_SOURCE_LABELS, DYNAMIC_LIST_CUSTOM_OPS, normalizeDynamicListRule } from "@aglyn/aglyn";
|
|
60
60
|
import { Autocomplete, Box, Button, Divider, MenuItem, Stack, TextField, Typography } from "@mui/material";
|
|
61
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
62
|
-
import {
|
|
63
|
-
import
|
|
61
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
62
|
+
import { useSiteContainerOptions, useOrgMemberOptions } from "@aglyn/tenant-feature-instance";
|
|
63
|
+
import ContainerPicker from "@aglyn/tenant-feature-instance/components/container-picker";
|
|
64
|
+
import { useContentStable } from "@aglyn/shared-ui-jsx/hooks/use-content-stable";
|
|
64
65
|
import { useOrgCompanyOptions } from "../hooks/use-org-company-options.js";
|
|
65
66
|
import { useOrgContactFields } from "../hooks/use-org-contact-fields.js";
|
|
66
67
|
import { useOrgContactSegments } from "../hooks/use-org-contact-segments.js";
|
|
@@ -585,7 +586,307 @@ export const EMPTY_RULE_DRAFT = {
|
|
|
585
586
|
negate: true
|
|
586
587
|
} : {})));
|
|
587
588
|
}
|
|
588
|
-
|
|
589
|
+
const companyLabel = (option)=>option.label;
|
|
590
|
+
const sameCompany = (option, chosen)=>option.id === chosen.id;
|
|
591
|
+
// The query already narrowed the hits; a second, client-side filter on the
|
|
592
|
+
// label would hide a match whose stored name differs from its search key.
|
|
593
|
+
const keepHits = (options)=>options;
|
|
594
|
+
const COMPANY_FIELD_SX = {
|
|
595
|
+
minWidth: 260,
|
|
596
|
+
flexGrow: 1,
|
|
597
|
+
maxWidth: 420
|
|
598
|
+
};
|
|
599
|
+
/**
|
|
600
|
+
* The rule's companies, as chips beside a search — MEMOIZED, and every prop it
|
|
601
|
+
* takes is stable while the chosen companies and the search are (AGL-3423).
|
|
602
|
+
*
|
|
603
|
+
* A multiple Autocomplete hands its input a new chip array as
|
|
604
|
+
* `startAdornment` on every render, and MUI's `InputBase` copies that into
|
|
605
|
+
* its `FormControl` from a passive effect: a state update left pending after
|
|
606
|
+
* every commit the field takes part in. React 19 counts each such commit as a
|
|
607
|
+
* nested update, and keystrokes delivered back to back commit one after
|
|
608
|
+
* another with nothing to clear the count, so the fifty-first throws
|
|
609
|
+
* "Maximum update depth exceeded" (#185). Every other field of the rule
|
|
610
|
+
* writes the same draft, so each keystroke in them re-rendered this one;
|
|
611
|
+
* kept out of those renders, it commits only when it changes.
|
|
612
|
+
*/ const RuleCompaniesField = /*#__PURE__*/ memo(function RuleCompaniesField(props) {
|
|
613
|
+
const { onSearch, onChange } = props;
|
|
614
|
+
return /*#__PURE__*/ _jsx(Autocomplete, {
|
|
615
|
+
multiple: true,
|
|
616
|
+
size: "small",
|
|
617
|
+
options: props.hits,
|
|
618
|
+
value: props.value,
|
|
619
|
+
getOptionLabel: companyLabel,
|
|
620
|
+
isOptionEqualToValue: sameCompany,
|
|
621
|
+
filterOptions: keepHits,
|
|
622
|
+
inputValue: props.search,
|
|
623
|
+
onInputChange: (_event, value, reason)=>{
|
|
624
|
+
if (reason !== 'reset') onSearch(value);
|
|
625
|
+
},
|
|
626
|
+
onChange: (_event, value)=>onChange(value.map((option)=>option.id)),
|
|
627
|
+
loading: props.searching,
|
|
628
|
+
noOptionsText: props.search ? 'No company by that name' : 'Type to search companies',
|
|
629
|
+
renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
|
|
630
|
+
label: "At company",
|
|
631
|
+
helperText: "Any of these companies"
|
|
632
|
+
})),
|
|
633
|
+
sx: COMPANY_FIELD_SX
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
/** A text field that redraws only when what it shows changes (AGL-3423). */ const MemoTextField = /*#__PURE__*/ memo(TextField);
|
|
637
|
+
const MemoContainerPicker = /*#__PURE__*/ memo(ContainerPicker);
|
|
638
|
+
/**
|
|
639
|
+
* One `{ minWidth }` object per width, so a memoized field's `sx` is the same
|
|
640
|
+
* object on every render.
|
|
641
|
+
*/ const MIN_WIDTHS = new Map();
|
|
642
|
+
const atLeast = (minWidth)=>{
|
|
643
|
+
let sx = MIN_WIDTHS.get(minWidth);
|
|
644
|
+
if (!sx) {
|
|
645
|
+
sx = {
|
|
646
|
+
minWidth
|
|
647
|
+
};
|
|
648
|
+
MIN_WIDTHS.set(minWidth, sx);
|
|
649
|
+
}
|
|
650
|
+
return sx;
|
|
651
|
+
};
|
|
652
|
+
const WRAP = {
|
|
653
|
+
flexWrap: 'wrap'
|
|
654
|
+
};
|
|
655
|
+
const WRAP_TOP = {
|
|
656
|
+
flexWrap: 'wrap',
|
|
657
|
+
alignItems: 'flex-start'
|
|
658
|
+
};
|
|
659
|
+
const SUMMARY_BOX = {
|
|
660
|
+
border: 1,
|
|
661
|
+
borderColor: 'divider',
|
|
662
|
+
borderRadius: 1,
|
|
663
|
+
p: 1.5
|
|
664
|
+
};
|
|
665
|
+
const CAMPAIGN_BOX = {
|
|
666
|
+
minWidth: 240,
|
|
667
|
+
flexGrow: 1,
|
|
668
|
+
maxWidth: 360
|
|
669
|
+
};
|
|
670
|
+
const CAPTION_BLOCK = {
|
|
671
|
+
display: 'block',
|
|
672
|
+
mt: 0.5
|
|
673
|
+
};
|
|
674
|
+
const MULTIPLE = {
|
|
675
|
+
select: {
|
|
676
|
+
multiple: true
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const SHRUNK_LABEL = {
|
|
680
|
+
inputLabel: {
|
|
681
|
+
shrink: true
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
const STAGES_SELECT = {
|
|
685
|
+
select: {
|
|
686
|
+
multiple: true,
|
|
687
|
+
renderValue: (selected)=>selected.map((stage)=>{
|
|
688
|
+
var _CONTACT_LIFECYCLE_STAGE_LABELS_stage;
|
|
689
|
+
return (_CONTACT_LIFECYCLE_STAGE_LABELS_stage = CONTACT_LIFECYCLE_STAGE_LABELS[stage]) != null ? _CONTACT_LIFECYCLE_STAGE_LABELS_stage : stage;
|
|
690
|
+
}).join(', ')
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
/*
|
|
694
|
+
* The fixed menus, built once. A select's options are its children, and a
|
|
695
|
+
* child list built during render is a new prop on every render.
|
|
696
|
+
*/ const SOURCE_MENU = Object.entries(SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
697
|
+
value: value,
|
|
698
|
+
children: label
|
|
699
|
+
}, value));
|
|
700
|
+
const MATCH_MENU = [
|
|
701
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
702
|
+
value: "all",
|
|
703
|
+
children: 'All of the filters below'
|
|
704
|
+
}, "all"),
|
|
705
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
706
|
+
value: "any",
|
|
707
|
+
children: 'Any one of the filters below'
|
|
708
|
+
}, "any"),
|
|
709
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
710
|
+
value: "none",
|
|
711
|
+
children: 'Nobody matching all of them'
|
|
712
|
+
}, "none")
|
|
713
|
+
];
|
|
714
|
+
const CAPTURE_MENU = Object.entries(CONTACT_SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
715
|
+
value: value,
|
|
716
|
+
children: label
|
|
717
|
+
}, value));
|
|
718
|
+
const STAGE_MENU = CONTACT_LIFECYCLE_STAGES.map((stage)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
719
|
+
value: stage,
|
|
720
|
+
children: CONTACT_LIFECYCLE_STAGE_LABELS[stage]
|
|
721
|
+
}, stage));
|
|
722
|
+
const OP_MENU = DYNAMIC_LIST_CUSTOM_OPS.map((op)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
723
|
+
value: op,
|
|
724
|
+
children: CUSTOM_OP_LABELS[op]
|
|
725
|
+
}, op));
|
|
726
|
+
const CHECKED_MENU = [
|
|
727
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
728
|
+
value: "true",
|
|
729
|
+
children: 'Checked'
|
|
730
|
+
}, "true"),
|
|
731
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
732
|
+
value: "false",
|
|
733
|
+
children: 'Not checked'
|
|
734
|
+
}, "false")
|
|
735
|
+
];
|
|
736
|
+
/** A multi-select hands back a string when only one option is chosen. */ const asArray = (value)=>typeof value === 'string' ? [
|
|
737
|
+
value
|
|
738
|
+
] : value != null ? value : [];
|
|
739
|
+
/** Saved segments, views and audiences as menu items: a name, else the id. */ const menuOf = (rows)=>rows.map((row)=>{
|
|
740
|
+
var _row_name;
|
|
741
|
+
return /*#__PURE__*/ _jsx(MenuItem, {
|
|
742
|
+
value: row.$id,
|
|
743
|
+
children: (_row_name = row.name) != null ? _row_name : row.$id
|
|
744
|
+
}, row.$id);
|
|
745
|
+
});
|
|
746
|
+
/** The first choice of a saved segment or view: none. No stored id is empty. */ const NONE_ITEM = /*#__PURE__*/ _jsx(MenuItem, {
|
|
747
|
+
value: "",
|
|
748
|
+
children: 'None'
|
|
749
|
+
}, "");
|
|
750
|
+
/**
|
|
751
|
+
* One field condition: the field, how it compares, and the value — typed by
|
|
752
|
+
* the field's definition, so a number field hands the matcher a number and a
|
|
753
|
+
* choice field offers its own options, and a presence test asks for no value
|
|
754
|
+
* at all.
|
|
755
|
+
*
|
|
756
|
+
* Memoized, with handlers that write through the row's index, so a keystroke
|
|
757
|
+
* in one condition's value redraws that value and nothing else (AGL-3423).
|
|
758
|
+
*/ const CustomClauseRow = /*#__PURE__*/ memo(function CustomClauseRow(props) {
|
|
759
|
+
var _clause_value, _definition_options;
|
|
760
|
+
const { index, clause, definition, fieldMenu, onUpdate, onRemove } = props;
|
|
761
|
+
const valueText = String((_clause_value = clause.value) != null ? _clause_value : '');
|
|
762
|
+
const setKey = useCallback((event)=>onUpdate(index, {
|
|
763
|
+
key: event.target.value,
|
|
764
|
+
value: ''
|
|
765
|
+
}), [
|
|
766
|
+
index,
|
|
767
|
+
onUpdate
|
|
768
|
+
]);
|
|
769
|
+
const setOp = useCallback((event)=>onUpdate(index, {
|
|
770
|
+
op: event.target.value
|
|
771
|
+
}), [
|
|
772
|
+
index,
|
|
773
|
+
onUpdate
|
|
774
|
+
]);
|
|
775
|
+
const setValue = useCallback((event)=>onUpdate(index, {
|
|
776
|
+
value: event.target.value
|
|
777
|
+
}), [
|
|
778
|
+
index,
|
|
779
|
+
onUpdate
|
|
780
|
+
]);
|
|
781
|
+
const setChecked = useCallback((event)=>onUpdate(index, {
|
|
782
|
+
value: event.target.value === 'true'
|
|
783
|
+
}), [
|
|
784
|
+
index,
|
|
785
|
+
onUpdate
|
|
786
|
+
]);
|
|
787
|
+
const setNumber = useCallback((event)=>{
|
|
788
|
+
// A number field stores a NUMBER, so the matcher compares 10 with 9
|
|
789
|
+
// and not "10" with "9"; half-typed text stays text until it parses,
|
|
790
|
+
// rather than becoming NaN.
|
|
791
|
+
const text = event.target.value;
|
|
792
|
+
const parsed = Number(text);
|
|
793
|
+
onUpdate(index, {
|
|
794
|
+
value: text.trim() !== '' && Number.isFinite(parsed) ? parsed : text
|
|
795
|
+
});
|
|
796
|
+
}, [
|
|
797
|
+
index,
|
|
798
|
+
onUpdate
|
|
799
|
+
]);
|
|
800
|
+
const remove = useCallback(()=>onRemove(index), [
|
|
801
|
+
index,
|
|
802
|
+
onRemove
|
|
803
|
+
]);
|
|
804
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
805
|
+
direction: "row",
|
|
806
|
+
spacing: 1,
|
|
807
|
+
useFlexGap: true,
|
|
808
|
+
sx: WRAP_TOP,
|
|
809
|
+
children: [
|
|
810
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
811
|
+
select: true,
|
|
812
|
+
size: "small",
|
|
813
|
+
label: "Field",
|
|
814
|
+
value: clause.key,
|
|
815
|
+
onChange: setKey,
|
|
816
|
+
sx: atLeast(180),
|
|
817
|
+
children: fieldMenu
|
|
818
|
+
}),
|
|
819
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
820
|
+
select: true,
|
|
821
|
+
size: "small",
|
|
822
|
+
label: "Condition",
|
|
823
|
+
value: clause.op,
|
|
824
|
+
onChange: setOp,
|
|
825
|
+
sx: atLeast(160),
|
|
826
|
+
children: OP_MENU
|
|
827
|
+
}),
|
|
828
|
+
PRESENCE_OPS.has(clause.op) ? null : (definition == null ? void 0 : definition.type) === 'select' ? /*#__PURE__*/ _jsx(MemoTextField, {
|
|
829
|
+
select: true,
|
|
830
|
+
size: "small",
|
|
831
|
+
label: "Value",
|
|
832
|
+
value: valueText,
|
|
833
|
+
onChange: setValue,
|
|
834
|
+
sx: atLeast(180),
|
|
835
|
+
children: ((_definition_options = definition.options) != null ? _definition_options : []).map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
836
|
+
value: option,
|
|
837
|
+
children: option
|
|
838
|
+
}, option))
|
|
839
|
+
}) : (definition == null ? void 0 : definition.type) === 'checkbox' ? /*#__PURE__*/ _jsx(MemoTextField, {
|
|
840
|
+
select: true,
|
|
841
|
+
size: "small",
|
|
842
|
+
label: "Value",
|
|
843
|
+
value: clause.value === true ? 'true' : clause.value === false ? 'false' : '',
|
|
844
|
+
onChange: setChecked,
|
|
845
|
+
sx: atLeast(140),
|
|
846
|
+
children: CHECKED_MENU
|
|
847
|
+
}) : (definition == null ? void 0 : definition.type) === 'number' ? /*#__PURE__*/ _jsx(MemoTextField, {
|
|
848
|
+
type: "number",
|
|
849
|
+
size: "small",
|
|
850
|
+
label: "Value",
|
|
851
|
+
value: valueText,
|
|
852
|
+
onChange: setNumber,
|
|
853
|
+
sx: atLeast(140)
|
|
854
|
+
}) : /*#__PURE__*/ _jsx(MemoTextField, {
|
|
855
|
+
type: (definition == null ? void 0 : definition.type) === 'date' ? 'date' : 'text',
|
|
856
|
+
size: "small",
|
|
857
|
+
label: "Value",
|
|
858
|
+
value: valueText,
|
|
859
|
+
onChange: setValue,
|
|
860
|
+
slotProps: (definition == null ? void 0 : definition.type) === 'date' ? SHRUNK_LABEL : undefined,
|
|
861
|
+
sx: atLeast(180)
|
|
862
|
+
}),
|
|
863
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
864
|
+
size: "small",
|
|
865
|
+
onClick: remove,
|
|
866
|
+
children: 'Remove'
|
|
867
|
+
})
|
|
868
|
+
]
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
/**
|
|
872
|
+
* The rule's controls.
|
|
873
|
+
*
|
|
874
|
+
* ## A keystroke redraws the field it lands in (AGL-3423)
|
|
875
|
+
*
|
|
876
|
+
* Every control writes the same draft, and the draft is the parent's state,
|
|
877
|
+
* so each keystroke renders this component again. Drawn inline, that redrew
|
|
878
|
+
* every control on the form — 27 inputs and some thousand components for one
|
|
879
|
+
* letter — and a keystroke that costs that much lets typed input queue, which
|
|
880
|
+
* is what turns a chip field's per-commit update into React #185. So every
|
|
881
|
+
* control is memoized and handed only props that keep their identity while
|
|
882
|
+
* what it shows is unchanged: handlers that write through the latest draft,
|
|
883
|
+
* menus built once or from their own data, and `sx` objects that are
|
|
884
|
+
* constants. The sentences above the controls are the one part that is meant
|
|
885
|
+
* to change with every keystroke.
|
|
886
|
+
*
|
|
887
|
+
* The component is memoized as a whole as well, so a keystroke in the page's
|
|
888
|
+
* own fields — the list's name — does not draw the rule at all.
|
|
889
|
+
*/ export const DynamicListRuleFields = /*#__PURE__*/ memo(function DynamicListRuleFields(props) {
|
|
589
890
|
var _team_error;
|
|
590
891
|
const { scope, hostId, draft, onChange, listId } = props;
|
|
591
892
|
const segmentDocs = useOrgContactSegments(scope);
|
|
@@ -598,7 +899,7 @@ export function DynamicListRuleFields(props) {
|
|
|
598
899
|
* picker beside fields a reader came for; here the picker IS one of the
|
|
599
900
|
* fields the reader came for, and the same reasoning that opens the segment
|
|
600
901
|
* listen opens this one.
|
|
601
|
-
*/ const siteCampaigns =
|
|
902
|
+
*/ const siteCampaigns = useSiteContainerOptions('campaign', hostId, {
|
|
602
903
|
enabled: true
|
|
603
904
|
});
|
|
604
905
|
/*
|
|
@@ -670,6 +971,12 @@ export function DynamicListRuleFields(props) {
|
|
|
670
971
|
listDocs,
|
|
671
972
|
listId
|
|
672
973
|
]);
|
|
974
|
+
const memberNames = useMemo(()=>Object.fromEntries(team.options.map((option)=>[
|
|
975
|
+
option.uid,
|
|
976
|
+
option.label
|
|
977
|
+
])), [
|
|
978
|
+
team.options
|
|
979
|
+
]);
|
|
673
980
|
/** Ids the pickers can show, so a clause names an audience rather than a uid. */ const names = useMemo(()=>({
|
|
674
981
|
lists: Object.fromEntries(listDocs.map((row)=>{
|
|
675
982
|
var _row_name;
|
|
@@ -693,10 +1000,7 @@ export function DynamicListRuleFields(props) {
|
|
|
693
1000
|
option.value,
|
|
694
1001
|
option.label
|
|
695
1002
|
])),
|
|
696
|
-
members:
|
|
697
|
-
option.uid,
|
|
698
|
-
option.label
|
|
699
|
-
])),
|
|
1003
|
+
members: memberNames,
|
|
700
1004
|
companies: companies.names,
|
|
701
1005
|
fields: Object.fromEntries(fieldDefinitions.fields.map((field)=>[
|
|
702
1006
|
field.key,
|
|
@@ -707,7 +1011,7 @@ export function DynamicListRuleFields(props) {
|
|
|
707
1011
|
segments,
|
|
708
1012
|
crmViews,
|
|
709
1013
|
siteCampaigns.options,
|
|
710
|
-
|
|
1014
|
+
memberNames,
|
|
711
1015
|
companies.names,
|
|
712
1016
|
fieldDefinitions.fields
|
|
713
1017
|
]);
|
|
@@ -732,7 +1036,11 @@ export function DynamicListRuleFields(props) {
|
|
|
732
1036
|
team.options,
|
|
733
1037
|
draft.ownerUids
|
|
734
1038
|
]);
|
|
735
|
-
|
|
1039
|
+
/*
|
|
1040
|
+
* Held by content: the options are read from the clauses' KEYS, and a value
|
|
1041
|
+
* typed into a clause is a new clause list with the same keys — which would
|
|
1042
|
+
* otherwise redraw every row's field picker on each keystroke (AGL-3423).
|
|
1043
|
+
*/ const fieldOptions = useContentStable(useMemo(()=>{
|
|
736
1044
|
const known = new Set(fieldDefinitions.fields.map((field)=>field.key));
|
|
737
1045
|
return [
|
|
738
1046
|
...draft.custom.filter((clause)=>!known.has(clause.key)).map((clause)=>({
|
|
@@ -747,11 +1055,101 @@ export function DynamicListRuleFields(props) {
|
|
|
747
1055
|
}, [
|
|
748
1056
|
fieldDefinitions.fields,
|
|
749
1057
|
draft.custom
|
|
1058
|
+
]));
|
|
1059
|
+
/* The menus that come from data, rebuilt only when their data changes. */ const segmentMenu = useMemo(()=>[
|
|
1060
|
+
NONE_ITEM,
|
|
1061
|
+
...menuOf(segments)
|
|
1062
|
+
], [
|
|
1063
|
+
segments
|
|
750
1064
|
]);
|
|
751
|
-
|
|
752
|
-
|
|
1065
|
+
const viewMenu = useMemo(()=>[
|
|
1066
|
+
NONE_ITEM,
|
|
1067
|
+
...menuOf(crmViews)
|
|
1068
|
+
], [
|
|
1069
|
+
crmViews
|
|
1070
|
+
]);
|
|
1071
|
+
const listMenu = useMemo(()=>menuOf(lists), [
|
|
1072
|
+
lists
|
|
1073
|
+
]);
|
|
1074
|
+
const ownerMenu = useMemo(()=>ownerOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1075
|
+
value: option.uid,
|
|
1076
|
+
children: option.label
|
|
1077
|
+
}, option.uid)), [
|
|
1078
|
+
ownerOptions
|
|
1079
|
+
]);
|
|
1080
|
+
const ownerSelect = useMemo(()=>({
|
|
1081
|
+
select: {
|
|
1082
|
+
multiple: true,
|
|
1083
|
+
renderValue: (selected)=>selected.map((uid)=>named(uid, memberNames)).join(', ')
|
|
1084
|
+
}
|
|
1085
|
+
}), [
|
|
1086
|
+
memberNames
|
|
1087
|
+
]);
|
|
1088
|
+
const fieldMenu = useMemo(()=>fieldOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1089
|
+
value: option.key,
|
|
1090
|
+
children: option.label
|
|
1091
|
+
}, option.key)), [
|
|
1092
|
+
fieldOptions
|
|
1093
|
+
]);
|
|
1094
|
+
/*
|
|
1095
|
+
* Every control writes `{ ...draft, [key]: value }` from the last rendered
|
|
1096
|
+
* draft, through handlers that keep their identity from render to render —
|
|
1097
|
+
* a handler rebuilt per render is a changed prop, and a changed prop redraws
|
|
1098
|
+
* the field it is on.
|
|
1099
|
+
*/ const latest = useRef({
|
|
1100
|
+
draft,
|
|
1101
|
+
onChange
|
|
1102
|
+
});
|
|
1103
|
+
latest.current = {
|
|
1104
|
+
draft,
|
|
1105
|
+
onChange
|
|
1106
|
+
};
|
|
1107
|
+
const set = useCallback((key, value)=>latest.current.onChange(_extends({}, latest.current.draft, {
|
|
753
1108
|
[key]: value
|
|
754
|
-
}));
|
|
1109
|
+
})), []);
|
|
1110
|
+
const on = useMemo(()=>{
|
|
1111
|
+
const typed = (key)=>(event)=>set(key, event.target.value);
|
|
1112
|
+
return {
|
|
1113
|
+
sources: (event)=>set('sources', asArray(event.target.value)),
|
|
1114
|
+
match: (event)=>set('match', event.target.value),
|
|
1115
|
+
createdAfter: typed('createdAfter'),
|
|
1116
|
+
createdBefore: typed('createdBefore'),
|
|
1117
|
+
formNames: typed('formNames'),
|
|
1118
|
+
campaignIds: (next)=>set('campaignIds', next),
|
|
1119
|
+
segmentId: typed('segmentId'),
|
|
1120
|
+
viewId: typed('viewId'),
|
|
1121
|
+
tags: typed('tags'),
|
|
1122
|
+
captureSources: (event)=>set('captureSources', asArray(event.target.value)),
|
|
1123
|
+
ownerUids: (event)=>set('ownerUids', asArray(event.target.value)),
|
|
1124
|
+
lifecycleStages: (event)=>set('lifecycleStages', asArray(event.target.value)),
|
|
1125
|
+
companyIds: (companyIds)=>set('companyIds', companyIds),
|
|
1126
|
+
engagedWithinDays: typed('engagedWithinDays'),
|
|
1127
|
+
ordersCountAtLeast: typed('ordersCountAtLeast'),
|
|
1128
|
+
ltvAtLeast: typed('ltvAtLeast'),
|
|
1129
|
+
lastPurchaseWithinDays: typed('lastPurchaseWithinDays'),
|
|
1130
|
+
noPurchaseForDays: typed('noPurchaseForDays'),
|
|
1131
|
+
openedWithinDays: typed('openedWithinDays'),
|
|
1132
|
+
clickedWithinDays: typed('clickedWithinDays'),
|
|
1133
|
+
notOpenedForDays: typed('notOpenedForDays'),
|
|
1134
|
+
notClickedForDays: typed('notClickedForDays'),
|
|
1135
|
+
inListIds: (event)=>set('inListIds', asArray(event.target.value)),
|
|
1136
|
+
notInListIds: (event)=>set('notInListIds', asArray(event.target.value))
|
|
1137
|
+
};
|
|
1138
|
+
}, [
|
|
1139
|
+
set
|
|
1140
|
+
]);
|
|
1141
|
+
const companyIds = draft.companyIds;
|
|
1142
|
+
const companyNames = companies.names;
|
|
1143
|
+
const chosenCompanies = useMemo(()=>companyIds.map((id)=>{
|
|
1144
|
+
var _companyNames_id;
|
|
1145
|
+
return {
|
|
1146
|
+
id,
|
|
1147
|
+
label: (_companyNames_id = companyNames[id]) != null ? _companyNames_id : id
|
|
1148
|
+
};
|
|
1149
|
+
}), [
|
|
1150
|
+
companyIds,
|
|
1151
|
+
companyNames
|
|
1152
|
+
]);
|
|
755
1153
|
/*
|
|
756
1154
|
* THE CUSTOM-FIELD ROWS. A new row names the first definition with an
|
|
757
1155
|
* empty value, so it is on screen and NOT yet a filter — `draftDimensions`
|
|
@@ -760,39 +1158,39 @@ export function DynamicListRuleFields(props) {
|
|
|
760
1158
|
* a value typed for a number field is not a value for the select that
|
|
761
1159
|
* replaced it; changing the operator to a presence test removes it, so the
|
|
762
1160
|
* stored clause carries no value the sentence does not read.
|
|
763
|
-
*/ const addClause = ()=>{
|
|
1161
|
+
*/ const addClause = useCallback(()=>{
|
|
764
1162
|
const first = fieldOptions[0];
|
|
765
1163
|
if (!first) return;
|
|
766
1164
|
set('custom', [
|
|
767
|
-
...draft.custom,
|
|
1165
|
+
...latest.current.draft.custom,
|
|
768
1166
|
{
|
|
769
1167
|
key: first.key,
|
|
770
1168
|
op: 'eq',
|
|
771
1169
|
value: ''
|
|
772
1170
|
}
|
|
773
1171
|
]);
|
|
774
|
-
}
|
|
775
|
-
|
|
1172
|
+
}, [
|
|
1173
|
+
fieldOptions,
|
|
1174
|
+
set
|
|
1175
|
+
]);
|
|
1176
|
+
const updateClause = useCallback((index, patch)=>set('custom', latest.current.draft.custom.map((clause, at)=>{
|
|
776
1177
|
if (at !== index) return clause;
|
|
777
1178
|
const next = _extends({}, clause, patch);
|
|
778
1179
|
if (PRESENCE_OPS.has(next.op)) delete next.value;
|
|
779
1180
|
return next;
|
|
780
|
-
}))
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1181
|
+
})), [
|
|
1182
|
+
set
|
|
1183
|
+
]);
|
|
1184
|
+
const removeClause = useCallback((index)=>set('custom', latest.current.draft.custom.filter((_, at)=>at !== index)), [
|
|
1185
|
+
set
|
|
1186
|
+
]);
|
|
1187
|
+
/** The definition a clause's key names, when the org still has one. */ const definitionFor = (key)=>fieldDefinitions.fields.find((field)=>field.key === key);
|
|
785
1188
|
const contactsOnly = draft.sources.includes('contacts');
|
|
786
1189
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
787
1190
|
spacing: 1.5,
|
|
788
1191
|
children: [
|
|
789
1192
|
/*#__PURE__*/ _jsxs(Box, {
|
|
790
|
-
sx:
|
|
791
|
-
border: 1,
|
|
792
|
-
borderColor: 'divider',
|
|
793
|
-
borderRadius: 1,
|
|
794
|
-
p: 1.5
|
|
795
|
-
},
|
|
1193
|
+
sx: SUMMARY_BOX,
|
|
796
1194
|
children: [
|
|
797
1195
|
/*#__PURE__*/ _jsx(Typography, {
|
|
798
1196
|
variant: "overline",
|
|
@@ -809,105 +1207,62 @@ export function DynamicListRuleFields(props) {
|
|
|
809
1207
|
direction: "row",
|
|
810
1208
|
spacing: 1,
|
|
811
1209
|
useFlexGap: true,
|
|
812
|
-
sx:
|
|
813
|
-
flexWrap: 'wrap'
|
|
814
|
-
},
|
|
1210
|
+
sx: WRAP,
|
|
815
1211
|
children: [
|
|
816
|
-
/*#__PURE__*/ _jsx(
|
|
1212
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
817
1213
|
select: true,
|
|
818
1214
|
size: "small",
|
|
819
1215
|
label: "People from",
|
|
820
1216
|
value: draft.sources,
|
|
821
|
-
onChange:
|
|
822
|
-
slotProps:
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
}
|
|
826
|
-
},
|
|
827
|
-
sx: {
|
|
828
|
-
minWidth: 220
|
|
829
|
-
},
|
|
830
|
-
children: Object.entries(SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
831
|
-
value: value,
|
|
832
|
-
children: label
|
|
833
|
-
}, value))
|
|
1217
|
+
onChange: on.sources,
|
|
1218
|
+
slotProps: MULTIPLE,
|
|
1219
|
+
sx: atLeast(220),
|
|
1220
|
+
children: SOURCE_MENU
|
|
834
1221
|
}),
|
|
835
|
-
/*#__PURE__*/
|
|
1222
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
836
1223
|
select: true,
|
|
837
1224
|
size: "small",
|
|
838
1225
|
label: "Match",
|
|
839
1226
|
value: draft.match,
|
|
840
|
-
onChange:
|
|
1227
|
+
onChange: on.match,
|
|
841
1228
|
helperText: "A saved segment always applies",
|
|
842
|
-
sx:
|
|
843
|
-
|
|
844
|
-
},
|
|
845
|
-
children: [
|
|
846
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
847
|
-
value: "all",
|
|
848
|
-
children: 'All of the filters below'
|
|
849
|
-
}),
|
|
850
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
851
|
-
value: "any",
|
|
852
|
-
children: 'Any one of the filters below'
|
|
853
|
-
}),
|
|
854
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
855
|
-
value: "none",
|
|
856
|
-
children: 'Nobody matching all of them'
|
|
857
|
-
})
|
|
858
|
-
]
|
|
1229
|
+
sx: atLeast(210),
|
|
1230
|
+
children: MATCH_MENU
|
|
859
1231
|
}),
|
|
860
|
-
/*#__PURE__*/ _jsx(
|
|
1232
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
861
1233
|
type: "date",
|
|
862
1234
|
size: "small",
|
|
863
1235
|
label: "Created after",
|
|
864
1236
|
value: draft.createdAfter,
|
|
865
|
-
onChange:
|
|
866
|
-
slotProps:
|
|
867
|
-
|
|
868
|
-
shrink: true
|
|
869
|
-
}
|
|
870
|
-
},
|
|
871
|
-
sx: {
|
|
872
|
-
minWidth: 170
|
|
873
|
-
}
|
|
1237
|
+
onChange: on.createdAfter,
|
|
1238
|
+
slotProps: SHRUNK_LABEL,
|
|
1239
|
+
sx: atLeast(170)
|
|
874
1240
|
}),
|
|
875
|
-
/*#__PURE__*/ _jsx(
|
|
1241
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
876
1242
|
type: "date",
|
|
877
1243
|
size: "small",
|
|
878
1244
|
label: "Created before",
|
|
879
1245
|
value: draft.createdBefore,
|
|
880
|
-
onChange:
|
|
881
|
-
slotProps:
|
|
882
|
-
|
|
883
|
-
shrink: true
|
|
884
|
-
}
|
|
885
|
-
},
|
|
886
|
-
sx: {
|
|
887
|
-
minWidth: 170
|
|
888
|
-
}
|
|
1246
|
+
onChange: on.createdBefore,
|
|
1247
|
+
slotProps: SHRUNK_LABEL,
|
|
1248
|
+
sx: atLeast(170)
|
|
889
1249
|
}),
|
|
890
|
-
/*#__PURE__*/ _jsx(
|
|
1250
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
891
1251
|
size: "small",
|
|
892
1252
|
label: "Submitted form",
|
|
893
1253
|
placeholder: "Contact us",
|
|
894
1254
|
helperText: "Form submissions only",
|
|
895
1255
|
value: draft.formNames,
|
|
896
|
-
onChange:
|
|
897
|
-
sx:
|
|
898
|
-
minWidth: 180
|
|
899
|
-
}
|
|
1256
|
+
onChange: on.formNames,
|
|
1257
|
+
sx: atLeast(180)
|
|
900
1258
|
}),
|
|
901
1259
|
/*#__PURE__*/ _jsx(Box, {
|
|
902
|
-
sx:
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
maxWidth: 360
|
|
906
|
-
},
|
|
907
|
-
children: /*#__PURE__*/ _jsx(CampaignPicker, {
|
|
1260
|
+
sx: CAMPAIGN_BOX,
|
|
1261
|
+
children: /*#__PURE__*/ _jsx(MemoContainerPicker, {
|
|
1262
|
+
kind: "campaign",
|
|
908
1263
|
options: siteCampaigns.options,
|
|
909
1264
|
value: draft.campaignIds,
|
|
910
|
-
onChange:
|
|
1265
|
+
onChange: on.campaignIds,
|
|
911
1266
|
label: "In campaign",
|
|
912
1267
|
helperText: "Contacts and form submissions filed under any campaign picked. Being in a campaign is not consent to be emailed.",
|
|
913
1268
|
empty: siteCampaigns.ready && !siteCampaigns.options.length,
|
|
@@ -931,84 +1286,46 @@ export function DynamicListRuleFields(props) {
|
|
|
931
1286
|
direction: "row",
|
|
932
1287
|
spacing: 1,
|
|
933
1288
|
useFlexGap: true,
|
|
934
|
-
sx:
|
|
935
|
-
flexWrap: 'wrap'
|
|
936
|
-
},
|
|
1289
|
+
sx: WRAP,
|
|
937
1290
|
children: [
|
|
938
|
-
/*#__PURE__*/
|
|
1291
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
939
1292
|
select: true,
|
|
940
1293
|
size: "small",
|
|
941
1294
|
label: "Saved segment",
|
|
942
1295
|
value: draft.segmentId,
|
|
943
|
-
onChange:
|
|
1296
|
+
onChange: on.segmentId,
|
|
944
1297
|
helperText: "Reuses that segment's tags and sources",
|
|
945
|
-
sx:
|
|
946
|
-
|
|
947
|
-
},
|
|
948
|
-
children: [
|
|
949
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
950
|
-
value: "",
|
|
951
|
-
children: 'None'
|
|
952
|
-
}),
|
|
953
|
-
segments.map((segment)=>{
|
|
954
|
-
var _segment_name;
|
|
955
|
-
return /*#__PURE__*/ _jsx(MenuItem, {
|
|
956
|
-
value: segment.$id,
|
|
957
|
-
children: (_segment_name = segment.name) != null ? _segment_name : segment.$id
|
|
958
|
-
}, segment.$id);
|
|
959
|
-
})
|
|
960
|
-
]
|
|
1298
|
+
sx: atLeast(200),
|
|
1299
|
+
children: segmentMenu
|
|
961
1300
|
}),
|
|
962
|
-
/*#__PURE__*/
|
|
1301
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
963
1302
|
select: true,
|
|
964
1303
|
size: "small",
|
|
965
1304
|
label: "Saved view",
|
|
966
1305
|
value: draft.viewId,
|
|
967
|
-
onChange:
|
|
1306
|
+
onChange: on.viewId,
|
|
968
1307
|
helperText: "Reuses that Contacts view's filters",
|
|
969
|
-
sx:
|
|
970
|
-
|
|
971
|
-
},
|
|
972
|
-
children: [
|
|
973
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
974
|
-
value: "",
|
|
975
|
-
children: 'None'
|
|
976
|
-
}),
|
|
977
|
-
crmViews.map((view)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
978
|
-
value: view.$id,
|
|
979
|
-
children: view.name
|
|
980
|
-
}, view.$id))
|
|
981
|
-
]
|
|
1308
|
+
sx: atLeast(200),
|
|
1309
|
+
children: viewMenu
|
|
982
1310
|
}),
|
|
983
|
-
/*#__PURE__*/ _jsx(
|
|
1311
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
984
1312
|
size: "small",
|
|
985
1313
|
label: "Tagged",
|
|
986
1314
|
placeholder: "vip, wholesale",
|
|
987
1315
|
value: draft.tags,
|
|
988
|
-
onChange:
|
|
989
|
-
sx:
|
|
990
|
-
minWidth: 180
|
|
991
|
-
}
|
|
1316
|
+
onChange: on.tags,
|
|
1317
|
+
sx: atLeast(180)
|
|
992
1318
|
}),
|
|
993
|
-
/*#__PURE__*/ _jsx(
|
|
1319
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
994
1320
|
select: true,
|
|
995
1321
|
size: "small",
|
|
996
1322
|
label: "Captured by",
|
|
997
1323
|
value: draft.captureSources,
|
|
998
|
-
onChange:
|
|
999
|
-
slotProps:
|
|
1000
|
-
select: {
|
|
1001
|
-
multiple: true
|
|
1002
|
-
}
|
|
1003
|
-
},
|
|
1324
|
+
onChange: on.captureSources,
|
|
1325
|
+
slotProps: MULTIPLE,
|
|
1004
1326
|
helperText: "How the contact reached you",
|
|
1005
|
-
sx:
|
|
1006
|
-
|
|
1007
|
-
},
|
|
1008
|
-
children: Object.entries(CONTACT_SOURCE_LABELS).map(([value, label])=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1009
|
-
value: value,
|
|
1010
|
-
children: label
|
|
1011
|
-
}, value))
|
|
1327
|
+
sx: atLeast(200),
|
|
1328
|
+
children: CAPTURE_MENU
|
|
1012
1329
|
})
|
|
1013
1330
|
]
|
|
1014
1331
|
}),
|
|
@@ -1016,228 +1333,58 @@ export function DynamicListRuleFields(props) {
|
|
|
1016
1333
|
direction: "row",
|
|
1017
1334
|
spacing: 1,
|
|
1018
1335
|
useFlexGap: true,
|
|
1019
|
-
sx:
|
|
1020
|
-
flexWrap: 'wrap'
|
|
1021
|
-
},
|
|
1336
|
+
sx: WRAP,
|
|
1022
1337
|
children: [
|
|
1023
|
-
/*#__PURE__*/ _jsx(
|
|
1338
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1024
1339
|
select: true,
|
|
1025
1340
|
size: "small",
|
|
1026
1341
|
label: "Owned by",
|
|
1027
1342
|
value: draft.ownerUids,
|
|
1028
|
-
onChange:
|
|
1029
|
-
slotProps:
|
|
1030
|
-
select: {
|
|
1031
|
-
multiple: true,
|
|
1032
|
-
renderValue: (selected)=>selected.map((uid)=>named(uid, names.members)).join(', ')
|
|
1033
|
-
}
|
|
1034
|
-
},
|
|
1343
|
+
onChange: on.ownerUids,
|
|
1344
|
+
slotProps: ownerSelect,
|
|
1035
1345
|
error: Boolean(team.error),
|
|
1036
1346
|
helperText: (_team_error = team.error) != null ? _team_error : 'Any of these team members. Unowned contacts are left out.',
|
|
1037
|
-
sx:
|
|
1038
|
-
|
|
1039
|
-
},
|
|
1040
|
-
children: ownerOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1041
|
-
value: option.uid,
|
|
1042
|
-
children: option.label
|
|
1043
|
-
}, option.uid))
|
|
1347
|
+
sx: atLeast(220),
|
|
1348
|
+
children: ownerMenu
|
|
1044
1349
|
}),
|
|
1045
|
-
/*#__PURE__*/ _jsx(
|
|
1350
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1046
1351
|
select: true,
|
|
1047
1352
|
size: "small",
|
|
1048
1353
|
label: "Lifecycle stage",
|
|
1049
1354
|
value: draft.lifecycleStages,
|
|
1050
|
-
onChange:
|
|
1051
|
-
slotProps:
|
|
1052
|
-
select: {
|
|
1053
|
-
multiple: true,
|
|
1054
|
-
renderValue: (selected)=>selected.map((stage)=>{
|
|
1055
|
-
var _CONTACT_LIFECYCLE_STAGE_LABELS_stage;
|
|
1056
|
-
return (_CONTACT_LIFECYCLE_STAGE_LABELS_stage = CONTACT_LIFECYCLE_STAGE_LABELS[stage]) != null ? _CONTACT_LIFECYCLE_STAGE_LABELS_stage : stage;
|
|
1057
|
-
}).join(', ')
|
|
1058
|
-
}
|
|
1059
|
-
},
|
|
1355
|
+
onChange: on.lifecycleStages,
|
|
1356
|
+
slotProps: STAGES_SELECT,
|
|
1060
1357
|
helperText: "Any of these stages",
|
|
1061
|
-
sx:
|
|
1062
|
-
|
|
1063
|
-
},
|
|
1064
|
-
children: CONTACT_LIFECYCLE_STAGES.map((stage)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1065
|
-
value: stage,
|
|
1066
|
-
children: CONTACT_LIFECYCLE_STAGE_LABELS[stage]
|
|
1067
|
-
}, stage))
|
|
1358
|
+
sx: atLeast(200),
|
|
1359
|
+
children: STAGE_MENU
|
|
1068
1360
|
}),
|
|
1069
|
-
/*#__PURE__*/ _jsx(
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
id,
|
|
1077
|
-
label: (_companies_names_id = companies.names[id]) != null ? _companies_names_id : id
|
|
1078
|
-
};
|
|
1079
|
-
}),
|
|
1080
|
-
getOptionLabel: (option)=>option.label,
|
|
1081
|
-
isOptionEqualToValue: (option, chosen)=>option.id === chosen.id,
|
|
1082
|
-
// The query already narrowed the hits; a second, client-side
|
|
1083
|
-
// filter on the label would hide a match whose stored name differs
|
|
1084
|
-
// from its search key.
|
|
1085
|
-
filterOptions: (options)=>options,
|
|
1086
|
-
inputValue: companySearch,
|
|
1087
|
-
onInputChange: (_event, value, reason)=>{
|
|
1088
|
-
if (reason !== 'reset') setCompanySearch(value);
|
|
1089
|
-
},
|
|
1090
|
-
onChange: (_event, value)=>set('companyIds', value.map((option)=>option.id)),
|
|
1091
|
-
loading: companies.searching,
|
|
1092
|
-
noOptionsText: companySearch ? 'No company by that name' : 'Type to search companies',
|
|
1093
|
-
renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
|
|
1094
|
-
label: "At company",
|
|
1095
|
-
helperText: "Any of these companies"
|
|
1096
|
-
})),
|
|
1097
|
-
sx: {
|
|
1098
|
-
minWidth: 260,
|
|
1099
|
-
flexGrow: 1,
|
|
1100
|
-
maxWidth: 420
|
|
1101
|
-
}
|
|
1361
|
+
/*#__PURE__*/ _jsx(RuleCompaniesField, {
|
|
1362
|
+
hits: companies.hits,
|
|
1363
|
+
value: chosenCompanies,
|
|
1364
|
+
search: companySearch,
|
|
1365
|
+
searching: companies.searching,
|
|
1366
|
+
onSearch: setCompanySearch,
|
|
1367
|
+
onChange: on.companyIds
|
|
1102
1368
|
}),
|
|
1103
|
-
/*#__PURE__*/ _jsx(
|
|
1369
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1104
1370
|
type: "number",
|
|
1105
1371
|
size: "small",
|
|
1106
1372
|
label: "Engaged with a campaign within (days)",
|
|
1107
1373
|
helperText: "Opened or clicked one of your campaigns. Never engaged is left out.",
|
|
1108
1374
|
value: draft.engagedWithinDays,
|
|
1109
|
-
onChange:
|
|
1110
|
-
sx:
|
|
1111
|
-
minWidth: 280
|
|
1112
|
-
}
|
|
1375
|
+
onChange: on.engagedWithinDays,
|
|
1376
|
+
sx: atLeast(280)
|
|
1113
1377
|
})
|
|
1114
1378
|
]
|
|
1115
1379
|
}),
|
|
1116
|
-
draft.custom.map((clause, index)
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
sx: {
|
|
1125
|
-
flexWrap: 'wrap',
|
|
1126
|
-
alignItems: 'flex-start'
|
|
1127
|
-
},
|
|
1128
|
-
children: [
|
|
1129
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
1130
|
-
select: true,
|
|
1131
|
-
size: "small",
|
|
1132
|
-
label: "Field",
|
|
1133
|
-
value: clause.key,
|
|
1134
|
-
onChange: (event)=>updateClause(index, {
|
|
1135
|
-
key: event.target.value,
|
|
1136
|
-
value: ''
|
|
1137
|
-
}),
|
|
1138
|
-
sx: {
|
|
1139
|
-
minWidth: 180
|
|
1140
|
-
},
|
|
1141
|
-
children: fieldOptions.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1142
|
-
value: option.key,
|
|
1143
|
-
children: option.label
|
|
1144
|
-
}, option.key))
|
|
1145
|
-
}),
|
|
1146
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
1147
|
-
select: true,
|
|
1148
|
-
size: "small",
|
|
1149
|
-
label: "Condition",
|
|
1150
|
-
value: clause.op,
|
|
1151
|
-
onChange: (event)=>updateClause(index, {
|
|
1152
|
-
op: event.target.value
|
|
1153
|
-
}),
|
|
1154
|
-
sx: {
|
|
1155
|
-
minWidth: 160
|
|
1156
|
-
},
|
|
1157
|
-
children: DYNAMIC_LIST_CUSTOM_OPS.map((op)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1158
|
-
value: op,
|
|
1159
|
-
children: CUSTOM_OP_LABELS[op]
|
|
1160
|
-
}, op))
|
|
1161
|
-
}),
|
|
1162
|
-
PRESENCE_OPS.has(clause.op) ? null : (definition == null ? void 0 : definition.type) === 'select' ? /*#__PURE__*/ _jsx(TextField, {
|
|
1163
|
-
select: true,
|
|
1164
|
-
size: "small",
|
|
1165
|
-
label: "Value",
|
|
1166
|
-
value: valueText,
|
|
1167
|
-
onChange: (event)=>updateClause(index, {
|
|
1168
|
-
value: event.target.value
|
|
1169
|
-
}),
|
|
1170
|
-
sx: {
|
|
1171
|
-
minWidth: 180
|
|
1172
|
-
},
|
|
1173
|
-
children: ((_definition_options = definition.options) != null ? _definition_options : []).map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
1174
|
-
value: option,
|
|
1175
|
-
children: option
|
|
1176
|
-
}, option))
|
|
1177
|
-
}) : (definition == null ? void 0 : definition.type) === 'checkbox' ? /*#__PURE__*/ _jsxs(TextField, {
|
|
1178
|
-
select: true,
|
|
1179
|
-
size: "small",
|
|
1180
|
-
label: "Value",
|
|
1181
|
-
value: clause.value === true ? 'true' : clause.value === false ? 'false' : '',
|
|
1182
|
-
onChange: (event)=>updateClause(index, {
|
|
1183
|
-
value: event.target.value === 'true'
|
|
1184
|
-
}),
|
|
1185
|
-
sx: {
|
|
1186
|
-
minWidth: 140
|
|
1187
|
-
},
|
|
1188
|
-
children: [
|
|
1189
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1190
|
-
value: "true",
|
|
1191
|
-
children: 'Checked'
|
|
1192
|
-
}),
|
|
1193
|
-
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1194
|
-
value: "false",
|
|
1195
|
-
children: 'Not checked'
|
|
1196
|
-
})
|
|
1197
|
-
]
|
|
1198
|
-
}) : (definition == null ? void 0 : definition.type) === 'number' ? /*#__PURE__*/ _jsx(TextField, {
|
|
1199
|
-
type: "number",
|
|
1200
|
-
size: "small",
|
|
1201
|
-
label: "Value",
|
|
1202
|
-
value: valueText,
|
|
1203
|
-
onChange: (event)=>{
|
|
1204
|
-
// A number field stores a NUMBER, so the matcher compares
|
|
1205
|
-
// 10 with 9 and not "10" with "9"; half-typed text stays
|
|
1206
|
-
// text until it parses, rather than becoming NaN.
|
|
1207
|
-
const text = event.target.value;
|
|
1208
|
-
const parsed = Number(text);
|
|
1209
|
-
updateClause(index, {
|
|
1210
|
-
value: text.trim() !== '' && Number.isFinite(parsed) ? parsed : text
|
|
1211
|
-
});
|
|
1212
|
-
},
|
|
1213
|
-
sx: {
|
|
1214
|
-
minWidth: 140
|
|
1215
|
-
}
|
|
1216
|
-
}) : /*#__PURE__*/ _jsx(TextField, {
|
|
1217
|
-
type: (definition == null ? void 0 : definition.type) === 'date' ? 'date' : 'text',
|
|
1218
|
-
size: "small",
|
|
1219
|
-
label: "Value",
|
|
1220
|
-
value: valueText,
|
|
1221
|
-
onChange: (event)=>updateClause(index, {
|
|
1222
|
-
value: event.target.value
|
|
1223
|
-
}),
|
|
1224
|
-
slotProps: (definition == null ? void 0 : definition.type) === 'date' ? {
|
|
1225
|
-
inputLabel: {
|
|
1226
|
-
shrink: true
|
|
1227
|
-
}
|
|
1228
|
-
} : undefined,
|
|
1229
|
-
sx: {
|
|
1230
|
-
minWidth: 180
|
|
1231
|
-
}
|
|
1232
|
-
}),
|
|
1233
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
1234
|
-
size: "small",
|
|
1235
|
-
onClick: ()=>removeClause(index),
|
|
1236
|
-
children: 'Remove'
|
|
1237
|
-
})
|
|
1238
|
-
]
|
|
1239
|
-
}, index);
|
|
1240
|
-
}),
|
|
1380
|
+
draft.custom.map((clause, index)=>/*#__PURE__*/ _jsx(CustomClauseRow, {
|
|
1381
|
+
index: index,
|
|
1382
|
+
clause: clause,
|
|
1383
|
+
definition: definitionFor(clause.key),
|
|
1384
|
+
fieldMenu: fieldMenu,
|
|
1385
|
+
onUpdate: updateClause,
|
|
1386
|
+
onRemove: removeClause
|
|
1387
|
+
}, index)),
|
|
1241
1388
|
/*#__PURE__*/ _jsxs(Box, {
|
|
1242
1389
|
children: [
|
|
1243
1390
|
/*#__PURE__*/ _jsx(Button, {
|
|
@@ -1250,10 +1397,7 @@ export function DynamicListRuleFields(props) {
|
|
|
1250
1397
|
fieldDefinitions.ready && !fieldDefinitions.fields.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
1251
1398
|
variant: "caption",
|
|
1252
1399
|
color: "text.secondary",
|
|
1253
|
-
sx:
|
|
1254
|
-
display: 'block',
|
|
1255
|
-
mt: 0.5
|
|
1256
|
-
},
|
|
1400
|
+
sx: CAPTION_BLOCK,
|
|
1257
1401
|
children: 'This workspace has no custom contact fields yet. Define them ' + 'under CRM → Fields to filter on them here.'
|
|
1258
1402
|
}) : null
|
|
1259
1403
|
]
|
|
@@ -1267,51 +1411,41 @@ export function DynamicListRuleFields(props) {
|
|
|
1267
1411
|
direction: "row",
|
|
1268
1412
|
spacing: 1,
|
|
1269
1413
|
useFlexGap: true,
|
|
1270
|
-
sx:
|
|
1271
|
-
flexWrap: 'wrap'
|
|
1272
|
-
},
|
|
1414
|
+
sx: WRAP,
|
|
1273
1415
|
children: [
|
|
1274
|
-
/*#__PURE__*/ _jsx(
|
|
1416
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1275
1417
|
type: "number",
|
|
1276
1418
|
size: "small",
|
|
1277
1419
|
label: "Orders at least",
|
|
1278
1420
|
value: draft.ordersCountAtLeast,
|
|
1279
|
-
onChange:
|
|
1280
|
-
sx:
|
|
1281
|
-
minWidth: 160
|
|
1282
|
-
}
|
|
1421
|
+
onChange: on.ordersCountAtLeast,
|
|
1422
|
+
sx: atLeast(160)
|
|
1283
1423
|
}),
|
|
1284
|
-
/*#__PURE__*/ _jsx(
|
|
1424
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1285
1425
|
type: "number",
|
|
1286
1426
|
size: "small",
|
|
1287
1427
|
label: "Spent at least",
|
|
1288
1428
|
helperText: "Lifetime, in your store's currency",
|
|
1289
1429
|
value: draft.ltvAtLeast,
|
|
1290
|
-
onChange:
|
|
1291
|
-
sx:
|
|
1292
|
-
minWidth: 190
|
|
1293
|
-
}
|
|
1430
|
+
onChange: on.ltvAtLeast,
|
|
1431
|
+
sx: atLeast(190)
|
|
1294
1432
|
}),
|
|
1295
|
-
/*#__PURE__*/ _jsx(
|
|
1433
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1296
1434
|
type: "number",
|
|
1297
1435
|
size: "small",
|
|
1298
1436
|
label: "Bought within (days)",
|
|
1299
1437
|
value: draft.lastPurchaseWithinDays,
|
|
1300
|
-
onChange:
|
|
1301
|
-
sx:
|
|
1302
|
-
minWidth: 190
|
|
1303
|
-
}
|
|
1438
|
+
onChange: on.lastPurchaseWithinDays,
|
|
1439
|
+
sx: atLeast(190)
|
|
1304
1440
|
}),
|
|
1305
|
-
/*#__PURE__*/ _jsx(
|
|
1441
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1306
1442
|
type: "number",
|
|
1307
1443
|
size: "small",
|
|
1308
1444
|
label: "Nothing bought for (days)",
|
|
1309
1445
|
helperText: "Lapsed customers — never bought is not lapsed",
|
|
1310
1446
|
value: draft.noPurchaseForDays,
|
|
1311
|
-
onChange:
|
|
1312
|
-
sx:
|
|
1313
|
-
minWidth: 220
|
|
1314
|
-
}
|
|
1447
|
+
onChange: on.noPurchaseForDays,
|
|
1448
|
+
sx: atLeast(220)
|
|
1315
1449
|
})
|
|
1316
1450
|
]
|
|
1317
1451
|
}),
|
|
@@ -1330,51 +1464,41 @@ export function DynamicListRuleFields(props) {
|
|
|
1330
1464
|
direction: "row",
|
|
1331
1465
|
spacing: 1,
|
|
1332
1466
|
useFlexGap: true,
|
|
1333
|
-
sx:
|
|
1334
|
-
flexWrap: 'wrap'
|
|
1335
|
-
},
|
|
1467
|
+
sx: WRAP,
|
|
1336
1468
|
children: [
|
|
1337
|
-
/*#__PURE__*/ _jsx(
|
|
1469
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1338
1470
|
type: "number",
|
|
1339
1471
|
size: "small",
|
|
1340
1472
|
label: "Opened within (days)",
|
|
1341
1473
|
value: draft.openedWithinDays,
|
|
1342
|
-
onChange:
|
|
1343
|
-
sx:
|
|
1344
|
-
minWidth: 190
|
|
1345
|
-
}
|
|
1474
|
+
onChange: on.openedWithinDays,
|
|
1475
|
+
sx: atLeast(190)
|
|
1346
1476
|
}),
|
|
1347
|
-
/*#__PURE__*/ _jsx(
|
|
1477
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1348
1478
|
type: "number",
|
|
1349
1479
|
size: "small",
|
|
1350
1480
|
label: "Clicked within (days)",
|
|
1351
1481
|
value: draft.clickedWithinDays,
|
|
1352
|
-
onChange:
|
|
1353
|
-
sx:
|
|
1354
|
-
minWidth: 190
|
|
1355
|
-
}
|
|
1482
|
+
onChange: on.clickedWithinDays,
|
|
1483
|
+
sx: atLeast(190)
|
|
1356
1484
|
}),
|
|
1357
|
-
/*#__PURE__*/ _jsx(
|
|
1485
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1358
1486
|
type: "number",
|
|
1359
1487
|
size: "small",
|
|
1360
1488
|
label: "Nothing opened for (days)",
|
|
1361
1489
|
helperText: "Never opened counts",
|
|
1362
1490
|
value: draft.notOpenedForDays,
|
|
1363
|
-
onChange:
|
|
1364
|
-
sx:
|
|
1365
|
-
minWidth: 210
|
|
1366
|
-
}
|
|
1491
|
+
onChange: on.notOpenedForDays,
|
|
1492
|
+
sx: atLeast(210)
|
|
1367
1493
|
}),
|
|
1368
|
-
/*#__PURE__*/ _jsx(
|
|
1494
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1369
1495
|
type: "number",
|
|
1370
1496
|
size: "small",
|
|
1371
1497
|
label: "Nothing clicked for (days)",
|
|
1372
1498
|
helperText: "Never clicked counts",
|
|
1373
1499
|
value: draft.notClickedForDays,
|
|
1374
|
-
onChange:
|
|
1375
|
-
sx:
|
|
1376
|
-
minWidth: 210
|
|
1377
|
-
}
|
|
1500
|
+
onChange: on.notClickedForDays,
|
|
1501
|
+
sx: atLeast(210)
|
|
1378
1502
|
})
|
|
1379
1503
|
]
|
|
1380
1504
|
}),
|
|
@@ -1388,61 +1512,35 @@ export function DynamicListRuleFields(props) {
|
|
|
1388
1512
|
direction: "row",
|
|
1389
1513
|
spacing: 1,
|
|
1390
1514
|
useFlexGap: true,
|
|
1391
|
-
sx:
|
|
1392
|
-
flexWrap: 'wrap'
|
|
1393
|
-
},
|
|
1515
|
+
sx: WRAP,
|
|
1394
1516
|
children: [
|
|
1395
|
-
/*#__PURE__*/ _jsx(
|
|
1517
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1396
1518
|
select: true,
|
|
1397
1519
|
size: "small",
|
|
1398
1520
|
label: "Already on",
|
|
1399
1521
|
value: draft.inListIds,
|
|
1400
|
-
onChange:
|
|
1401
|
-
slotProps:
|
|
1402
|
-
select: {
|
|
1403
|
-
multiple: true
|
|
1404
|
-
}
|
|
1405
|
-
},
|
|
1522
|
+
onChange: on.inListIds,
|
|
1523
|
+
slotProps: MULTIPLE,
|
|
1406
1524
|
helperText: "Members of every audience picked",
|
|
1407
|
-
sx:
|
|
1408
|
-
|
|
1409
|
-
},
|
|
1410
|
-
children: lists.map((row)=>{
|
|
1411
|
-
var _row_name;
|
|
1412
|
-
return /*#__PURE__*/ _jsx(MenuItem, {
|
|
1413
|
-
value: row.$id,
|
|
1414
|
-
children: (_row_name = row.name) != null ? _row_name : row.$id
|
|
1415
|
-
}, row.$id);
|
|
1416
|
-
})
|
|
1525
|
+
sx: atLeast(220),
|
|
1526
|
+
children: listMenu
|
|
1417
1527
|
}),
|
|
1418
|
-
/*#__PURE__*/ _jsx(
|
|
1528
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
1419
1529
|
select: true,
|
|
1420
1530
|
size: "small",
|
|
1421
1531
|
label: "Not on",
|
|
1422
1532
|
value: draft.notInListIds,
|
|
1423
|
-
onChange:
|
|
1424
|
-
slotProps:
|
|
1425
|
-
select: {
|
|
1426
|
-
multiple: true
|
|
1427
|
-
}
|
|
1428
|
-
},
|
|
1533
|
+
onChange: on.notInListIds,
|
|
1534
|
+
slotProps: MULTIPLE,
|
|
1429
1535
|
helperText: "Excludes members of any audience picked",
|
|
1430
|
-
sx:
|
|
1431
|
-
|
|
1432
|
-
},
|
|
1433
|
-
children: lists.map((row)=>{
|
|
1434
|
-
var _row_name;
|
|
1435
|
-
return /*#__PURE__*/ _jsx(MenuItem, {
|
|
1436
|
-
value: row.$id,
|
|
1437
|
-
children: (_row_name = row.name) != null ? _row_name : row.$id
|
|
1438
|
-
}, row.$id);
|
|
1439
|
-
})
|
|
1536
|
+
sx: atLeast(220),
|
|
1537
|
+
children: listMenu
|
|
1440
1538
|
})
|
|
1441
1539
|
]
|
|
1442
1540
|
})
|
|
1443
1541
|
]
|
|
1444
1542
|
});
|
|
1445
|
-
}
|
|
1543
|
+
});
|
|
1446
1544
|
DynamicListRuleFields.displayName = 'DynamicListRuleFields';
|
|
1447
1545
|
/**
|
|
1448
1546
|
* A draft seeded from a rule that arrives after the form does.
|