@aglyn/plugins-crm 1.0.0-beta.293 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-stage-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_FORECAST_CATEGORY_LABELS,\n dealForecastCategory,\n dealProbability,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { mdiThumbDownOutline, mdiTrophyOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n MenuItem,\n Stack,\n Step,\n StepButton,\n Stepper,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useState } from 'react'\nimport { type DealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n daysInStage,\n DEAL_STATUS_LABELS,\n type DealDoc,\n openStages,\n type PipelineDoc,\n} from '../model/deal-board-model'\nimport { LostReasonDialog } from './lost-reason-dialog'\n\nexport interface DealStageCardProps {\n deal: DealDoc\n pipeline: PipelineDoc | null\n api: DealStageApi\n nowMs: number\n}\n\n/**\n * Where the deal is, and the controls that move it (AGL-2598).\n *\n * A stepper across the open stages — click one to move there — and the two\n * closing buttons. Every one of them calls the stage route, never Firestore:\n * the move is what an automation listens for, and the route is the only\n * writer that emits it. On a closed deal the stepper gives way to the\n * verdict — Won or Lost, when, and why — and a way back: reopening is a\n * move into an open stage, which the route emits as `dealStageChanged` like\n * any other move.\n *\n * The line under the stepper reads the deal's own probability and forecast\n * category (AGL-3516) — a move gives it the new stage's, which the route\n * stamps, and the stepper says so on hover.\n */\nexport function DealStageCard(props: DealStageCardProps) {\n const { deal, pipeline, api, nowMs } = props\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n const [losing, setLosing] = useState(false)\n const stages = openStages(pipeline)\n const activeIndex = stages.findIndex((stage) => stage.id === deal.stageId)\n\n const run = useCallback(\n async (request: () => Promise<unknown>, done: string) => {\n setBusy(true)\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setBusy(false)\n }\n },\n [enqueueSnackbar],\n )\n\n const closed = deal.status !== 'open'\n const days = daysInStage(deal, nowMs)\n const current = activeIndex === -1 ? null : stages[activeIndex]\n const probability = dealProbability(deal, current)\n const forecast = CRM_FORECAST_CATEGORY_LABELS[dealForecastCategory(deal, current)]\n\n return (\n <CardDisplay\n header={'Stage'}\n help={pluginDocsHelp('deals', { anchor: '#moving-winning-and-losing' })}\n HeaderProps={{\n action: !closed ? (\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n color=\"success\"\n variant=\"outlined\"\n disabled={busy || !pipeline}\n startIcon={<MdiIcon path={mdiTrophyOutline.path} size={0.8} />}\n onClick={() => void run(() => api.markWon(deal), 'Deal won')}\n >\n {'Won'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n variant=\"outlined\"\n disabled={busy || !pipeline}\n startIcon={<MdiIcon path={mdiThumbDownOutline.path} size={0.8} />}\n onClick={() => setLosing(true)}\n >\n {'Lost'}\n </Button>\n </Stack>\n ) : null,\n }}\n contentGutterX\n contentGutterY\n >\n {!pipeline ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {\"This deal's pipeline no longer exists, so it has no stages to move through.\"}\n </Typography>\n ) : closed ? (\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip\n label={DEAL_STATUS_LABELS[deal.status]}\n color={deal.status === 'won' ? 'success' : 'default'}\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {typeof deal.closedAtMs === 'number'\n ? `Closed ${new Date(deal.closedAtMs).toLocaleDateString()}`\n : 'Closed'}\n </Typography>\n </Stack>\n {deal.status === 'lost' && deal.lostReason ? (\n <Typography variant=\"body2\">{deal.lostReason}</Typography>\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"Reopen into\"\n value=\"\"\n disabled={busy}\n onChange={(event) => {\n const stageId = event.target.value\n if (!stageId) return\n void run(() => api.moveToStage(deal, stageId), 'Deal reopened')\n }}\n helperText=\"Reopening puts the deal back in an open stage and tells the automations.\"\n sx={{ maxWidth: 320 }}\n >\n <MenuItem value=\"\">{'Pick a stage'}</MenuItem>\n {stages.map((stage) => (\n <MenuItem key={stage.id} value={stage.id}>\n {stage.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : (\n <Stack spacing={1.5}>\n <Stepper nonLinear activeStep={activeIndex} alternativeLabel>\n {stages.map((stage, index) => (\n <Step key={stage.id} completed={index < activeIndex}>\n <StepButton\n title={`Move here: ${stage.probability}% likely to close`}\n disabled={busy || stage.id === deal.stageId}\n onClick={() =>\n void run(\n () => api.moveToStage(deal, stage.id),\n `Moved to ${stage.name}`,\n )\n }\n >\n {stage.name}\n </StepButton>\n </Step>\n ))}\n </Stepper>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ textAlign: 'center' }}>\n {!current\n ? 'This deal sits in a stage the pipeline no longer has. Pick one above.'\n : `${days} ${days === 1 ? 'day' : 'days'} in ${current.name} · ` +\n `${probability ?? current.probability}% likely to close · ${forecast}`}\n </Typography>\n </Stack>\n )}\n <LostReasonDialog\n open={losing}\n dealTitle={deal.title}\n busy={busy}\n onClose={() => setLosing(false)}\n onConfirm={(reason) =>\n void run(() => api.markLost(deal, reason), 'Deal marked lost').then(\n (ok) => ok && setLosing(false),\n )\n }\n />\n </CardDisplay>\n )\n}\nDealStageCard.displayName = 'DealStageCard'\n\nexport default DealStageCard\n"],"names":["CRM_FORECAST_CATEGORY_LABELS","dealForecastCategory","dealProbability","pluginDocsHelp","mdiThumbDownOutline","mdiTrophyOutline","CardDisplay","MdiIcon","useSnackbar","Button","Chip","MenuItem","Stack","Step","StepButton","Stepper","TextField","Typography","useCallback","useState","daysInStage","DEAL_STATUS_LABELS","openStages","LostReasonDialog","DealStageCard","props","deal","pipeline","api","nowMs","enqueueSnackbar","busy","setBusy","losing","setLosing","stages","activeIndex","findIndex","stage","id","stageId","run","request","done","variant","persist","error","Error","message","allowDuplicate","closed","status","days","current","probability","forecast","header","help","anchor","HeaderProps","action","direction","spacing","size","color","disabled","startIcon","path","onClick","markWon","contentGutterX","contentGutterY","sx","alignItems","label","closedAtMs","Date","toLocaleDateString","lostReason","select","value","onChange","event","target","moveToStage","helperText","maxWidth","map","name","nonLinear","activeStep","alternativeLabel","index","completed","title","textAlign","open","dealTitle","onClose","onConfirm","reason","markLost","then","ok","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/deal-stage-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_FORECAST_CATEGORY_LABELS,\n dealForecastCategory,\n dealProbability,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { mdiThumbDownOutline, mdiTrophyOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n ListItemIcon,\n ListItemText,\n Menu,\n MenuItem,\n Stack,\n Step,\n StepButton,\n StepLabel,\n Stepper,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useState } from 'react'\nimport { type DealStageApi } from '../hooks/use-deal-stage-api'\nimport {\n closingStage,\n daysInStage,\n DEAL_STATUS_LABELS,\n type DealDoc,\n openStages,\n type PipelineDoc,\n} from '../model/deal-board-model'\nimport { LostReasonDialog } from './lost-reason-dialog'\n\nexport interface DealStageCardProps {\n deal: DealDoc\n pipeline: PipelineDoc | null\n api: DealStageApi\n nowMs: number\n}\n\n/**\n * Where the deal is, and the controls that move it (AGL-2598).\n *\n * A path across the whole pipeline, the way Salesforce draws one: every open\n * stage — click one to move there — and then one closing step, so the stages\n * still ahead of the deal are always in view and the path ends where every\n * deal ends. The closing step reads \"Closed\" while the deal is open and asks\n * won or lost when chosen, the same two moves as the buttons in the header;\n * once the deal is closed it reads the pipeline's own name for the verdict.\n * Every move calls the stage route, never Firestore: the move is what an\n * automation listens for, and the route is the only writer that emits it.\n * Under a closed deal's path sits the verdict — when, and why — and a way\n * back: reopening is a move into an open stage, which the route emits as\n * `dealStageChanged` like any other move.\n *\n * The line under the stepper reads the deal's own probability and forecast\n * category (AGL-3516) — a move gives it the new stage's, which the route\n * stamps, and the stepper says so on hover.\n */\nexport function DealStageCard(props: DealStageCardProps) {\n const { deal, pipeline, api, nowMs } = props\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n const [losing, setLosing] = useState(false)\n const [closeMenu, setCloseMenu] = useState<HTMLElement | null>(null)\n const stages = openStages(pipeline)\n const activeIndex = stages.findIndex((stage) => stage.id === deal.stageId)\n const wonName = closingStage(pipeline, 'won')?.name ?? DEAL_STATUS_LABELS.won\n const lostName = closingStage(pipeline, 'lost')?.name ?? DEAL_STATUS_LABELS.lost\n\n const run = useCallback(\n async (request: () => Promise<unknown>, done: string) => {\n setBusy(true)\n try {\n await request()\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n enqueueSnackbar(\n error instanceof Error ? error.message : 'The deal could not be moved.',\n { variant: 'warning', allowDuplicate: true },\n )\n return false\n } finally {\n setBusy(false)\n }\n },\n [enqueueSnackbar],\n )\n\n const closed = deal.status !== 'open'\n const days = daysInStage(deal, nowMs)\n const current = activeIndex === -1 ? null : stages[activeIndex]\n const probability = dealProbability(deal, current)\n const forecast = CRM_FORECAST_CATEGORY_LABELS[dealForecastCategory(deal, current)]\n\n const path = (\n <Stepper nonLinear activeStep={closed ? stages.length : activeIndex} alternativeLabel>\n {stages.map((stage, index) => (\n <Step key={stage.id} completed={closed || index < activeIndex}>\n <StepButton\n title={closed ? undefined : `Move here: ${stage.probability}% likely to close`}\n disabled={busy || closed || stage.id === deal.stageId}\n onClick={() =>\n void run(() => api.moveToStage(deal, stage.id), `Moved to ${stage.name}`)\n }\n >\n {stage.name}\n </StepButton>\n </Step>\n ))}\n <Step key=\"$closed\" completed={deal.status === 'won'}>\n <StepButton\n data-testid=\"deal-stage-closing-step\"\n title={closed ? undefined : 'Close the deal as won or lost'}\n disabled={busy || closed}\n onClick={(event) => setCloseMenu(event.currentTarget)}\n icon={\n deal.status === 'won' ? (\n <MdiIcon path={mdiTrophyOutline.path} color=\"success\" fontSize=\"medium\" />\n ) : deal.status === 'lost' ? (\n <MdiIcon path={mdiThumbDownOutline.path} color=\"error\" fontSize=\"medium\" />\n ) : undefined\n }\n >\n <StepLabel error={deal.status === 'lost'}>\n {deal.status === 'won' ? wonName : deal.status === 'lost' ? lostName : 'Closed'}\n </StepLabel>\n </StepButton>\n </Step>\n </Stepper>\n )\n\n return (\n <CardDisplay\n header={'Stage'}\n help={pluginDocsHelp('deals', { anchor: '#moving-winning-and-losing' })}\n HeaderProps={{\n action: !closed ? (\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n color=\"success\"\n variant=\"outlined\"\n disabled={busy || !pipeline}\n startIcon={<MdiIcon path={mdiTrophyOutline.path} size={0.8} />}\n onClick={() => void run(() => api.markWon(deal), 'Deal won')}\n >\n {'Won'}\n </Button>\n <Button\n size=\"small\"\n color=\"error\"\n variant=\"outlined\"\n disabled={busy || !pipeline}\n startIcon={<MdiIcon path={mdiThumbDownOutline.path} size={0.8} />}\n onClick={() => setLosing(true)}\n >\n {'Lost'}\n </Button>\n </Stack>\n ) : null,\n }}\n contentGutterX\n contentGutterY\n >\n {!pipeline ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {\"This deal's pipeline no longer exists, so it has no stages to move through.\"}\n </Typography>\n ) : closed ? (\n <Stack spacing={1.5}>\n {path}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip\n label={DEAL_STATUS_LABELS[deal.status]}\n color={deal.status === 'won' ? 'success' : 'default'}\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {typeof deal.closedAtMs === 'number'\n ? `Closed ${new Date(deal.closedAtMs).toLocaleDateString()}`\n : 'Closed'}\n </Typography>\n </Stack>\n {deal.status === 'lost' && deal.lostReason ? (\n <Typography variant=\"body2\">{deal.lostReason}</Typography>\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"Reopen into\"\n value=\"\"\n disabled={busy}\n onChange={(event) => {\n const stageId = event.target.value\n if (!stageId) return\n void run(() => api.moveToStage(deal, stageId), 'Deal reopened')\n }}\n helperText=\"Reopening puts the deal back in an open stage and tells the automations.\"\n sx={{ maxWidth: 320 }}\n >\n <MenuItem value=\"\">{'Pick a stage'}</MenuItem>\n {stages.map((stage) => (\n <MenuItem key={stage.id} value={stage.id}>\n {stage.name}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : (\n <Stack spacing={1.5}>\n {path}\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ textAlign: 'center' }}>\n {!current\n ? 'This deal sits in a stage the pipeline no longer has. Pick one above.'\n : `${days} ${days === 1 ? 'day' : 'days'} in ${current.name} · ` +\n `${probability ?? current.probability}% likely to close · ${forecast}`}\n </Typography>\n </Stack>\n )}\n <Menu anchorEl={closeMenu} open={Boolean(closeMenu)} onClose={() => setCloseMenu(null)}>\n <MenuItem\n onClick={() => {\n setCloseMenu(null)\n void run(() => api.markWon(deal), 'Deal won')\n }}\n >\n <ListItemIcon>\n <MdiIcon path={mdiTrophyOutline.path} color=\"success\" />\n </ListItemIcon>\n <ListItemText>{wonName}</ListItemText>\n </MenuItem>\n <MenuItem\n onClick={() => {\n setCloseMenu(null)\n setLosing(true)\n }}\n >\n <ListItemIcon>\n <MdiIcon path={mdiThumbDownOutline.path} color=\"error\" />\n </ListItemIcon>\n <ListItemText>{lostName}</ListItemText>\n </MenuItem>\n </Menu>\n <LostReasonDialog\n open={losing}\n dealTitle={deal.title}\n busy={busy}\n onClose={() => setLosing(false)}\n onConfirm={(reason) =>\n void run(() => api.markLost(deal, reason), 'Deal marked lost').then(\n (ok) => ok && setLosing(false),\n )\n }\n />\n </CardDisplay>\n )\n}\nDealStageCard.displayName = 'DealStageCard'\n\nexport default DealStageCard\n"],"names":["CRM_FORECAST_CATEGORY_LABELS","dealForecastCategory","dealProbability","pluginDocsHelp","mdiThumbDownOutline","mdiTrophyOutline","CardDisplay","MdiIcon","useSnackbar","Button","Chip","ListItemIcon","ListItemText","Menu","MenuItem","Stack","Step","StepButton","StepLabel","Stepper","TextField","Typography","useCallback","useState","closingStage","daysInStage","DEAL_STATUS_LABELS","openStages","LostReasonDialog","DealStageCard","props","deal","pipeline","api","nowMs","enqueueSnackbar","busy","setBusy","losing","setLosing","closeMenu","setCloseMenu","stages","activeIndex","findIndex","stage","id","stageId","wonName","name","won","lostName","lost","run","request","done","variant","persist","error","Error","message","allowDuplicate","closed","status","days","current","probability","forecast","path","nonLinear","activeStep","length","alternativeLabel","map","index","completed","title","undefined","disabled","onClick","moveToStage","data-testid","event","currentTarget","icon","color","fontSize","header","help","anchor","HeaderProps","action","direction","spacing","size","startIcon","markWon","contentGutterX","contentGutterY","sx","alignItems","label","closedAtMs","Date","toLocaleDateString","lostReason","select","value","onChange","target","helperText","maxWidth","textAlign","anchorEl","open","Boolean","onClose","dealTitle","onConfirm","reason","markLost","then","ok","displayName"],"mappings":"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"}
@@ -102,20 +102,32 @@ import { useCrmSharingFollowUp } from "../hooks/use-crm-sharing.js";
102
102
  }
103
103
  setSaving(true);
104
104
  const next = containerMembershipValue(selected);
105
- const verdict = await writeGuardedBySeed({
106
- subject: 'lead',
107
- fromCache,
108
- unreadable: leadStatus === 'error'
109
- }, async ()=>{
110
- await updateDoc(doc(firestore, 'orgs', orgId, 'leads', leadId), _extends({
111
- campaignIds: next
112
- }, crmClientListFields('leads', lead, {
113
- campaignIds: next
114
- }, LEAD_CAMPAIGN_LIST_FIELDS), {
115
- updatedAt: serverTimestamp()
116
- }));
117
- });
118
- setSaving(false);
105
+ let verdict;
106
+ try {
107
+ verdict = await writeGuardedBySeed({
108
+ subject: 'lead',
109
+ fromCache,
110
+ unreadable: leadStatus === 'error'
111
+ }, async ()=>{
112
+ await updateDoc(doc(firestore, 'orgs', orgId, 'leads', leadId), _extends({
113
+ campaignIds: next
114
+ }, crmClientListFields('leads', lead, {
115
+ campaignIds: next
116
+ }, LEAD_CAMPAIGN_LIST_FIELDS), {
117
+ updatedAt: serverTimestamp()
118
+ }));
119
+ });
120
+ } catch (error) {
121
+ // A refused write has to say so, not leave the button spinning (AGL-4120).
122
+ console.error(error);
123
+ enqueueSnackbar('The filing could not be saved.', {
124
+ variant: 'error',
125
+ allowDuplicate: true
126
+ });
127
+ return;
128
+ } finally{
129
+ setSaving(false);
130
+ }
119
131
  // A rule may share by campaign (AGL-3336).
120
132
  if (verdict.ok) followUpSharing('leads', [
121
133
  leadId
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-campaigns-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 containerMembershipUnchanged,\n containerMembershipValue,\n pluginDocsHelp,\n readContainerIds,\n} from '@aglyn/aglyn'\nimport ContainerPicker, {\n type ContainerPickerOption,\n} from '@aglyn/tenant-feature-instance/components/container-picker'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n type FirestoreDocStatus,\n useFirestore,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Stack } from '@mui/material'\nimport { doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useState } from 'react'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { crmClientListFields } from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/** The list field a campaign filing moves (AGL-3321). */\nconst LEAD_CAMPAIGN_LIST_FIELDS = ['scopedCampaignIds'] as const\n\n/** The helper line under the picker — the contact card's, word for word. */\nexport const LEAD_CAMPAIGNS_HELPER_TEXT =\n 'Your own filing. It never adds anyone to a send — a campaign mails its lists.'\n\n/**\n * The campaigns a lead is filed under, as NAMES for the record header.\n *\n * Resolved from the site's containers; an id with no live container — a\n * campaign the console deleted, or one still loading — is left out rather\n * than shown, because a document id on a chip tells a reader nothing and\n * looks like a fault. The picker below keeps such an id visible as itself,\n * so the filing is never silently lost; the header only ever names.\n */\nexport function leadCampaignNames(\n lead: Record<string, unknown> | null | undefined,\n options: readonly ContainerPickerOption[],\n): string[] {\n const labels = new Map(options.map((option) => [option.value, option.label]))\n return readContainerIds(lead, 'campaign')\n .map((id) => labels.get(id))\n .filter((name): name is string => Boolean(name))\n}\n\nexport interface LeadCampaignsCardProps {\n /**\n * The lead's own site, or `null` at the organization level for a lead no\n * site captured (AGL-3278). Read for the org root alone — the containers\n * themselves are handed down as `options` — so the card serves both\n * levels; the org-level mount answers the same org without a site.\n */\n hostId: string | null\n leadId: string\n lead: Record<string, unknown>\n leadStatus: FirestoreDocStatus\n /** The listener has not confirmed this document with the server yet. */\n fromCache: boolean\n /** The site's campaigns, read once by the page for this card and the header. */\n options: readonly ContainerPickerOption[]\n /** The campaign read has answered — false while it settles. */\n optionsReady: boolean\n /**\n * What a saved filing changed, once it has landed: the ids added to and\n * removed from the lead's membership. The page files the activity\n * entries (AGL-3274); the card only knows the document.\n */\n onFiled?: (change: { added: string[]; removed: string[] }) => void\n}\n\n/**\n * WHICH CAMPAIGNS THIS LEAD IS FILED UNDER (AGL-3274).\n *\n * The contact's Relationship card has carried this picker since AGL-2596;\n * the lead's page had none, so a lead filed under a campaign by the New\n * lead drawer, the bulk bar or a sequence's enroll showed nothing of it —\n * the one field on the record the reader had no way to see or change.\n *\n * The same control, the same value helper, the same helper line. The save\n * is the lead's own client door — a one-field `update` on\n * `hosts/{hostId}/leads`, as the properties card writes status and notes —\n * guarded by the seed like every draft edited over a cached read, and\n * `containerMembershipUnchanged` keeps Save quiet while the picker only\n * reorders what is stored. **Save filing** sits in the card header.\n *\n * It is FILING, not audience: the helper says so, in the sentence the\n * contact card uses, because a reader who finds a campaign picker on a\n * person's page assumes the opposite.\n */\nexport function LeadCampaignsCard(props: LeadCampaignsCardProps) {\n const { hostId, leadId, lead, leadStatus, fromCache, options, optionsReady, onFiled } = props\n // The lead's collection is the org's now (AGL-3275); the site still names\n // the surface, and the scope hook resolves the org from it.\n const { orgId } = useCrmScope({ hostId })\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const stored = readContainerIds(lead, 'campaign')\n const [selected, setSelected] = useState<string[]>(stored)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n // A newer membership from the server replaces an UNEDITED selection — a\n // sequence's enroll or a bulk add lands while the page is open — and an\n // edited one is the reader's, decided by the guard on save.\n const storedKey = stored.join(',')\n useEffect(() => {\n if (!dirty) setSelected(stored)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storedKey, dirty])\n\n const unchanged = containerMembershipUnchanged(stored, selected)\n const empty = optionsReady && !options.length\n\n const save = async () => {\n if (unchanged) return\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n setSaving(true)\n const next = containerMembershipValue(selected)\n const verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n async () => {\n await updateDoc(doc(firestore, 'orgs', orgId, 'leads', leadId), {\n campaignIds: next,\n // The Leads list's Campaign filter under a site reads these\n // (AGL-3321), from the lead with its new campaigns.\n ...crmClientListFields('leads', lead, { campaignIds: next }, LEAD_CAMPAIGN_LIST_FIELDS),\n updatedAt: serverTimestamp(),\n })\n },\n )\n setSaving(false)\n // A rule may share by campaign (AGL-3336).\n if (verdict.ok) followUpSharing('leads', [leadId])\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The filing could not be saved.', { variant: 'warning' })\n return\n }\n setDirty(false)\n enqueueSnackbar('Filing saved', { variant: 'success', persist: false })\n onFiled?.({\n added: next.filter((id) => !stored.includes(id)),\n removed: stored.filter((id) => !next.includes(id)),\n })\n }\n\n return (\n <CardDisplay\n header={'Campaigns'}\n help={pluginDocsHelp('crmLeads', { anchor: '#lead-campaigns' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: empty ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={saving || unchanged}\n onClick={() => void save().catch((error: unknown) => {\n console.error(error)\n enqueueSnackbar(\n error instanceof Error && error.message ? error.message : 'An error has occurred',\n { variant: 'error', allowDuplicate: true },\n )\n setSaving(false)\n })}\n >\n {saving ? 'Saving…' : 'Save filing'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <ContainerPicker\n kind=\"campaign\"\n options={options}\n value={selected}\n onChange={(next) => {\n setSelected(next)\n setDirty(true)\n }}\n label=\"Filed under campaigns\"\n helperText={LEAD_CAMPAIGNS_HELPER_TEXT}\n disabled={saving}\n empty={empty}\n emptyText=\"There are no campaigns to file this lead under yet. Create one from Marketing.\"\n />\n </Stack>\n </CardDisplay>\n )\n}\nLeadCampaignsCard.displayName = 'LeadCampaignsCard'\n\nexport default LeadCampaignsCard\n"],"names":["containerMembershipUnchanged","containerMembershipValue","pluginDocsHelp","readContainerIds","ContainerPicker","CardDisplay","useSnackbar","useFirestore","writeGuardedBySeed","Button","Stack","doc","serverTimestamp","updateDoc","useEffect","useState","useCrmScope","crmClientListFields","useCrmSharingFollowUp","LEAD_CAMPAIGN_LIST_FIELDS","LEAD_CAMPAIGNS_HELPER_TEXT","leadCampaignNames","lead","options","labels","Map","map","option","value","label","id","get","filter","name","Boolean","LeadCampaignsCard","props","hostId","leadId","leadStatus","fromCache","optionsReady","onFiled","orgId","followUpSharing","firestore","enqueueSnackbar","stored","selected","setSelected","dirty","setDirty","saving","setSaving","storedKey","join","unchanged","empty","length","save","variant","persist","next","verdict","subject","unreadable","campaignIds","updatedAt","ok","message","added","includes","removed","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","disabled","onClick","catch","error","console","Error","allowDuplicate","spacing","kind","onChange","helperText","emptyText","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/lead-campaigns-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 containerMembershipUnchanged,\n containerMembershipValue,\n pluginDocsHelp,\n readContainerIds,\n} from '@aglyn/aglyn'\nimport ContainerPicker, {\n type ContainerPickerOption,\n} from '@aglyn/tenant-feature-instance/components/container-picker'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n type FirestoreDocStatus,\n useFirestore,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport { Button, Stack } from '@mui/material'\nimport { doc, serverTimestamp, updateDoc } from 'firebase/firestore'\nimport { useEffect, useState } from 'react'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { crmClientListFields } from '../model/crm-list-query'\nimport { useCrmSharingFollowUp } from '../hooks/use-crm-sharing'\n\n/** The list field a campaign filing moves (AGL-3321). */\nconst LEAD_CAMPAIGN_LIST_FIELDS = ['scopedCampaignIds'] as const\n\n/** The helper line under the picker — the contact card's, word for word. */\nexport const LEAD_CAMPAIGNS_HELPER_TEXT =\n 'Your own filing. It never adds anyone to a send — a campaign mails its lists.'\n\n/**\n * The campaigns a lead is filed under, as NAMES for the record header.\n *\n * Resolved from the site's containers; an id with no live container — a\n * campaign the console deleted, or one still loading — is left out rather\n * than shown, because a document id on a chip tells a reader nothing and\n * looks like a fault. The picker below keeps such an id visible as itself,\n * so the filing is never silently lost; the header only ever names.\n */\nexport function leadCampaignNames(\n lead: Record<string, unknown> | null | undefined,\n options: readonly ContainerPickerOption[],\n): string[] {\n const labels = new Map(options.map((option) => [option.value, option.label]))\n return readContainerIds(lead, 'campaign')\n .map((id) => labels.get(id))\n .filter((name): name is string => Boolean(name))\n}\n\nexport interface LeadCampaignsCardProps {\n /**\n * The lead's own site, or `null` at the organization level for a lead no\n * site captured (AGL-3278). Read for the org root alone — the containers\n * themselves are handed down as `options` — so the card serves both\n * levels; the org-level mount answers the same org without a site.\n */\n hostId: string | null\n leadId: string\n lead: Record<string, unknown>\n leadStatus: FirestoreDocStatus\n /** The listener has not confirmed this document with the server yet. */\n fromCache: boolean\n /** The site's campaigns, read once by the page for this card and the header. */\n options: readonly ContainerPickerOption[]\n /** The campaign read has answered — false while it settles. */\n optionsReady: boolean\n /**\n * What a saved filing changed, once it has landed: the ids added to and\n * removed from the lead's membership. The page files the activity\n * entries (AGL-3274); the card only knows the document.\n */\n onFiled?: (change: { added: string[]; removed: string[] }) => void\n}\n\n/**\n * WHICH CAMPAIGNS THIS LEAD IS FILED UNDER (AGL-3274).\n *\n * The contact's Relationship card has carried this picker since AGL-2596;\n * the lead's page had none, so a lead filed under a campaign by the New\n * lead drawer, the bulk bar or a sequence's enroll showed nothing of it —\n * the one field on the record the reader had no way to see or change.\n *\n * The same control, the same value helper, the same helper line. The save\n * is the lead's own client door — a one-field `update` on\n * `hosts/{hostId}/leads`, as the properties card writes status and notes —\n * guarded by the seed like every draft edited over a cached read, and\n * `containerMembershipUnchanged` keeps Save quiet while the picker only\n * reorders what is stored. **Save filing** sits in the card header.\n *\n * It is FILING, not audience: the helper says so, in the sentence the\n * contact card uses, because a reader who finds a campaign picker on a\n * person's page assumes the opposite.\n */\nexport function LeadCampaignsCard(props: LeadCampaignsCardProps) {\n const { hostId, leadId, lead, leadStatus, fromCache, options, optionsReady, onFiled } = props\n // The lead's collection is the org's now (AGL-3275); the site still names\n // the surface, and the scope hook resolves the org from it.\n const { orgId } = useCrmScope({ hostId })\n const followUpSharing = useCrmSharingFollowUp(hostId, orgId)\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const stored = readContainerIds(lead, 'campaign')\n const [selected, setSelected] = useState<string[]>(stored)\n const [dirty, setDirty] = useState(false)\n const [saving, setSaving] = useState(false)\n // A newer membership from the server replaces an UNEDITED selection — a\n // sequence's enroll or a bulk add lands while the page is open — and an\n // edited one is the reader's, decided by the guard on save.\n const storedKey = stored.join(',')\n useEffect(() => {\n if (!dirty) setSelected(stored)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storedKey, dirty])\n\n const unchanged = containerMembershipUnchanged(stored, selected)\n const empty = optionsReady && !options.length\n\n const save = async () => {\n if (unchanged) return\n if (!orgId) {\n enqueueSnackbar('Still loading this workspace — try again in a moment.', {\n variant: 'warning',\n persist: false,\n })\n return\n }\n setSaving(true)\n const next = containerMembershipValue(selected)\n let verdict: Awaited<ReturnType<typeof writeGuardedBySeed>>\n try {\n verdict = await writeGuardedBySeed(\n { subject: 'lead', fromCache, unreadable: leadStatus === 'error' },\n async () => {\n await updateDoc(doc(firestore, 'orgs', orgId, 'leads', leadId), {\n campaignIds: next,\n // The Leads list's Campaign filter under a site reads these\n // (AGL-3321), from the lead with its new campaigns.\n ...crmClientListFields('leads', lead, { campaignIds: next }, LEAD_CAMPAIGN_LIST_FIELDS),\n updatedAt: serverTimestamp(),\n })\n },\n )\n } catch (error) {\n // A refused write has to say so, not leave the button spinning (AGL-4120).\n console.error(error)\n enqueueSnackbar('The filing could not be saved.', {\n variant: 'error',\n allowDuplicate: true,\n })\n return\n } finally {\n setSaving(false)\n }\n // A rule may share by campaign (AGL-3336).\n if (verdict.ok) followUpSharing('leads', [leadId])\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The filing could not be saved.', { variant: 'warning' })\n return\n }\n setDirty(false)\n enqueueSnackbar('Filing saved', { variant: 'success', persist: false })\n onFiled?.({\n added: next.filter((id) => !stored.includes(id)),\n removed: stored.filter((id) => !next.includes(id)),\n })\n }\n\n return (\n <CardDisplay\n header={'Campaigns'}\n help={pluginDocsHelp('crmLeads', { anchor: '#lead-campaigns' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: empty ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={saving || unchanged}\n onClick={() => void save().catch((error: unknown) => {\n console.error(error)\n enqueueSnackbar(\n error instanceof Error && error.message ? error.message : 'An error has occurred',\n { variant: 'error', allowDuplicate: true },\n )\n setSaving(false)\n })}\n >\n {saving ? 'Saving…' : 'Save filing'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <ContainerPicker\n kind=\"campaign\"\n options={options}\n value={selected}\n onChange={(next) => {\n setSelected(next)\n setDirty(true)\n }}\n label=\"Filed under campaigns\"\n helperText={LEAD_CAMPAIGNS_HELPER_TEXT}\n disabled={saving}\n empty={empty}\n emptyText=\"There are no campaigns to file this lead under yet. Create one from Marketing.\"\n />\n </Stack>\n </CardDisplay>\n )\n}\nLeadCampaignsCard.displayName = 'LeadCampaignsCard'\n\nexport default LeadCampaignsCard\n"],"names":["containerMembershipUnchanged","containerMembershipValue","pluginDocsHelp","readContainerIds","ContainerPicker","CardDisplay","useSnackbar","useFirestore","writeGuardedBySeed","Button","Stack","doc","serverTimestamp","updateDoc","useEffect","useState","useCrmScope","crmClientListFields","useCrmSharingFollowUp","LEAD_CAMPAIGN_LIST_FIELDS","LEAD_CAMPAIGNS_HELPER_TEXT","leadCampaignNames","lead","options","labels","Map","map","option","value","label","id","get","filter","name","Boolean","LeadCampaignsCard","props","hostId","leadId","leadStatus","fromCache","optionsReady","onFiled","orgId","followUpSharing","firestore","enqueueSnackbar","stored","selected","setSelected","dirty","setDirty","saving","setSaving","storedKey","join","unchanged","empty","length","save","variant","persist","next","verdict","subject","unreadable","campaignIds","updatedAt","error","console","allowDuplicate","ok","message","added","includes","removed","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","disabled","onClick","catch","Error","spacing","kind","onChange","helperText","emptyText","displayName"],"mappings":"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"}
@@ -109,7 +109,7 @@ import { crmTaskCallScope } from "../model/task-routes.js";
109
109
  const failed = `The ${definition.plural} could not be saved.`;
110
110
  const isStandard = (value)=>isStandardPicklistValueId(definition, value.id);
111
111
  /** Write the whole list — the list-only moves above. */ const save = useCallback(async (next, done)=>{
112
- if (!orgId || busy) return;
112
+ if (!orgId || busy) return false;
113
113
  setBusy(true);
114
114
  try {
115
115
  const verdict = await writeGuardedBySeed({
@@ -132,18 +132,20 @@ import { crmTaskCallScope } from "../model/task-routes.js";
132
132
  variant: 'warning',
133
133
  persist: false
134
134
  });
135
- return;
135
+ return false;
136
136
  }
137
137
  enqueueSnackbar(done, {
138
138
  variant: 'success',
139
139
  persist: false
140
140
  });
141
+ return true;
141
142
  } catch (error) {
142
143
  console.error(error);
143
144
  enqueueSnackbar(failed, {
144
145
  variant: 'error',
145
146
  allowDuplicate: true
146
147
  });
148
+ return false;
147
149
  } finally{
148
150
  setBusy(false);
149
151
  }
@@ -255,8 +257,9 @@ import { crmTaskCallScope } from "../model/task-routes.js";
255
257
  });
256
258
  return;
257
259
  }
