@aglyn/plugins-crm 1.0.0-beta.224 → 1.0.0-beta.226

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/picklist-values-card.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 CRM_COLLECTIONS,\n CRM_LEAD_TEXT_MAX,\n type CrmPicklist,\n type CrmPicklistDefinition,\n type CrmPicklistId,\n type CrmPicklistObject,\n type CrmPicklistValue,\n isStandardPicklistValueId,\n newResourceScopeFields,\n ORG_SCOPE_TOKEN,\n} from '@aglyn/aglyn'\nimport {\n mdiArrowDown,\n mdiArrowUp,\n mdiDeleteOutline,\n mdiDragVertical,\n mdiEyeOffOutline,\n mdiEyeOutline,\n mdiPencilOutline,\n mdiStarOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n IconButton,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, serverTimestamp, setDoc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport {\n addPicklistValue,\n crmPicklistValuesRouteUrl,\n movePicklistValue,\n picklistAddableMeanings,\n picklistMeaningLabel,\n picklistReplacements,\n type PicklistValuesAction,\n type PicklistValuesRequest,\n type PicklistValuesResponse,\n setPicklistDefault,\n setPicklistValueActive,\n setPicklistValueGroup,\n sortPicklistValues,\n} from '../model/picklist-values'\nimport { crmTaskCallScope } from '../model/task-routes'\n\nexport interface PicklistValuesCardProps {\n /** Which of the CRM's standard picklists this card manages. */\n picklistId: CrmPicklistId\n /** The mounted site, or `null` at the organization level. */\n hostId: string | null\n /** The org whose list this is; `null` while it settles. */\n orgId: string | null\n /** The site a newly written list records as its definer — the Fields page's own. */\n createHostId: string | null\n}\n\n/** Each object's noun, singular and plural, for the card's sentences. */\nconst OBJECT_NOUNS: Record<CrmPicklistObject, [string, string]> = {\n contact: ['contact', 'contacts'],\n company: ['company', 'companies'],\n deal: ['deal', 'deals'],\n lead: ['lead', 'leads'],\n task: ['task', 'tasks'],\n}\n\n/** \"a\", \"a and b\", \"a, b and c\". */\nfunction sentenceList(words: readonly string[]): string {\n return words.length < 2\n ? (words[0] ?? '')\n : `${words.slice(0, -1).join(', ')} and ${words[words.length - 1]}`\n}\n\n/** The objects whose records hold the field's label, in the definition's order. */\nfunction targetObjects(definition: CrmPicklistDefinition): CrmPicklistObject[] {\n return [...new Set(definition.targets.map((target) => target.object))]\n}\n\n/** The add and rename dialog's state: which value, if any, is being renamed. */\ntype NameDialog = { mode: 'add' } | { mode: 'rename'; value: CrmPicklistValue } | null\n\n/**\n * ONE STANDARD PICKLIST'S VALUES (AGL-3298, AGL-3510) — Salesforce's\n * picklist value set for a standard field, managed on the Fields tab of the\n * object the field belongs to.\n *\n * Standard values are marked, and cannot be deleted — every org has them —\n * but rename, reorder, regroup, deactivate and default are theirs as much\n * as an added value's. Every move that touches only the LIST — add,\n * reorder by drag or arrows, sort A–Z, activate, deactivate, group,\n * default — is one client write of the whole document, guarded against a\n * cached read like every seeded save on the Fields page. The two that\n * change RECORDS — rename, and delete with a replacement — go to\n * `crm/picklist-values`, which rewrites the list and every record the\n * definition's targets name together; see that route.\n *\n * An org that has never edited the list reads the standard values, and the\n * first move here writes them down as the org's own.\n */\nexport function PicklistValuesCard(props: PicklistValuesCardProps) {\n const { picklistId, hostId, orgId, createHostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { definition, picklist, stored, ready, fromCache } = useCrmPicklist(picklistId, orgId)\n const [busy, setBusy] = useState(false)\n const [nameDialog, setNameDialog] = useState<NameDialog>(null)\n const [draftName, setDraftName] = useState('')\n const [draftGroup, setDraftGroup] = useState('')\n const [draftMeaning, setDraftMeaning] = useState('')\n const [nameError, setNameError] = useState('')\n const [deleting, setDeleting] = useState<CrmPicklistValue | null>(null)\n const [replaceWith, setReplaceWith] = useState('')\n const [dragFrom, setDragFrom] = useState<number | null>(null)\n\n const groups = definition.groups ?? []\n const meanings = definition.meanings ?? []\n // An added value may not take a meaning only the platform sets (AGL-3512).\n const addableMeanings = picklistAddableMeanings(definition)\n const [noun, nouns] = OBJECT_NOUNS[definition.object]\n const holders = targetObjects(definition).map((object) => OBJECT_NOUNS[object])\n // A list of suggestions only — a task's subjects (AGL-3517) — that no record holds.\n const suggestions = holders.length === 0\n const field = definition.label.toLowerCase()\n const failed = `The ${definition.plural} could not be saved.`\n const isStandard = (value: CrmPicklistValue) => isStandardPicklistValueId(definition, value.id)\n\n /** Write the whole list — the list-only moves above. */\n const save = useCallback(\n async (next: CrmPicklist, done: string) => {\n if (!orgId || busy) return\n setBusy(true)\n try {\n const verdict = await writeGuardedBySeed({ subject: `${field} list`, fromCache }, () =>\n setDoc(\n doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.picklists, picklistId),\n {\n values: next.values,\n defaultValueId: next.defaultValueId,\n updatedAt: serverTimestamp(),\n // The org-wide stamp the rules require of a new CRM document,\n // the same one a field definition carries.\n ...(stored\n ? {}\n : {\n hostId: createHostId,\n createdAt: serverTimestamp(),\n ...newResourceScopeFields([ORG_SCOPE_TOKEN]),\n }),\n },\n { merge: true },\n ),\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar(failed, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(false)\n }\n },\n [orgId, busy, field, fromCache, firestore, picklistId, stored, createHostId, enqueueSnackbar, failed],\n )\n\n /** A rename or a delete — the moves that change records too. */\n const post = useCallback(\n async (request: PicklistValuesAction): Promise<PicklistValuesResponse | null> => {\n const scope = crmTaskCallScope(hostId, orgId)\n if (!scope || busy) return null\n setBusy(true)\n try {\n const response = await authorizedFetch(user, crmPicklistValuesRouteUrl(), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...request, picklistId, ...scope } satisfies PicklistValuesRequest),\n })\n const payload = (await response.json().catch(() => ({}))) as { error?: string }\n if (!response.ok) {\n enqueueSnackbar(payload.error || failed, { variant: 'warning', persist: false })\n return null\n }\n return payload as PicklistValuesResponse\n } catch (error) {\n console.error(error)\n enqueueSnackbar(failed, { variant: 'error', allowDuplicate: true })\n return null\n } finally {\n setBusy(false)\n }\n },\n [hostId, orgId, busy, user, picklistId, enqueueSnackbar, failed],\n )\n\n /** \" — 2 leads and 1 contact updated\", or nothing when no record held the value. */\n const moved = (result: PicklistValuesResponse) => {\n const counts = targetObjects(definition).map((object) => {\n const count = result.updated?.[object] ?? 0\n const [one, many] = OBJECT_NOUNS[object]\n return { count, text: `${count.toLocaleString()} ${count === 1 ? one : many}` }\n })\n return counts.some((entry) => entry.count)\n ? ` — ${sentenceList(counts.map((entry) => entry.text))} updated`\n : ''\n }\n\n const openAdd = () => {\n setDraftName('')\n setDraftGroup('')\n setDraftMeaning('')\n setNameError('')\n setNameDialog({ mode: 'add' })\n }\n const openRename = (value: CrmPicklistValue) => {\n setDraftName(value.label)\n setNameError('')\n setNameDialog({ mode: 'rename', value })\n }\n\n const submitName = async () => {\n if (!nameDialog) return\n if (nameDialog.mode === 'add') {\n const move = addPicklistValue(definition, picklist, draftName, {\n group: draftGroup || null,\n meaning: draftMeaning || null,\n })\n if (move.ok === false) return void setNameError(move.error)\n setNameDialog(null)\n await save(move.picklist, `“${draftName.trim()}” added`)\n return\n }\n const result = await post({ action: 'rename', valueId: nameDialog.value.id, label: draftName })\n if (!result) return\n setNameDialog(null)\n enqueueSnackbar(`Renamed${moved(result)}`, { variant: 'success', persist: false })\n }\n\n const openDelete = (value: CrmPicklistValue) => {\n setReplaceWith('')\n setDeleting(value)\n }\n const submitDelete = async () => {\n if (!deleting) return\n const result = await post({\n action: 'delete',\n valueId: deleting.id,\n replaceWith: replaceWith || null,\n })\n if (!result) return\n setDeleting(null)\n enqueueSnackbar(`“${deleting.label}” deleted${moved(result)}`, {\n variant: 'success',\n persist: false,\n })\n }\n\n const move = (from: number, to: number) =>\n void save(movePicklistValue(picklist, from, to), 'Order saved')\n\n const rowActions = (value: CrmPicklistValue): RowActionsMenuItem[] => {\n const isDefault = picklist.defaultValueId === value.id\n const items: RowActionsMenuItem[] = [\n {\n key: 'rename',\n label: 'Rename',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n disabled: busy,\n onClick: () => openRename(value),\n },\n {\n key: 'default',\n label: isDefault ? 'Clear default' : 'Make default',\n icon: <MdiIcon path={mdiStarOutline.path} size={0.8} />,\n disabled: busy || (!isDefault && !value.active),\n disabledReason: !value.active ? 'An inactive value cannot be the default' : undefined,\n onClick: () =>\n void save(\n setPicklistDefault(picklist, isDefault ? null : value.id),\n isDefault ? 'Default cleared' : `New ${nouns} start as “${value.label}”`,\n ),\n },\n {\n key: 'active',\n label: value.active ? 'Deactivate' : 'Activate',\n icon: (\n <MdiIcon path={value.active ? mdiEyeOffOutline.path : mdiEyeOutline.path} size={0.8} />\n ),\n disabled: busy,\n onClick: () =>\n void save(\n setPicklistValueActive(picklist, value.id, !value.active),\n value.active ? `“${value.label}” deactivated` : `“${value.label}” activated`,\n ),\n },\n ]\n // A standard value is every organization's: it is deactivated, never deleted.\n if (!isStandard(value)) {\n items.push({\n key: 'delete',\n label: 'Delete…',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: busy,\n onClick: () => openDelete(value),\n })\n }\n return items\n }\n\n const replacements = deleting ? picklistReplacements(definition, picklist, deleting.id) : []\n const columns = 4 + (groups.length ? 1 : 0) + (meanings.length ? 1 : 0)\n const heldBy = sentenceList(holders.map(([, many]) => many)).replace(/^./, (letter) =>\n letter.toUpperCase(),\n )\n\n return (\n <Stack spacing={1.5}>\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ justifyContent: 'space-between', alignItems: { sm: 'center' } }}\n >\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle1\">{`${definition.label} values`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {(suggestions\n ? `The suggestions every ${noun}’s ${definition.label} offers, in this order. ` +\n 'Any text may still be typed, and renaming or deleting a suggestion ' +\n `changes no ${noun}. `\n : `The choices in every ${noun}’s ${definition.label} select, in this order. ` +\n (definition.restricted\n ? 'An import or an API write naming anything else is refused. '\n : '') +\n 'A deactivated value stays on the records that hold it. ') +\n 'Standard values come with every organization and cannot be deleted; ' +\n 'add your own beside them.'}\n </Typography>\n </Stack>\n {orgId ? (\n <Stack direction=\"row\" spacing={1} sx={{ flexShrink: 0 }}>\n <Button\n size=\"small\"\n disabled={busy || picklist.values.length < 2}\n onClick={() => void save(sortPicklistValues(picklist), 'Sorted A–Z')}\n >\n {'Sort A–Z'}\n </Button>\n <Button size=\"small\" variant=\"outlined\" disabled={busy} onClick={openAdd}>\n {'Add value'}\n </Button>\n </Stack>\n ) : null}\n </Stack>\n {!ready ? null : (\n <ScrollTable size=\"small\" aria-label={`${definition.label} values`}>\n <TableHead>\n <TableRow>\n <TableCell sx={{ width: 120 }}>{'Order'}</TableCell>\n <TableCell>{'Value'}</TableCell>\n {groups.length ? <TableCell>{'Group'}</TableCell> : null}\n {meanings.length ? <TableCell>{'Means'}</TableCell> : null}\n <TableCell>{'Status'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {picklist.values.length === 0 ? (\n <TableRow>\n <TableCell colSpan={columns}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`No values yet. Add one, and every ${noun} can be given it.`}\n </Typography>\n </TableCell>\n </TableRow>\n ) : null}\n {picklist.values.map((value, index) => (\n <TableRow\n key={value.id}\n hover\n // Native drag and drop for a pointer; the arrows beside the\n // handle are the same move for a keyboard or a touch screen.\n draggable={!busy}\n onDragStart={(event) => {\n setDragFrom(index)\n event.dataTransfer.effectAllowed = 'move'\n }}\n onDragOver={(event) => {\n if (dragFrom === null) return\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n }}\n onDrop={(event) => {\n event.preventDefault()\n if (dragFrom !== null && dragFrom !== index) move(dragFrom, index)\n setDragFrom(null)\n }}\n onDragEnd={() => setDragFrom(null)}\n sx={{\n ...(value.active ? {} : { opacity: 0.6 }),\n ...(dragFrom === index ? { outline: 1, outlineColor: 'primary.main' } : {}),\n }}\n >\n <TableCell>\n <Stack direction=\"row\" spacing={0} sx={{ alignItems: 'center' }}>\n <MdiIcon\n path={mdiDragVertical.path}\n size={0.8}\n style={{ cursor: busy ? 'default' : 'grab' }}\n aria-hidden\n />\n <IconButton\n size=\"small\"\n disabled={index === 0 || busy}\n onClick={() => move(index, index - 1)}\n >\n <MdiIcon path={mdiArrowUp.path} size={0.7} />\n <SrOnly>{`Move ${value.label} up`}</SrOnly>\n </IconButton>\n <IconButton\n size=\"small\"\n disabled={index === picklist.values.length - 1 || busy}\n onClick={() => move(index, index + 1)}\n >\n <MdiIcon path={mdiArrowDown.path} size={0.7} />\n <SrOnly>{`Move ${value.label} down`}</SrOnly>\n </IconButton>\n </Stack>\n </TableCell>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{value.label}</Typography>\n {isStandard(value) ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Standard\" />\n ) : null}\n </Stack>\n </TableCell>\n {groups.length ? (\n <TableCell sx={{ minWidth: 150 }}>\n <TextField\n select\n size=\"small\"\n variant=\"standard\"\n value={value.group ?? ''}\n disabled={busy || !orgId}\n onChange={(event) => {\n const group = String(event.target.value) || null\n const name = groups.find((entry) => entry.id === group)?.label\n void save(\n setPicklistValueGroup(definition, picklist, value.id, group),\n name ? `“${value.label}” is ${name}` : `“${value.label}” has no group`,\n )\n }}\n fullWidth\n slotProps={{\n select: { displayEmpty: true, 'aria-label': `Group of ${value.label}` },\n }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {groups.map((group) => (\n <MenuItem key={group.id} value={group.id}>\n {group.label}\n </MenuItem>\n ))}\n </TextField>\n </TableCell>\n ) : null}\n {meanings.length ? (\n // What the value means to the CRM: fixed for a standard value, and\n // chosen when an added one is made.\n <TableCell>\n <Typography variant=\"body2\" color={value.meaning ? 'text.primary' : 'text.secondary'}>\n {value.meaning ? picklistMeaningLabel(definition, value.meaning) : '—'}\n </Typography>\n </TableCell>\n ) : null}\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', alignItems: 'center' }}>\n {value.active ? null : (\n <Chip size=\"small\" variant=\"outlined\" color=\"warning\" label=\"Inactive\" />\n )}\n {picklist.defaultValueId === value.id ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"primary\" label=\"Default\" />\n ) : null}\n {value.active && picklist.defaultValueId !== value.id ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Active'}\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 56 }}>\n <RowActionsMenu label={value.label} items={rowActions(value)} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n {ready && !stored ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'These are the standard values. Your first change here makes the list your ' +\n 'organization’s own.'}\n </Typography>\n ) : null}\n\n <Dialog open={nameDialog !== null} onClose={() => setNameDialog(null)} fullWidth maxWidth=\"xs\">\n <DialogTitle>\n {nameDialog?.mode === 'rename' ? `Rename “${nameDialog.value.label}”` : `Add a ${field}`}\n </DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n {nameDialog?.mode === 'rename' && !suggestions ? (\n <DialogContentText variant=\"body2\">\n {`Every ${sentenceList(holders.map(([one]) => one))} holding this value ` +\n 'is updated to the new name.'}\n </DialogContentText>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Value\"\n value={draftName}\n onChange={(event) => {\n setDraftName(event.target.value)\n setNameError('')\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter') void submitName()\n }}\n error={Boolean(nameError)}\n helperText={nameError || ' '}\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n autoFocus\n fullWidth\n />\n {nameDialog?.mode === 'add' && groups.length ? (\n <TextField\n select\n size=\"small\"\n label=\"Group\"\n value={draftGroup}\n onChange={(event) => setDraftGroup(String(event.target.value))}\n fullWidth\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {groups.map((group) => (\n <MenuItem key={group.id} value={group.id}>\n {group.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {nameDialog?.mode === 'add' && meanings.length ? (\n <TextField\n select\n size=\"small\"\n label=\"Means\"\n value={draftMeaning}\n onChange={(event) => {\n setDraftMeaning(String(event.target.value))\n setNameError('')\n }}\n fullWidth\n >\n {addableMeanings.map((meaning) => (\n <MenuItem key={meaning} value={meaning}>\n {picklistMeaningLabel(definition, meaning)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setNameDialog(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n disabled={busy || !draftName.trim()}\n onClick={() => void submitName()}\n >\n {nameDialog?.mode === 'rename' ? 'Rename' : 'Add'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog open={deleting !== null} onClose={() => setDeleting(null)} fullWidth maxWidth=\"xs\">\n <DialogTitle>{`Delete “${deleting?.label ?? ''}”?`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <DialogContentText variant=\"body2\">\n {suggestions\n ? `The suggestion leaves the list for good. No ${noun} changes.`\n : `The value leaves the list for good. ${heldBy} that hold it ` +\n (meanings.length\n ? 'are moved to the value you pick here, which means the same. '\n : 'are moved to the value you pick here, or cleared. ') +\n 'To keep it on those records instead, deactivate it.'}\n </DialogContentText>\n {suggestions ? null : (\n <TextField\n select\n size=\"small\"\n label=\"Replace it on existing records with\"\n value={replaceWith}\n onChange={(event) => setReplaceWith(String(event.target.value))}\n fullWidth\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n {/* A value with a meaning moves its records to another of that meaning, never off. */}\n {meanings.length ? null : <MenuItem value=\"\">{'None — clear it'}</MenuItem>}\n {replacements.map((value) => (\n <MenuItem key={value.id} value={value.label}>\n {value.label}\n </MenuItem>\n ))}\n </TextField>\n )}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDeleting(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"error\"\n disabled={busy || (meanings.length > 0 && !replaceWith)}\n onClick={() => void submitDelete()}\n >\n {'Delete value'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nPicklistValuesCard.displayName = 'PicklistValuesCard'\n\nexport default PicklistValuesCard\n"],"names":["CRM_COLLECTIONS","CRM_LEAD_TEXT_MAX","isStandardPicklistValueId","newResourceScopeFields","ORG_SCOPE_TOKEN","mdiArrowDown","mdiArrowUp","mdiDeleteOutline","mdiDragVertical","mdiEyeOffOutline","mdiEyeOutline","mdiPencilOutline","mdiStarOutline","MdiIcon","SrOnly","RowActionsMenu","ScrollTable","useSnackbar","authorizedFetch","useFirestore","useUser","writeGuardedBySeed","Button","Chip","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","IconButton","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","doc","serverTimestamp","setDoc","useCallback","useState","useCrmPicklist","addPicklistValue","crmPicklistValuesRouteUrl","movePicklistValue","picklistAddableMeanings","picklistMeaningLabel","picklistReplacements","setPicklistDefault","setPicklistValueActive","setPicklistValueGroup","sortPicklistValues","crmTaskCallScope","OBJECT_NOUNS","contact","company","deal","lead","task","sentenceList","words","length","slice","join","targetObjects","definition","Set","targets","map","target","object","PicklistValuesCard","props","picklistId","hostId","orgId","createHostId","firestore","data","user","enqueueSnackbar","picklist","stored","ready","fromCache","busy","setBusy","nameDialog","setNameDialog","draftName","setDraftName","draftGroup","setDraftGroup","draftMeaning","setDraftMeaning","nameError","setNameError","deleting","setDeleting","replaceWith","setReplaceWith","dragFrom","setDragFrom","groups","meanings","addableMeanings","noun","nouns","holders","suggestions","field","label","toLowerCase","failed","plural","isStandard","value","id","save","next","done","verdict","subject","picklists","values","defaultValueId","updatedAt","createdAt","merge","ok","message","variant","persist","error","console","allowDuplicate","post","request","scope","response","method","headers","body","JSON","stringify","payload","json","catch","moved","result","counts","count","updated","one","many","text","toLocaleString","some","entry","openAdd","mode","openRename","submitName","move","group","meaning","trim","action","valueId","openDelete","submitDelete","from","to","rowActions","isDefault","items","key","icon","path","size","disabled","onClick","active","disabledReason","undefined","push","destructive","replacements","columns","heldBy","replace","letter","toUpperCase","spacing","direction","xs","sm","sx","justifyContent","alignItems","color","restricted","flexShrink","aria-label","width","align","colSpan","index","hover","draggable","onDragStart","event","dataTransfer","effectAllowed","onDragOver","preventDefault","dropEffect","onDrop","onDragEnd","opacity","outline","outlineColor","style","cursor","aria-hidden","minWidth","select","onChange","String","name","find","fullWidth","slotProps","displayEmpty","flexWrap","open","onClose","maxWidth","pt","onKeyDown","Boolean","helperText","htmlInput","maxLength","autoFocus","inputLabel","shrink","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/picklist-values-card.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 CRM_COLLECTIONS,\n CRM_LEAD_TEXT_MAX,\n type CrmPicklist,\n type CrmPicklistDefinition,\n type CrmPicklistId,\n type CrmPicklistObject,\n type CrmPicklistValue,\n isStandardPicklistValueId,\n newResourceScopeFields,\n ORG_SCOPE_TOKEN,\n} from '@aglyn/aglyn'\nimport {\n mdiArrowDown,\n mdiArrowUp,\n mdiDeleteOutline,\n mdiDragVertical,\n mdiEyeOffOutline,\n mdiEyeOutline,\n mdiPencilOutline,\n mdiStarOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n IconButton,\n MenuItem,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, serverTimestamp, setDoc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useCrmPicklist } from '../hooks/use-crm-picklist'\nimport {\n addPicklistValue,\n crmPicklistValuesRouteUrl,\n movePicklistValue,\n picklistAddableMeanings,\n picklistKeepsGroups,\n picklistMeaningLabel,\n picklistReplacements,\n type PicklistValuesAction,\n type PicklistValuesRequest,\n type PicklistValuesResponse,\n setPicklistDefault,\n setPicklistValueActive,\n setPicklistValueGroup,\n sortPicklistValues,\n} from '../model/picklist-values'\nimport { crmTaskCallScope } from '../model/task-routes'\n\nexport interface PicklistValuesCardProps {\n /** Which of the CRM's standard picklists this card manages. */\n picklistId: CrmPicklistId\n /** The mounted site, or `null` at the organization level. */\n hostId: string | null\n /** The org whose list this is; `null` while it settles. */\n orgId: string | null\n /** The site a newly written list records as its definer — the Fields page's own. */\n createHostId: string | null\n}\n\n/** Each object's noun, singular and plural, for the card's sentences. */\nconst OBJECT_NOUNS: Record<CrmPicklistObject, [string, string]> = {\n contact: ['contact', 'contacts'],\n company: ['company', 'companies'],\n deal: ['deal', 'deals'],\n lead: ['lead', 'leads'],\n task: ['task', 'tasks'],\n}\n\n/** \"a\", \"a and b\", \"a, b and c\". */\nfunction sentenceList(words: readonly string[]): string {\n return words.length < 2\n ? (words[0] ?? '')\n : `${words.slice(0, -1).join(', ')} and ${words[words.length - 1]}`\n}\n\n/** The objects whose records hold the field's label, in the definition's order. */\nfunction targetObjects(definition: CrmPicklistDefinition): CrmPicklistObject[] {\n return [...new Set(definition.targets.map((target) => target.object))]\n}\n\n/** The add and rename dialog's state: which value, if any, is being renamed. */\ntype NameDialog = { mode: 'add' } | { mode: 'rename'; value: CrmPicklistValue } | null\n\n/**\n * ONE STANDARD PICKLIST'S VALUES (AGL-3298, AGL-3510) — Salesforce's\n * picklist value set for a standard field, managed on the Fields tab of the\n * object the field belongs to.\n *\n * Standard values are marked, and cannot be deleted — every org has them —\n * but rename, reorder, regroup, deactivate and default are theirs as much\n * as an added value's. Every move that touches only the LIST — add,\n * reorder by drag or arrows, sort A–Z, activate, deactivate, group,\n * default — is one client write of the whole document, guarded against a\n * cached read like every seeded save on the Fields page. The ones that\n * change RECORDS — rename, and delete with a replacement, and on a list\n * whose records keep the value's group (Lead source's direction, AGL-3577)\n * group and add — go to `crm/picklist-values`, which rewrites the list and\n * every record the definition's targets name together; see that route.\n *\n * An org that has never edited the list reads the standard values, and the\n * first move here writes them down as the org's own.\n */\nexport function PicklistValuesCard(props: PicklistValuesCardProps) {\n const { picklistId, hostId, orgId, createHostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { definition, picklist, stored, ready, fromCache } = useCrmPicklist(picklistId, orgId)\n const [busy, setBusy] = useState(false)\n const [nameDialog, setNameDialog] = useState<NameDialog>(null)\n const [draftName, setDraftName] = useState('')\n const [draftGroup, setDraftGroup] = useState('')\n const [draftMeaning, setDraftMeaning] = useState('')\n const [nameError, setNameError] = useState('')\n const [deleting, setDeleting] = useState<CrmPicklistValue | null>(null)\n const [replaceWith, setReplaceWith] = useState('')\n const [dragFrom, setDragFrom] = useState<number | null>(null)\n\n const groups = definition.groups ?? []\n // Whether records keep a value's group, so a regroup and an add change them too.\n const keepsGroups = picklistKeepsGroups(definition)\n const meanings = definition.meanings ?? []\n // An added value may not take a meaning only the platform sets (AGL-3512).\n const addableMeanings = picklistAddableMeanings(definition)\n const [noun, nouns] = OBJECT_NOUNS[definition.object]\n const holders = targetObjects(definition).map((object) => OBJECT_NOUNS[object])\n // A list of suggestions only — a task's subjects (AGL-3517) — that no record holds.\n const suggestions = holders.length === 0\n const field = definition.label.toLowerCase()\n const failed = `The ${definition.plural} could not be saved.`\n const isStandard = (value: CrmPicklistValue) => isStandardPicklistValueId(definition, value.id)\n\n /** Write the whole list — the list-only moves above. */\n const save = useCallback(\n async (next: CrmPicklist, done: string) => {\n if (!orgId || busy) return\n setBusy(true)\n try {\n const verdict = await writeGuardedBySeed({ subject: `${field} list`, fromCache }, () =>\n setDoc(\n doc(firestore, 'orgs', orgId, CRM_COLLECTIONS.picklists, picklistId),\n {\n values: next.values,\n defaultValueId: next.defaultValueId,\n updatedAt: serverTimestamp(),\n // The org-wide stamp the rules require of a new CRM document,\n // the same one a field definition carries.\n ...(stored\n ? {}\n : {\n hostId: createHostId,\n createdAt: serverTimestamp(),\n ...newResourceScopeFields([ORG_SCOPE_TOKEN]),\n }),\n },\n { merge: true },\n ),\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message, { variant: 'warning', persist: false })\n return\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar(failed, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(false)\n }\n },\n [orgId, busy, field, fromCache, firestore, picklistId, stored, createHostId, enqueueSnackbar, failed],\n )\n\n /** A rename or a delete — the moves that change records too. */\n const post = useCallback(\n async (request: PicklistValuesAction): Promise<PicklistValuesResponse | null> => {\n const scope = crmTaskCallScope(hostId, orgId)\n if (!scope || busy) return null\n setBusy(true)\n try {\n const response = await authorizedFetch(user, crmPicklistValuesRouteUrl(), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...request, picklistId, ...scope } satisfies PicklistValuesRequest),\n })\n const payload = (await response.json().catch(() => ({}))) as { error?: string }\n if (!response.ok) {\n enqueueSnackbar(payload.error || failed, { variant: 'warning', persist: false })\n return null\n }\n return payload as PicklistValuesResponse\n } catch (error) {\n console.error(error)\n enqueueSnackbar(failed, { variant: 'error', allowDuplicate: true })\n return null\n } finally {\n setBusy(false)\n }\n },\n [hostId, orgId, busy, user, picklistId, enqueueSnackbar, failed],\n )\n\n /** \" — 2 leads and 1 contact updated\", or nothing when no record held the value. */\n const moved = (result: PicklistValuesResponse) => {\n const counts = targetObjects(definition).map((object) => {\n const count = result.updated?.[object] ?? 0\n const [one, many] = OBJECT_NOUNS[object]\n return { count, text: `${count.toLocaleString()} ${count === 1 ? one : many}` }\n })\n return counts.some((entry) => entry.count)\n ? ` — ${sentenceList(counts.map((entry) => entry.text))} updated`\n : ''\n }\n\n const openAdd = () => {\n setDraftName('')\n setDraftGroup('')\n setDraftMeaning('')\n setNameError('')\n setNameDialog({ mode: 'add' })\n }\n const openRename = (value: CrmPicklistValue) => {\n setDraftName(value.label)\n setNameError('')\n setNameDialog({ mode: 'rename', value })\n }\n\n const submitName = async () => {\n if (!nameDialog) return\n if (nameDialog.mode === 'add') {\n const move = addPicklistValue(definition, picklist, draftName, {\n group: draftGroup || null,\n meaning: draftMeaning || null,\n })\n if (move.ok === false) return void setNameError(move.error)\n if (keepsGroups) {\n // Records may already hold the label, and keep its group (AGL-3577).\n const result = await post({\n action: 'add',\n label: draftName,\n group: draftGroup || null,\n meaning: draftMeaning || null,\n })\n if (!result) return\n setNameDialog(null)\n enqueueSnackbar(`“${draftName.trim()}” added${moved(result)}`, {\n variant: 'success',\n persist: false,\n })\n return\n }\n setNameDialog(null)\n await save(move.picklist, `“${draftName.trim()}” added`)\n return\n }\n const result = await post({ action: 'rename', valueId: nameDialog.value.id, label: draftName })\n if (!result) return\n setNameDialog(null)\n enqueueSnackbar(`Renamed${moved(result)}`, { variant: 'success', persist: false })\n }\n\n const openDelete = (value: CrmPicklistValue) => {\n setReplaceWith('')\n setDeleting(value)\n }\n const submitDelete = async () => {\n if (!deleting) return\n const result = await post({\n action: 'delete',\n valueId: deleting.id,\n replaceWith: replaceWith || null,\n })\n if (!result) return\n setDeleting(null)\n enqueueSnackbar(`“${deleting.label}” deleted${moved(result)}`, {\n variant: 'success',\n persist: false,\n })\n }\n\n const move = (from: number, to: number) =>\n void save(movePicklistValue(picklist, from, to), 'Order saved')\n\n const rowActions = (value: CrmPicklistValue): RowActionsMenuItem[] => {\n const isDefault = picklist.defaultValueId === value.id\n const items: RowActionsMenuItem[] = [\n {\n key: 'rename',\n label: 'Rename',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n disabled: busy,\n onClick: () => openRename(value),\n },\n {\n key: 'default',\n label: isDefault ? 'Clear default' : 'Make default',\n icon: <MdiIcon path={mdiStarOutline.path} size={0.8} />,\n disabled: busy || (!isDefault && !value.active),\n disabledReason: !value.active ? 'An inactive value cannot be the default' : undefined,\n onClick: () =>\n void save(\n setPicklistDefault(picklist, isDefault ? null : value.id),\n isDefault ? 'Default cleared' : `New ${nouns} start as “${value.label}”`,\n ),\n },\n {\n key: 'active',\n label: value.active ? 'Deactivate' : 'Activate',\n icon: (\n <MdiIcon path={value.active ? mdiEyeOffOutline.path : mdiEyeOutline.path} size={0.8} />\n ),\n disabled: busy,\n onClick: () =>\n void save(\n setPicklistValueActive(picklist, value.id, !value.active),\n value.active ? `“${value.label}” deactivated` : `“${value.label}” activated`,\n ),\n },\n ]\n // A standard value is every organization's: it is deactivated, never deleted.\n if (!isStandard(value)) {\n items.push({\n key: 'delete',\n label: 'Delete…',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: busy,\n onClick: () => openDelete(value),\n })\n }\n return items\n }\n\n const replacements = deleting ? picklistReplacements(definition, picklist, deleting.id) : []\n const columns = 4 + (groups.length ? 1 : 0) + (meanings.length ? 1 : 0)\n const heldBy = sentenceList(holders.map(([, many]) => many)).replace(/^./, (letter) =>\n letter.toUpperCase(),\n )\n\n return (\n <Stack spacing={1.5}>\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ justifyContent: 'space-between', alignItems: { sm: 'center' } }}\n >\n <Stack spacing={0.25}>\n <Typography variant=\"subtitle1\">{`${definition.label} values`}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {(suggestions\n ? `The suggestions every ${noun}’s ${definition.label} offers, in this order. ` +\n 'Any text may still be typed, and renaming or deleting a suggestion ' +\n `changes no ${noun}. `\n : `The choices in every ${noun}’s ${definition.label} select, in this order. ` +\n (definition.restricted\n ? 'An import or an API write naming anything else is refused. '\n : '') +\n 'A deactivated value stays on the records that hold it. ') +\n 'Standard values come with every organization and cannot be deleted; ' +\n 'add your own beside them.'}\n </Typography>\n </Stack>\n {orgId ? (\n <Stack direction=\"row\" spacing={1} sx={{ flexShrink: 0 }}>\n <Button\n size=\"small\"\n disabled={busy || picklist.values.length < 2}\n onClick={() => void save(sortPicklistValues(picklist), 'Sorted A–Z')}\n >\n {'Sort A–Z'}\n </Button>\n <Button size=\"small\" variant=\"outlined\" disabled={busy} onClick={openAdd}>\n {'Add value'}\n </Button>\n </Stack>\n ) : null}\n </Stack>\n {!ready ? null : (\n <ScrollTable size=\"small\" aria-label={`${definition.label} values`}>\n <TableHead>\n <TableRow>\n <TableCell sx={{ width: 120 }}>{'Order'}</TableCell>\n <TableCell>{'Value'}</TableCell>\n {groups.length ? <TableCell>{'Group'}</TableCell> : null}\n {meanings.length ? <TableCell>{'Means'}</TableCell> : null}\n <TableCell>{'Status'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {picklist.values.length === 0 ? (\n <TableRow>\n <TableCell colSpan={columns}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`No values yet. Add one, and every ${noun} can be given it.`}\n </Typography>\n </TableCell>\n </TableRow>\n ) : null}\n {picklist.values.map((value, index) => (\n <TableRow\n key={value.id}\n hover\n // Native drag and drop for a pointer; the arrows beside the\n // handle are the same move for a keyboard or a touch screen.\n draggable={!busy}\n onDragStart={(event) => {\n setDragFrom(index)\n event.dataTransfer.effectAllowed = 'move'\n }}\n onDragOver={(event) => {\n if (dragFrom === null) return\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n }}\n onDrop={(event) => {\n event.preventDefault()\n if (dragFrom !== null && dragFrom !== index) move(dragFrom, index)\n setDragFrom(null)\n }}\n onDragEnd={() => setDragFrom(null)}\n sx={{\n ...(value.active ? {} : { opacity: 0.6 }),\n ...(dragFrom === index ? { outline: 1, outlineColor: 'primary.main' } : {}),\n }}\n >\n <TableCell>\n <Stack direction=\"row\" spacing={0} sx={{ alignItems: 'center' }}>\n <MdiIcon\n path={mdiDragVertical.path}\n size={0.8}\n style={{ cursor: busy ? 'default' : 'grab' }}\n aria-hidden\n />\n <IconButton\n size=\"small\"\n disabled={index === 0 || busy}\n onClick={() => move(index, index - 1)}\n >\n <MdiIcon path={mdiArrowUp.path} size={0.7} />\n <SrOnly>{`Move ${value.label} up`}</SrOnly>\n </IconButton>\n <IconButton\n size=\"small\"\n disabled={index === picklist.values.length - 1 || busy}\n onClick={() => move(index, index + 1)}\n >\n <MdiIcon path={mdiArrowDown.path} size={0.7} />\n <SrOnly>{`Move ${value.label} down`}</SrOnly>\n </IconButton>\n </Stack>\n </TableCell>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{value.label}</Typography>\n {isStandard(value) ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Standard\" />\n ) : null}\n </Stack>\n </TableCell>\n {groups.length ? (\n <TableCell sx={{ minWidth: 150 }}>\n <TextField\n select\n size=\"small\"\n variant=\"standard\"\n value={value.group ?? ''}\n disabled={busy || !orgId}\n onChange={(event) => {\n const group = String(event.target.value) || null\n const name = groups.find((entry) => entry.id === group)?.label\n const done = name ? `“${value.label}” is ${name}` : `“${value.label}” has no group`\n if (!keepsGroups) {\n void save(setPicklistValueGroup(definition, picklist, value.id, group), done)\n return\n }\n // Its records keep the group too (AGL-3577): the route moves both.\n void post({ action: 'group', valueId: value.id, group }).then((result) => {\n if (result) {\n enqueueSnackbar(`${done}${moved(result)}`, { variant: 'success', persist: false })\n }\n })\n }}\n fullWidth\n slotProps={{\n select: { displayEmpty: true, 'aria-label': `Group of ${value.label}` },\n }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {groups.map((group) => (\n <MenuItem key={group.id} value={group.id}>\n {group.label}\n </MenuItem>\n ))}\n </TextField>\n </TableCell>\n ) : null}\n {meanings.length ? (\n // What the value means to the CRM: fixed for a standard value, and\n // chosen when an added one is made.\n <TableCell>\n <Typography variant=\"body2\" color={value.meaning ? 'text.primary' : 'text.secondary'}>\n {value.meaning ? picklistMeaningLabel(definition, value.meaning) : '—'}\n </Typography>\n </TableCell>\n ) : null}\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', alignItems: 'center' }}>\n {value.active ? null : (\n <Chip size=\"small\" variant=\"outlined\" color=\"warning\" label=\"Inactive\" />\n )}\n {picklist.defaultValueId === value.id ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"primary\" label=\"Default\" />\n ) : null}\n {value.active && picklist.defaultValueId !== value.id ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Active'}\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 56 }}>\n <RowActionsMenu label={value.label} items={rowActions(value)} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n {ready && !stored ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'These are the standard values. Your first change here makes the list your ' +\n 'organization’s own.'}\n </Typography>\n ) : null}\n\n <Dialog open={nameDialog !== null} onClose={() => setNameDialog(null)} fullWidth maxWidth=\"xs\">\n <DialogTitle>\n {nameDialog?.mode === 'rename' ? `Rename “${nameDialog.value.label}”` : `Add a ${field}`}\n </DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n {nameDialog?.mode === 'rename' && !suggestions ? (\n <DialogContentText variant=\"body2\">\n {`Every ${sentenceList(holders.map(([one]) => one))} holding this value ` +\n 'is updated to the new name.'}\n </DialogContentText>\n ) : null}\n <TextField\n size=\"small\"\n label=\"Value\"\n value={draftName}\n onChange={(event) => {\n setDraftName(event.target.value)\n setNameError('')\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter') void submitName()\n }}\n error={Boolean(nameError)}\n helperText={nameError || ' '}\n slotProps={{ htmlInput: { maxLength: CRM_LEAD_TEXT_MAX } }}\n autoFocus\n fullWidth\n />\n {nameDialog?.mode === 'add' && groups.length ? (\n <TextField\n select\n size=\"small\"\n label=\"Group\"\n value={draftGroup}\n onChange={(event) => setDraftGroup(String(event.target.value))}\n fullWidth\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {groups.map((group) => (\n <MenuItem key={group.id} value={group.id}>\n {group.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {nameDialog?.mode === 'add' && meanings.length ? (\n <TextField\n select\n size=\"small\"\n label=\"Means\"\n value={draftMeaning}\n onChange={(event) => {\n setDraftMeaning(String(event.target.value))\n setNameError('')\n }}\n fullWidth\n >\n {addableMeanings.map((meaning) => (\n <MenuItem key={meaning} value={meaning}>\n {picklistMeaningLabel(definition, meaning)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setNameDialog(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n disabled={busy || !draftName.trim()}\n onClick={() => void submitName()}\n >\n {nameDialog?.mode === 'rename' ? 'Rename' : 'Add'}\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog open={deleting !== null} onClose={() => setDeleting(null)} fullWidth maxWidth=\"xs\">\n <DialogTitle>{`Delete “${deleting?.label ?? ''}”?`}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <DialogContentText variant=\"body2\">\n {suggestions\n ? `The suggestion leaves the list for good. No ${noun} changes.`\n : `The value leaves the list for good. ${heldBy} that hold it ` +\n (meanings.length\n ? 'are moved to the value you pick here, which means the same. '\n : 'are moved to the value you pick here, or cleared. ') +\n 'To keep it on those records instead, deactivate it.'}\n </DialogContentText>\n {suggestions ? null : (\n <TextField\n select\n size=\"small\"\n label=\"Replace it on existing records with\"\n value={replaceWith}\n onChange={(event) => setReplaceWith(String(event.target.value))}\n fullWidth\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n >\n {/* A value with a meaning moves its records to another of that meaning, never off. */}\n {meanings.length ? null : <MenuItem value=\"\">{'None — clear it'}</MenuItem>}\n {replacements.map((value) => (\n <MenuItem key={value.id} value={value.label}>\n {value.label}\n </MenuItem>\n ))}\n </TextField>\n )}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDeleting(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"error\"\n disabled={busy || (meanings.length > 0 && !replaceWith)}\n onClick={() => void submitDelete()}\n >\n {'Delete value'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nPicklistValuesCard.displayName = 'PicklistValuesCard'\n\nexport default PicklistValuesCard\n"],"names":["CRM_COLLECTIONS","CRM_LEAD_TEXT_MAX","isStandardPicklistValueId","newResourceScopeFields","ORG_SCOPE_TOKEN","mdiArrowDown","mdiArrowUp","mdiDeleteOutline","mdiDragVertical","mdiEyeOffOutline","mdiEyeOutline","mdiPencilOutline","mdiStarOutline","MdiIcon","SrOnly","RowActionsMenu","ScrollTable","useSnackbar","authorizedFetch","useFirestore","useUser","writeGuardedBySeed","Button","Chip","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","IconButton","MenuItem","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","doc","serverTimestamp","setDoc","useCallback","useState","useCrmPicklist","addPicklistValue","crmPicklistValuesRouteUrl","movePicklistValue","picklistAddableMeanings","picklistKeepsGroups","picklistMeaningLabel","picklistReplacements","setPicklistDefault","setPicklistValueActive","setPicklistValueGroup","sortPicklistValues","crmTaskCallScope","OBJECT_NOUNS","contact","company","deal","lead","task","sentenceList","words","length","slice","join","targetObjects","definition","Set","targets","map","target","object","PicklistValuesCard","props","picklistId","hostId","orgId","createHostId","firestore","data","user","enqueueSnackbar","picklist","stored","ready","fromCache","busy","setBusy","nameDialog","setNameDialog","draftName","setDraftName","draftGroup","setDraftGroup","draftMeaning","setDraftMeaning","nameError","setNameError","deleting","setDeleting","replaceWith","setReplaceWith","dragFrom","setDragFrom","groups","keepsGroups","meanings","addableMeanings","noun","nouns","holders","suggestions","field","label","toLowerCase","failed","plural","isStandard","value","id","save","next","done","verdict","subject","picklists","values","defaultValueId","updatedAt","createdAt","merge","ok","message","variant","persist","error","console","allowDuplicate","post","request","scope","response","method","headers","body","JSON","stringify","payload","json","catch","moved","result","counts","count","updated","one","many","text","toLocaleString","some","entry","openAdd","mode","openRename","submitName","move","group","meaning","action","trim","valueId","openDelete","submitDelete","from","to","rowActions","isDefault","items","key","icon","path","size","disabled","onClick","active","disabledReason","undefined","push","destructive","replacements","columns","heldBy","replace","letter","toUpperCase","spacing","direction","xs","sm","sx","justifyContent","alignItems","color","restricted","flexShrink","aria-label","width","align","colSpan","index","hover","draggable","onDragStart","event","dataTransfer","effectAllowed","onDragOver","preventDefault","dropEffect","onDrop","onDragEnd","opacity","outline","outlineColor","style","cursor","aria-hidden","minWidth","select","onChange","String","name","find","then","fullWidth","slotProps","displayEmpty","flexWrap","open","onClose","maxWidth","pt","onKeyDown","Boolean","helperText","htmlInput","maxLength","autoFocus","inputLabel","shrink","displayName"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type CrmListCollection } from '@aglyn/aglyn';
17
+ import { type CrmListCollection, type CrmListFieldsContext } from '@aglyn/aglyn';
18
18
  import type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter';
19
19
  import type { ListFilterOption } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
20
20
  import type { ListQueryDeclaration, ListQueryFilter, ListQueryPlan } from '@aglyn/shared-ui-jsx/const/list-query-plan';
@@ -84,9 +84,10 @@ export declare function crmSelectField(column: string, path?: string): ListFilte
84
84
  * sentinel in the patch reads as the field removed — through
85
85
  * `crmListFields`, narrowed to `fields`. A client never writes a field the
86
86
  * rules keep for the server (a lead's `emailStatus`), which is why the
87
- * caller names the ones it may.
87
+ * caller names the ones it may. A lead's direction is read off the org's
88
+ * lead source list, which the caller passes in `context` once it has it.
88
89
  */
89
- export declare function crmClientListFields(collection: CrmListCollection, record: Readonly<Record<string, unknown>>, patch: Readonly<Record<string, unknown>>, fields: readonly string[]): Record<string, unknown>;
90
+ export declare function crmClientListFields(collection: CrmListCollection, record: Readonly<Record<string, unknown>>, patch: Readonly<Record<string, unknown>>, fields: readonly string[], context?: CrmListFieldsContext): Record<string, unknown>;
90
91
  /** The search fields every CRM record carries — what a client edit of searched text rewrites. */
91
92
  export declare const CRM_CLIENT_SEARCH_FIELDS: readonly ["searchTokens", "scopedSearchTokens"];
92
93
  /**
@@ -159,15 +159,16 @@ const sameClause = (a, b)=>a.field === b.field && a.op === b.op && a.value === b
159
159
  * sentinel in the patch reads as the field removed — through
160
160
  * `crmListFields`, narrowed to `fields`. A client never writes a field the
161
161
  * rules keep for the server (a lead's `emailStatus`), which is why the
162
- * caller names the ones it may.
163
- */ export function crmClientListFields(collection, record, patch, fields) {
162
+ * caller names the ones it may. A lead's direction is read off the org's
163
+ * lead source list, which the caller passes in `context` once it has it.
164
+ */ export function crmClientListFields(collection, record, patch, fields, context = {}) {
164
165
  const merged = _extends({}, record);
165
166
  for (const [key, value] of Object.entries(patch)){
166
167
  // A dotted path is a map entry (`custom.tier`), never a searched field.
167
168
  if (key.includes('.')) continue;
168
169
  merged[key] = value === null || isSentinel(value) ? undefined : value;
169
170
  }
170
- const computed = crmListFields(collection, merged);
171
+ const computed = crmListFields(collection, merged, context);
171
172
  return Object.fromEntries(fields.filter((field)=>field in computed).map((field)=>[
172
173
  field,
173
174
  computed[field]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/crm-list-query.ts"],"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\nimport {\n CRM_SCOPED_SEARCH_TOKENS_FIELD,\n CRM_SEARCH_TOKENS_FIELD,\n type CrmListCollection,\n crmListFields,\n} from '@aglyn/aglyn'\nimport { scopedSearch } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type { ListFilterOption } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { listQueryRefusals } from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQueryPlan,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\n\n/*\n * THE CRM LISTS ON THEIR QUERIES (AGL-3321).\n *\n * Every CRM list — Leads, Contacts, Companies, Deals, Tasks — declares its\n * filterable fields, its one order and its search as a\n * `ListQueryDeclaration`, and `planListQuery` puts every clause and the\n * search word on ONE Firestore query paged by `useListQuery`. What a query\n * cannot hold is refused by name (`ListQueryNotices`), never matched over\n * the rows a page happened to load.\n *\n * This module is what the five share: the search every CRM record carries\n * (`searchTokens`, and `scopedSearchTokens` under a site — see\n * `crmListFields` in `@aglyn/aglyn`), the scope clause as the plan's base,\n * a stored clause translated to the one its query asks, and the refusals\n * read back through the clause the reader set.\n */\n\n/** Every CRM list's quick search: the tokens every writer stamps. */\nexport const CRM_LIST_SEARCH: NonNullable<ListQueryDeclaration['search']> = {\n tokensPath: CRM_SEARCH_TOKENS_FIELD,\n scoped: scopedSearch(CRM_SCOPED_SEARCH_TOKENS_FIELD),\n}\n\n/**\n * The scope clause as the plan's base: `visibleTo array-contains-any` over\n * the reader's tokens under a site, nothing at the organization level\n * (`null` tokens), where an org-wide member's listeners carry none.\n */\nexport function crmListBase(visibleTo: readonly string[] | null): ListQueryFilter[] {\n return visibleTo ? [{ path: 'visibleTo', op: 'array-contains-any', value: [...visibleTo] }] : []\n}\n\n/** The base's shape, for `listQueryIndexes`: the scope clause is an array clause. */\nexport const CRM_LIST_BASE_INDEX = [{ path: 'visibleTo', array: true }] as const\n\n/**\n * The declaration a reader may run.\n *\n * Under a site the search folds into the scope clause (`search.scoped`),\n * and a query without `visibleTo` is one the rules can prove only for an\n * ORG-WIDE member — `canReadScoped()` short-circuits on it, and for anybody\n * else inspects `visibleTo`, which the folded query no longer constrains.\n * A member whose reach is some sites keeps the scope clause, and the plan\n * refuses their search by name rather than sending a query the rules deny.\n */\nexport function crmListDeclarationFor(\n declaration: ListQueryDeclaration,\n foldsScope: boolean,\n): ListQueryDeclaration {\n if (foldsScope || !declaration.search?.scoped) return declaration\n return { ...declaration, search: { tokensPath: declaration.search.tokensPath } }\n}\n\n/*------------------------------------------\n * WHAT A LIST STORES, AND WHAT IT ASKS.\n *\n * A saved view stores clauses in the grid's grammar — \"Status is Open\",\n * \"Owner is Dana\", \"Lead source is none\" — and some of them name no stored\n * field one query can ask as it is: Open is two statuses, an owner under a\n * site is that holder's facet key, a lead source is compared by its key. A\n * list translates each stored clause into the clause its query asks, plans\n * those, and reads the plan's refusals back through the stored clause, so\n * the notice names what the reader set (\"Status is Open is not applied\")\n * and never the field it was translated to.\n *-----------------------------------------*/\n\n/** How one stored clause is asked: a query clause, or why it cannot be. */\nexport type CrmClauseAsked = ListFilterRequest | { refused: string }\n\nexport interface CrmAskedClauses {\n /** What the plan is asked, in the order the reader set them. */\n clauses: ListFilterRequest[]\n /** The stored clause each asked one stands for, index for index. */\n origins: ListFilterRequest[]\n /** Stored clauses refused before the plan saw them, with why. */\n refused: Array<{ clause: ListFilterRequest; reason: string }>\n}\n\n/** Every stored clause through `ask`, keeping where each came from. */\nexport function crmAskClauses(\n stored: readonly ListFilterRequest[],\n ask: (clause: ListFilterRequest) => CrmClauseAsked,\n): CrmAskedClauses {\n const clauses: ListFilterRequest[] = []\n const origins: ListFilterRequest[] = []\n const refused: CrmAskedClauses['refused'] = []\n for (const clause of stored) {\n const asked = ask(clause)\n if ('refused' in asked) {\n refused.push({ clause, reason: asked.refused })\n } else {\n clauses.push(asked)\n origins.push(clause)\n }\n }\n return { clauses, origins, refused }\n}\n\nconst sameClause = (a: ListFilterRequest, b: ListFilterRequest): boolean =>\n a.field === b.field && a.op === b.op && a.value === b.value\n\n/**\n * The plan's refusals and the translation's, each named by the clause the\n * reader SET, as `ListQueryNotices` takes them — through the platform's one\n * wording (`listQueryRefusals`), so a notice and its chip read alike.\n */\nexport function crmQueryRefusals(\n plan: Pick<ListQueryPlan, 'refused'>,\n asked: CrmAskedClauses,\n context: {\n fields: readonly ListFilterField[]\n headers?: Readonly<Record<string, string>>\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n },\n): Array<{ label: string; reason: string }> {\n const planned = plan.refused.map((entry) => {\n if (entry.clause === 'search') return entry\n const clause = entry.clause\n const at = asked.clauses.findIndex((candidate) => sameClause(candidate, clause))\n return { clause: at >= 0 ? asked.origins[at] : clause, reason: entry.reason }\n })\n return listQueryRefusals([...asked.refused, ...planned], context)\n}\n\n/** The comma-separated values of an `isAnyOf`, or the one value of anything else. */\nexport function crmClauseValues(clause: Pick<ListFilterRequest, 'op' | 'value'>): string[] {\n const raw = clause.op === 'isAnyOf' ? clause.value.split(',') : [clause.value]\n return [...new Set(raw.map((value) => value.trim()).filter(Boolean))]\n}\n\n/** One value as `equals`, several as `isAnyOf` — the shape the plan takes a list in. */\nexport function crmAnyOf(field: string, values: readonly string[]): CrmClauseAsked {\n const unique = [...new Set(values)]\n if (!unique.length) return { refused: 'no value yet' }\n return unique.length === 1\n ? { field, op: 'equals', value: unique[0] }\n : { field, op: 'isAnyOf', value: unique.join(',') }\n}\n\n/** A `select` field: equality and \"any of\" over stored values, nothing else. */\nexport function crmSelectField(column: string, path: string = column): ListFilterField {\n return { column, kind: 'exact', path, operators: ['equals', 'isAnyOf'] }\n}\n\n/**\n * The list fields a CLIENT edit writes beside its patch (AGL-3321): the\n * record as the listener holds it with the patch laid over — a `null` or a\n * sentinel in the patch reads as the field removed — through\n * `crmListFields`, narrowed to `fields`. A client never writes a field the\n * rules keep for the server (a lead's `emailStatus`), which is why the\n * caller names the ones it may.\n */\nexport function crmClientListFields(\n collection: CrmListCollection,\n record: Readonly<Record<string, unknown>>,\n patch: Readonly<Record<string, unknown>>,\n fields: readonly string[],\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...record }\n for (const [key, value] of Object.entries(patch)) {\n // A dotted path is a map entry (`custom.tier`), never a searched field.\n if (key.includes('.')) continue\n merged[key] = value === null || isSentinel(value) ? undefined : value\n }\n const computed = crmListFields(collection, merged)\n return Object.fromEntries(fields.filter((field) => field in computed).map((field) => [field, computed[field]]))\n}\n\n/** A web-SDK `FieldValue` — `deleteField()`, the one a client edit of these fields sends. */\nconst isSentinel = (value: unknown): boolean =>\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date) &&\n typeof (value as { isEqual?: unknown }).isEqual === 'function'\n\n/** The search fields every CRM record carries — what a client edit of searched text rewrites. */\nexport const CRM_CLIENT_SEARCH_FIELDS = ['searchTokens', 'scopedSearchTokens'] as const\n\n\n/**\n * The list fields a deal's contact roles are found by (AGL-3521) — what a\n * client write of `contactRoles` or `contactId` rewrites beside them.\n */\nexport const CRM_DEAL_CONTACT_ROLE_LIST_FIELDS = [\n 'contactRoleContactIds',\n 'scopedContactRoleContactIds',\n 'contactRoleKeys',\n] as const\n"],"names":["CRM_SCOPED_SEARCH_TOKENS_FIELD","CRM_SEARCH_TOKENS_FIELD","crmListFields","scopedSearch","listQueryRefusals","CRM_LIST_SEARCH","tokensPath","scoped","crmListBase","visibleTo","path","op","value","CRM_LIST_BASE_INDEX","array","crmListDeclarationFor","declaration","foldsScope","search","crmAskClauses","stored","ask","clauses","origins","refused","clause","asked","push","reason","sameClause","a","b","field","crmQueryRefusals","plan","context","planned","map","entry","at","findIndex","candidate","crmClauseValues","raw","split","Set","trim","filter","Boolean","crmAnyOf","values","unique","length","join","crmSelectField","column","kind","operators","crmClientListFields","collection","record","patch","fields","merged","key","Object","entries","includes","isSentinel","undefined","computed","fromEntries","Array","isArray","Date","isEqual","CRM_CLIENT_SEARCH_FIELDS","CRM_DEAL_CONTACT_ROLE_LIST_FIELDS"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,8BAA8B,EAC9BC,uBAAuB,EAEvBC,aAAa,QACR,eAAc;AACrB,SAASC,YAAY,QAAQ,qCAAoC;AAGjE,SAASC,iBAAiB,QAAQ,+DAA8D;AAOhG;;;;;;;;;;;;;;;CAeC,GAED,mEAAmE,GACnE,OAAO,MAAMC,kBAA+D;IAC1EC,YAAYL;IACZM,QAAQJ,aAAaH;AACvB,EAAC;AAED;;;;CAIC,GACD,OAAO,SAASQ,YAAYC,SAAmC;IAC7D,OAAOA,YAAY;QAAC;YAAEC,MAAM;YAAaC,IAAI;YAAsBC,OAAO;mBAAIH;aAAU;QAAC;KAAE,GAAG,EAAE;AAClG;AAEA,mFAAmF,GACnF,OAAO,MAAMI,sBAAsB;IAAC;QAAEH,MAAM;QAAaI,OAAO;IAAK;CAAE,CAAS;AAEhF;;;;;;;;;CASC,GACD,OAAO,SAASC,sBACdC,WAAiC,EACjCC,UAAmB;QAEAD;IAAnB,IAAIC,cAAc,GAACD,sBAAAA,YAAYE,MAAM,qBAAlBF,oBAAoBT,MAAM,GAAE,OAAOS;IACtD,OAAO,aAAKA;QAAaE,QAAQ;YAAEZ,YAAYU,YAAYE,MAAM,CAACZ,UAAU;QAAC;;AAC/E;AA2BA,qEAAqE,GACrE,OAAO,SAASa,cACdC,MAAoC,EACpCC,GAAkD;IAElD,MAAMC,UAA+B,EAAE;IACvC,MAAMC,UAA+B,EAAE;IACvC,MAAMC,UAAsC,EAAE;IAC9C,KAAK,MAAMC,UAAUL,OAAQ;QAC3B,MAAMM,QAAQL,IAAII;QAClB,IAAI,aAAaC,OAAO;YACtBF,QAAQG,IAAI,CAAC;gBAAEF;gBAAQG,QAAQF,MAAMF,OAAO;YAAC;QAC/C,OAAO;YACLF,QAAQK,IAAI,CAACD;YACbH,QAAQI,IAAI,CAACF;QACf;IACF;IACA,OAAO;QAAEH;QAASC;QAASC;IAAQ;AACrC;AAEA,MAAMK,aAAa,CAACC,GAAsBC,IACxCD,EAAEE,KAAK,KAAKD,EAAEC,KAAK,IAAIF,EAAEnB,EAAE,KAAKoB,EAAEpB,EAAE,IAAImB,EAAElB,KAAK,KAAKmB,EAAEnB,KAAK;AAE7D;;;;CAIC,GACD,OAAO,SAASqB,iBACdC,IAAoC,EACpCR,KAAsB,EACtBS,OAIC;IAED,MAAMC,UAAUF,KAAKV,OAAO,CAACa,GAAG,CAAC,CAACC;QAChC,IAAIA,MAAMb,MAAM,KAAK,UAAU,OAAOa;QACtC,MAAMb,SAASa,MAAMb,MAAM;QAC3B,MAAMc,KAAKb,MAAMJ,OAAO,CAACkB,SAAS,CAAC,CAACC,YAAcZ,WAAWY,WAAWhB;QACxE,OAAO;YAAEA,QAAQc,MAAM,IAAIb,MAAMH,OAAO,CAACgB,GAAG,GAAGd;YAAQG,QAAQU,MAAMV,MAAM;QAAC;IAC9E;IACA,OAAOxB,kBAAkB;WAAIsB,MAAMF,OAAO;WAAKY;KAAQ,EAAED;AAC3D;AAEA,mFAAmF,GACnF,OAAO,SAASO,gBAAgBjB,MAA+C;IAC7E,MAAMkB,MAAMlB,OAAOd,EAAE,KAAK,YAAYc,OAAOb,KAAK,CAACgC,KAAK,CAAC,OAAO;QAACnB,OAAOb,KAAK;KAAC;IAC9E,OAAO;WAAI,IAAIiC,IAAIF,IAAIN,GAAG,CAAC,CAACzB,QAAUA,MAAMkC,IAAI,IAAIC,MAAM,CAACC;KAAU;AACvE;AAEA,sFAAsF,GACtF,OAAO,SAASC,SAASjB,KAAa,EAAEkB,MAAyB;IAC/D,MAAMC,SAAS;WAAI,IAAIN,IAAIK;KAAQ;IACnC,IAAI,CAACC,OAAOC,MAAM,EAAE,OAAO;QAAE5B,SAAS;IAAe;IACrD,OAAO2B,OAAOC,MAAM,KAAK,IACrB;QAAEpB;QAAOrB,IAAI;QAAUC,OAAOuC,MAAM,CAAC,EAAE;IAAC,IACxC;QAAEnB;QAAOrB,IAAI;QAAWC,OAAOuC,OAAOE,IAAI,CAAC;IAAK;AACtD;AAEA,8EAA8E,GAC9E,OAAO,SAASC,eAAeC,MAAc,EAAE7C,OAAe6C,MAAM;IAClE,OAAO;QAAEA;QAAQC,MAAM;QAAS9C;QAAM+C,WAAW;YAAC;YAAU;SAAU;IAAC;AACzE;AAEA;;;;;;;CAOC,GACD,OAAO,SAASC,oBACdC,UAA6B,EAC7BC,MAAyC,EACzCC,KAAwC,EACxCC,MAAyB;IAEzB,MAAMC,SAAkC,aAAKH;IAC7C,KAAK,MAAM,CAACI,KAAKpD,MAAM,IAAIqD,OAAOC,OAAO,CAACL,OAAQ;QAChD,wEAAwE;QACxE,IAAIG,IAAIG,QAAQ,CAAC,MAAM;QACvBJ,MAAM,CAACC,IAAI,GAAGpD,UAAU,QAAQwD,WAAWxD,SAASyD,YAAYzD;IAClE;IACA,MAAM0D,WAAWpE,cAAcyD,YAAYI;IAC3C,OAAOE,OAAOM,WAAW,CAACT,OAAOf,MAAM,CAAC,CAACf,QAAUA,SAASsC,UAAUjC,GAAG,CAAC,CAACL,QAAU;YAACA;YAAOsC,QAAQ,CAACtC,MAAM;SAAC;AAC/G;AAEA,2FAA2F,GAC3F,MAAMoC,aAAa,CAACxD,QAClB,OAAOA,UAAU,YACjBA,UAAU,QACV,CAAC4D,MAAMC,OAAO,CAAC7D,UACf,CAAEA,CAAAA,iBAAiB8D,IAAG,KACtB,OAAO,AAAC9D,MAAgC+D,OAAO,KAAK;AAEtD,+FAA+F,GAC/F,OAAO,MAAMC,2BAA2B;IAAC;IAAgB;CAAqB,CAAS;AAGvF;;;CAGC,GACD,OAAO,MAAMC,oCAAoC;IAC/C;IACA;IACA;CACD,CAAS"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/model/crm-list-query.ts"],"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\nimport {\n CRM_SCOPED_SEARCH_TOKENS_FIELD,\n CRM_SEARCH_TOKENS_FIELD,\n type CrmListCollection,\n type CrmListFieldsContext,\n crmListFields,\n} from '@aglyn/aglyn'\nimport { scopedSearch } from '@aglyn/aglyn/app-utils/name-search'\nimport type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type { ListFilterOption } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { listQueryRefusals } from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQueryPlan,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\n\n/*\n * THE CRM LISTS ON THEIR QUERIES (AGL-3321).\n *\n * Every CRM list — Leads, Contacts, Companies, Deals, Tasks — declares its\n * filterable fields, its one order and its search as a\n * `ListQueryDeclaration`, and `planListQuery` puts every clause and the\n * search word on ONE Firestore query paged by `useListQuery`. What a query\n * cannot hold is refused by name (`ListQueryNotices`), never matched over\n * the rows a page happened to load.\n *\n * This module is what the five share: the search every CRM record carries\n * (`searchTokens`, and `scopedSearchTokens` under a site — see\n * `crmListFields` in `@aglyn/aglyn`), the scope clause as the plan's base,\n * a stored clause translated to the one its query asks, and the refusals\n * read back through the clause the reader set.\n */\n\n/** Every CRM list's quick search: the tokens every writer stamps. */\nexport const CRM_LIST_SEARCH: NonNullable<ListQueryDeclaration['search']> = {\n tokensPath: CRM_SEARCH_TOKENS_FIELD,\n scoped: scopedSearch(CRM_SCOPED_SEARCH_TOKENS_FIELD),\n}\n\n/**\n * The scope clause as the plan's base: `visibleTo array-contains-any` over\n * the reader's tokens under a site, nothing at the organization level\n * (`null` tokens), where an org-wide member's listeners carry none.\n */\nexport function crmListBase(visibleTo: readonly string[] | null): ListQueryFilter[] {\n return visibleTo ? [{ path: 'visibleTo', op: 'array-contains-any', value: [...visibleTo] }] : []\n}\n\n/** The base's shape, for `listQueryIndexes`: the scope clause is an array clause. */\nexport const CRM_LIST_BASE_INDEX = [{ path: 'visibleTo', array: true }] as const\n\n/**\n * The declaration a reader may run.\n *\n * Under a site the search folds into the scope clause (`search.scoped`),\n * and a query without `visibleTo` is one the rules can prove only for an\n * ORG-WIDE member — `canReadScoped()` short-circuits on it, and for anybody\n * else inspects `visibleTo`, which the folded query no longer constrains.\n * A member whose reach is some sites keeps the scope clause, and the plan\n * refuses their search by name rather than sending a query the rules deny.\n */\nexport function crmListDeclarationFor(\n declaration: ListQueryDeclaration,\n foldsScope: boolean,\n): ListQueryDeclaration {\n if (foldsScope || !declaration.search?.scoped) return declaration\n return { ...declaration, search: { tokensPath: declaration.search.tokensPath } }\n}\n\n/*------------------------------------------\n * WHAT A LIST STORES, AND WHAT IT ASKS.\n *\n * A saved view stores clauses in the grid's grammar — \"Status is Open\",\n * \"Owner is Dana\", \"Lead source is none\" — and some of them name no stored\n * field one query can ask as it is: Open is two statuses, an owner under a\n * site is that holder's facet key, a lead source is compared by its key. A\n * list translates each stored clause into the clause its query asks, plans\n * those, and reads the plan's refusals back through the stored clause, so\n * the notice names what the reader set (\"Status is Open is not applied\")\n * and never the field it was translated to.\n *-----------------------------------------*/\n\n/** How one stored clause is asked: a query clause, or why it cannot be. */\nexport type CrmClauseAsked = ListFilterRequest | { refused: string }\n\nexport interface CrmAskedClauses {\n /** What the plan is asked, in the order the reader set them. */\n clauses: ListFilterRequest[]\n /** The stored clause each asked one stands for, index for index. */\n origins: ListFilterRequest[]\n /** Stored clauses refused before the plan saw them, with why. */\n refused: Array<{ clause: ListFilterRequest; reason: string }>\n}\n\n/** Every stored clause through `ask`, keeping where each came from. */\nexport function crmAskClauses(\n stored: readonly ListFilterRequest[],\n ask: (clause: ListFilterRequest) => CrmClauseAsked,\n): CrmAskedClauses {\n const clauses: ListFilterRequest[] = []\n const origins: ListFilterRequest[] = []\n const refused: CrmAskedClauses['refused'] = []\n for (const clause of stored) {\n const asked = ask(clause)\n if ('refused' in asked) {\n refused.push({ clause, reason: asked.refused })\n } else {\n clauses.push(asked)\n origins.push(clause)\n }\n }\n return { clauses, origins, refused }\n}\n\nconst sameClause = (a: ListFilterRequest, b: ListFilterRequest): boolean =>\n a.field === b.field && a.op === b.op && a.value === b.value\n\n/**\n * The plan's refusals and the translation's, each named by the clause the\n * reader SET, as `ListQueryNotices` takes them — through the platform's one\n * wording (`listQueryRefusals`), so a notice and its chip read alike.\n */\nexport function crmQueryRefusals(\n plan: Pick<ListQueryPlan, 'refused'>,\n asked: CrmAskedClauses,\n context: {\n fields: readonly ListFilterField[]\n headers?: Readonly<Record<string, string>>\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n },\n): Array<{ label: string; reason: string }> {\n const planned = plan.refused.map((entry) => {\n if (entry.clause === 'search') return entry\n const clause = entry.clause\n const at = asked.clauses.findIndex((candidate) => sameClause(candidate, clause))\n return { clause: at >= 0 ? asked.origins[at] : clause, reason: entry.reason }\n })\n return listQueryRefusals([...asked.refused, ...planned], context)\n}\n\n/** The comma-separated values of an `isAnyOf`, or the one value of anything else. */\nexport function crmClauseValues(clause: Pick<ListFilterRequest, 'op' | 'value'>): string[] {\n const raw = clause.op === 'isAnyOf' ? clause.value.split(',') : [clause.value]\n return [...new Set(raw.map((value) => value.trim()).filter(Boolean))]\n}\n\n/** One value as `equals`, several as `isAnyOf` — the shape the plan takes a list in. */\nexport function crmAnyOf(field: string, values: readonly string[]): CrmClauseAsked {\n const unique = [...new Set(values)]\n if (!unique.length) return { refused: 'no value yet' }\n return unique.length === 1\n ? { field, op: 'equals', value: unique[0] }\n : { field, op: 'isAnyOf', value: unique.join(',') }\n}\n\n/** A `select` field: equality and \"any of\" over stored values, nothing else. */\nexport function crmSelectField(column: string, path: string = column): ListFilterField {\n return { column, kind: 'exact', path, operators: ['equals', 'isAnyOf'] }\n}\n\n/**\n * The list fields a CLIENT edit writes beside its patch (AGL-3321): the\n * record as the listener holds it with the patch laid over — a `null` or a\n * sentinel in the patch reads as the field removed — through\n * `crmListFields`, narrowed to `fields`. A client never writes a field the\n * rules keep for the server (a lead's `emailStatus`), which is why the\n * caller names the ones it may. A lead's direction is read off the org's\n * lead source list, which the caller passes in `context` once it has it.\n */\nexport function crmClientListFields(\n collection: CrmListCollection,\n record: Readonly<Record<string, unknown>>,\n patch: Readonly<Record<string, unknown>>,\n fields: readonly string[],\n context: CrmListFieldsContext = {},\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...record }\n for (const [key, value] of Object.entries(patch)) {\n // A dotted path is a map entry (`custom.tier`), never a searched field.\n if (key.includes('.')) continue\n merged[key] = value === null || isSentinel(value) ? undefined : value\n }\n const computed = crmListFields(collection, merged, context)\n return Object.fromEntries(fields.filter((field) => field in computed).map((field) => [field, computed[field]]))\n}\n\n/** A web-SDK `FieldValue` — `deleteField()`, the one a client edit of these fields sends. */\nconst isSentinel = (value: unknown): boolean =>\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date) &&\n typeof (value as { isEqual?: unknown }).isEqual === 'function'\n\n/** The search fields every CRM record carries — what a client edit of searched text rewrites. */\nexport const CRM_CLIENT_SEARCH_FIELDS = ['searchTokens', 'scopedSearchTokens'] as const\n\n\n/**\n * The list fields a deal's contact roles are found by (AGL-3521) — what a\n * client write of `contactRoles` or `contactId` rewrites beside them.\n */\nexport const CRM_DEAL_CONTACT_ROLE_LIST_FIELDS = [\n 'contactRoleContactIds',\n 'scopedContactRoleContactIds',\n 'contactRoleKeys',\n] as const\n"],"names":["CRM_SCOPED_SEARCH_TOKENS_FIELD","CRM_SEARCH_TOKENS_FIELD","crmListFields","scopedSearch","listQueryRefusals","CRM_LIST_SEARCH","tokensPath","scoped","crmListBase","visibleTo","path","op","value","CRM_LIST_BASE_INDEX","array","crmListDeclarationFor","declaration","foldsScope","search","crmAskClauses","stored","ask","clauses","origins","refused","clause","asked","push","reason","sameClause","a","b","field","crmQueryRefusals","plan","context","planned","map","entry","at","findIndex","candidate","crmClauseValues","raw","split","Set","trim","filter","Boolean","crmAnyOf","values","unique","length","join","crmSelectField","column","kind","operators","crmClientListFields","collection","record","patch","fields","merged","key","Object","entries","includes","isSentinel","undefined","computed","fromEntries","Array","isArray","Date","isEqual","CRM_CLIENT_SEARCH_FIELDS","CRM_DEAL_CONTACT_ROLE_LIST_FIELDS"],"mappings":"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"}
@@ -18,7 +18,7 @@ import { type EmailStateStatus, type CrmLeadStatus, type CrmPicklist } from '@ag
18
18
  import type { CrmViewFilterClause } from '@aglyn/aglyn';
19
19
  import type { ListFilterField, ListFilterRequest } from '@aglyn/shared-ui-jsx/const/list-filter';
20
20
  import { type ListGridFilterCodec } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
21
- import { type ListQueryDeclaration, type ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
21
+ import type { ListQueryDeclaration, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
22
22
  import { type CrmClauseAsked } from './crm-list-query';
23
23
  /**
24
24
  * What the Leads section's `Show` control offers (AGL-2608): the two
@@ -77,7 +77,7 @@ export declare const LEAD_EMAIL_FILTER_OPTIONS: {
77
77
  }[];
78
78
  /** The choices for Lead source direction: each group of the Lead source picklist. */
79
79
  export declare const LEAD_SOURCE_DIRECTION_FILTER_OPTIONS: {
80
- value: import("./lead-source-direction").LeadSourceDirection;
80
+ value: "inbound" | "outbound";
81
81
  label: string;
82
82
  }[];
83
83
  /**
@@ -134,11 +134,6 @@ export interface LeadQueryReader {
134
134
  scopeTokens: readonly string[] | null;
135
135
  /** Whether the reader may run a query without the scope clause (`useCrmFoldsScope`). */
136
136
  foldsScope: boolean;
137
- /**
138
- * The org's Lead source list, which a direction is expanded through;
139
- * the standard values alone while it has not been read.
140
- */
141
- leadSources?: CrmPicklist;
142
137
  }
143
138
  /**
144
139
  * One grid clause as the clause the Leads query asks — see the block above.
@@ -14,12 +14,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, CRM_LEAD_STATUS_LABELS, CRM_LEAD_OPEN_STATUSES, CRM_LEAD_SCOPED_CAMPAIGNS_FIELD, CRM_LEAD_STATUSES, crmLeadSourceKey, crmPicklistKey } from "@aglyn/aglyn";
17
+ */ import { EMAIL_STATE_LABELS, EMAIL_STATE_STATUSES, CRM_LEAD_STATUS_LABELS, CRM_LEAD_OPEN_STATUSES, CRM_LEAD_SCOPED_CAMPAIGNS_FIELD, CRM_LEAD_SOURCE_DIRECTION_FIELD, CRM_LEAD_STATUSES, crmLeadSourceKey, crmPicklistKey } from "@aglyn/aglyn";
18
18
  import { SCOPED_SEARCH_JOIN } from "@aglyn/aglyn/app-utils/name-search";
19
19
  import { listSelectCodec } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
20
- import { LIST_QUERY_DISJUNCTIONS } from "@aglyn/shared-ui-jsx/const/list-query-plan";
21
20
  import { CRM_LIST_SEARCH, crmAnyOf, crmClauseValues, crmSelectField } from "./crm-list-query.js";
22
- import { isLeadSourceDirection, LEAD_SOURCE_DIRECTION_LABELS, LEAD_SOURCE_DIRECTIONS, leadSourceLabelsOfDirection, STANDARD_LEAD_SOURCES } from "./lead-source-direction.js";
21
+ import { isLeadSourceDirection, LEAD_SOURCE_DIRECTION_LABELS, LEAD_SOURCE_DIRECTIONS } from "./lead-source-direction.js";
23
22
  export const LEAD_FILTERS = [
24
23
  'open',
25
24
  'new',
@@ -290,12 +289,11 @@ const STATUS_ALL = {
290
289
  * emailed" is every verdict but `ok`
291
290
  * leadSource `leadSourceKey`, the label as the picklist compares it, or
292
291
  * `null` for none
293
- * leadSourceDirection `leadSourceKey in [...]` over the key of every
294
- * value the org's list files under that group, read when
295
- * the clause is asked — a direction is never stored on a
296
- * lead, so regrouping a value moves its leads with it. One
297
- * `in` holds thirty keys, and a group holding more is
298
- * refused by name rather than half asked
292
+ * leadSourceDirection `leadSourceDirection` (AGL-3577), the group of
293
+ * the lead's value in the org's list, stamped by the writers
294
+ * from that list and rewritten on every lead holding a value
295
+ * the list regroups — so one equality asks a group however
296
+ * many values it holds
299
297
  * ownerUid as stored
300
298
  * industry, rating `industryKey`, `ratingKey` (AGL-3513), each label
301
299
  * as the picklist compares it
@@ -340,6 +338,8 @@ const STATUS_ALL = {
340
338
  'contains'
341
339
  ]
342
340
  },
341
+ // The lead source's group (AGL-3577), as stamped on the lead.
342
+ crmSelectField(CRM_LEAD_SOURCE_DIRECTION_FIELD),
343
343
  // Industry and Rating (AGL-3513), by the key each writer stores.
344
344
  crmSelectField('industryKey'),
345
345
  crmSelectField('ratingKey'),
@@ -433,30 +433,12 @@ const ORG_READER = {
433
433
  }
434
434
  case 'leadSourceDirection':
435
435
  {
436
- var _reader_leadSources;
437
- const picklist = (_reader_leadSources = reader.leadSources) != null ? _reader_leadSources : STANDARD_LEAD_SOURCES;
438
- const keys = [];
439
436
  for (const value of values){
440
437
  if (!isLeadSourceDirection(value)) return {
441
438
  refused: `${value} is not a lead source direction`
442
439
  };
443
- for (const label of leadSourceLabelsOfDirection(picklist, value)){
444
- const key = crmLeadSourceKey(label);
445
- if (key) keys.push(key);
446
- }
447
- }
448
- const unique = [
449
- ...new Set(keys)
450
- ];
451
- if (!unique.length) return {
452
- refused: 'no lead source is in that direction yet'
453
- };
454
- if (unique.length > LIST_QUERY_DISJUNCTIONS) {
455
- return {
456
- refused: `more than ${LIST_QUERY_DISJUNCTIONS} lead sources are in that direction — filter by Lead source instead`
457
- };
458
440
  }
459
- return crmAnyOf('leadSourceKey', unique);
441
+ return crmAnyOf(CRM_LEAD_SOURCE_DIRECTION_FIELD, values);
460
442
  }
461
443
  case 'ownerUid':
462
444
  return crmAnyOf('ownerUid', values);