@aglyn/plugins-crm 1.0.0-beta.157 → 1.0.0-beta.159

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.
Files changed (29) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/assignment-rule-drawer.js +2 -2
  3. package/src/lib/components/assignment-rule-drawer.js.map +1 -1
  4. package/src/lib/components/companies-bulk-bar.js +2 -2
  5. package/src/lib/components/companies-bulk-bar.js.map +1 -1
  6. package/src/lib/components/companies-section.js +2 -2
  7. package/src/lib/components/companies-section.js.map +1 -1
  8. package/src/lib/components/company-edit-drawer.js +2 -2
  9. package/src/lib/components/company-edit-drawer.js.map +1 -1
  10. package/src/lib/components/contact-properties-card.js +1 -1
  11. package/src/lib/components/contact-properties-card.js.map +1 -1
  12. package/src/lib/components/contacts-bulk-bar.js +2 -2
  13. package/src/lib/components/contacts-bulk-bar.js.map +1 -1
  14. package/src/lib/components/contacts-section.js +2 -1
  15. package/src/lib/components/contacts-section.js.map +1 -1
  16. package/src/lib/components/deal-edit-drawer.js +2 -2
  17. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  18. package/src/lib/components/deals-bulk-bar.js +2 -2
  19. package/src/lib/components/deals-bulk-bar.js.map +1 -1
  20. package/src/lib/components/lead-owner-select.js +2 -2
  21. package/src/lib/components/lead-owner-select.js.map +1 -1
  22. package/src/lib/components/new-contact-drawer.js +2 -2
  23. package/src/lib/components/new-contact-drawer.js.map +1 -1
  24. package/src/lib/components/settings-section.js +10 -4
  25. package/src/lib/components/settings-section.js.map +1 -1
  26. package/src/lib/components/task-edit-drawer.js +2 -2
  27. package/src/lib/components/task-edit-drawer.js.map +1 -1
  28. package/src/lib/components/tasks-bulk-bar.js +2 -2
  29. package/src/lib/components/tasks-bulk-bar.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-crm",
3
- "version": "1.0.0-beta.157",
3
+ "version": "1.0.0-beta.159",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,18 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.157",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.157",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.157",
31
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.157",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.157",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.157",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.157",
35
- "@aglyn/shared-util-email": "1.0.0-beta.157",
36
- "@aglyn/shared-util-http": "1.0.0-beta.157",
37
- "@aglyn/tenant-data-admin": "1.0.0-beta.157",
38
- "@aglyn/tenant-feature-instance": "1.0.0-beta.157",
39
- "@aglyn/tenant-runtime": "1.0.0-beta.157",
28
+ "@aglyn/aglyn": "1.0.0-beta.159",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.159",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.159",
31
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.159",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.159",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.159",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.159",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.159",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.159",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.159",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.159",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.159",
40
40
  "@dnd-kit/core": "^6.3.1",
41
41
  "@dnd-kit/utilities": "^3.2.2",
42
42
  "@swc/helpers": "0.5.23",
@@ -16,7 +16,7 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
- import { CONTACT_SOURCE_LABELS, newAssignmentRuleId, readCrmAssignmentRule } from "@aglyn/aglyn";
19
+ import { CONTACT_SOURCE_LABELS, newAssignmentRuleId, readCrmAssignmentRule, crmMemberPickerLabel } from "@aglyn/aglyn";
20
20
  import { Button, Drawer, FormControl, FormControlLabel, FormHelperText, FormLabel, MenuItem, Radio, RadioGroup, Stack, TextField, Typography } from "@mui/material";
21
21
  import { useCallback, useEffect, useState } from "react";
22
22
  /** The `value` of the source picker's "any door" entry. */ const ANY_SOURCE = '';
@@ -252,7 +252,7 @@ import { useCallback, useEffect, useState } from "react";
252
252
  fullWidth: true,
253
253
  children: members.map((member)=>/*#__PURE__*/ _jsx(MenuItem, {
254
254
  value: member.uid,
255
- children: member.label
255
+ children: crmMemberPickerLabel(member)
256
256
  }, member.uid))
257
257
  }) : null,