258
- setNameDialog(null);
259
- await save(move.picklist, `“${draftName.trim()}” added`);
260
+ // The dialog holds what was typed until the list has really saved
261
+ // (AGL-4120): closing first made a refused add read as done.
262
+ if (await save(move.picklist, `“${draftName.trim()}” added`)) setNameDialog(null);
260
263
  return;
261
264
  }
262
265
  const result = await post({
@@ -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 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 useFlexGap 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","useFlexGap","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): Promise<boolean> => {\n if (!orgId || busy) return false\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 false\n }\n enqueueSnackbar(done, { variant: 'success', persist: false })\n return true\n } catch (error) {\n console.error(error)\n enqueueSnackbar(failed, { variant: 'error', allowDuplicate: true })\n return false\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 // The dialog holds what was typed until the list has really saved\n // (AGL-4120): closing first made a refused add read as done.\n if (await save(move.picklist, `“${draftName.trim()}” added`)) setNameDialog(null)\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 useFlexGap 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","useFlexGap","flexWrap","open","onClose","maxWidth","pt","onKeyDown","Boolean","helperText","htmlInput","maxLength","autoFocus","inputLabel","shrink","displayName"],"mappings":"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"}
@@ -169,7 +169,7 @@ const TASK_DONE_CEILING = 1000;
169
169
  header: 'Activity by teammate',
170
170
  help: Aglyn.pluginDocsHelp('crmReports', {
171
171
  anchor: '#activity-by-teammate',
172
- excerpt: 'The calls, emails, meetings and notes each teammate logged in ' + 'the period, and the tasks they completed, busiest first. ' + 'Grouped from the period’s newest thousand of each.'
172
+ excerpt: 'The calls, emails, messages, meetings and notes each teammate logged in ' + 'the period, and the tasks they completed, busiest first. ' + 'Grouped from the period’s newest thousand of each.'
173
173
  }),
174
174
  contentGutterX: true,
175
175
  contentGutterY: true,