258
258
  conditions.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/assignment-rule-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n CONTACT_SOURCE_LABELS,\n type ContactSource,\n type CrmAssignmentRule,\n newAssignmentRuleId,\n readCrmAssignmentRule,\n} from '@aglyn/aglyn'\nimport {\n Button,\n Drawer,\n FormControl,\n FormControlLabel,\n FormHelperText,\n FormLabel,\n MenuItem,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { OrgMemberOption } from '../hooks/use-org-member-directory'\n\n/** The `value` of the source picker's \"any door\" entry. */\nconst ANY_SOURCE = ''\n\nexport interface AssignmentRuleDrawerProps {\n open: boolean\n onClose: () => void\n /** The roster the member target is picked from. */\n members: readonly OrgMemberOption[]\n membersLoading: boolean\n /** How many people the round-robin pool holds, for the target's helper text. */\n poolSize: number\n /** The ids the org already holds, so the new one is minted apart from them. */\n existingIds: readonly string[]\n /** Receives the rule; closing on success is the caller's job. */\n onSubmit: (rule: CrmAssignmentRule) => Promise<void> | void\n}\n\n/**\n * The one form an assignment rule is written through (AGL-2618).\n *\n * ## Every condition is optional, and none is a form above the list\n *\n * A rule names the captures it claims — a source, a form, an email domain,\n * a tag — and a field left blank is not a condition, so a rule with every\n * field blank is the catch-all, which is a rule a team writes on purpose\n * (\"everything else goes to Sam\"). The drawer says so rather than refusing\n * it. The email domain is typed the way a person sees one in an address,\n * `@acme.com` or `acme.com`, and the reader strips the `@`; what it cannot\n * read as a domain is refused here, with the field, rather than stored as\n * a condition nothing will ever match.\n *\n * ## The target is a member, or the rotation\n *\n * A member is picked from the roster by name, never typed as an address —\n * an address is resolved at run time and a rule must not depend on a\n * resolution that can change. The rotation is offered whatever the pool\n * holds, with the pool's size beside it: an empty pool is a rule the pass\n * steps over until somebody fills the pool, which the card below explains,\n * and refusing the rule here would make the two cards order-dependent.\n */\nexport function AssignmentRuleDrawer(props: AssignmentRuleDrawerProps) {\n const { open, onClose, members, membersLoading, poolSize, existingIds, onSubmit } =\n props\n const [source, setSource] = useState<string>(ANY_SOURCE)\n const [formId, setFormId] = useState('')\n const [emailDomain, setEmailDomain] = useState('')\n const [tag, setTag] = useState('')\n const [target, setTarget] = useState<'member' | 'roundRobin'>('member')\n const [memberUid, setMemberUid] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // Seeded on every open, so a drawer that was cancelled halfway does not\n // offer the abandoned draft as the next rule.\n useEffect(() => {\n if (!open) return\n setSource(ANY_SOURCE)\n setFormId('')\n setEmailDomain('')\n setTag('')\n setTarget('member')\n setMemberUid('')\n setBusy(false)\n setError(null)\n }, [open])\n\n const domainTyped = emailDomain.trim().length > 0\n const domainReadable =\n !domainTyped ||\n readCrmAssignmentRule({\n id: 'probe',\n when: { emailDomain },\n assign: { roundRobin: true },\n })?.when.emailDomain !== undefined\n const canSubmit =\n !busy && domainReadable && (target === 'roundRobin' || memberUid.length > 0)\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return\n const rule = readCrmAssignmentRule({\n id: newAssignmentRuleId(existingIds),\n when: {\n ...(source ? { source } : {}),\n ...(formId.trim() ? { formId: formId.trim() } : {}),\n ...(emailDomain.trim() ? { emailDomain: emailDomain.trim() } : {}),\n ...(tag.trim() ? { tag: tag.trim() } : {}),\n },\n assign: target === 'roundRobin' ? { roundRobin: true } : { memberUid },\n })\n if (!rule) {\n setError('The rule could not be read back. Check each field.')\n return\n }\n setBusy(true)\n setError(null)\n try {\n await onSubmit(rule)\n } catch (caught) {\n console.error(caught)\n setError('Could not save the rule. Try again.')\n } finally {\n setBusy(false)\n }\n }, [canSubmit, existingIds, source, formId, emailDomain, tag, target, memberUid, onSubmit])\n\n const conditions = [source, formId.trim(), emailDomain.trim(), tag.trim()].filter(Boolean)\n\n return (\n <Drawer anchor=\"right\" open={open} onClose={onClose}>\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{'New assignment rule'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'When a new contact matches every condition below, it is assigned ' +\n 'as the rule says. Leave every condition blank for a rule that ' +\n 'catches everything the rules above it did not.'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Source\"\n value={source}\n onChange={(event) => setSource(event.target.value)}\n fullWidth\n >\n <MenuItem value={ANY_SOURCE}>{'Any source'}</MenuItem>\n {(Object.keys(CONTACT_SOURCE_LABELS) as ContactSource[]).map((option) => (\n <MenuItem key={option} value={option}>\n {CONTACT_SOURCE_LABELS[option]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n helperText=\"The form's id, from its address in Forms. Blank for any form.\"\n slotProps={{ htmlInput: { maxLength: 128, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Email domain\"\n value={emailDomain}\n onChange={(event) => setEmailDomain(event.target.value)}\n error={!domainReadable}\n helperText={\n domainReadable\n ? 'acme.com — the domain of the captured address. Public mailboxes such as gmail.com count.'\n : 'That does not look like a domain.'\n }\n slotProps={{ htmlInput: { maxLength: 253, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Tag\"\n value={tag}\n onChange={(event) => setTag(event.target.value)}\n helperText=\"A tag the capture carries, or the contact already has.\"\n slotProps={{ htmlInput: { maxLength: 60 } }}\n fullWidth\n />\n <FormControl>\n <FormLabel id=\"assignment-rule-target\">{'Assign to'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"assignment-rule-target\"\n value={target}\n onChange={(event) =>\n setTarget(event.target.value === 'roundRobin' ? 'roundRobin' : 'member')\n }\n >\n <FormControlLabel value=\"member\" control={<Radio size=\"small\" />} label=\"A team member\" />\n <FormControlLabel\n value=\"roundRobin\"\n control={<Radio size=\"small\" />}\n label=\"Round robin — the next member of the pool\"\n />\n </RadioGroup>\n {target === 'roundRobin' ? (\n <FormHelperText>\n {poolSize > 0\n ? `The pool has ${poolSize} ${poolSize === 1 ? 'member' : 'members'}; each new contact goes to the next in turn.`\n : 'The pool is empty. This rule is skipped until somebody is added to it below.'}\n </FormHelperText>\n ) : null}\n </FormControl>\n {target === 'member' ? (\n <TextField\n select\n size=\"small\"\n label=\"Member\"\n value={memberUid}\n onChange={(event) => setMemberUid(event.target.value)}\n disabled={membersLoading}\n helperText={\n membersLoading\n ? 'Loading the team…'\n : members.length\n ? undefined\n : 'The team could not be listed.'\n }\n fullWidth\n >\n {members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {member.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {conditions.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'No conditions: this rule matches every new contact the rules above it did not claim.'}\n </Typography>\n ) : null}\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" color=\"primary\" disabled={!canSubmit} onClick={handleSubmit}>\n {'Add rule'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Drawer>\n )\n}\nAssignmentRuleDrawer.displayName = 'AssignmentRuleDrawer'\n\nexport default AssignmentRuleDrawer\n"],"names":["CONTACT_SOURCE_LABELS","newAssignmentRuleId","readCrmAssignmentRule","Button","Drawer","FormControl","FormControlLabel","FormHelperText","FormLabel","MenuItem","Radio","RadioGroup","Stack","TextField","Typography","useCallback","useEffect","useState","ANY_SOURCE","AssignmentRuleDrawer","props","open","onClose","members","membersLoading","poolSize","existingIds","onSubmit","source","setSource","formId","setFormId","emailDomain","setEmailDomain","tag","setTag","target","setTarget","memberUid","setMemberUid","busy","setBusy","error","setError","domainTyped","trim","length","domainReadable","id","when","assign","roundRobin","undefined","canSubmit","handleSubmit","rule","caught","console","conditions","filter","Boolean","anchor","spacing","sx","width","p","variant","color","select","size","label","value","onChange","event","fullWidth","Object","keys","map","option","helperText","slotProps","htmlInput","maxLength","spellCheck","aria-labelledby","control","disabled","member","uid","direction","onClick","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/assignment-rule-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n CONTACT_SOURCE_LABELS,\n type ContactSource,\n type CrmAssignmentRule,\n newAssignmentRuleId,\n readCrmAssignmentRule,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport {\n Button,\n Drawer,\n FormControl,\n FormControlLabel,\n FormHelperText,\n FormLabel,\n MenuItem,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { OrgMemberOption } from '../hooks/use-org-member-directory'\n\n/** The `value` of the source picker's \"any door\" entry. */\nconst ANY_SOURCE = ''\n\nexport interface AssignmentRuleDrawerProps {\n open: boolean\n onClose: () => void\n /** The roster the member target is picked from. */\n members: readonly OrgMemberOption[]\n membersLoading: boolean\n /** How many people the round-robin pool holds, for the target's helper text. */\n poolSize: number\n /** The ids the org already holds, so the new one is minted apart from them. */\n existingIds: readonly string[]\n /** Receives the rule; closing on success is the caller's job. */\n onSubmit: (rule: CrmAssignmentRule) => Promise<void> | void\n}\n\n/**\n * The one form an assignment rule is written through (AGL-2618).\n *\n * ## Every condition is optional, and none is a form above the list\n *\n * A rule names the captures it claims — a source, a form, an email domain,\n * a tag — and a field left blank is not a condition, so a rule with every\n * field blank is the catch-all, which is a rule a team writes on purpose\n * (\"everything else goes to Sam\"). The drawer says so rather than refusing\n * it. The email domain is typed the way a person sees one in an address,\n * `@acme.com` or `acme.com`, and the reader strips the `@`; what it cannot\n * read as a domain is refused here, with the field, rather than stored as\n * a condition nothing will ever match.\n *\n * ## The target is a member, or the rotation\n *\n * A member is picked from the roster by name, never typed as an address —\n * an address is resolved at run time and a rule must not depend on a\n * resolution that can change. The rotation is offered whatever the pool\n * holds, with the pool's size beside it: an empty pool is a rule the pass\n * steps over until somebody fills the pool, which the card below explains,\n * and refusing the rule here would make the two cards order-dependent.\n */\nexport function AssignmentRuleDrawer(props: AssignmentRuleDrawerProps) {\n const { open, onClose, members, membersLoading, poolSize, existingIds, onSubmit } =\n props\n const [source, setSource] = useState<string>(ANY_SOURCE)\n const [formId, setFormId] = useState('')\n const [emailDomain, setEmailDomain] = useState('')\n const [tag, setTag] = useState('')\n const [target, setTarget] = useState<'member' | 'roundRobin'>('member')\n const [memberUid, setMemberUid] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // Seeded on every open, so a drawer that was cancelled halfway does not\n // offer the abandoned draft as the next rule.\n useEffect(() => {\n if (!open) return\n setSource(ANY_SOURCE)\n setFormId('')\n setEmailDomain('')\n setTag('')\n setTarget('member')\n setMemberUid('')\n setBusy(false)\n setError(null)\n }, [open])\n\n const domainTyped = emailDomain.trim().length > 0\n const domainReadable =\n !domainTyped ||\n readCrmAssignmentRule({\n id: 'probe',\n when: { emailDomain },\n assign: { roundRobin: true },\n })?.when.emailDomain !== undefined\n const canSubmit =\n !busy && domainReadable && (target === 'roundRobin' || memberUid.length > 0)\n\n const handleSubmit = useCallback(async () => {\n if (!canSubmit) return\n const rule = readCrmAssignmentRule({\n id: newAssignmentRuleId(existingIds),\n when: {\n ...(source ? { source } : {}),\n ...(formId.trim() ? { formId: formId.trim() } : {}),\n ...(emailDomain.trim() ? { emailDomain: emailDomain.trim() } : {}),\n ...(tag.trim() ? { tag: tag.trim() } : {}),\n },\n assign: target === 'roundRobin' ? { roundRobin: true } : { memberUid },\n })\n if (!rule) {\n setError('The rule could not be read back. Check each field.')\n return\n }\n setBusy(true)\n setError(null)\n try {\n await onSubmit(rule)\n } catch (caught) {\n console.error(caught)\n setError('Could not save the rule. Try again.')\n } finally {\n setBusy(false)\n }\n }, [canSubmit, existingIds, source, formId, emailDomain, tag, target, memberUid, onSubmit])\n\n const conditions = [source, formId.trim(), emailDomain.trim(), tag.trim()].filter(Boolean)\n\n return (\n <Drawer anchor=\"right\" open={open} onClose={onClose}>\n <Stack spacing={2} sx={{ width: 380, p: 3 }}>\n <Typography variant=\"h6\">{'New assignment rule'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'When a new contact matches every condition below, it is assigned ' +\n 'as the rule says. Leave every condition blank for a rule that ' +\n 'catches everything the rules above it did not.'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Source\"\n value={source}\n onChange={(event) => setSource(event.target.value)}\n fullWidth\n >\n <MenuItem value={ANY_SOURCE}>{'Any source'}</MenuItem>\n {(Object.keys(CONTACT_SOURCE_LABELS) as ContactSource[]).map((option) => (\n <MenuItem key={option} value={option}>\n {CONTACT_SOURCE_LABELS[option]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Form\"\n value={formId}\n onChange={(event) => setFormId(event.target.value)}\n helperText=\"The form's id, from its address in Forms. Blank for any form.\"\n slotProps={{ htmlInput: { maxLength: 128, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Email domain\"\n value={emailDomain}\n onChange={(event) => setEmailDomain(event.target.value)}\n error={!domainReadable}\n helperText={\n domainReadable\n ? 'acme.com — the domain of the captured address. Public mailboxes such as gmail.com count.'\n : 'That does not look like a domain.'\n }\n slotProps={{ htmlInput: { maxLength: 253, spellCheck: false } }}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Tag\"\n value={tag}\n onChange={(event) => setTag(event.target.value)}\n helperText=\"A tag the capture carries, or the contact already has.\"\n slotProps={{ htmlInput: { maxLength: 60 } }}\n fullWidth\n />\n <FormControl>\n <FormLabel id=\"assignment-rule-target\">{'Assign to'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"assignment-rule-target\"\n value={target}\n onChange={(event) =>\n setTarget(event.target.value === 'roundRobin' ? 'roundRobin' : 'member')\n }\n >\n <FormControlLabel value=\"member\" control={<Radio size=\"small\" />} label=\"A team member\" />\n <FormControlLabel\n value=\"roundRobin\"\n control={<Radio size=\"small\" />}\n label=\"Round robin — the next member of the pool\"\n />\n </RadioGroup>\n {target === 'roundRobin' ? (\n <FormHelperText>\n {poolSize > 0\n ? `The pool has ${poolSize} ${poolSize === 1 ? 'member' : 'members'}; each new contact goes to the next in turn.`\n : 'The pool is empty. This rule is skipped until somebody is added to it below.'}\n </FormHelperText>\n ) : null}\n </FormControl>\n {target === 'member' ? (\n <TextField\n select\n size=\"small\"\n label=\"Member\"\n value={memberUid}\n onChange={(event) => setMemberUid(event.target.value)}\n disabled={membersLoading}\n helperText={\n membersLoading\n ? 'Loading the team…'\n : members.length\n ? undefined\n : 'The team could not be listed.'\n }\n fullWidth\n >\n {members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {conditions.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'No conditions: this rule matches every new contact the rules above it did not claim.'}\n </Typography>\n ) : null}\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" color=\"primary\" disabled={!canSubmit} onClick={handleSubmit}>\n {'Add rule'}\n </Button>\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n </Drawer>\n )\n}\nAssignmentRuleDrawer.displayName = 'AssignmentRuleDrawer'\n\nexport default AssignmentRuleDrawer\n"],"names":["CONTACT_SOURCE_LABELS","newAssignmentRuleId","readCrmAssignmentRule","crmMemberPickerLabel","Button","Drawer","FormControl","FormControlLabel","FormHelperText","FormLabel","MenuItem","Radio","RadioGroup","Stack","TextField","Typography","useCallback","useEffect","useState","ANY_SOURCE","AssignmentRuleDrawer","props","open","onClose","members","membersLoading","poolSize","existingIds","onSubmit","source","setSource","formId","setFormId","emailDomain","setEmailDomain","tag","setTag","target","setTarget","memberUid","setMemberUid","busy","setBusy","error","setError","domainTyped","trim","length","domainReadable","id","when","assign","roundRobin","undefined","canSubmit","handleSubmit","rule","caught","console","conditions","filter","Boolean","anchor","spacing","sx","width","p","variant","color","select","size","label","value","onChange","event","fullWidth","Object","keys","map","option","helperText","slotProps","htmlInput","maxLength","spellCheck","aria-labelledby","control","disabled","member","uid","direction","onClick","displayName"],"mappings":"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"}
@@ -34,7 +34,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
34
34
  * would be from its own page. A bulk delete of forty companies is forty
35
35
  * acts and forty lines; a single "deleted 40 companies" line would name
36
36
  * none of them, and the feed exists to answer "where did Acme go".
37
- */ import { CRM_COLLECTIONS } from "@aglyn/aglyn";
37
+ */ import { CRM_COLLECTIONS, crmMemberPickerLabel } from "@aglyn/aglyn";
38
38
  import { useConfirmationContext } from "@aglyn/shared-ui-jsx";
39
39
  import { useFirestore, useHostActivityLogger } from "@aglyn/tenant-feature-instance";
40
40
  import { Button, MenuItem, TextField } from "@mui/material";
@@ -222,7 +222,7 @@ function CompaniesBulkBarBody(props) {
222
222
  }),
223
223
  members.options.map((member)=>/*#__PURE__*/ _jsx(MenuItem, {
224
224
  value: member.uid,
225
- children: member.label
225
+ children: crmMemberPickerLabel(member)
226
226
  }, member.uid))
227
227
  ]
228
228
  }) : null
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-bulk-bar.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The bar over the companies table, for whatever rows are ticked\n * (AGL-2621).\n *\n * Tag or untag them, hand them to an owner, take them into a spreadsheet,\n * or delete them. The patches come from `companies-bulk-writes.ts` and go\n * through the shared runner; the delete is the record page's own\n * `crm/company-delete`, per company, one after another — the route unlinks a\n * company's contacts before it goes, and a company past the detach bound is\n * left standing and NAMED, the way the page says it, so a second delete\n * continues where this one stopped.\n *\n * ## The delete is logged, per company\n *\n * Each company that goes is one line in the site's activity feed, as it\n * would be from its own page. A bulk delete of forty companies is forty\n * acts and forty lines; a single \"deleted 40 companies\" line would name\n * none of them, and the feed exists to answer \"where did Acme go\".\n */\n\nimport { CRM_COLLECTIONS } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useHostActivityLogger } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyBulkRow,\n planCompanyAddTag,\n planCompanyRemoveTag,\n planCompanySetOwner,\n} from '../model/companies-bulk-writes'\nimport {\n type CompanyCsvOptions,\n type CompanyCsvRow,\n companiesCsv,\n} from '../model/companies-csv'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport { deleteCompanyThroughRoute } from '../model/company-delete'\nimport { normalizeBulkTag } from '../model/contacts-bulk-writes'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport {\n type CrmBulkPlan,\n crmBulkWriters,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport CrmExportAllButton from './crm-export-all-button'\nimport { useCrmApi } from './use-crm-api'\n\nexport interface CompaniesBulkBarProps {\n /**\n * The site whose activity feed the bar's acts are logged in, or `null`\n * at the organization level (AGL-2630), where the selection spans sites\n * and no one feed is written.\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly ['orgs', string] | null\n rows: readonly (CompanyBulkRow & CompanyCsvRow)[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Owner column. */\n members: OrgMemberOptions\n /** How the export names an owner — the table's own options. */\n csv?: CompanyCsvOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'company', plural: 'companies' }\n\ntype PendingAction = 'add-tag' | 'remove-tag' | 'owner'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n 'add-tag': 'Add a tag',\n 'remove-tag': 'Remove a tag',\n owner: 'Set the owner',\n}\n\nconst doneSentence = (action: PendingAction, done: number): string => {\n const rows = countNoun(done, NOUN)\n switch (action) {\n case 'add-tag':\n return `Tagged ${rows}`\n case 'remove-tag':\n return `Removed the tag from ${rows}`\n case 'owner':\n return `Owner set on ${rows}`\n }\n}\n\n/**\n * The bar, or nothing — a separate boundary so the body's hooks never run\n * on the visit to the list that ticks no row.\n */\nexport function CompaniesBulkBar(props: CompaniesBulkBarProps) {\n if (!props.selected.length) return null\n return <CompaniesBulkBarBody {...props} />\n}\nCompaniesBulkBar.displayName = 'CompaniesBulkBar'\n\nfunction CompaniesBulkBarBody(props: CompaniesBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props\n const firestore = useFirestore()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId ?? undefined)\n const callCrm = useCrmApi(hostId)\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'company' })\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.companies, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n const runPlan = useCallback(\n (action: PendingAction, plan: CrmBulkPlan) =>\n apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: () => runCrmBulkWrites(writers, plan.writes, (write) => write.label),\n done: (count) => doneSentence(action, count),\n }),\n [apply, writers],\n )\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const nowMs = Date.now()\n const tag = normalizeBulkTag(value)\n if (pending !== 'owner' && !tag) return\n const plan: CrmBulkPlan =\n pending === 'add-tag'\n ? planCompanyAddTag(selectedRows, tag ?? '', nowMs)\n : pending === 'remove-tag'\n ? planCompanyRemoveTag(selectedRows, tag ?? '', nowMs)\n : planCompanySetOwner(selectedRows, value || null, nowMs)\n const action = pending\n setPending(null)\n await runPlan(action, plan)\n }, [pending, scope, value, selectedRows, runPlan])\n\n const handleExport = useCallback(() => {\n downloadTextFile('companies-selected.csv', 'text/csv', companiesCsv(selectedRows, csv))\n }, [selectedRows, csv])\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this company?' : `Delete ${count} companies?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed from Companies and ` +\n 'unlinked from every contact at them. The contacts themselves are ' +\n 'kept, and so is anything else filed against the companies.',\n confirmationText: count === 1 ? 'Delete company' : 'Delete companies',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const outcome = await apply({\n attempted: count,\n skipped: [],\n job: () =>\n runCrmBulkCalls(\n selectedRows,\n (row) => String(row.name || row.$id),\n async (row) => {\n const result = await deleteCompanyThroughRoute(callCrm, row.$id)\n if (!result.deleted) {\n throw new Error(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n 'and more remain — delete again to continue',\n )\n }\n logActivity('Deleted company', {\n type: 'company',\n id: row.$id,\n name: row.name,\n })\n },\n ),\n done: (done) => `Deleted ${countNoun(done, NOUN)}`,\n })\n // The rows that went are gone from the table; the refused ones stay\n // selected, so the reader can see which they are and try again.\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows\n .filter((row) => refused.has(String(row.name || row.$id)))\n .map((row) => row.$id),\n )\n }, [scope, selectedRows, confirm, apply, callCrm, logActivity, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply={pending === 'owner' || Boolean(normalizeBulkTag(value))}\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'add-tag' || pending === 'remove-tag' ? (\n <TextField\n autoFocus\n size=\"small\"\n label=\"Tag\"\n placeholder=\"enterprise\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText={\n pending === 'add-tag'\n ? 'Lowercased, like the tags on a company'\n : 'Removed wherever it is present'\n }\n />\n ) : pending === 'owner' ? (\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={!members.ready}\n error={Boolean(members.error)}\n helperText={members.error ?? (members.ready ? undefined : 'Loading the team…')}\n >\n <MenuItem value=\"\">{'Nobody — clear the owner'}</MenuItem>\n {members.options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {member.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('add-tag')}>\n {'Add tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('remove-tag')}>\n {'Remove tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('owner')}>\n {'Set owner'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={handleExport}>\n {'Export CSV'}\n </Button>\n {/*\n The selection's file is the rows on screen; this one is the whole\n collection, streamed by the server (AGL-2662).\n */}\n <CrmExportAllButton\n resource=\"companies\"\n orgId={scope?.[1] ?? null}\n hostId={hostId}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nCompaniesBulkBarBody.displayName = 'CompaniesBulkBarBody'\n\nexport default CompaniesBulkBar\n"],"names":["CRM_COLLECTIONS","useConfirmationContext","useFirestore","useHostActivityLogger","Button","MenuItem","TextField","doc","useCallback","useMemo","useState","useCrmBulkApply","planCompanyAddTag","planCompanyRemoveTag","planCompanySetOwner","companiesCsv","COMPANY_DETACH_LIMIT","deleteCompanyThroughRoute","normalizeBulkTag","downloadTextFile","crmBulkWriters","runCrmBulkCalls","runCrmBulkWrites","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportAllButton","useCrmApi","NOUN","singular","plural","ACTION_TITLES","owner","doneSentence","action","done","rows","CompaniesBulkBar","props","selected","length","CompaniesBulkBarBody","displayName","members","hostId","scope","onSelectedChange","csv","firestore","confirm","logActivity","undefined","callCrm","busy","report","apply","dismissReport","recordKind","selectedRows","chosen","Set","filter","row","has","$id","pending","setPending","value","setValue","writers","id","companies","openAction","runPlan","plan","attempted","writes","skipped","job","write","label","count","handleApply","nowMs","Date","now","tag","handleExport","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","outcome","String","name","result","deleted","Error","toLocaleString","type","refused","map","noun","onClear","onDismissReport","extras","open","canApply","Boolean","onClose","onApply","autoFocus","size","placeholder","onChange","event","target","helperText","select","disabled","ready","error","options","member","uid","onClick","resource","orgId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-bulk-bar.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The bar over the companies table, for whatever rows are ticked\n * (AGL-2621).\n *\n * Tag or untag them, hand them to an owner, take them into a spreadsheet,\n * or delete them. The patches come from `companies-bulk-writes.ts` and go\n * through the shared runner; the delete is the record page's own\n * `crm/company-delete`, per company, one after another — the route unlinks a\n * company's contacts before it goes, and a company past the detach bound is\n * left standing and NAMED, the way the page says it, so a second delete\n * continues where this one stopped.\n *\n * ## The delete is logged, per company\n *\n * Each company that goes is one line in the site's activity feed, as it\n * would be from its own page. A bulk delete of forty companies is forty\n * acts and forty lines; a single \"deleted 40 companies\" line would name\n * none of them, and the feed exists to answer \"where did Acme go\".\n */\n\nimport { CRM_COLLECTIONS, crmMemberPickerLabel } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useHostActivityLogger } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { OrgMemberOptions } from '../hooks/use-org-member-options'\nimport {\n type CompanyBulkRow,\n planCompanyAddTag,\n planCompanyRemoveTag,\n planCompanySetOwner,\n} from '../model/companies-bulk-writes'\nimport {\n type CompanyCsvOptions,\n type CompanyCsvRow,\n companiesCsv,\n} from '../model/companies-csv'\nimport { COMPANY_DETACH_LIMIT } from '../model/companies'\nimport { deleteCompanyThroughRoute } from '../model/company-delete'\nimport { normalizeBulkTag } from '../model/contacts-bulk-writes'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport {\n type CrmBulkPlan,\n crmBulkWriters,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport CrmExportAllButton from './crm-export-all-button'\nimport { useCrmApi } from './use-crm-api'\n\nexport interface CompaniesBulkBarProps {\n /**\n * The site whose activity feed the bar's acts are logged in, or `null`\n * at the organization level (AGL-2630), where the selection spans sites\n * and no one feed is written.\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly ['orgs', string] | null\n rows: readonly (CompanyBulkRow & CompanyCsvRow)[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Owner column. */\n members: OrgMemberOptions\n /** How the export names an owner — the table's own options. */\n csv?: CompanyCsvOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'company', plural: 'companies' }\n\ntype PendingAction = 'add-tag' | 'remove-tag' | 'owner'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n 'add-tag': 'Add a tag',\n 'remove-tag': 'Remove a tag',\n owner: 'Set the owner',\n}\n\nconst doneSentence = (action: PendingAction, done: number): string => {\n const rows = countNoun(done, NOUN)\n switch (action) {\n case 'add-tag':\n return `Tagged ${rows}`\n case 'remove-tag':\n return `Removed the tag from ${rows}`\n case 'owner':\n return `Owner set on ${rows}`\n }\n}\n\n/**\n * The bar, or nothing — a separate boundary so the body's hooks never run\n * on the visit to the list that ticks no row.\n */\nexport function CompaniesBulkBar(props: CompaniesBulkBarProps) {\n if (!props.selected.length) return null\n return <CompaniesBulkBarBody {...props} />\n}\nCompaniesBulkBar.displayName = 'CompaniesBulkBar'\n\nfunction CompaniesBulkBarBody(props: CompaniesBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, members, csv } = props\n const firestore = useFirestore()\n const { confirm } = useConfirmationContext()\n const logActivity = useHostActivityLogger(hostId ?? undefined)\n const callCrm = useCrmApi(hostId)\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'company' })\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.companies, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n const runPlan = useCallback(\n (action: PendingAction, plan: CrmBulkPlan) =>\n apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: () => runCrmBulkWrites(writers, plan.writes, (write) => write.label),\n done: (count) => doneSentence(action, count),\n }),\n [apply, writers],\n )\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const nowMs = Date.now()\n const tag = normalizeBulkTag(value)\n if (pending !== 'owner' && !tag) return\n const plan: CrmBulkPlan =\n pending === 'add-tag'\n ? planCompanyAddTag(selectedRows, tag ?? '', nowMs)\n : pending === 'remove-tag'\n ? planCompanyRemoveTag(selectedRows, tag ?? '', nowMs)\n : planCompanySetOwner(selectedRows, value || null, nowMs)\n const action = pending\n setPending(null)\n await runPlan(action, plan)\n }, [pending, scope, value, selectedRows, runPlan])\n\n const handleExport = useCallback(() => {\n downloadTextFile('companies-selected.csv', 'text/csv', companiesCsv(selectedRows, csv))\n }, [selectedRows, csv])\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this company?' : `Delete ${count} companies?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed from Companies and ` +\n 'unlinked from every contact at them. The contacts themselves are ' +\n 'kept, and so is anything else filed against the companies.',\n confirmationText: count === 1 ? 'Delete company' : 'Delete companies',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const outcome = await apply({\n attempted: count,\n skipped: [],\n job: () =>\n runCrmBulkCalls(\n selectedRows,\n (row) => String(row.name || row.$id),\n async (row) => {\n const result = await deleteCompanyThroughRoute(callCrm, row.$id)\n if (!result.deleted) {\n throw new Error(\n `${COMPANY_DETACH_LIMIT.toLocaleString()} contacts were unlinked ` +\n 'and more remain — delete again to continue',\n )\n }\n logActivity('Deleted company', {\n type: 'company',\n id: row.$id,\n name: row.name,\n })\n },\n ),\n done: (done) => `Deleted ${countNoun(done, NOUN)}`,\n })\n // The rows that went are gone from the table; the refused ones stay\n // selected, so the reader can see which they are and try again.\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows\n .filter((row) => refused.has(String(row.name || row.$id)))\n .map((row) => row.$id),\n )\n }, [scope, selectedRows, confirm, apply, callCrm, logActivity, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply={pending === 'owner' || Boolean(normalizeBulkTag(value))}\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'add-tag' || pending === 'remove-tag' ? (\n <TextField\n autoFocus\n size=\"small\"\n label=\"Tag\"\n placeholder=\"enterprise\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText={\n pending === 'add-tag'\n ? 'Lowercased, like the tags on a company'\n : 'Removed wherever it is present'\n }\n />\n ) : pending === 'owner' ? (\n <TextField\n select\n size=\"small\"\n label=\"Owner\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={!members.ready}\n error={Boolean(members.error)}\n helperText={members.error ?? (members.ready ? undefined : 'Loading the team…')}\n >\n <MenuItem value=\"\">{'Nobody — clear the owner'}</MenuItem>\n {members.options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('add-tag')}>\n {'Add tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('remove-tag')}>\n {'Remove tag'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('owner')}>\n {'Set owner'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={handleExport}>\n {'Export CSV'}\n </Button>\n {/*\n The selection's file is the rows on screen; this one is the whole\n collection, streamed by the server (AGL-2662).\n */}\n <CrmExportAllButton\n resource=\"companies\"\n orgId={scope?.[1] ?? null}\n hostId={hostId}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nCompaniesBulkBarBody.displayName = 'CompaniesBulkBarBody'\n\nexport default CompaniesBulkBar\n"],"names":["CRM_COLLECTIONS","crmMemberPickerLabel","useConfirmationContext","useFirestore","useHostActivityLogger","Button","MenuItem","TextField","doc","useCallback","useMemo","useState","useCrmBulkApply","planCompanyAddTag","planCompanyRemoveTag","planCompanySetOwner","companiesCsv","COMPANY_DETACH_LIMIT","deleteCompanyThroughRoute","normalizeBulkTag","downloadTextFile","crmBulkWriters","runCrmBulkCalls","runCrmBulkWrites","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportAllButton","useCrmApi","NOUN","singular","plural","ACTION_TITLES","owner","doneSentence","action","done","rows","CompaniesBulkBar","props","selected","length","CompaniesBulkBarBody","displayName","members","hostId","scope","onSelectedChange","csv","firestore","confirm","logActivity","undefined","callCrm","busy","report","apply","dismissReport","recordKind","selectedRows","chosen","Set","filter","row","has","$id","pending","setPending","value","setValue","writers","id","companies","openAction","runPlan","plan","attempted","writes","skipped","job","write","label","count","handleApply","nowMs","Date","now","tag","handleExport","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","outcome","String","name","result","deleted","Error","toLocaleString","type","refused","map","noun","onClear","onDismissReport","extras","open","canApply","Boolean","onClose","onApply","autoFocus","size","placeholder","onChange","event","target","helperText","select","disabled","ready","error","options","member","uid","onClick","resource","orgId"],"mappings":"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"}
@@ -16,7 +16,7 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
- import { CRM_COLLECTIONS, filterByNextActivity, isNoNextActivityClause, pluginDocsHelp } from "@aglyn/aglyn";
19
+ import { CRM_COLLECTIONS, filterByNextActivity, isNoNextActivityClause, pluginDocsHelp, crmMemberPickerLabel } from "@aglyn/aglyn";
20
20
  import { mdiPlus } from "@aglyn/shared-data-mdi";
21
21
  import { CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
22
22
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
@@ -313,7 +313,7 @@ import { customFieldColumns } from "./contact-custom-columns.js";
313
313
  */ type: 'singleSelect',
314
314
  valueOptions: members.options.map((option)=>({
315
315
  value: option.uid,
316
- label: option.label
316
+ label: crmMemberPickerLabel(option)
317
317
  })),
318
318
  filterable: members.options.length > 0,
319
319
  filterOperators: getGridSingleSelectOperators().filter((operator)=>operator.value === 'is'),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n CRM_COLLECTIONS,\n type CrmCompany,\n filterByNextActivity,\n isNoNextActivityClause,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n gridFilterRequest,\n listFilterColumn,\n type ListFilterRequest,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport { NoNextActivityToggle, nextActivityColumn } from './crm-next-activity-column'\nimport CrmViewsControl from './crm-views-control'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport {\n listFilterConstraints,\n usePagedCollection,\n useFirestore,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport {\n getGridSingleSelectOperators,\n type GridColDef,\n} from '@mui/x-data-grid'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { COMPANY_LIST_FILTER_FIELDS } from '../constants/company-filters'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { crmVisibleToClause, useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CompanyCsvOptions, companiesCsv } from '../model/companies-csv'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport CompaniesBulkBar from './companies-bulk-bar'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { CompanyImportButton } from './company-import-drawer'\nimport { customFieldColumns } from './contact-custom-columns'\n\nexport interface CompaniesSectionProps {\n /** The site the list is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling>\n /** The CRM hub URL, which every company route hangs beneath. */\n basePath: string\n}\n\ntype CompanyRow = Partial<CrmCompany> & { $id: string; updatedAt?: any }\n\n/**\n * `/crm/companies` — the organizations behind the people (AGL-2597).\n *\n * A section of its own rather than a column on the contact list, because a\n * company is known by several contacts and carries records of its own: a\n * domain the email suggestion keys on, an address, an owner, and the deals\n * and tasks filed against it. The row opens `/crm/companies/{id}`, where all\n * of that lives; this list is the cheaper surface and reads nothing a row\n * does not show.\n *\n * ## What the listener carries\n *\n * Paged and ordered by the server — `updatedAt desc`, so a company somebody\n * just touched is on page one — and SCOPED by the same `visibleTo\n * array-contains-any` predicate the contact list runs. That predicate is what\n * the rules evaluate: a filtered query is provable per document, and an\n * unfiltered one is refused rather than quietly returning the whole org's\n * accounts to a site that may see one client's.\n *\n * ## What a filter may be\n *\n * The scope predicate is an array clause, and Firestore carries one per\n * query, so the word-prefix search the contact list offers cannot run here.\n * The name is a prefix range and an exact match instead, and the owner is a\n * choice from the roster — each reaching the whole collection, which is the\n * property a search must have: a company on page four is found, not reported\n * missing. `COMPANY_LIST_FILTER_FIELDS` states what the indexes serve.\n *\n * ## Creating is a drawer\n *\n * \"New company\" opens the same eight-field drawer the company's page edits\n * with, and the new record's page opens when it is saved. A form above a\n * list has nowhere to grow, and this one has an address in it.\n *\n * ## Selection, the bar and the file (AGL-2621)\n *\n * The rows are selectable, and a selection raises `CompaniesBulkBar` over\n * the table. Export CSV writes the loaded page through `companiesCsv()` —\n * the same file the bar writes over the selection, headed as the import\n * reads it — and Import CSV opens the companies drawer beside it.\n */\nexport function CompaniesSection(props: CompaniesSectionProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const { scope, orgId, visibleTo } = useCrmScope({ hostId, org })\n /*\n * The team, read once for this surface: the Owner column names a person\n * from a uid, and the create drawer picks from the same roster. One fetch\n * serves both, which is why the drawer takes it as a prop.\n */\n const members = useOrgMemberOptions(orgId)\n // The org's company fields, for the optional columns below (AGL-2661).\n const companyFields = useContactFieldDefinitions(orgId, 'company')\n\n /*\n * The column filter is the saved VIEW'S first clause (AGL-2617): this\n * grid's panel holds one, and a view of companies holds that one beside\n * the columns and the sort. Declared BEFORE the listener that reads it,\n * as the filter always was — the query is rebuilt from it.\n */\n const views = useCrmSavedView({ section: 'companies', hostId, org, basePath })\n /*\n * ...beside the \"No next activity\" clause (AGL-2661), which is not a\n * query the listener can run — absence has no index — and narrows the\n * loaded page below instead. The grid's one clause is the first that is\n * not it, and setting the grid's clause keeps it.\n */\n const viewFilters = views.state.filters\n const filter: ListFilterRequest | null =\n viewFilters.find((clause) => !isNoNextActivityClause(clause)) ?? null\n const setFilter = useCallback(\n (request: ListFilterRequest | null) =>\n views.setFilters([\n ...(request ? [request] : []),\n ...viewFilters.filter(isNoNextActivityClause),\n ]),\n [views.setFilters, viewFilters],\n )\n\n const {\n rows: loaded,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<CompanyRow>(\n (pageLimit) => {\n if (!scope) return null\n const constraints = listFilterConstraints(\n COMPANY_LIST_FILTER_FIELDS,\n filter,\n )\n return query(\n collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies),\n // Absent at the organization level, where the tokens are `null` (AGL-2630).\n ...crmVisibleToClause(visibleTo),\n ...(constraints ?? [orderBy('updatedAt', 'desc')]),\n limit(pageLimit),\n )\n },\n [firestore, scope, visibleTo, filter],\n { idField: '$id' },\n )\n const companies = useMemo(\n () => filterByNextActivity(loaded, viewFilters),\n [loaded, viewFilters],\n )\n const nowMs = useMemo(() => Date.now(), [])\n\n const [createOpen, setCreateOpen] = useState(false)\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n /*\n * The owner is written by ADDRESS, because the companies import resolves\n * an owner by email; the same options reach the bar, so the page's file\n * and the selection's are one format.\n */\n const csvOptions: CompanyCsvOptions = useMemo(\n () => ({ ownerEmail: members.emailFor }),\n [members.emailFor],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('companies.csv', 'text/csv', companiesCsv(companies, csvOptions))\n }, [companies, csvOptions])\n const openCompany = useCallback(\n (id: string) => router.push(routes.company(id)),\n [router, routes],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Company',\n flex: 1.6,\n minWidth: 240,\n ...listFilterColumn(COMPANY_LIST_FILTER_FIELDS, 'name'),\n valueGetter: (_value, row: CompanyRow) => String(row.name ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Stack\n sx={{ justifyContent: 'center', height: '100%', lineHeight: 1.25 }}\n >\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.name}\n </Typography>\n {row.industry ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ lineHeight: 1.25 }}\n noWrap\n >\n {row.industry}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'domain',\n headerName: 'Domain',\n flex: 1,\n minWidth: 160,\n // Stored normalized, so it could be filtered exactly — but a domain\n // lookup is what the company suggestion on a contact does, and a\n // filter here would need an index of its own for one more way to\n // find a row the name filter already finds.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => String(row.domain ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.domain ? (\n <Typography variant=\"body2\">{row.domain}</Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'contactsCount',\n headerName: 'Contacts',\n width: 110,\n align: 'right',\n headerAlign: 'right',\n /*\n * The denormalized count every link moves (AGL-2613) — a stored\n * number, so a page of companies costs no read per row. Absent on a\n * company nobody has linked since the counter existed, which reads\n * as zero; the company's own page takes the live aggregate.\n */\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => Number(row.contactsCount ?? 0),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.contactsCount ? 'text.primary' : 'text.secondary'}\n >\n {Number(row.contactsCount ?? 0).toLocaleString()}\n </Typography>\n ),\n },\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 1,\n minWidth: 140,\n // Written by the drawer and the bulk bar (AGL-2621); read here, not\n // filtered — a tag filter would need an `array-contains` index of\n // its own beside the scope predicate.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => (row.tags ?? []).join(', '),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.tags?.length ? (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', overflow: 'hidden' }}>\n {row.tags.slice(0, 3).map((tag) => (\n <Chip key={tag} size=\"small\" variant=\"outlined\" label={tag} />\n ))}\n {row.tags.length > 3 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`+${row.tags.length - 3}`}\n </Typography>\n ) : null}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 160,\n sortable: false,\n /*\n * A CHOICE from the roster, not a text box for a uid. The grid's\n * single-select `is` operator is what the panel offers; the handler\n * below maps it onto the declared field's `equals`, which the\n * translator turns into the equality the index serves.\n */\n type: 'singleSelect',\n valueOptions: members.options.map((option) => ({\n value: option.uid,\n label: option.label,\n })),\n filterable: members.options.length > 0,\n filterOperators: getGridSingleSelectOperators().filter(\n (operator) => operator.value === 'is',\n ),\n valueGetter: (_value, row: CompanyRow) => String(row.ownerUid ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.ownerUid ? 'text.primary' : 'text.secondary'}\n >\n {row.ownerUid\n ? members.ready\n ? members.labelFor(row.ownerUid)\n : '…'\n : '—'}\n </Typography>\n ),\n },\n {\n field: 'updatedAt',\n headerName: 'Updated',\n flex: 0.8,\n minWidth: 140,\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n row.updatedAt?.seconds ? new Date(row.updatedAt.seconds * 1000) : null,\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {row.updatedAt?.seconds\n ? new Date(row.updatedAt.seconds * 1000).toLocaleDateString()\n : '—'}\n </Typography>\n ),\n },\n // When the earliest open task against the company is due (AGL-2661).\n nextActivityColumn(nowMs),\n // The org's company fields as optional columns (AGL-2661), read off\n // the row's own `custom` map the way the contacts list reads its own.\n ...customFieldColumns(companyFields.active),\n ],\n [members, companyFields.active, nowMs],\n )\n\n /*\n * The grid's models are the view's (AGL-2617). The filter model shows the\n * view's clause in the panel as a typed one would appear — the owner's\n * stored `equals` back as the single-select `is` the panel offers — so a\n * view opened from its address reads as filtered, not as a mystery.\n */\n const grid = useCrmViewGrid(views, columns)\n const filterModel = useMemo(\n () => ({\n items: filter\n ? [\n {\n id: 'view',\n field: filter.field,\n operator:\n filter.field === 'ownerUid' && filter.op === 'equals' ? 'is' : filter.op,\n value: filter.value,\n },\n ]\n : [],\n }),\n [filter],\n )\n\n const newCompanyButton = (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={!scope}\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n onClick={() => setCreateOpen(true)}\n >\n {'New company'}\n </Button>\n )\n\n return (\n <CardDisplay\n header={'Companies'}\n help={pluginDocsHelp('companies', { anchor: '#the-companies-list' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CrmViewsControl controller={views} allLabel=\"All companies\" />\n <NoNextActivityToggle filters={viewFilters} onChange={views.setFilters} />\n {newCompanyButton}\n </Stack>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flex: 1 }}>\n {'The organizations your contacts belong to. Open one to see its ' +\n 'people, its deals and its open tasks, or to link a contact ' +\n 'to it.'}\n </Typography>\n <CompanyImportButton hostId={hostId} org={org} />\n <Button size=\"small\" onClick={handleExport} disabled={!companies.length}>\n {'Export CSV'}\n </Button>\n </Stack>\n <CompaniesBulkBar\n hostId={hostId}\n scope={scope}\n rows={companies}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n members={members}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rowHeight={TABLE_ROW_HEIGHT}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n rows={companies}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n noRowsLabel=\"No companies yet\"\n noRowsDescription=\"A company groups the contacts who work at one business, with its domain, owner and address. Create the first one, or link a contact to a company from their page.\"\n noRowsAction={newCompanyButton}\n onOpen={(id) => openCompany(String(id))}\n // An empty table while the read is in flight reads as \"you have\n // none\" rather than \"these are on their way\".\n loading={!scope || status === 'loading'}\n /*\n * The grid must NOT also filter. The query answers it, so a client\n * pass could only drop rows the query already matched.\n */\n filterMode=\"server\"\n filterModel={filterModel}\n onFilterModelChange={(model) => {\n const request = gridFilterRequest(model)\n setFilter(\n request && request.field === 'ownerUid' && request.op === 'is'\n ? { ...request, op: 'equals' }\n : request,\n )\n }}\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={companies.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n {/*\n Mounted only while it is open, so the list pays for none of the\n drawer's state — and a fresh mount is what seeds an empty form.\n */}\n {createOpen ? (\n <CompanyEditDrawer\n open\n onClose={() => setCreateOpen(false)}\n hostId={hostId}\n org={org}\n members={members}\n onSaved={openCompany}\n />\n ) : null}\n </CardDisplay>\n )\n}\nCompaniesSection.displayName = 'CompaniesSection'\n\nexport default CompaniesSection\n"],"names":["CRM_COLLECTIONS","filterByNextActivity","isNoNextActivityClause","pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","ListPagination","ListTable","gridFilterRequest","listFilterColumn","useCrmSavedView","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","NoNextActivityToggle","nextActivityColumn","CrmViewsControl","TABLE_ROW_HEIGHT","listFilterConstraints","usePagedCollection","useFirestore","Button","Chip","Stack","Typography","getGridSingleSelectOperators","collection","limit","orderBy","query","useRouter","useCallback","useMemo","useState","COMPANY_LIST_FILTER_FIELDS","useContactFieldDefinitions","crmVisibleToClause","useCrmScope","useOrgMemberOptions","companiesCsv","downloadTextFile","crmRoutes","CompaniesBulkBar","CompanyEditDrawer","CompanyImportButton","customFieldColumns","CompaniesSection","props","viewFilters","hostId","org","basePath","routes","router","firestore","scope","orgId","visibleTo","members","companyFields","views","section","state","filters","filter","find","clause","setFilter","request","setFilters","rows","loaded","status","hasMore","page","setPage","pageSize","setPageSize","pageLimit","constraints","companies","idField","nowMs","Date","now","createOpen","setCreateOpen","selectedIds","setSelectedIds","csvOptions","ownerEmail","emailFor","handleExport","openCompany","id","push","company","columns","field","headerName","flex","minWidth","valueGetter","_value","row","String","name","renderCell","sx","justifyContent","height","lineHeight","variant","industry","color","noWrap","filterable","sortable","domain","width","align","headerAlign","Number","contactsCount","toLocaleString","tags","join","length","direction","spacing","alignItems","overflow","slice","map","tag","size","label","type","valueOptions","options","option","value","uid","filterOperators","operator","ownerUid","ready","labelFor","updatedAt","seconds","toLocaleDateString","active","grid","filterModel","items","op","newCompanyButton","disabled","startIcon","path","onClick","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","controller","allLabel","onChange","flexWrap","rowGap","selected","onSelectedChange","csv","columnOrder","rowHeight","slots","selectable","noRowsLabel","noRowsDescription","noRowsAction","onOpen","loading","filterMode","onFilterModelChange","model","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","onSaved","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/companies-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n CRM_COLLECTIONS,\n type CrmCompany,\n filterByNextActivity,\n isNoNextActivityClause,\n pluginDocsHelp,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n gridFilterRequest,\n listFilterColumn,\n type ListFilterRequest,\n} from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { useCrmSavedView } from '../hooks/use-crm-saved-view'\nimport { useCrmViewGrid } from '../hooks/use-crm-view-grid'\nimport { CRM_LIST_SLOTS, CrmColumnOrderProvider } from './crm-column-menu'\nimport { NoNextActivityToggle, nextActivityColumn } from './crm-next-activity-column'\nimport CrmViewsControl from './crm-views-control'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport {\n listFilterConstraints,\n usePagedCollection,\n useFirestore,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport {\n getGridSingleSelectOperators,\n type GridColDef,\n} from '@mui/x-data-grid'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { COMPANY_LIST_FILTER_FIELDS } from '../constants/company-filters'\nimport { useContactFieldDefinitions } from '../hooks/use-contact-field-definitions'\nimport { crmVisibleToClause, useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberOptions } from '../hooks/use-org-member-options'\nimport { type CompanyCsvOptions, companiesCsv } from '../model/companies-csv'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { crmRoutes } from '../model/crm-routes'\nimport CompaniesBulkBar from './companies-bulk-bar'\nimport CompanyEditDrawer from './company-edit-drawer'\nimport { CompanyImportButton } from './company-import-drawer'\nimport { customFieldColumns } from './contact-custom-columns'\n\nexport interface CompaniesSectionProps {\n /** The site the list is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling>\n /** The CRM hub URL, which every company route hangs beneath. */\n basePath: string\n}\n\ntype CompanyRow = Partial<CrmCompany> & { $id: string; updatedAt?: any }\n\n/**\n * `/crm/companies` — the organizations behind the people (AGL-2597).\n *\n * A section of its own rather than a column on the contact list, because a\n * company is known by several contacts and carries records of its own: a\n * domain the email suggestion keys on, an address, an owner, and the deals\n * and tasks filed against it. The row opens `/crm/companies/{id}`, where all\n * of that lives; this list is the cheaper surface and reads nothing a row\n * does not show.\n *\n * ## What the listener carries\n *\n * Paged and ordered by the server — `updatedAt desc`, so a company somebody\n * just touched is on page one — and SCOPED by the same `visibleTo\n * array-contains-any` predicate the contact list runs. That predicate is what\n * the rules evaluate: a filtered query is provable per document, and an\n * unfiltered one is refused rather than quietly returning the whole org's\n * accounts to a site that may see one client's.\n *\n * ## What a filter may be\n *\n * The scope predicate is an array clause, and Firestore carries one per\n * query, so the word-prefix search the contact list offers cannot run here.\n * The name is a prefix range and an exact match instead, and the owner is a\n * choice from the roster — each reaching the whole collection, which is the\n * property a search must have: a company on page four is found, not reported\n * missing. `COMPANY_LIST_FILTER_FIELDS` states what the indexes serve.\n *\n * ## Creating is a drawer\n *\n * \"New company\" opens the same eight-field drawer the company's page edits\n * with, and the new record's page opens when it is saved. A form above a\n * list has nowhere to grow, and this one has an address in it.\n *\n * ## Selection, the bar and the file (AGL-2621)\n *\n * The rows are selectable, and a selection raises `CompaniesBulkBar` over\n * the table. Export CSV writes the loaded page through `companiesCsv()` —\n * the same file the bar writes over the selection, headed as the import\n * reads it — and Import CSV opens the companies drawer beside it.\n */\nexport function CompaniesSection(props: CompaniesSectionProps) {\n const { hostId, org, basePath } = props\n const routes = crmRoutes(basePath)\n const router = useRouter()\n const firestore = useFirestore()\n const { scope, orgId, visibleTo } = useCrmScope({ hostId, org })\n /*\n * The team, read once for this surface: the Owner column names a person\n * from a uid, and the create drawer picks from the same roster. One fetch\n * serves both, which is why the drawer takes it as a prop.\n */\n const members = useOrgMemberOptions(orgId)\n // The org's company fields, for the optional columns below (AGL-2661).\n const companyFields = useContactFieldDefinitions(orgId, 'company')\n\n /*\n * The column filter is the saved VIEW'S first clause (AGL-2617): this\n * grid's panel holds one, and a view of companies holds that one beside\n * the columns and the sort. Declared BEFORE the listener that reads it,\n * as the filter always was — the query is rebuilt from it.\n */\n const views = useCrmSavedView({ section: 'companies', hostId, org, basePath })\n /*\n * ...beside the \"No next activity\" clause (AGL-2661), which is not a\n * query the listener can run — absence has no index — and narrows the\n * loaded page below instead. The grid's one clause is the first that is\n * not it, and setting the grid's clause keeps it.\n */\n const viewFilters = views.state.filters\n const filter: ListFilterRequest | null =\n viewFilters.find((clause) => !isNoNextActivityClause(clause)) ?? null\n const setFilter = useCallback(\n (request: ListFilterRequest | null) =>\n views.setFilters([\n ...(request ? [request] : []),\n ...viewFilters.filter(isNoNextActivityClause),\n ]),\n [views.setFilters, viewFilters],\n )\n\n const {\n rows: loaded,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n } = usePagedCollection<CompanyRow>(\n (pageLimit) => {\n if (!scope) return null\n const constraints = listFilterConstraints(\n COMPANY_LIST_FILTER_FIELDS,\n filter,\n )\n return query(\n collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.companies),\n // Absent at the organization level, where the tokens are `null` (AGL-2630).\n ...crmVisibleToClause(visibleTo),\n ...(constraints ?? [orderBy('updatedAt', 'desc')]),\n limit(pageLimit),\n )\n },\n [firestore, scope, visibleTo, filter],\n { idField: '$id' },\n )\n const companies = useMemo(\n () => filterByNextActivity(loaded, viewFilters),\n [loaded, viewFilters],\n )\n const nowMs = useMemo(() => Date.now(), [])\n\n const [createOpen, setCreateOpen] = useState(false)\n const [selectedIds, setSelectedIds] = useState<string[]>([])\n /*\n * The owner is written by ADDRESS, because the companies import resolves\n * an owner by email; the same options reach the bar, so the page's file\n * and the selection's are one format.\n */\n const csvOptions: CompanyCsvOptions = useMemo(\n () => ({ ownerEmail: members.emailFor }),\n [members.emailFor],\n )\n const handleExport = useCallback(() => {\n downloadTextFile('companies.csv', 'text/csv', companiesCsv(companies, csvOptions))\n }, [companies, csvOptions])\n const openCompany = useCallback(\n (id: string) => router.push(routes.company(id)),\n [router, routes],\n )\n\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Company',\n flex: 1.6,\n minWidth: 240,\n ...listFilterColumn(COMPANY_LIST_FILTER_FIELDS, 'name'),\n valueGetter: (_value, row: CompanyRow) => String(row.name ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Stack\n sx={{ justifyContent: 'center', height: '100%', lineHeight: 1.25 }}\n >\n <Typography variant=\"body2\" sx={{ lineHeight: 1.25 }}>\n {row.name}\n </Typography>\n {row.industry ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ lineHeight: 1.25 }}\n noWrap\n >\n {row.industry}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'domain',\n headerName: 'Domain',\n flex: 1,\n minWidth: 160,\n // Stored normalized, so it could be filtered exactly — but a domain\n // lookup is what the company suggestion on a contact does, and a\n // filter here would need an index of its own for one more way to\n // find a row the name filter already finds.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => String(row.domain ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.domain ? (\n <Typography variant=\"body2\">{row.domain}</Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'contactsCount',\n headerName: 'Contacts',\n width: 110,\n align: 'right',\n headerAlign: 'right',\n /*\n * The denormalized count every link moves (AGL-2613) — a stored\n * number, so a page of companies costs no read per row. Absent on a\n * company nobody has linked since the counter existed, which reads\n * as zero; the company's own page takes the live aggregate.\n */\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => Number(row.contactsCount ?? 0),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.contactsCount ? 'text.primary' : 'text.secondary'}\n >\n {Number(row.contactsCount ?? 0).toLocaleString()}\n </Typography>\n ),\n },\n {\n field: 'tags',\n headerName: 'Tags',\n flex: 1,\n minWidth: 140,\n // Written by the drawer and the bulk bar (AGL-2621); read here, not\n // filtered — a tag filter would need an `array-contains` index of\n // its own beside the scope predicate.\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) => (row.tags ?? []).join(', '),\n renderCell: ({ row }: { row: CompanyRow }) =>\n row.tags?.length ? (\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', overflow: 'hidden' }}>\n {row.tags.slice(0, 3).map((tag) => (\n <Chip key={tag} size=\"small\" variant=\"outlined\" label={tag} />\n ))}\n {row.tags.length > 3 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`+${row.tags.length - 3}`}\n </Typography>\n ) : null}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'—'}\n </Typography>\n ),\n },\n {\n field: 'ownerUid',\n headerName: 'Owner',\n flex: 1,\n minWidth: 160,\n sortable: false,\n /*\n * A CHOICE from the roster, not a text box for a uid. The grid's\n * single-select `is` operator is what the panel offers; the handler\n * below maps it onto the declared field's `equals`, which the\n * translator turns into the equality the index serves.\n */\n type: 'singleSelect',\n valueOptions: members.options.map((option) => ({\n value: option.uid,\n label: crmMemberPickerLabel(option),\n })),\n filterable: members.options.length > 0,\n filterOperators: getGridSingleSelectOperators().filter(\n (operator) => operator.value === 'is',\n ),\n valueGetter: (_value, row: CompanyRow) => String(row.ownerUid ?? ''),\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography\n variant=\"body2\"\n color={row.ownerUid ? 'text.primary' : 'text.secondary'}\n >\n {row.ownerUid\n ? members.ready\n ? members.labelFor(row.ownerUid)\n : '…'\n : '—'}\n </Typography>\n ),\n },\n {\n field: 'updatedAt',\n headerName: 'Updated',\n flex: 0.8,\n minWidth: 140,\n filterable: false,\n sortable: false,\n valueGetter: (_value, row: CompanyRow) =>\n row.updatedAt?.seconds ? new Date(row.updatedAt.seconds * 1000) : null,\n renderCell: ({ row }: { row: CompanyRow }) => (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {row.updatedAt?.seconds\n ? new Date(row.updatedAt.seconds * 1000).toLocaleDateString()\n : '—'}\n </Typography>\n ),\n },\n // When the earliest open task against the company is due (AGL-2661).\n nextActivityColumn(nowMs),\n // The org's company fields as optional columns (AGL-2661), read off\n // the row's own `custom` map the way the contacts list reads its own.\n ...customFieldColumns(companyFields.active),\n ],\n [members, companyFields.active, nowMs],\n )\n\n /*\n * The grid's models are the view's (AGL-2617). The filter model shows the\n * view's clause in the panel as a typed one would appear — the owner's\n * stored `equals` back as the single-select `is` the panel offers — so a\n * view opened from its address reads as filtered, not as a mystery.\n */\n const grid = useCrmViewGrid(views, columns)\n const filterModel = useMemo(\n () => ({\n items: filter\n ? [\n {\n id: 'view',\n field: filter.field,\n operator:\n filter.field === 'ownerUid' && filter.op === 'equals' ? 'is' : filter.op,\n value: filter.value,\n },\n ]\n : [],\n }),\n [filter],\n )\n\n const newCompanyButton = (\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={!scope}\n startIcon={<MdiIcon path={mdiPlus.path} size={0.8} />}\n onClick={() => setCreateOpen(true)}\n >\n {'New company'}\n </Button>\n )\n\n return (\n <CardDisplay\n header={'Companies'}\n help={pluginDocsHelp('companies', { anchor: '#the-companies-list' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CrmViewsControl controller={views} allLabel=\"All companies\" />\n <NoNextActivityToggle filters={viewFilters} onChange={views.setFilters} />\n {newCompanyButton}\n </Stack>\n ),\n }}\n >\n <Stack spacing={1.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flex: 1 }}>\n {'The organizations your contacts belong to. Open one to see its ' +\n 'people, its deals and its open tasks, or to link a contact ' +\n 'to it.'}\n </Typography>\n <CompanyImportButton hostId={hostId} org={org} />\n <Button size=\"small\" onClick={handleExport} disabled={!companies.length}>\n {'Export CSV'}\n </Button>\n </Stack>\n <CompaniesBulkBar\n hostId={hostId}\n scope={scope}\n rows={companies}\n selected={selectedIds}\n onSelectedChange={setSelectedIds}\n members={members}\n csv={csvOptions}\n />\n <CrmColumnOrderProvider value={grid.columnOrder}>\n <ListTable\n rowHeight={TABLE_ROW_HEIGHT}\n columns={grid.columns}\n slots={CRM_LIST_SLOTS}\n rows={companies}\n selectable={{ selected: selectedIds, onChange: setSelectedIds }}\n noRowsLabel=\"No companies yet\"\n noRowsDescription=\"A company groups the contacts who work at one business, with its domain, owner and address. Create the first one, or link a contact to a company from their page.\"\n noRowsAction={newCompanyButton}\n onOpen={(id) => openCompany(String(id))}\n // An empty table while the read is in flight reads as \"you have\n // none\" rather than \"these are on their way\".\n loading={!scope || status === 'loading'}\n /*\n * The grid must NOT also filter. The query answers it, so a client\n * pass could only drop rows the query already matched.\n */\n filterMode=\"server\"\n filterModel={filterModel}\n onFilterModelChange={(model) => {\n const request = gridFilterRequest(model)\n setFilter(\n request && request.field === 'ownerUid' && request.op === 'is'\n ? { ...request, op: 'equals' }\n : request,\n )\n }}\n // Columns and sort are the view's, controlled (AGL-2617).\n columnVisibilityModel={grid.columnVisibilityModel}\n onColumnVisibilityModelChange={grid.onColumnVisibilityModelChange}\n sortModel={grid.sortModel}\n onSortModelChange={grid.onSortModelChange}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n />\n </CrmColumnOrderProvider>\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={companies.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n {/*\n Mounted only while it is open, so the list pays for none of the\n drawer's state — and a fresh mount is what seeds an empty form.\n */}\n {createOpen ? (\n <CompanyEditDrawer\n open\n onClose={() => setCreateOpen(false)}\n hostId={hostId}\n org={org}\n members={members}\n onSaved={openCompany}\n />\n ) : null}\n </CardDisplay>\n )\n}\nCompaniesSection.displayName = 'CompaniesSection'\n\nexport default CompaniesSection\n"],"names":["CRM_COLLECTIONS","filterByNextActivity","isNoNextActivityClause","pluginDocsHelp","crmMemberPickerLabel","mdiPlus","CardDisplay","MdiIcon","ListPagination","ListTable","gridFilterRequest","listFilterColumn","useCrmSavedView","useCrmViewGrid","CRM_LIST_SLOTS","CrmColumnOrderProvider","NoNextActivityToggle","nextActivityColumn","CrmViewsControl","TABLE_ROW_HEIGHT","listFilterConstraints","usePagedCollection","useFirestore","Button","Chip","Stack","Typography","getGridSingleSelectOperators","collection","limit","orderBy","query","useRouter","useCallback","useMemo","useState","COMPANY_LIST_FILTER_FIELDS","useContactFieldDefinitions","crmVisibleToClause","useCrmScope","useOrgMemberOptions","companiesCsv","downloadTextFile","crmRoutes","CompaniesBulkBar","CompanyEditDrawer","CompanyImportButton","customFieldColumns","CompaniesSection","props","viewFilters","hostId","org","basePath","routes","router","firestore","scope","orgId","visibleTo","members","companyFields","views","section","state","filters","filter","find","clause","setFilter","request","setFilters","rows","loaded","status","hasMore","page","setPage","pageSize","setPageSize","pageLimit","constraints","companies","idField","nowMs","Date","now","createOpen","setCreateOpen","selectedIds","setSelectedIds","csvOptions","ownerEmail","emailFor","handleExport","openCompany","id","push","company","columns","field","headerName","flex","minWidth","valueGetter","_value","row","String","name","renderCell","sx","justifyContent","height","lineHeight","variant","industry","color","noWrap","filterable","sortable","domain","width","align","headerAlign","Number","contactsCount","toLocaleString","tags","join","length","direction","spacing","alignItems","overflow","slice","map","tag","size","label","type","valueOptions","options","option","value","uid","filterOperators","operator","ownerUid","ready","labelFor","updatedAt","seconds","toLocaleDateString","active","grid","filterModel","items","op","newCompanyButton","disabled","startIcon","path","onClick","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","controller","allLabel","onChange","flexWrap","rowGap","selected","onSelectedChange","csv","columnOrder","rowHeight","slots","selectable","noRowsLabel","noRowsDescription","noRowsAction","onOpen","loading","filterMode","onFilterModelChange","model","columnVisibilityModel","onColumnVisibilityModelChange","sortModel","onSortModelChange","hideFooter","rowCount","onPageChange","onPageSizeChange","open","onClose","onSaved","displayName"],"mappings":"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"}
@@ -16,7 +16,7 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
- import { createResourceUid, CRM_COLLECTIONS, CRM_RECORDS_BAND_FULL_MESSAGE, pluginDocsHelp } from "@aglyn/aglyn";
19
+ import { createResourceUid, CRM_COLLECTIONS, CRM_RECORDS_BAND_FULL_MESSAGE, pluginDocsHelp, crmMemberPickerLabel } from "@aglyn/aglyn";
20
20
  import { ICON_VARIANT_CLOSE } from "@aglyn/shared-data-enums";
21
21
  import { Container, HelpTip, MdiIcon, SrOnly } from "@aglyn/shared-ui-jsx";
22
22
  import { NavigationDrawerComponent } from "@aglyn/shared-ui-jsx/components/navigation-drawer.component";
@@ -350,7 +350,7 @@ const ADDRESS_FIELDS = [
350
350
  }) : null,
351
351
  members.options.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
352
352
  value: option.uid,
353
- children: option.label
353
+ children: crmMemberPickerLabel(option)
354
354
  }, option.uid))
355
355
  ]
356
356
  }),