@aglyn/plugins-crm 1.0.0-beta.157 → 1.0.0-beta.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/assignment-rule-drawer.js +2 -2
- package/src/lib/components/assignment-rule-drawer.js.map +1 -1
- package/src/lib/components/companies-bulk-bar.js +2 -2
- package/src/lib/components/companies-bulk-bar.js.map +1 -1
- package/src/lib/components/companies-section.js +2 -2
- package/src/lib/components/companies-section.js.map +1 -1
- package/src/lib/components/company-edit-drawer.js +2 -2
- package/src/lib/components/company-edit-drawer.js.map +1 -1
- package/src/lib/components/contact-properties-card.js +1 -1
- package/src/lib/components/contact-properties-card.js.map +1 -1
- package/src/lib/components/contacts-bulk-bar.js +2 -2
- package/src/lib/components/contacts-bulk-bar.js.map +1 -1
- package/src/lib/components/contacts-section.js +2 -1
- package/src/lib/components/contacts-section.js.map +1 -1
- package/src/lib/components/deal-edit-drawer.js +2 -2
- package/src/lib/components/deal-edit-drawer.js.map +1 -1
- package/src/lib/components/deals-bulk-bar.js +2 -2
- package/src/lib/components/deals-bulk-bar.js.map +1 -1
- package/src/lib/components/lead-owner-select.js +2 -2
- package/src/lib/components/lead-owner-select.js.map +1 -1
- package/src/lib/components/new-contact-drawer.js +2 -2
- package/src/lib/components/new-contact-drawer.js.map +1 -1
- package/src/lib/components/settings-section.js +10 -4
- package/src/lib/components/settings-section.js.map +1 -1
- package/src/lib/components/task-edit-drawer.js +2 -2
- package/src/lib/components/task-edit-drawer.js.map +1 -1
- package/src/lib/components/tasks-bulk-bar.js +2 -2
- package/src/lib/components/tasks-bulk-bar.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/settings-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n canManageOrg,\n type ConsolePluginPageProps,\n CRM_ASSIGNMENT_RULES_MAX,\n CRM_ASSIGNMENT_RULES_PATH,\n CRM_AUTO_CREATE_COMPANIES_PATH,\n CRM_ROUND_ROBIN_POOL_MAX,\n CRM_ROUND_ROBIN_POOL_PATH,\n type CrmAssignmentRule,\n crmHostDefaultOwnerSegments,\n describeAssignmentRule,\n orgAutoCreatesCompanies,\n type OrgRole,\n pluginDocsHelp,\n readCrmAssignmentSettings,\n roundRobinOrder,\n} from '@aglyn/aglyn'\nimport { mdiArrowDown, mdiArrowUp, mdiDeleteOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu 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 {\n useFirestore,\n useFirestoreDoc,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n FormControlLabel,\n FormGroup,\n FormHelperText,\n IconButton,\n MenuItem,\n Stack,\n Switch,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { deleteField, doc, FieldPath, updateDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport AssignmentRuleDrawer from './assignment-rule-drawer'\nimport EmailTemplatesCard from './email-templates-card'\nimport EmailCaptureCard from './email-capture-card'\nimport RecipesCard from './recipes-card'\nimport SendingAddressesCard from './sending-addresses-card'\n\nexport type CrmSettingsSectionProps = Pick<ConsolePluginPageProps, 'hostId' | 'org'>\n\n/**\n * Whether the signed-in member may change an org-wide CRM setting, and\n * whether that is known yet.\n *\n * The org document's client branch admits an OWNER or ADMIN and nobody\n * else — `canManageOrg()` in the rules — so the question is the caller's\n * org role, read off their own membership document, which the rules let a\n * member read for themselves. A scoped collaborator, an editor, a viewer:\n * each sees the switch and cannot move it, with the reason beside it,\n * rather than a switch that moves and snaps back with a bare\n * `permission-denied`. `ready` separates \"no\" from \"not yet\", so the\n * control disables with a reason instead of hiding until the read lands.\n */\nexport function useCanManageCrmSettings(orgId: string | undefined): {\n canManage: boolean\n ready: boolean\n} {\n const firestore = useFirestore()\n const { data: user } = useUser()\n const uid = user?.uid ?? ''\n const { data: member, status } = useFirestoreDoc<{ role?: OrgRole }>(\n () => (orgId && uid ? doc(firestore, 'orgs', orgId, 'members', uid) : null),\n [firestore, orgId, uid],\n )\n return {\n canManage: canManageOrg(member?.role ?? null),\n ready: Boolean(orgId && uid) && status !== 'loading',\n }\n}\n\nexport interface AutoCreateCompaniesCardProps {\n /** The site the section is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Create companies from work email domains\" — the org's one switch over\n * what a capture does with a company nobody has filed yet (AGL-2613).\n *\n * ## What the switch decides, and what it does not\n *\n * A contact captured from `jane@acme.com` is linked to the company at\n * `acme.com` whether this is on or off, provided exactly one such company\n * is visible to the capturing site. The switch decides the case where NO\n * company carries the domain: on, the capture creates one named after the\n * domain and links the contact; off — the default — it creates nothing and\n * the contact waits for a person to file them. Public mailbox domains never\n * create a company either way; a workspace's consumer list is not a list\n * of accounts.\n *\n * ## Written where it is read\n *\n * One dotted-path `update()` onto `orgs/{orgId}`, so the org's other keys\n * are untouched and the map under `crm` can grow a key per setting. The\n * shell's org listener delivers the new value back, which is what the\n * switch reflects; a local copy holds the click only until then.\n */\nexport function AutoCreateCompaniesCard(props: AutoCreateCompaniesCardProps) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n\n const stored = orgAutoCreatesCompanies(org as Record<string, unknown> | undefined)\n const [checked, setChecked] = useState(stored)\n const [busy, setBusy] = useState(false)\n // The stored value wins whenever it changes: the click is optimistic, and\n // the org document is what the capture door will actually read.\n useEffect(() => setChecked(stored), [stored])\n\n const handleChange = async (next: boolean) => {\n // The switch is disabled for a member who may not move it; the guard\n // stands anyway, because a disabled control still delivers a change\n // event in some environments and the rules would refuse the write.\n if (!orgId || !canManage) return\n setChecked(next)\n setBusy(true)\n try {\n await updateDoc(doc(firestore, 'orgs', orgId), {\n [CRM_AUTO_CREATE_COMPANIES_PATH]: next,\n })\n enqueueSnackbar(\n next\n ? 'Companies will be created from work email domains'\n : 'Companies will no longer be created from email domains',\n { variant: 'success', persist: false },\n )\n } catch (error) {\n console.error(error)\n setChecked(stored)\n enqueueSnackbar('The setting could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }\n\n const ready = scopeReady && roleReady\n return (\n <CardDisplay\n header={'Companies'}\n help={pluginDocsHelp('crmSettings', { anchor: '#companies' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={checked}\n disabled={!ready || !canManage || busy}\n onChange={(event) => void handleChange(event.target.checked)}\n />\n }\n label=\"Create companies from work email domains\"\n />\n <FormHelperText>\n {'A contact captured with a work email address is linked to the ' +\n 'company whose domain matches it. When this is on and no such ' +\n 'company exists yet, one is created from the domain — acme.com ' +\n 'becomes Acme — and the contact is linked to it. Public mailbox ' +\n 'domains such as gmail.com never create a company.'}\n </FormHelperText>\n {ready && !canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only a workspace owner or admin can change this.'}\n </Typography>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nAutoCreateCompaniesCard.displayName = 'AutoCreateCompaniesCard'\n\n/** The caption every card shows a member who may read but not change it. */\nfunction ManagersOnlyNote(props: { ready: boolean; canManage: boolean }) {\n return props.ready && !props.canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only a workspace owner or admin can change this.'}\n </Typography>\n ) : null\n}\nManagersOnlyNote.displayName = 'ManagersOnlyNote'\n\n/** The `value` of the default-owner picker's \"nobody\" entry. */\nconst NO_DEFAULT_OWNER = ''\n\nexport interface AssignmentCardProps {\n /** The site the section is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Default owner\" — who gets the records captured on a site when no\n * assignment rule claims them (AGL-2618).\n *\n * Per site, on the org document: the reader is the org-level assignment\n * pass, which reads one document for every site's default, and the writer\n * is the same owner-or-admin the rest of the `crm` map admits. Under a\n * site the card is that site's one picker; at the ORGANIZATION level\n * (AGL-2630) it is one picker per site in the org, because the map is per\n * site and this is the one place all of it is on screen at once. The\n * field is addressed by `FieldPath` segments rather than a dotted string\n * because a host id is a document id and may contain a dot — joined with\n * dots, the write would land beside the setting rather than in it.\n * Clearing a picker deletes its field, so an org that once set a default\n * and unset it reads exactly like one that never did.\n */\nexport function DefaultOwnerCard(props: AssignmentCardProps) {\n const { hostId, org } = props\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n const roster = useOrgMemberDirectory(orgId)\n const mount = useCrmOrgMount()\n const sites = useMemo<ReadonlyArray<{ id: string; name: string | null }>>(\n () =>\n hostId\n ? [{ id: hostId, name: null }]\n : (mount?.hosts ?? []).map((host) => ({ id: host.id, name: host.name })),\n [hostId, mount?.hosts],\n )\n const ready = scopeReady && roleReady && (Boolean(hostId) || Boolean(mount?.hostsReady))\n return (\n <CardDisplay\n header={'Default owner'}\n help={pluginDocsHelp('crmSettings', { anchor: '#default-owner' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {sites.map((site) => (\n <DefaultOwnerPicker\n key={site.id}\n orgId={orgId}\n siteId={site.id}\n siteName={site.name}\n org={org}\n roster={roster}\n canManage={canManage}\n ready={ready}\n />\n ))}\n {!hostId && ready && sites.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nobody to route to.'}\n </Typography>\n ) : null}\n <FormHelperText>\n {'Every contact captured on a site — a form, a sign-up, a booking, ' +\n 'an order — that no assignment rule claims is handed to that ' +\n \"site's default owner, and they are notified. A contact added by \" +\n 'hand or imported with an owner keeps the owner you chose.'}\n </FormHelperText>\n {roster.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {roster.error}\n </Typography>\n ) : null}\n <ManagersOnlyNote ready={ready} canManage={canManage} />\n </Stack>\n </CardDisplay>\n )\n}\nDefaultOwnerCard.displayName = 'DefaultOwnerCard'\n\n/** One site's slot in the org-wide default-owner map, as a picker. */\nfunction DefaultOwnerPicker(props: {\n orgId: string | undefined\n siteId: string\n /** How the site reads; `null` under the site itself, where \"this site\" will do. */\n siteName: string | null\n org?: Partial<AglynOrgBilling> | null\n roster: ReturnType<typeof useOrgMemberDirectory>\n canManage: boolean\n ready: boolean\n}) {\n const { orgId, siteId, siteName, org, roster, canManage, ready } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const siteLabel = siteName ?? 'this site'\n\n const stored =\n readCrmAssignmentSettings(org as Record<string, unknown> | undefined)\n .hostDefaultOwners[siteId] ?? NO_DEFAULT_OWNER\n const [value, setValue] = useState(stored)\n const [busy, setBusy] = useState(false)\n useEffect(() => setValue(stored), [stored])\n\n const handleChange = async (next: string) => {\n if (!orgId || !canManage) return\n setValue(next)\n setBusy(true)\n try {\n await updateDoc(\n doc(firestore, 'orgs', orgId),\n new FieldPath(...crmHostDefaultOwnerSegments(siteId)),\n next || deleteField(),\n )\n enqueueSnackbar(\n next\n ? `New contacts on ${siteLabel} go to ${roster.nameOf(next)} unless a rule says otherwise`\n : `New contacts on ${siteLabel} stay unassigned unless a rule says otherwise`,\n { variant: 'success', persist: false },\n )\n } catch (error) {\n console.error(error)\n setValue(stored)\n enqueueSnackbar('The default owner could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }\n\n // A stored owner the roster no longer lists is still shown, by uid, so\n // the picker never silently reads \"Nobody\" for a setting that is there.\n const options = useMemo(\n () =>\n stored && !roster.members.some((member) => member.uid === stored)\n ? [...roster.members, { uid: stored, label: `${stored} (former member)` }]\n : roster.members,\n [roster.members, stored],\n )\n return (\n <TextField\n select\n size=\"small\"\n label={siteName ? `Default owner · ${siteName}` : 'Default owner for this site'}\n value={value}\n onChange={(event) => void handleChange(event.target.value)}\n disabled={!ready || !canManage || busy || roster.loading}\n // \"Nobody\" is the empty value, which a select would otherwise\n // render as a blank rather than as the choice it is. The label is\n // shrunk with it: a field that always draws a value has no empty\n // state for the label to sit in, and left to itself it decides from\n // the value alone and paints the label over the choice.\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n sx={{ maxWidth: 360 }}\n >\n <MenuItem value={NO_DEFAULT_OWNER}>{'Nobody — leave unassigned'}</MenuItem>\n {options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {member.label}\n </MenuItem>\n ))}\n </TextField>\n )\n}\nDefaultOwnerPicker.displayName = 'DefaultOwnerPicker'\n\n/**\n * \"Assignment rules\" — the ordered list the capture pass tries first\n * (AGL-2618), with Add rule in a drawer, reorder and delete.\n *\n * ## The whole list is the unit of write\n *\n * The rules are an array on the org document and every edit — a new rule,\n * a move, a delete — writes the whole array by its dotted path. A per-rule\n * write has no address (an array element is not a field), and the list is\n * bounded at fifty, so the write is small. The array the card writes is\n * the array it read off the org prop plus the one edit, so two admins\n * editing at once last-write-wins at the granularity of one edit, which\n * is what a list this short and this rarely edited needs.\n *\n * ## First match wins, so order is the meaning\n *\n * A rule's position is a fact about it — \"bookings go to Kim, and\n * everything else to Sam\" is two rules in that order and nonsense in the\n * other — which is why the reorder controls sit in the first column and\n * the row reads \"1st, 2nd…\" rather than a name. Up and down rather than\n * drag, the way the custom-field list does it: a drag handle needs a\n * pointer, and the reorder is rare.\n */\nexport function AssignmentRulesCard(props: AssignmentCardProps) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n const roster = useOrgMemberDirectory(orgId)\n const settings = useMemo(\n () => readCrmAssignmentSettings(org as Record<string, unknown> | undefined),\n [org],\n )\n const { rules } = settings\n const [drawerOpen, setDrawerOpen] = useState(false)\n const [busy, setBusy] = useState(false)\n\n const writeRules = useCallback(\n async (next: CrmAssignmentRule[], said: string) => {\n if (!orgId || !canManage) return\n setBusy(true)\n try {\n await updateDoc(doc(firestore, 'orgs', orgId), {\n [CRM_ASSIGNMENT_RULES_PATH]: next,\n })\n enqueueSnackbar(said, { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The rules could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n throw error\n } finally {\n setBusy(false)\n }\n },\n [firestore, orgId, canManage, enqueueSnackbar],\n )\n\n const move = (index: number, by: -1 | 1) => {\n const target = index + by\n if (target < 0 || target >= rules.length) return\n const next = [...rules]\n ;[next[index], next[target]] = [next[target], next[index]]\n void writeRules(next, 'Rule moved').catch(() => undefined)\n }\n const remove = (rule: CrmAssignmentRule) => {\n void writeRules(\n rules.filter((entry) => entry.id !== rule.id),\n 'Rule deleted',\n ).catch(() => undefined)\n }\n const handleAdd = async (rule: CrmAssignmentRule) => {\n await writeRules([...rules, rule], 'Rule added')\n setDrawerOpen(false)\n }\n\n const ready = scopeReady && roleReady\n const atCap = rules.length >= CRM_ASSIGNMENT_RULES_MAX\n const canEdit = ready && canManage && !busy\n const ordinal = (index: number) => {\n const n = index + 1\n const suffix =\n n % 100 >= 11 && n % 100 <= 13\n ? 'th'\n : (['th', 'st', 'nd', 'rd'] as const)[n % 10] ?? 'th'\n return `${n}${suffix}`\n }\n return (\n <CardDisplay\n header={'Assignment rules'}\n help={pluginDocsHelp('crmSettings', { anchor: '#assignment-rules' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Button\n variant=\"contained\"\n disabled={!canEdit || atCap}\n onClick={() => setDrawerOpen(true)}\n >\n {'Add rule'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Tried in order for every new contact captured on any site in the ' +\n 'workspace; the first rule whose every condition holds assigns the ' +\n 'owner. A contact no rule claims goes to the capturing site’s ' +\n 'default owner, or stays unassigned.'}\n </Typography>\n {rules.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No rules yet. Add one to route new contacts by where they came from.'}\n </Typography>\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell sx={{ width: 120 }}>{'Order'}</TableCell>\n <TableCell>{'When'}</TableCell>\n <TableCell>{'Assign to'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {rules.map((rule, index) => {\n const described = describeAssignmentRule(rule, roster.nameOf)\n return (\n <TableRow key={rule.id} hover>\n <TableCell>\n <Stack direction=\"row\" spacing={0} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ minWidth: 32 }}>\n {ordinal(index)}\n </Typography>\n <IconButton\n size=\"small\"\n disabled={!canEdit || index === 0}\n onClick={() => move(index, -1)}\n >\n <MdiIcon path={mdiArrowUp.path} size={0.7} />\n <SrOnly>{`Move rule ${index + 1} up`}</SrOnly>\n </IconButton>\n <IconButton\n size=\"small\"\n disabled={!canEdit || index === rules.length - 1}\n onClick={() => move(index, 1)}\n >\n <MdiIcon path={mdiArrowDown.path} size={0.7} />\n <SrOnly>{`Move rule ${index + 1} down`}</SrOnly>\n </IconButton>\n </Stack>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{described.when}</Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{described.assign}</Typography>\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 56 }}>\n <RowActionsMenu\n label={`Rule ${index + 1}`}\n items={[\n {\n key: 'delete',\n label: 'Delete rule',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: !canEdit,\n onClick: () => remove(rule),\n },\n ]}\n />\n </TableCell>\n </TableRow>\n )\n })}\n </TableBody>\n </ScrollTable>\n )}\n {atCap ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`A workspace keeps at most ${CRM_ASSIGNMENT_RULES_MAX} rules. Delete one to add another.`}\n </Typography>\n ) : null}\n <ManagersOnlyNote ready={ready} canManage={canManage} />\n </Stack>\n <AssignmentRuleDrawer\n open={drawerOpen}\n onClose={() => setDrawerOpen(false)}\n members={roster.members}\n membersLoading={roster.loading}\n poolSize={settings.pool.memberUids.length}\n existingIds={rules.map((rule) => rule.id)}\n onSubmit={handleAdd}\n />\n </CardDisplay>\n )\n}\nAssignmentRulesCard.displayName = 'AssignmentRulesCard'\n\n/**\n * \"Round robin\" — the members handed records in turn (AGL-2618).\n *\n * The pool is the roster with a checkbox per member, and its ORDER is the\n * order members were checked: a member checked later joins the end of the\n * rotation, and unchecking removes them without disturbing the others. The\n * pointer — who got the last record — is the server's to move and is only\n * read here, as \"next up\", so an admin can see where the rotation stands\n * without being able to put a thumb on it.\n */\nexport function RoundRobinCard(props: AssignmentCardProps) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n const roster = useOrgMemberDirectory(orgId)\n const { pool } = useMemo(\n () => readCrmAssignmentSettings(org as Record<string, unknown> | undefined),\n [org],\n )\n const [busy, setBusy] = useState(false)\n\n const toggle = async (uid: string, inPool: boolean) => {\n if (!orgId || !canManage) return\n const next = inPool\n ? [...pool.memberUids.filter((entry) => entry !== uid), uid]\n : pool.memberUids.filter((entry) => entry !== uid)\n if (next.length > CRM_ROUND_ROBIN_POOL_MAX) return\n setBusy(true)\n try {\n await updateDoc(doc(firestore, 'orgs', orgId), {\n [CRM_ROUND_ROBIN_POOL_PATH]: next,\n })\n enqueueSnackbar(\n inPool\n ? `${roster.nameOf(uid)} joined the rotation`\n : `${roster.nameOf(uid)} left the rotation`,\n { variant: 'success', persist: false },\n )\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The pool could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }\n\n const ready = scopeReady && roleReady\n const canEdit = ready && canManage && !busy && !roster.loading\n const order = roundRobinOrder(pool.memberUids, pool.lastAssignedUid)\n const nextUp = order[0]\n return (\n <CardDisplay\n header={'Round robin'}\n help={pluginDocsHelp('crmSettings', { anchor: '#round-robin' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The members a round-robin rule, or an automation set to round ' +\n 'robin, hands records to in turn. Each new record goes to the ' +\n 'member after the last one who got a record, wrapping round.'}\n </Typography>\n {roster.loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading the team…'}\n </Typography>\n ) : roster.error ? (\n <Typography variant=\"body2\" color=\"error\">\n {roster.error}\n </Typography>\n ) : (\n <FormGroup>\n {roster.members.map((member) => (\n <FormControlLabel\n key={member.uid}\n control={\n <Checkbox\n size=\"small\"\n checked={pool.memberUids.includes(member.uid)}\n disabled={!canEdit}\n onChange={(event) => void toggle(member.uid, event.target.checked)}\n />\n }\n label={member.label}\n />\n ))}\n </FormGroup>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {pool.memberUids.length === 0\n ? 'Nobody is in the rotation; a round-robin rule is skipped until somebody is.'\n : `Rotation: ${pool.memberUids.map(roster.nameOf).join(' → ')}. Next up: ${roster.nameOf(nextUp)}.`}\n </Typography>\n <ManagersOnlyNote ready={ready} canManage={canManage} />\n </Stack>\n </CardDisplay>\n )\n}\nRoundRobinCard.displayName = 'RoundRobinCard'\n\n/**\n * `/crm/settings` — what the CRM does on its own, for every site in the\n * workspace (AGL-2613).\n *\n * A stack of cards, one per concern, so a later setting arrives as a card\n * beside this one rather than a field inside it. Every card writes the org\n * document, because a CRM setting is a fact about how the business files\n * people and not about one site; the section is reached from a site's hub\n * or from the organization's (AGL-2630), and writes the same document from\n * either. The one per-site setting — the default owner — is the site's\n * slot in an org-wide map: under a site the card names the site it is for,\n * and at the organization level it lists every site's slot.\n *\n * The Recipes card (AGL-2639) is the one card that mounts at the\n * ORGANIZATION level only. It installs an automation onto a site of the\n * reader's choosing, which is the org's need; under a site the Actions\n * page's own Recipes menu is the door, and a second one here would write\n * the same action by a different route. Last, because the welcome recipe\n * leans on the round-robin pool set up in the card above it.\n *\n * The Email templates card (AGL-2658) is the one card every CRM editor may\n * write to — a template is working material, not policy — and mounts at\n * both levels, listing the site's letters under a site and the whole\n * workspace's from the organization's hub.\n *\n * Your sending addresses (AGL-2975) sits directly under Email capture,\n * because it is where a member who copies that address from an alias looks\n * next: it is the reader's OWN list, at both levels, and writes through the\n * core route rather than the org document.\n */\nexport function CrmSettingsSection(props: CrmSettingsSectionProps) {\n const { hostId, org } = props\n const mount = useCrmOrgMount()\n // The Email capture card (AGL-2657) rotates on the same owner-or-admin\n // bar every other card writes on; the scope and the role are resolved\n // here once and handed down, since the card itself writes no document.\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n return (\n <Stack spacing={3}>\n <AutoCreateCompaniesCard hostId={hostId} org={org} />\n <DefaultOwnerCard hostId={hostId} org={org} />\n <AssignmentRulesCard hostId={hostId} org={org} />\n <RoundRobinCard hostId={hostId} org={org} />\n <EmailTemplatesCard hostId={hostId} org={org} />\n <EmailCaptureCard hostId={hostId} canManage={canManage} ready={scopeReady && roleReady} />\n <SendingAddressesCard orgId={orgId} ready={scopeReady} />\n {mount ? <RecipesCard org={org} /> : null}\n </Stack>\n )\n}\nCrmSettingsSection.displayName = 'CrmSettingsSection'\n\nexport default CrmSettingsSection\n"],"names":["canManageOrg","CRM_ASSIGNMENT_RULES_MAX","CRM_ASSIGNMENT_RULES_PATH","CRM_AUTO_CREATE_COMPANIES_PATH","CRM_ROUND_ROBIN_POOL_MAX","CRM_ROUND_ROBIN_POOL_PATH","crmHostDefaultOwnerSegments","describeAssignmentRule","orgAutoCreatesCompanies","pluginDocsHelp","readCrmAssignmentSettings","roundRobinOrder","mdiArrowDown","mdiArrowUp","mdiDeleteOutline","CardDisplay","MdiIcon","SrOnly","RowActionsMenu","ScrollTable","useSnackbar","useFirestore","useFirestoreDoc","useUser","Button","Checkbox","FormControlLabel","FormGroup","FormHelperText","IconButton","MenuItem","Stack","Switch","TableBody","TableCell","TableHead","TableRow","TextField","Typography","deleteField","doc","FieldPath","updateDoc","useCallback","useEffect","useMemo","useState","useCrmOrgMount","useCrmScope","useOrgMemberDirectory","AssignmentRuleDrawer","EmailTemplatesCard","EmailCaptureCard","RecipesCard","SendingAddressesCard","useCanManageCrmSettings","orgId","firestore","data","user","uid","member","status","canManage","role","ready","Boolean","AutoCreateCompaniesCard","props","hostId","org","enqueueSnackbar","scopeReady","roleReady","stored","checked","setChecked","busy","setBusy","handleChange","next","variant","persist","error","console","allowDuplicate","header","help","anchor","contentGutterX","contentGutterY","spacing","control","size","disabled","onChange","event","target","label","color","displayName","ManagersOnlyNote","NO_DEFAULT_OWNER","DefaultOwnerCard","roster","mount","sites","id","name","hosts","map","host","hostsReady","site","DefaultOwnerPicker","siteId","siteName","length","siteLabel","hostDefaultOwners","value","setValue","nameOf","options","members","some","select","loading","slotProps","displayEmpty","inputLabel","shrink","sx","maxWidth","AssignmentRulesCard","settings","rules","drawerOpen","setDrawerOpen","writeRules","said","move","index","by","catch","undefined","remove","rule","filter","entry","handleAdd","atCap","canEdit","ordinal","n","suffix","contentBordered","HeaderProps","action","onClick","width","align","described","hover","direction","alignItems","minWidth","path","when","assign","items","key","icon","destructive","open","onClose","membersLoading","poolSize","pool","memberUids","existingIds","onSubmit","RoundRobinCard","toggle","inPool","order","lastAssignedUid","nextUp","includes","join","CrmSettingsSection"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/settings-section.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n type AglynOrgBilling,\n canManageOrg,\n type ConsolePluginPageProps,\n CRM_ASSIGNMENT_RULES_MAX,\n CRM_ASSIGNMENT_RULES_PATH,\n CRM_AUTO_CREATE_COMPANIES_PATH,\n CRM_ROUND_ROBIN_POOL_MAX,\n CRM_ROUND_ROBIN_POOL_PATH,\n type CrmAssignmentRule,\n crmHostDefaultOwnerSegments,\n describeAssignmentRule,\n orgAutoCreatesCompanies,\n type OrgRole,\n pluginDocsHelp,\n readCrmAssignmentSettings,\n roundRobinOrder,\n crmMemberPickerLabel,\n} from '@aglyn/aglyn'\nimport { mdiArrowDown, mdiArrowUp, mdiDeleteOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, SrOnly } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu 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 {\n useFirestore,\n useFirestoreDoc,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Checkbox,\n FormControlLabel,\n FormGroup,\n FormHelperText,\n IconButton,\n MenuItem,\n Stack,\n Switch,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { deleteField, doc, FieldPath, updateDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport AssignmentRuleDrawer from './assignment-rule-drawer'\nimport EmailTemplatesCard from './email-templates-card'\nimport EmailCaptureCard from './email-capture-card'\nimport RecipesCard from './recipes-card'\nimport SendingAddressesCard from './sending-addresses-card'\n\nexport type CrmSettingsSectionProps = Pick<ConsolePluginPageProps, 'hostId' | 'org'>\n\n/**\n * Whether the signed-in member may change an org-wide CRM setting, and\n * whether that is known yet.\n *\n * The org document's client branch admits an OWNER or ADMIN and nobody\n * else — `canManageOrg()` in the rules — so the question is the caller's\n * org role, read off their own membership document, which the rules let a\n * member read for themselves. A scoped collaborator, an editor, a viewer:\n * each sees the switch and cannot move it, with the reason beside it,\n * rather than a switch that moves and snaps back with a bare\n * `permission-denied`. `ready` separates \"no\" from \"not yet\", so the\n * control disables with a reason instead of hiding until the read lands.\n */\nexport function useCanManageCrmSettings(orgId: string | undefined): {\n canManage: boolean\n ready: boolean\n} {\n const firestore = useFirestore()\n const { data: user } = useUser()\n const uid = user?.uid ?? ''\n const { data: member, status } = useFirestoreDoc<{ role?: OrgRole }>(\n () => (orgId && uid ? doc(firestore, 'orgs', orgId, 'members', uid) : null),\n [firestore, orgId, uid],\n )\n return {\n canManage: canManageOrg(member?.role ?? null),\n ready: Boolean(orgId && uid) && status !== 'loading',\n }\n}\n\nexport interface AutoCreateCompaniesCardProps {\n /** The site the section is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Create companies from work email domains\" — the org's one switch over\n * what a capture does with a company nobody has filed yet (AGL-2613).\n *\n * ## What the switch decides, and what it does not\n *\n * A contact captured from `jane@acme.com` is linked to the company at\n * `acme.com` whether this is on or off, provided exactly one such company\n * is visible to the capturing site. The switch decides the case where NO\n * company carries the domain: on, the capture creates one named after the\n * domain and links the contact; off — the default — it creates nothing and\n * the contact waits for a person to file them. Public mailbox domains never\n * create a company either way; a workspace's consumer list is not a list\n * of accounts.\n *\n * ## Written where it is read\n *\n * One dotted-path `update()` onto `orgs/{orgId}`, so the org's other keys\n * are untouched and the map under `crm` can grow a key per setting. The\n * shell's org listener delivers the new value back, which is what the\n * switch reflects; a local copy holds the click only until then.\n */\nexport function AutoCreateCompaniesCard(props: AutoCreateCompaniesCardProps) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n\n const stored = orgAutoCreatesCompanies(org as Record<string, unknown> | undefined)\n const [checked, setChecked] = useState(stored)\n const [busy, setBusy] = useState(false)\n // The stored value wins whenever it changes: the click is optimistic, and\n // the org document is what the capture door will actually read.\n useEffect(() => setChecked(stored), [stored])\n\n const handleChange = async (next: boolean) => {\n // The switch is disabled for a member who may not move it; the guard\n // stands anyway, because a disabled control still delivers a change\n // event in some environments and the rules would refuse the write.\n if (!orgId || !canManage) return\n setChecked(next)\n setBusy(true)\n try {\n await updateDoc(doc(firestore, 'orgs', orgId), {\n [CRM_AUTO_CREATE_COMPANIES_PATH]: next,\n })\n enqueueSnackbar(\n next\n ? 'Companies will be created from work email domains'\n : 'Companies will no longer be created from email domains',\n { variant: 'success', persist: false },\n )\n } catch (error) {\n console.error(error)\n setChecked(stored)\n enqueueSnackbar('The setting could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }\n\n const ready = scopeReady && roleReady\n return (\n <CardDisplay\n header={'Companies'}\n help={pluginDocsHelp('crmSettings', { anchor: '#companies' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <FormControlLabel\n control={\n <Switch\n size=\"small\"\n checked={checked}\n disabled={!ready || !canManage || busy}\n onChange={(event) => void handleChange(event.target.checked)}\n />\n }\n label=\"Create companies from work email domains\"\n />\n <FormHelperText>\n {'A contact captured with a work email address is linked to the ' +\n 'company whose domain matches it. When this is on and no such ' +\n 'company exists yet, one is created from the domain — acme.com ' +\n 'becomes Acme — and the contact is linked to it. Public mailbox ' +\n 'domains such as gmail.com never create a company.'}\n </FormHelperText>\n {ready && !canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only a workspace owner or admin can change this.'}\n </Typography>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nAutoCreateCompaniesCard.displayName = 'AutoCreateCompaniesCard'\n\n/** The caption every card shows a member who may read but not change it. */\nfunction ManagersOnlyNote(props: { ready: boolean; canManage: boolean }) {\n return props.ready && !props.canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only a workspace owner or admin can change this.'}\n </Typography>\n ) : null\n}\nManagersOnlyNote.displayName = 'ManagersOnlyNote'\n\n/** The `value` of the default-owner picker's \"nobody\" entry. */\nconst NO_DEFAULT_OWNER = ''\n\nexport interface AssignmentCardProps {\n /** The site the section is read under, or `null` at the organization level. */\n hostId: string | null\n org?: Partial<AglynOrgBilling> | null\n}\n\n/**\n * \"Default owner\" — who gets the records captured on a site when no\n * assignment rule claims them (AGL-2618).\n *\n * Per site, on the org document: the reader is the org-level assignment\n * pass, which reads one document for every site's default, and the writer\n * is the same owner-or-admin the rest of the `crm` map admits. Under a\n * site the card is that site's one picker; at the ORGANIZATION level\n * (AGL-2630) it is one picker per site in the org, because the map is per\n * site and this is the one place all of it is on screen at once. The\n * field is addressed by `FieldPath` segments rather than a dotted string\n * because a host id is a document id and may contain a dot — joined with\n * dots, the write would land beside the setting rather than in it.\n * Clearing a picker deletes its field, so an org that once set a default\n * and unset it reads exactly like one that never did.\n */\nexport function DefaultOwnerCard(props: AssignmentCardProps) {\n const { hostId, org } = props\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n const roster = useOrgMemberDirectory(orgId)\n const mount = useCrmOrgMount()\n const sites = useMemo<ReadonlyArray<{ id: string; name: string | null }>>(\n () =>\n hostId\n ? [{ id: hostId, name: null }]\n : (mount?.hosts ?? []).map((host) => ({ id: host.id, name: host.name })),\n [hostId, mount?.hosts],\n )\n const ready = scopeReady && roleReady && (Boolean(hostId) || Boolean(mount?.hostsReady))\n return (\n <CardDisplay\n header={'Default owner'}\n help={pluginDocsHelp('crmSettings', { anchor: '#default-owner' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {sites.map((site) => (\n <DefaultOwnerPicker\n key={site.id}\n orgId={orgId}\n siteId={site.id}\n siteName={site.name}\n org={org}\n roster={roster}\n canManage={canManage}\n ready={ready}\n />\n ))}\n {!hostId && ready && sites.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet, so there is nobody to route to.'}\n </Typography>\n ) : null}\n <FormHelperText>\n {'Every contact captured on a site — a form, a sign-up, a booking, ' +\n 'an order — that no assignment rule claims is handed to that ' +\n \"site's default owner, and they are notified. A contact added by \" +\n 'hand or imported with an owner keeps the owner you chose.'}\n </FormHelperText>\n {roster.error ? (\n <Typography variant=\"caption\" color=\"error\">\n {roster.error}\n </Typography>\n ) : null}\n <ManagersOnlyNote ready={ready} canManage={canManage} />\n </Stack>\n </CardDisplay>\n )\n}\nDefaultOwnerCard.displayName = 'DefaultOwnerCard'\n\n/** One site's slot in the org-wide default-owner map, as a picker. */\nfunction DefaultOwnerPicker(props: {\n orgId: string | undefined\n siteId: string\n /** How the site reads; `null` under the site itself, where \"this site\" will do. */\n siteName: string | null\n org?: Partial<AglynOrgBilling> | null\n roster: ReturnType<typeof useOrgMemberDirectory>\n canManage: boolean\n ready: boolean\n}) {\n const { orgId, siteId, siteName, org, roster, canManage, ready } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const siteLabel = siteName ?? 'this site'\n\n const stored =\n readCrmAssignmentSettings(org as Record<string, unknown> | undefined)\n .hostDefaultOwners[siteId] ?? NO_DEFAULT_OWNER\n const [value, setValue] = useState(stored)\n const [busy, setBusy] = useState(false)\n useEffect(() => setValue(stored), [stored])\n\n const handleChange = async (next: string) => {\n if (!orgId || !canManage) return\n setValue(next)\n setBusy(true)\n try {\n await updateDoc(\n doc(firestore, 'orgs', orgId),\n new FieldPath(...crmHostDefaultOwnerSegments(siteId)),\n next || deleteField(),\n )\n enqueueSnackbar(\n next\n ? `New contacts on ${siteLabel} go to ${roster.nameOf(next)} unless a rule says otherwise`\n : `New contacts on ${siteLabel} stay unassigned unless a rule says otherwise`,\n { variant: 'success', persist: false },\n )\n } catch (error) {\n console.error(error)\n setValue(stored)\n enqueueSnackbar('The default owner could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }\n\n // A stored owner the roster no longer lists is still shown, by uid, so\n // the picker never silently reads \"Nobody\" for a setting that is there.\n const options = useMemo(\n () =>\n stored && !roster.members.some((member) => member.uid === stored)\n ? [...roster.members, { uid: stored, label: `${stored} (former member)` }]\n : roster.members,\n [roster.members, stored],\n )\n return (\n <TextField\n select\n size=\"small\"\n label={siteName ? `Default owner · ${siteName}` : 'Default owner for this site'}\n value={value}\n onChange={(event) => void handleChange(event.target.value)}\n disabled={!ready || !canManage || busy || roster.loading}\n // \"Nobody\" is the empty value, which a select would otherwise\n // render as a blank rather than as the choice it is. The label is\n // shrunk with it: a field that always draws a value has no empty\n // state for the label to sit in, and left to itself it decides from\n // the value alone and paints the label over the choice.\n slotProps={{ select: { displayEmpty: true }, inputLabel: { shrink: true } }}\n sx={{ maxWidth: 360 }}\n >\n <MenuItem value={NO_DEFAULT_OWNER}>{'Nobody — leave unassigned'}</MenuItem>\n {options.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n )\n}\nDefaultOwnerPicker.displayName = 'DefaultOwnerPicker'\n\n/**\n * \"Assignment rules\" — the ordered list the capture pass tries first\n * (AGL-2618), with Add rule in a drawer, reorder and delete.\n *\n * ## The whole list is the unit of write\n *\n * The rules are an array on the org document and every edit — a new rule,\n * a move, a delete — writes the whole array by its dotted path. A per-rule\n * write has no address (an array element is not a field), and the list is\n * bounded at fifty, so the write is small. The array the card writes is\n * the array it read off the org prop plus the one edit, so two admins\n * editing at once last-write-wins at the granularity of one edit, which\n * is what a list this short and this rarely edited needs.\n *\n * ## First match wins, so order is the meaning\n *\n * A rule's position is a fact about it — \"bookings go to Kim, and\n * everything else to Sam\" is two rules in that order and nonsense in the\n * other — which is why the reorder controls sit in the first column and\n * the row reads \"1st, 2nd…\" rather than a name. Up and down rather than\n * drag, the way the custom-field list does it: a drag handle needs a\n * pointer, and the reorder is rare.\n */\nexport function AssignmentRulesCard(props: AssignmentCardProps) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n const roster = useOrgMemberDirectory(orgId)\n const settings = useMemo(\n () => readCrmAssignmentSettings(org as Record<string, unknown> | undefined),\n [org],\n )\n const { rules } = settings\n const [drawerOpen, setDrawerOpen] = useState(false)\n const [busy, setBusy] = useState(false)\n\n const writeRules = useCallback(\n async (next: CrmAssignmentRule[], said: string) => {\n if (!orgId || !canManage) return\n setBusy(true)\n try {\n await updateDoc(doc(firestore, 'orgs', orgId), {\n [CRM_ASSIGNMENT_RULES_PATH]: next,\n })\n enqueueSnackbar(said, { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The rules could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n throw error\n } finally {\n setBusy(false)\n }\n },\n [firestore, orgId, canManage, enqueueSnackbar],\n )\n\n const move = (index: number, by: -1 | 1) => {\n const target = index + by\n if (target < 0 || target >= rules.length) return\n const next = [...rules]\n ;[next[index], next[target]] = [next[target], next[index]]\n void writeRules(next, 'Rule moved').catch(() => undefined)\n }\n const remove = (rule: CrmAssignmentRule) => {\n void writeRules(\n rules.filter((entry) => entry.id !== rule.id),\n 'Rule deleted',\n ).catch(() => undefined)\n }\n const handleAdd = async (rule: CrmAssignmentRule) => {\n await writeRules([...rules, rule], 'Rule added')\n setDrawerOpen(false)\n }\n\n const ready = scopeReady && roleReady\n const atCap = rules.length >= CRM_ASSIGNMENT_RULES_MAX\n const canEdit = ready && canManage && !busy\n const ordinal = (index: number) => {\n const n = index + 1\n const suffix =\n n % 100 >= 11 && n % 100 <= 13\n ? 'th'\n : (['th', 'st', 'nd', 'rd'] as const)[n % 10] ?? 'th'\n return `${n}${suffix}`\n }\n return (\n <CardDisplay\n header={'Assignment rules'}\n help={pluginDocsHelp('crmSettings', { anchor: '#assignment-rules' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Button\n variant=\"contained\"\n disabled={!canEdit || atCap}\n onClick={() => setDrawerOpen(true)}\n >\n {'Add rule'}\n </Button>\n ),\n }}\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Tried in order for every new contact captured on any site in the ' +\n 'workspace; the first rule whose every condition holds assigns the ' +\n 'owner. A contact no rule claims goes to the capturing site’s ' +\n 'default owner, or stays unassigned.'}\n </Typography>\n {rules.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No rules yet. Add one to route new contacts by where they came from.'}\n </Typography>\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell sx={{ width: 120 }}>{'Order'}</TableCell>\n <TableCell>{'When'}</TableCell>\n <TableCell>{'Assign to'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {rules.map((rule, index) => {\n const described = describeAssignmentRule(rule, roster.nameOf)\n return (\n <TableRow key={rule.id} hover>\n <TableCell>\n <Stack direction=\"row\" spacing={0} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ minWidth: 32 }}>\n {ordinal(index)}\n </Typography>\n <IconButton\n size=\"small\"\n disabled={!canEdit || index === 0}\n onClick={() => move(index, -1)}\n >\n <MdiIcon path={mdiArrowUp.path} size={0.7} />\n <SrOnly>{`Move rule ${index + 1} up`}</SrOnly>\n </IconButton>\n <IconButton\n size=\"small\"\n disabled={!canEdit || index === rules.length - 1}\n onClick={() => move(index, 1)}\n >\n <MdiIcon path={mdiArrowDown.path} size={0.7} />\n <SrOnly>{`Move rule ${index + 1} down`}</SrOnly>\n </IconButton>\n </Stack>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{described.when}</Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{described.assign}</Typography>\n </TableCell>\n <TableCell align=\"right\" sx={{ width: 56 }}>\n <RowActionsMenu\n label={`Rule ${index + 1}`}\n items={[\n {\n key: 'delete',\n label: 'Delete rule',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n disabled: !canEdit,\n onClick: () => remove(rule),\n },\n ]}\n />\n </TableCell>\n </TableRow>\n )\n })}\n </TableBody>\n </ScrollTable>\n )}\n {atCap ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`A workspace keeps at most ${CRM_ASSIGNMENT_RULES_MAX} rules. Delete one to add another.`}\n </Typography>\n ) : null}\n <ManagersOnlyNote ready={ready} canManage={canManage} />\n </Stack>\n <AssignmentRuleDrawer\n open={drawerOpen}\n onClose={() => setDrawerOpen(false)}\n members={roster.members}\n membersLoading={roster.loading}\n poolSize={settings.pool.memberUids.length}\n existingIds={rules.map((rule) => rule.id)}\n onSubmit={handleAdd}\n />\n </CardDisplay>\n )\n}\nAssignmentRulesCard.displayName = 'AssignmentRulesCard'\n\n/**\n * \"Round robin\" — the members handed records in turn (AGL-2618).\n *\n * The pool is the roster with a checkbox per member, and its ORDER is the\n * order members were checked: a member checked later joins the end of the\n * rotation, and unchecking removes them without disturbing the others. The\n * pointer — who got the last record — is the server's to move and is only\n * read here, as \"next up\", so an admin can see where the rotation stands\n * without being able to put a thumb on it.\n */\nexport function RoundRobinCard(props: AssignmentCardProps) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n const roster = useOrgMemberDirectory(orgId)\n const { pool } = useMemo(\n () => readCrmAssignmentSettings(org as Record<string, unknown> | undefined),\n [org],\n )\n const [busy, setBusy] = useState(false)\n\n const toggle = async (uid: string, inPool: boolean) => {\n if (!orgId || !canManage) return\n const next = inPool\n ? [...pool.memberUids.filter((entry) => entry !== uid), uid]\n : pool.memberUids.filter((entry) => entry !== uid)\n if (next.length > CRM_ROUND_ROBIN_POOL_MAX) return\n setBusy(true)\n try {\n await updateDoc(doc(firestore, 'orgs', orgId), {\n [CRM_ROUND_ROBIN_POOL_PATH]: next,\n })\n enqueueSnackbar(\n inPool\n ? `${roster.nameOf(uid)} joined the rotation`\n : `${roster.nameOf(uid)} left the rotation`,\n { variant: 'success', persist: false },\n )\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The pool could not be saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }\n\n const ready = scopeReady && roleReady\n const canEdit = ready && canManage && !busy && !roster.loading\n const order = roundRobinOrder(pool.memberUids, pool.lastAssignedUid)\n const nextUp = order[0]\n // The rotation names people the way the checkboxes above do — with the\n // address, since two members can share a display name.\n const rotationName = (uid: string): string => {\n const member = roster.members.find((row) => row.uid === uid)\n return member ? crmMemberPickerLabel(member) : roster.nameOf(uid)\n }\n return (\n <CardDisplay\n header={'Round robin'}\n help={pluginDocsHelp('crmSettings', { anchor: '#round-robin' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The members a round-robin rule, or an automation set to round ' +\n 'robin, hands records to in turn. Each new record goes to the ' +\n 'member after the last one who got a record, wrapping round.'}\n </Typography>\n {roster.loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading the team…'}\n </Typography>\n ) : roster.error ? (\n <Typography variant=\"body2\" color=\"error\">\n {roster.error}\n </Typography>\n ) : (\n <FormGroup>\n {roster.members.map((member) => (\n <FormControlLabel\n key={member.uid}\n control={\n <Checkbox\n size=\"small\"\n checked={pool.memberUids.includes(member.uid)}\n disabled={!canEdit}\n onChange={(event) => void toggle(member.uid, event.target.checked)}\n />\n }\n label={crmMemberPickerLabel(member)}\n />\n ))}\n </FormGroup>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {pool.memberUids.length === 0\n ? 'Nobody is in the rotation; a round-robin rule is skipped until somebody is.'\n : `Rotation: ${pool.memberUids.map(rotationName).join(' → ')}. Next up: ${rotationName(nextUp)}.`}\n </Typography>\n <ManagersOnlyNote ready={ready} canManage={canManage} />\n </Stack>\n </CardDisplay>\n )\n}\nRoundRobinCard.displayName = 'RoundRobinCard'\n\n/**\n * `/crm/settings` — what the CRM does on its own, for every site in the\n * workspace (AGL-2613).\n *\n * A stack of cards, one per concern, so a later setting arrives as a card\n * beside this one rather than a field inside it. Every card writes the org\n * document, because a CRM setting is a fact about how the business files\n * people and not about one site; the section is reached from a site's hub\n * or from the organization's (AGL-2630), and writes the same document from\n * either. The one per-site setting — the default owner — is the site's\n * slot in an org-wide map: under a site the card names the site it is for,\n * and at the organization level it lists every site's slot.\n *\n * The Recipes card (AGL-2639) is the one card that mounts at the\n * ORGANIZATION level only. It installs an automation onto a site of the\n * reader's choosing, which is the org's need; under a site the Actions\n * page's own Recipes menu is the door, and a second one here would write\n * the same action by a different route. Last, because the welcome recipe\n * leans on the round-robin pool set up in the card above it.\n *\n * The Email templates card (AGL-2658) is the one card every CRM editor may\n * write to — a template is working material, not policy — and mounts at\n * both levels, listing the site's letters under a site and the whole\n * workspace's from the organization's hub.\n *\n * Your sending addresses (AGL-2975) sits directly under Email capture,\n * because it is where a member who copies that address from an alias looks\n * next: it is the reader's OWN list, at both levels, and writes through the\n * core route rather than the org document.\n */\nexport function CrmSettingsSection(props: CrmSettingsSectionProps) {\n const { hostId, org } = props\n const mount = useCrmOrgMount()\n // The Email capture card (AGL-2657) rotates on the same owner-or-admin\n // bar every other card writes on; the scope and the role are resolved\n // here once and handed down, since the card itself writes no document.\n const { orgId, ready: scopeReady } = useCrmScope({ hostId, org })\n const { canManage, ready: roleReady } = useCanManageCrmSettings(orgId)\n return (\n <Stack spacing={3}>\n <AutoCreateCompaniesCard hostId={hostId} org={org} />\n <DefaultOwnerCard hostId={hostId} org={org} />\n <AssignmentRulesCard hostId={hostId} org={org} />\n <RoundRobinCard hostId={hostId} org={org} />\n <EmailTemplatesCard hostId={hostId} org={org} />\n <EmailCaptureCard hostId={hostId} canManage={canManage} ready={scopeReady && roleReady} />\n <SendingAddressesCard orgId={orgId} ready={scopeReady} />\n {mount ? <RecipesCard org={org} /> : null}\n </Stack>\n )\n}\nCrmSettingsSection.displayName = 'CrmSettingsSection'\n\nexport default CrmSettingsSection\n"],"names":["canManageOrg","CRM_ASSIGNMENT_RULES_MAX","CRM_ASSIGNMENT_RULES_PATH","CRM_AUTO_CREATE_COMPANIES_PATH","CRM_ROUND_ROBIN_POOL_MAX","CRM_ROUND_ROBIN_POOL_PATH","crmHostDefaultOwnerSegments","describeAssignmentRule","orgAutoCreatesCompanies","pluginDocsHelp","readCrmAssignmentSettings","roundRobinOrder","crmMemberPickerLabel","mdiArrowDown","mdiArrowUp","mdiDeleteOutline","CardDisplay","MdiIcon","SrOnly","RowActionsMenu","ScrollTable","useSnackbar","useFirestore","useFirestoreDoc","useUser","Button","Checkbox","FormControlLabel","FormGroup","FormHelperText","IconButton","MenuItem","Stack","Switch","TableBody","TableCell","TableHead","TableRow","TextField","Typography","deleteField","doc","FieldPath","updateDoc","useCallback","useEffect","useMemo","useState","useCrmOrgMount","useCrmScope","useOrgMemberDirectory","AssignmentRuleDrawer","EmailTemplatesCard","EmailCaptureCard","RecipesCard","SendingAddressesCard","useCanManageCrmSettings","orgId","firestore","data","user","uid","member","status","canManage","role","ready","Boolean","AutoCreateCompaniesCard","props","hostId","org","enqueueSnackbar","scopeReady","roleReady","stored","checked","setChecked","busy","setBusy","handleChange","next","variant","persist","error","console","allowDuplicate","header","help","anchor","contentGutterX","contentGutterY","spacing","control","size","disabled","onChange","event","target","label","color","displayName","ManagersOnlyNote","NO_DEFAULT_OWNER","DefaultOwnerCard","roster","mount","sites","id","name","hosts","map","host","hostsReady","site","DefaultOwnerPicker","siteId","siteName","length","siteLabel","hostDefaultOwners","value","setValue","nameOf","options","members","some","select","loading","slotProps","displayEmpty","inputLabel","shrink","sx","maxWidth","AssignmentRulesCard","settings","rules","drawerOpen","setDrawerOpen","writeRules","said","move","index","by","catch","undefined","remove","rule","filter","entry","handleAdd","atCap","canEdit","ordinal","n","suffix","contentBordered","HeaderProps","action","onClick","width","align","described","hover","direction","alignItems","minWidth","path","when","assign","items","key","icon","destructive","open","onClose","membersLoading","poolSize","pool","memberUids","existingIds","onSubmit","RoundRobinCard","toggle","inPool","order","lastAssignedUid","nextUp","rotationName","find","row","includes","join","CrmSettingsSection"],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
-
import { CRM_COLLECTIONS, findOrgMember } from "@aglyn/aglyn";
|
|
19
|
+
import { CRM_COLLECTIONS, findOrgMember, crmMemberPickerLabel } from "@aglyn/aglyn";
|
|
20
20
|
import { useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
21
21
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
22
22
|
import { useFirestore, useUser, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
@@ -386,7 +386,7 @@ function TaskForm(props) {
|
|
|
386
386
|
directory.members.map((member)=>/*#__PURE__*/ _jsxs(MenuItem, {
|
|
387
387
|
value: member.uid,
|
|
388
388
|
children: [
|
|
389
|
-
member
|
|
389
|
+
crmMemberPickerLabel(member),
|
|
390
390
|
member.uid === (user == null ? void 0 : user.uid) ? ' (you)' : ''
|
|
391
391
|
]
|
|
392
392
|
}, member.uid))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/task-edit-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, findOrgMember } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Drawer,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { deleteDoc, doc } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport type { CrmTaskRow } from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { saveCrmTask } from '../model/task-api'\nimport {\n CRM_TASK_NOTES_MAX,\n CRM_TASK_TITLE_MAX,\n type CrmTaskFields,\n crmTaskFieldsOf,\n} from '../model/task-routes'\nimport {\n CRM_TASK_KIND_LABELS,\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\n CRM_TASK_PRIORITY_LABELS,\n dueAtToLocalInput,\n localInputToDueAt,\n} from '../model/task-views'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { isOrgTask } from '../model/task-scope'\nimport CrmRecordPicker from './crm-record-picker'\nimport { CrmSitePicker } from './crm-site-picker'\nimport TaskSnoozeMenu from './task-snooze-menu'\n\nexport interface TaskEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW task asks which site it is filed from —\n * or whether it is the organization's own, with no site (AGL-2637) —\n * and an edit keeps the task's own.\n */\n hostId: string | null\n org?: Record<string, unknown> | null\n orgId: string | null | undefined\n scope: readonly [string, string] | null\n /** The reader's tokens, or `null` at the organization level — no clause. */\n readTokens: readonly string[] | null\n /** The task being edited; absent while creating. */\n task?: CrmTaskRow | null\n /**\n * What a \"New task\" button pressed on a record fills in before the person\n * types: the record's own id, so the link is made for them.\n */\n prefill?: Partial<CrmTaskFields>\n /**\n * The verdict of the listener the edited row came from. An edit is refused\n * when the seed is unconfirmed, for the reason the contacts drawer refuses:\n * every field on this form is written back on save, and a form seeded from\n * a stale cache writes the stale values over newer ones.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n onSaved?: (taskId: string) => void\n}\n\n/**\n * Create or edit one task (AGL-2599).\n *\n * A drawer on the list, the console's standing shape for creating — the\n * list stays where it was and the form slides over it. The same drawer edits,\n * because a task is small enough that \"the record's own page\" would be this\n * form with more air around it.\n *\n * The form is mounted only while the drawer is open and keyed by the task,\n * so opening a different task or \"New task\" after an edit starts from that\n * task's values and not from whatever was last typed.\n */\nexport function TaskEditDrawer(props: TaskEditDrawerProps) {\n const { open, onClose, task } = props\n return (\n <Drawer anchor=\"right\" open={open} onClose={onClose}>\n {open ? <TaskForm key={task?.$id ?? 'new'} {...props} /> : null}\n </Drawer>\n )\n}\nTaskEditDrawer.displayName = 'TaskEditDrawer'\n\nfunction TaskForm(props: TaskEditDrawerProps) {\n const { onClose, hostId, org, orgId, scope, readTokens, task, prefill, seed, onSaved } =\n props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const directory = useOrgMemberDirectory(orgId)\n const mount = useCrmOrgMount()\n // The viewing group under a site, for a linked contact's facet name; null\n // at the organization level, where the picker names each contact through\n // its own holder. The site the route files a NEW task from is the mounted\n // one or the picked one — or none, when the reader files the task with\n // the organization itself; an edit goes back to the task's own.\n const { consentGroup, createHostId } = useCrmScope({ hostId, org })\n const groupId = consentGroup?.groupId ?? null\n const [noSite, setNoSite] = useState(false)\n const filedFrom = task ? (task.hostId ?? null) : noSite ? null : createHostId\n /*\n * Whether the form knows where the task goes: an edit always does (the\n * document does), and a create does once a site is picked or the\n * organization is. `filedFrom` alone cannot say — it is null for both\n * \"not yet picked\" and \"the organization's own\".\n */\n const filed = Boolean(task) || noSite || Boolean(createHostId)\n /*\n * Which level the route runs at: the site the drawer is mounted under, or\n * the organization, with the site a NEW task is filed from named beside\n * it. An edit names no site — the route reads the task's own.\n */\n const routeScope = hostId\n ? { hostId }\n : orgId\n ? { orgId, ...(task || !filedFrom ? {} : { hostId: filedFrom }) }\n : null\n\n const [fields, setFields] = useState<CrmTaskFields>(() => {\n const base = crmTaskFieldsOf(task ?? {})\n if (task) return base\n // A new task is the creator's own unless a button said otherwise: it\n // lands in \"My tasks\", and assigning it elsewhere is a choice made in\n // the picker rather than a default that notifies a teammate by accident.\n // Its reminder is left UNSAID (AGL-2659) — the field shows the due time\n // and the route decides — until the person touches it.\n return { ...base, remindAtMs: undefined, assigneeUid: user?.uid ?? null, ...prefill }\n })\n const assignee = findOrgMember(directory.members, fields.assigneeUid)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const set = <K extends keyof CrmTaskFields>(key: K, value: CrmTaskFields[K]) =>\n setFields((prev) => ({ ...prev, [key]: value }))\n /*\n * The due date, and the reminder with it (AGL-2659): a reminder left\n * unsaid shows the due time and needs no help, and one a person set to\n * the due time follows it here as the route would follow it — so what\n * the field shows after the change is what the save will store.\n */\n const setDue = (dueAtMs: number | null) =>\n setFields((prev) => ({\n ...prev,\n dueAtMs,\n ...(typeof prev.remindAtMs === 'number' && prev.remindAtMs === prev.dueAtMs\n ? { remindAtMs: dueAtMs }\n : {}),\n }))\n /** What the reminder field shows: the due time while nothing is said. */\n const remindAtMs = fields.remindAtMs === undefined ? fields.dueAtMs : fields.remindAtMs\n const reminderHelp =\n remindAtMs === null\n ? 'No reminder.'\n : remindAtMs === fields.dueAtMs\n ? 'At the due time — the assignee is reminded in the console and by email.'\n : 'The assignee is reminded in the console and by email.'\n\n const save = async () => {\n if (!fields.title.trim()) {\n setError('A task needs a title.')\n return\n }\n if (!filed) {\n setError('Pick the site this task is filed from, or file it with the organization.')\n return\n }\n if (!routeScope) {\n setError('The organization is still loading. Try again in a moment.')\n return\n }\n setBusy(true)\n setError(null)\n try {\n const write = async () => {\n const result = await saveCrmTask(user, {\n ...routeScope,\n ...(task ? { taskId: task.$id } : {}),\n task: { ...fields, title: fields.title.trim() },\n })\n enqueueSnackbar(\n task\n ? 'Task saved'\n : result.notified\n ? 'Task created and the assignee notified'\n : 'Task created',\n { variant: 'success' },\n )\n onSaved?.(result.taskId)\n onClose()\n }\n if (task) {\n const verdict = await writeGuardedBySeed(\n {\n subject: 'task',\n fromCache: seed?.fromCache ?? false,\n unreadable: seed?.unreadable ?? false,\n },\n write,\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The task could not be saved.', {\n variant: 'warning',\n })\n }\n } else {\n await write()\n }\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : 'The task could not be saved.')\n } finally {\n setBusy(false)\n }\n }\n\n /*\n * Deleting is client-direct: it has no side effect outside the document,\n * and the rules gate it on the same predicate as any other CRM write.\n */\n const remove = async () => {\n if (!task || !scope) return\n const confirmed = await confirm({\n title: 'Delete this task?',\n description: `\"${task.title}\" is removed for everyone who can see it. Completed tasks can be kept instead — tick it done.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n if (!confirmed) return\n setBusy(true)\n try {\n await deleteDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n )\n enqueueSnackbar('Task deleted', { variant: 'success' })\n onClose()\n // A client-direct write: the records it named are told (AGL-2661).\n await refreshCrmNextActivity(user, routeScope, [task])\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : 'The task could not be deleted.')\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Stack\n component=\"form\"\n spacing={2}\n sx={{ width: { xs: '100vw', sm: 420 }, p: 3 }}\n onSubmit={(event: { preventDefault: () => void }) => {\n event.preventDefault()\n void save()\n }}\n >\n <Typography variant=\"h6\">{task ? 'Edit task' : 'New task'}</Typography>\n {/* Only a new task at the organization level asks — see `filedFrom`. */}\n {task ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n noSite={{\n label: 'This organization (no site)',\n helperText:\n 'A task of the organization itself, listed from the ' +\n 'organization hub and from every site. Completing it runs no ' +\n \"site's automations.\",\n picked: noSite,\n onPick: setNoSite,\n }}\n />\n )}\n {/* An edit at the organization level says where the task is filed. */}\n {task && mount ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {isOrgTask(task)\n ? 'Filed with the organization — no site.'\n : `Filed from ${mount.siteName(task.hostId as string)}.`}\n </Typography>\n ) : null}\n <TextField\n label=\"Title\"\n value={fields.title}\n onChange={(event) => set('title', event.target.value)}\n size=\"small\"\n autoFocus\n required\n slotProps={{ htmlInput: { maxLength: CRM_TASK_TITLE_MAX } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n label=\"Kind\"\n value={fields.kind}\n onChange={(event) => set('kind', event.target.value as CrmTaskFields['kind'])}\n size=\"small\"\n sx={{ flex: 1 }}\n >\n {CRM_TASK_KINDS.map((kind) => (\n <MenuItem key={kind} value={kind}>\n {CRM_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n label=\"Priority\"\n value={fields.priority}\n onChange={(event) =>\n set('priority', event.target.value as CrmTaskFields['priority'])\n }\n size=\"small\"\n sx={{ flex: 1 }}\n >\n {CRM_TASK_PRIORITIES.map((priority) => (\n <MenuItem key={priority} value={priority}>\n {CRM_TASK_PRIORITY_LABELS[priority]}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Due\"\n type=\"datetime-local\"\n value={dueAtToLocalInput(fields.dueAtMs)}\n onChange={(event) => setDue(localInputToDueAt(event.target.value))}\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n helperText=\"Leave empty for a task with no due date.\"\n sx={{ flex: 1 }}\n />\n {/*\n A stored task is snoozed in place — the one write lands and the\n field follows it, so a later Save carries the same date. A task\n that does not exist yet has nothing to write, so the same menu\n fills the field and the save is the write.\n */}\n <TaskSnoozeMenu\n variant=\"button\"\n dueAtMs={fields.dueAtMs}\n remindAtMs={remindAtMs}\n disabled={busy}\n target={\n task && scope\n ? { write: { scope, taskId: task.$id } }\n : { pick: setDue }\n }\n onSnoozed={setDue}\n />\n </Stack>\n {/*\n The reminder (AGL-2659), beside the due date it follows by default.\n Its own field rather than a switch, because \"remind me an hour\n before\" is the second thing a person wants after \"remind me\".\n */}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Remind me\"\n type=\"datetime-local\"\n value={dueAtToLocalInput(remindAtMs)}\n onChange={(event) => set('remindAtMs', localInputToDueAt(event.target.value))}\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n helperText={reminderHelp}\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n onClick={() => set('remindAtMs', null)}\n disabled={busy || remindAtMs === null}\n >\n {'No reminder'}\n </Button>\n </Stack>\n <TextField\n select\n label=\"Assignee\"\n // The stored assignee resolved to a member — by uid, or by an\n // address the roster has — so the picker highlights the person and\n // a save writes their uid. One the roster does not hold is kept as\n // its own option: a controlled select whose value is absent from\n // its options renders empty, which reads as unassigned.\n value={assignee?.uid ?? fields.assigneeUid ?? ''}\n onChange={(event) => set('assigneeUid', event.target.value || null)}\n size=\"small\"\n disabled={directory.loading}\n helperText={\n directory.error ??\n (fields.assigneeUid && fields.assigneeUid !== user?.uid\n ? 'They will be notified when you save.'\n : undefined)\n }\n >\n <MenuItem value=\"\">{'Unassigned'}</MenuItem>\n {fields.assigneeUid && !assignee ? (\n <MenuItem value={fields.assigneeUid}>{directory.nameOf(fields.assigneeUid)}</MenuItem>\n ) : null}\n {directory.members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {member.label}\n {member.uid === user?.uid ? ' (you)' : ''}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Linked to'}\n </Typography>\n <CrmRecordPicker\n kind=\"contact\"\n scope={scope}\n readTokens={readTokens}\n groupId={groupId}\n org={org}\n value={fields.contactId}\n onChange={(id) => set('contactId', id)}\n disabled={busy}\n />\n <CrmRecordPicker\n kind=\"company\"\n scope={scope}\n readTokens={readTokens}\n groupId={groupId}\n org={org}\n value={fields.companyId}\n onChange={(id) => set('companyId', id)}\n disabled={busy}\n />\n <CrmRecordPicker\n kind=\"deal\"\n scope={scope}\n readTokens={readTokens}\n groupId={groupId}\n org={org}\n value={fields.dealId}\n onChange={(id) => set('dealId', id)}\n disabled={busy}\n />\n <TextField\n label=\"Notes\"\n value={fields.notes}\n onChange={(event) => set('notes', event.target.value)}\n size=\"small\"\n multiline\n minRows={3}\n slotProps={{ htmlInput: { maxLength: CRM_TASK_NOTES_MAX } }}\n />\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n {task ? (\n <Button\n color=\"error\"\n onClick={() => void remove()}\n disabled={busy}\n sx={{ mr: 'auto' }}\n >\n {'Delete'}\n </Button>\n ) : null}\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button type=\"submit\" variant=\"contained\" disabled={busy || !filed}>\n {task ? 'Save' : 'Create task'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nTaskForm.displayName = 'TaskForm'\n\nexport default TaskEditDrawer\n"],"names":["CRM_COLLECTIONS","findOrgMember","useConfirmationContext","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Button","Drawer","MenuItem","Stack","TextField","Typography","deleteDoc","doc","useState","useOrgMemberDirectory","refreshCrmNextActivity","saveCrmTask","CRM_TASK_NOTES_MAX","CRM_TASK_TITLE_MAX","crmTaskFieldsOf","CRM_TASK_KIND_LABELS","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","dueAtToLocalInput","localInputToDueAt","useCrmOrgMount","useCrmScope","isOrgTask","CrmRecordPicker","CrmSitePicker","TaskSnoozeMenu","TaskEditDrawer","props","open","onClose","task","anchor","TaskForm","$id","displayName","assignee","directory","hostId","org","orgId","scope","readTokens","prefill","seed","onSaved","firestore","data","user","enqueueSnackbar","confirm","mount","consentGroup","createHostId","groupId","noSite","setNoSite","filedFrom","filed","Boolean","routeScope","fields","setFields","base","remindAtMs","undefined","assigneeUid","uid","members","busy","setBusy","error","setError","set","key","value","prev","setDue","dueAtMs","reminderHelp","save","title","trim","write","result","taskId","notified","variant","verdict","subject","fromCache","unreadable","ok","message","cause","Error","remove","confirmed","description","confirmationText","confirmationButtonProps","color","tasks","component","spacing","sx","width","xs","sm","p","onSubmit","event","preventDefault","disabled","label","helperText","picked","onPick","siteName","onChange","target","size","autoFocus","required","slotProps","htmlInput","maxLength","direction","select","kind","flex","map","priority","alignItems","type","inputLabel","shrink","pick","onSnoozed","onClick","loading","nameOf","member","contactId","id","companyId","dealId","notes","multiline","minRows","justifyContent","mr"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/task-edit-drawer.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { CRM_COLLECTIONS, findOrgMember, crmMemberPickerLabel } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n useFirestore,\n useUser,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Button,\n Drawer,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { deleteDoc, doc } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport type { CrmTaskRow } from '../hooks/use-crm-tasks'\nimport { useOrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport { saveCrmTask } from '../model/task-api'\nimport {\n CRM_TASK_NOTES_MAX,\n CRM_TASK_TITLE_MAX,\n type CrmTaskFields,\n crmTaskFieldsOf,\n} from '../model/task-routes'\nimport {\n CRM_TASK_KIND_LABELS,\n CRM_TASK_KINDS,\n CRM_TASK_PRIORITIES,\n CRM_TASK_PRIORITY_LABELS,\n dueAtToLocalInput,\n localInputToDueAt,\n} from '../model/task-views'\nimport { useCrmOrgMount } from '../hooks/use-crm-org-mount'\nimport { useCrmScope } from '../hooks/use-crm-scope'\nimport { isOrgTask } from '../model/task-scope'\nimport CrmRecordPicker from './crm-record-picker'\nimport { CrmSitePicker } from './crm-site-picker'\nimport TaskSnoozeMenu from './task-snooze-menu'\n\nexport interface TaskEditDrawerProps {\n open: boolean\n onClose: () => void\n /**\n * The site the drawer is opened under, or `null` at the organization\n * level (AGL-2630), where a NEW task asks which site it is filed from —\n * or whether it is the organization's own, with no site (AGL-2637) —\n * and an edit keeps the task's own.\n */\n hostId: string | null\n org?: Record<string, unknown> | null\n orgId: string | null | undefined\n scope: readonly [string, string] | null\n /** The reader's tokens, or `null` at the organization level — no clause. */\n readTokens: readonly string[] | null\n /** The task being edited; absent while creating. */\n task?: CrmTaskRow | null\n /**\n * What a \"New task\" button pressed on a record fills in before the person\n * types: the record's own id, so the link is made for them.\n */\n prefill?: Partial<CrmTaskFields>\n /**\n * The verdict of the listener the edited row came from. An edit is refused\n * when the seed is unconfirmed, for the reason the contacts drawer refuses:\n * every field on this form is written back on save, and a form seeded from\n * a stale cache writes the stale values over newer ones.\n */\n seed?: { fromCache: boolean; unreadable: boolean }\n onSaved?: (taskId: string) => void\n}\n\n/**\n * Create or edit one task (AGL-2599).\n *\n * A drawer on the list, the console's standing shape for creating — the\n * list stays where it was and the form slides over it. The same drawer edits,\n * because a task is small enough that \"the record's own page\" would be this\n * form with more air around it.\n *\n * The form is mounted only while the drawer is open and keyed by the task,\n * so opening a different task or \"New task\" after an edit starts from that\n * task's values and not from whatever was last typed.\n */\nexport function TaskEditDrawer(props: TaskEditDrawerProps) {\n const { open, onClose, task } = props\n return (\n <Drawer anchor=\"right\" open={open} onClose={onClose}>\n {open ? <TaskForm key={task?.$id ?? 'new'} {...props} /> : null}\n </Drawer>\n )\n}\nTaskEditDrawer.displayName = 'TaskEditDrawer'\n\nfunction TaskForm(props: TaskEditDrawerProps) {\n const { onClose, hostId, org, orgId, scope, readTokens, task, prefill, seed, onSaved } =\n props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const directory = useOrgMemberDirectory(orgId)\n const mount = useCrmOrgMount()\n // The viewing group under a site, for a linked contact's facet name; null\n // at the organization level, where the picker names each contact through\n // its own holder. The site the route files a NEW task from is the mounted\n // one or the picked one — or none, when the reader files the task with\n // the organization itself; an edit goes back to the task's own.\n const { consentGroup, createHostId } = useCrmScope({ hostId, org })\n const groupId = consentGroup?.groupId ?? null\n const [noSite, setNoSite] = useState(false)\n const filedFrom = task ? (task.hostId ?? null) : noSite ? null : createHostId\n /*\n * Whether the form knows where the task goes: an edit always does (the\n * document does), and a create does once a site is picked or the\n * organization is. `filedFrom` alone cannot say — it is null for both\n * \"not yet picked\" and \"the organization's own\".\n */\n const filed = Boolean(task) || noSite || Boolean(createHostId)\n /*\n * Which level the route runs at: the site the drawer is mounted under, or\n * the organization, with the site a NEW task is filed from named beside\n * it. An edit names no site — the route reads the task's own.\n */\n const routeScope = hostId\n ? { hostId }\n : orgId\n ? { orgId, ...(task || !filedFrom ? {} : { hostId: filedFrom }) }\n : null\n\n const [fields, setFields] = useState<CrmTaskFields>(() => {\n const base = crmTaskFieldsOf(task ?? {})\n if (task) return base\n // A new task is the creator's own unless a button said otherwise: it\n // lands in \"My tasks\", and assigning it elsewhere is a choice made in\n // the picker rather than a default that notifies a teammate by accident.\n // Its reminder is left UNSAID (AGL-2659) — the field shows the due time\n // and the route decides — until the person touches it.\n return { ...base, remindAtMs: undefined, assigneeUid: user?.uid ?? null, ...prefill }\n })\n const assignee = findOrgMember(directory.members, fields.assigneeUid)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const set = <K extends keyof CrmTaskFields>(key: K, value: CrmTaskFields[K]) =>\n setFields((prev) => ({ ...prev, [key]: value }))\n /*\n * The due date, and the reminder with it (AGL-2659): a reminder left\n * unsaid shows the due time and needs no help, and one a person set to\n * the due time follows it here as the route would follow it — so what\n * the field shows after the change is what the save will store.\n */\n const setDue = (dueAtMs: number | null) =>\n setFields((prev) => ({\n ...prev,\n dueAtMs,\n ...(typeof prev.remindAtMs === 'number' && prev.remindAtMs === prev.dueAtMs\n ? { remindAtMs: dueAtMs }\n : {}),\n }))\n /** What the reminder field shows: the due time while nothing is said. */\n const remindAtMs = fields.remindAtMs === undefined ? fields.dueAtMs : fields.remindAtMs\n const reminderHelp =\n remindAtMs === null\n ? 'No reminder.'\n : remindAtMs === fields.dueAtMs\n ? 'At the due time — the assignee is reminded in the console and by email.'\n : 'The assignee is reminded in the console and by email.'\n\n const save = async () => {\n if (!fields.title.trim()) {\n setError('A task needs a title.')\n return\n }\n if (!filed) {\n setError('Pick the site this task is filed from, or file it with the organization.')\n return\n }\n if (!routeScope) {\n setError('The organization is still loading. Try again in a moment.')\n return\n }\n setBusy(true)\n setError(null)\n try {\n const write = async () => {\n const result = await saveCrmTask(user, {\n ...routeScope,\n ...(task ? { taskId: task.$id } : {}),\n task: { ...fields, title: fields.title.trim() },\n })\n enqueueSnackbar(\n task\n ? 'Task saved'\n : result.notified\n ? 'Task created and the assignee notified'\n : 'Task created',\n { variant: 'success' },\n )\n onSaved?.(result.taskId)\n onClose()\n }\n if (task) {\n const verdict = await writeGuardedBySeed(\n {\n subject: 'task',\n fromCache: seed?.fromCache ?? false,\n unreadable: seed?.unreadable ?? false,\n },\n write,\n )\n if (!verdict.ok) {\n enqueueSnackbar(verdict.message ?? 'The task could not be saved.', {\n variant: 'warning',\n })\n }\n } else {\n await write()\n }\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : 'The task could not be saved.')\n } finally {\n setBusy(false)\n }\n }\n\n /*\n * Deleting is client-direct: it has no side effect outside the document,\n * and the rules gate it on the same predicate as any other CRM write.\n */\n const remove = async () => {\n if (!task || !scope) return\n const confirmed = await confirm({\n title: 'Delete this task?',\n description: `\"${task.title}\" is removed for everyone who can see it. Completed tasks can be kept instead — tick it done.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n if (!confirmed) return\n setBusy(true)\n try {\n await deleteDoc(\n doc(firestore, scope[0], scope[1], CRM_COLLECTIONS.tasks, task.$id),\n )\n enqueueSnackbar('Task deleted', { variant: 'success' })\n onClose()\n // A client-direct write: the records it named are told (AGL-2661).\n await refreshCrmNextActivity(user, routeScope, [task])\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : 'The task could not be deleted.')\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Stack\n component=\"form\"\n spacing={2}\n sx={{ width: { xs: '100vw', sm: 420 }, p: 3 }}\n onSubmit={(event: { preventDefault: () => void }) => {\n event.preventDefault()\n void save()\n }}\n >\n <Typography variant=\"h6\">{task ? 'Edit task' : 'New task'}</Typography>\n {/* Only a new task at the organization level asks — see `filedFrom`. */}\n {task ? null : (\n <CrmSitePicker\n hostId={hostId}\n disabled={busy}\n noSite={{\n label: 'This organization (no site)',\n helperText:\n 'A task of the organization itself, listed from the ' +\n 'organization hub and from every site. Completing it runs no ' +\n \"site's automations.\",\n picked: noSite,\n onPick: setNoSite,\n }}\n />\n )}\n {/* An edit at the organization level says where the task is filed. */}\n {task && mount ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {isOrgTask(task)\n ? 'Filed with the organization — no site.'\n : `Filed from ${mount.siteName(task.hostId as string)}.`}\n </Typography>\n ) : null}\n <TextField\n label=\"Title\"\n value={fields.title}\n onChange={(event) => set('title', event.target.value)}\n size=\"small\"\n autoFocus\n required\n slotProps={{ htmlInput: { maxLength: CRM_TASK_TITLE_MAX } }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n label=\"Kind\"\n value={fields.kind}\n onChange={(event) => set('kind', event.target.value as CrmTaskFields['kind'])}\n size=\"small\"\n sx={{ flex: 1 }}\n >\n {CRM_TASK_KINDS.map((kind) => (\n <MenuItem key={kind} value={kind}>\n {CRM_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n label=\"Priority\"\n value={fields.priority}\n onChange={(event) =>\n set('priority', event.target.value as CrmTaskFields['priority'])\n }\n size=\"small\"\n sx={{ flex: 1 }}\n >\n {CRM_TASK_PRIORITIES.map((priority) => (\n <MenuItem key={priority} value={priority}>\n {CRM_TASK_PRIORITY_LABELS[priority]}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Due\"\n type=\"datetime-local\"\n value={dueAtToLocalInput(fields.dueAtMs)}\n onChange={(event) => setDue(localInputToDueAt(event.target.value))}\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n helperText=\"Leave empty for a task with no due date.\"\n sx={{ flex: 1 }}\n />\n {/*\n A stored task is snoozed in place — the one write lands and the\n field follows it, so a later Save carries the same date. A task\n that does not exist yet has nothing to write, so the same menu\n fills the field and the save is the write.\n */}\n <TaskSnoozeMenu\n variant=\"button\"\n dueAtMs={fields.dueAtMs}\n remindAtMs={remindAtMs}\n disabled={busy}\n target={\n task && scope\n ? { write: { scope, taskId: task.$id } }\n : { pick: setDue }\n }\n onSnoozed={setDue}\n />\n </Stack>\n {/*\n The reminder (AGL-2659), beside the due date it follows by default.\n Its own field rather than a switch, because \"remind me an hour\n before\" is the second thing a person wants after \"remind me\".\n */}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Remind me\"\n type=\"datetime-local\"\n value={dueAtToLocalInput(remindAtMs)}\n onChange={(event) => set('remindAtMs', localInputToDueAt(event.target.value))}\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n helperText={reminderHelp}\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n onClick={() => set('remindAtMs', null)}\n disabled={busy || remindAtMs === null}\n >\n {'No reminder'}\n </Button>\n </Stack>\n <TextField\n select\n label=\"Assignee\"\n // The stored assignee resolved to a member — by uid, or by an\n // address the roster has — so the picker highlights the person and\n // a save writes their uid. One the roster does not hold is kept as\n // its own option: a controlled select whose value is absent from\n // its options renders empty, which reads as unassigned.\n value={assignee?.uid ?? fields.assigneeUid ?? ''}\n onChange={(event) => set('assigneeUid', event.target.value || null)}\n size=\"small\"\n disabled={directory.loading}\n helperText={\n directory.error ??\n (fields.assigneeUid && fields.assigneeUid !== user?.uid\n ? 'They will be notified when you save.'\n : undefined)\n }\n >\n <MenuItem value=\"\">{'Unassigned'}</MenuItem>\n {fields.assigneeUid && !assignee ? (\n <MenuItem value={fields.assigneeUid}>{directory.nameOf(fields.assigneeUid)}</MenuItem>\n ) : null}\n {directory.members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n {member.uid === user?.uid ? ' (you)' : ''}\n </MenuItem>\n ))}\n </TextField>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Linked to'}\n </Typography>\n <CrmRecordPicker\n kind=\"contact\"\n scope={scope}\n readTokens={readTokens}\n groupId={groupId}\n org={org}\n value={fields.contactId}\n onChange={(id) => set('contactId', id)}\n disabled={busy}\n />\n <CrmRecordPicker\n kind=\"company\"\n scope={scope}\n readTokens={readTokens}\n groupId={groupId}\n org={org}\n value={fields.companyId}\n onChange={(id) => set('companyId', id)}\n disabled={busy}\n />\n <CrmRecordPicker\n kind=\"deal\"\n scope={scope}\n readTokens={readTokens}\n groupId={groupId}\n org={org}\n value={fields.dealId}\n onChange={(id) => set('dealId', id)}\n disabled={busy}\n />\n <TextField\n label=\"Notes\"\n value={fields.notes}\n onChange={(event) => set('notes', event.target.value)}\n size=\"small\"\n multiline\n minRows={3}\n slotProps={{ htmlInput: { maxLength: CRM_TASK_NOTES_MAX } }}\n />\n {error ? (\n <Typography variant=\"body2\" color=\"error\">\n {error}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n {task ? (\n <Button\n color=\"error\"\n onClick={() => void remove()}\n disabled={busy}\n sx={{ mr: 'auto' }}\n >\n {'Delete'}\n </Button>\n ) : null}\n <Button onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button type=\"submit\" variant=\"contained\" disabled={busy || !filed}>\n {task ? 'Save' : 'Create task'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nTaskForm.displayName = 'TaskForm'\n\nexport default TaskEditDrawer\n"],"names":["CRM_COLLECTIONS","findOrgMember","crmMemberPickerLabel","useConfirmationContext","useSnackbar","useFirestore","useUser","writeGuardedBySeed","Button","Drawer","MenuItem","Stack","TextField","Typography","deleteDoc","doc","useState","useOrgMemberDirectory","refreshCrmNextActivity","saveCrmTask","CRM_TASK_NOTES_MAX","CRM_TASK_TITLE_MAX","crmTaskFieldsOf","CRM_TASK_KIND_LABELS","CRM_TASK_KINDS","CRM_TASK_PRIORITIES","CRM_TASK_PRIORITY_LABELS","dueAtToLocalInput","localInputToDueAt","useCrmOrgMount","useCrmScope","isOrgTask","CrmRecordPicker","CrmSitePicker","TaskSnoozeMenu","TaskEditDrawer","props","open","onClose","task","anchor","TaskForm","$id","displayName","assignee","directory","hostId","org","orgId","scope","readTokens","prefill","seed","onSaved","firestore","data","user","enqueueSnackbar","confirm","mount","consentGroup","createHostId","groupId","noSite","setNoSite","filedFrom","filed","Boolean","routeScope","fields","setFields","base","remindAtMs","undefined","assigneeUid","uid","members","busy","setBusy","error","setError","set","key","value","prev","setDue","dueAtMs","reminderHelp","save","title","trim","write","result","taskId","notified","variant","verdict","subject","fromCache","unreadable","ok","message","cause","Error","remove","confirmed","description","confirmationText","confirmationButtonProps","color","tasks","component","spacing","sx","width","xs","sm","p","onSubmit","event","preventDefault","disabled","label","helperText","picked","onPick","siteName","onChange","target","size","autoFocus","required","slotProps","htmlInput","maxLength","direction","select","kind","flex","map","priority","alignItems","type","inputLabel","shrink","pick","onSnoozed","onClick","loading","nameOf","member","contactId","id","companyId","dealId","notes","multiline","minRows","justifyContent","mr"],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
33
33
|
* answered per task. The due date and the delete have no effect beyond
|
|
34
34
|
* the document, so they are batched client-direct writes under the rules,
|
|
35
35
|
* as reopening is.
|
|
36
|
-
*/ import { CRM_COLLECTIONS } from "@aglyn/aglyn";
|
|
36
|
+
*/ import { CRM_COLLECTIONS, crmMemberPickerLabel } from "@aglyn/aglyn";
|
|
37
37
|
import { useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
38
38
|
import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
|
|
39
39
|
import { Button, MenuItem, TextField } from "@mui/material";
|
|
@@ -278,7 +278,7 @@ function TasksBulkBarBody(props) {
|
|
|
278
278
|
}),
|
|
279
279
|
directory.members.map((member)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
280
280
|
value: member.uid,
|
|
281
|
-
children: member
|
|
281
|
+
children: crmMemberPickerLabel(member)
|
|
282
282
|
}, member.uid))
|
|
283
283
|
]
|
|
284
284
|
}) : pending === 'due' ? /*#__PURE__*/ _jsx(TextField, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/tasks-bulk-bar.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The bar over the tasks list, for whatever rows are ticked (AGL-2621).\n *\n * Complete them, hand them to somebody, move their due date, take them\n * into a spreadsheet, or delete them. Two of these have a side effect\n * outside the document and go through their ROUTE: completing fires\n * `taskCompleted`, which only the server emits, and assigning tells the\n * assignee, which only the server may write into somebody's inbox — so\n * Complete goes through `crm/task-complete` and Assign through\n * `crm/task-save`, exactly as the checkbox and the drawer do for one task.\n * Under a site that is one request per task, each authorized against the\n * site; beneath the organization hub it is ONE request per action, the\n * routes' org-level batch form (AGL-2637), authorized once by the org and\n * answered per task. The due date and the delete have no effect beyond\n * the document, so they are batched client-direct writes under the rules,\n * as reopening is.\n */\n\nimport { CRM_COLLECTIONS } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc, serverTimestamp } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { CrmTaskRow } from '../hooks/use-crm-tasks'\nimport type { OrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport {\n type CrmBulkPlan,\n type CrmBulkWrite,\n crmBulkWriters,\n runCrmBulkBatch,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n completeCrmTask,\n completeCrmTasks,\n saveCrmTask,\n saveCrmTasks,\n} from '../model/task-api'\nimport { crmTaskCallScope, crmTaskFieldsOf } from '../model/task-routes'\nimport { dueAtToLocalInput, localInputToDueAt } from '../model/task-views'\nimport { type TaskCsvOptions, tasksCsv } from '../model/tasks-csv'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport CrmExportAllButton from './crm-export-all-button'\n\nexport interface TasksBulkBarProps {\n /**\n * The site the task routes run as, or `null` at the organization level\n * (AGL-2630), where the routes run as the org — their batch form, one\n * request per action (AGL-2637).\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly [string, string] | null\n rows: readonly CrmTaskRow[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Assignee column. */\n directory: OrgMemberDirectory\n /** How the export names the assignee and the linked records — the list's own. */\n csv?: TaskCsvOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'task', plural: 'tasks' }\n\ntype PendingAction = 'assign' | 'due'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n assign: 'Assign to',\n due: 'Set the due date',\n}\n\nconst labelOf = (task: CrmTaskRow): string => task.title || task.$id\n\nexport function TasksBulkBar(props: TasksBulkBarProps) {\n if (!props.selected.length) return null\n return <TasksBulkBarBody {...props} />\n}\nTasksBulkBar.displayName = 'TasksBulkBar'\n\nfunction TasksBulkBarBody(props: TasksBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, directory, csv } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { confirm } = useConfirmationContext()\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'task' })\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.tasks, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n // The org the batch form names — the scope's, which is the org's root.\n const orgId = scope?.[1] ?? null\n\n const runPlan = useCallback(\n async (plan: CrmBulkPlan, done: (count: number) => string) => {\n const outcome = await apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: () => runCrmBulkWrites(writers, plan.writes, (write) => write.label),\n done,\n })\n // Client-direct writes: the records the selection names are told\n // (AGL-2661), once each however many tasks named them.\n const written = new Set(plan.writes.map((write) => write.id))\n await refreshCrmNextActivity(\n user,\n crmTaskCallScope(hostId, orgId),\n selectedRows.filter((task) => written.has(task.$id)),\n )\n return outcome\n },\n [apply, writers, user, hostId, orgId, selectedRows],\n )\n\n /**\n * The routes over the rows: under a site one request per task, in\n * order, named by title; beneath the org hub one request for them all,\n * tallied off its per-task answers.\n */\n const runCalls = useCallback(\n (\n tasks: readonly CrmTaskRow[],\n call: {\n each: (task: CrmTaskRow) => Promise<unknown>\n batch: (tasks: readonly CrmTaskRow[]) => Promise<ReadonlyArray<{ taskId: string; ok: boolean; error?: string }>>\n },\n done: (count: number) => string,\n ) =>\n apply({\n attempted: tasks.length,\n skipped: [],\n job: () =>\n hostId\n ? runCrmBulkCalls(tasks, labelOf, call.each)\n : runCrmBulkBatch(\n tasks,\n (task) => task.$id,\n labelOf,\n async (rows) =>\n (await call.batch(rows)).map(({ taskId, ...rest }) => ({ id: taskId, ...rest })),\n ),\n done,\n }),\n [apply, hostId],\n )\n\n /*\n * A task already done is left alone rather than sent: the route would\n * answer `alreadyDone` and write nothing, and a request that can only\n * answer nothing is not worth its round trip.\n */\n const handleComplete = useCallback(async () => {\n if (!orgId) return\n const open = selectedRows.filter((task) => task.status !== 'done')\n await runCalls(\n open,\n {\n each: (task) => completeCrmTask(user, { hostId: hostId as string, taskId: task.$id }),\n batch: async (tasks) =>\n (await completeCrmTasks(user, { orgId, taskIds: tasks.map((task) => task.$id) }))\n .results,\n },\n (count) => `Completed ${countNoun(count, NOUN)}`,\n )\n }, [selectedRows, runCalls, user, hostId, orgId])\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const action = pending\n setPending(null)\n if (action === 'assign') {\n const assigneeUid = value || null\n const who = assigneeUid ? directory.nameOf(assigneeUid) : 'nobody'\n const reassigned = (task: CrmTaskRow) => ({\n taskId: task.$id,\n task: { ...crmTaskFieldsOf(task), assigneeUid },\n })\n await runCalls(\n selectedRows,\n {\n each: (task) => saveCrmTask(user, { hostId: hostId as string, ...reassigned(task) }),\n batch: async (tasks) =>\n (await saveCrmTasks(user, { orgId: scope[1], tasks: tasks.map(reassigned) })).results,\n },\n (count) => `Assigned ${countNoun(count, NOUN)} to ${who}`,\n )\n return\n }\n const dueAtMs = localInputToDueAt(value)\n const writes: CrmBulkWrite[] = selectedRows.map((task) => ({\n id: task.$id,\n label: labelOf(task),\n kind: 'update',\n // `null` rather than a deleted field: every view orders by `dueAtMs`,\n // and a document missing it drops out of the `orderBy` entirely.\n data: { dueAtMs, updatedAt: serverTimestamp() },\n }))\n await runPlan(\n { writes, skipped: [] },\n (count) =>\n dueAtMs === null\n ? `Due date cleared on ${countNoun(count, NOUN)}`\n : `Due date set on ${countNoun(count, NOUN)}`,\n )\n }, [pending, scope, value, directory, selectedRows, runCalls, user, hostId, runPlan])\n\n const handleExport = useCallback(() => {\n downloadTextFile('tasks-selected.csv', 'text/csv', tasksCsv(selectedRows, csv))\n }, [selectedRows, csv])\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this task?' : `Delete ${count} tasks?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed for everyone who can ` +\n 'see them. A finished task is better ticked done, which keeps it in ' +\n 'the Done view.',\n confirmationText: count === 1 ? 'Delete task' : 'Delete tasks',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const writes: CrmBulkWrite[] = selectedRows.map((task) => ({\n id: task.$id,\n label: labelOf(task),\n kind: 'delete',\n }))\n const outcome = await runPlan(\n { writes, skipped: [] },\n (done) => `Deleted ${countNoun(done, NOUN)}`,\n )\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows.filter((task) => refused.has(labelOf(task))).map((task) => task.$id),\n )\n }, [scope, selectedRows, confirm, runPlan, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'assign' ? (\n <TextField\n select\n size=\"small\"\n label=\"Assignee\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={directory.loading}\n error={Boolean(directory.error)}\n helperText={\n directory.error ??\n (directory.loading\n ? 'Loading the team…'\n : 'Somebody other than you is told, as the drawer tells them')\n }\n >\n <MenuItem value=\"\">{'Nobody — clear the assignee'}</MenuItem>\n {directory.members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {member.label}\n </MenuItem>\n ))}\n </TextField>\n ) : pending === 'due' ? (\n <TextField\n size=\"small\"\n type=\"datetime-local\"\n label=\"Due\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText=\"Leave it empty to clear the due date\"\n slotProps={{ inputLabel: { shrink: true } }}\n />\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => void handleComplete()}>\n {'Complete'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('assign')}>\n {'Assign'}\n </Button>\n <Button\n size=\"small\"\n disabled={busy || !scope}\n onClick={() => {\n openAction('due')\n // Start from the first task's own date, so a nudge is an edit\n // rather than a retype.\n setValue(dueAtToLocalInput(selectedRows[0]?.dueAtMs))\n }}\n >\n {'Set due'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={handleExport}>\n {'Export CSV'}\n </Button>\n {/*\n The selection's file is the rows on screen; this one is the whole\n collection, streamed by the server (AGL-2662).\n */}\n <CrmExportAllButton\n resource=\"tasks\"\n orgId={scope?.[1] ?? null}\n hostId={hostId}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nTasksBulkBarBody.displayName = 'TasksBulkBarBody'\n\nexport default TasksBulkBar\n"],"names":["CRM_COLLECTIONS","useConfirmationContext","useFirestore","useUser","Button","MenuItem","TextField","doc","serverTimestamp","useCallback","useMemo","useState","useCrmBulkApply","downloadTextFile","refreshCrmNextActivity","crmBulkWriters","runCrmBulkBatch","runCrmBulkCalls","runCrmBulkWrites","completeCrmTask","completeCrmTasks","saveCrmTask","saveCrmTasks","crmTaskCallScope","crmTaskFieldsOf","dueAtToLocalInput","localInputToDueAt","tasksCsv","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportAllButton","NOUN","singular","plural","ACTION_TITLES","assign","due","labelOf","task","title","$id","TasksBulkBar","props","selected","length","TasksBulkBarBody","displayName","directory","hostId","scope","rows","onSelectedChange","csv","firestore","data","user","confirm","busy","report","apply","dismissReport","recordKind","selectedRows","chosen","Set","filter","row","has","pending","setPending","value","setValue","writers","id","tasks","openAction","action","orgId","runPlan","plan","done","outcome","attempted","writes","skipped","job","write","label","written","map","runCalls","call","each","batch","taskId","rest","handleComplete","open","status","taskIds","results","count","handleApply","assigneeUid","who","nameOf","reassigned","dueAtMs","kind","updatedAt","handleExport","handleDelete","confirmed","description","confirmationText","confirmationButtonProps","color","then","catch","refused","noun","onClear","onDismissReport","extras","canApply","onClose","onApply","select","size","onChange","event","target","disabled","loading","error","Boolean","helperText","members","member","uid","type","slotProps","inputLabel","shrink","onClick","resource"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/components/tasks-bulk-bar.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The bar over the tasks list, for whatever rows are ticked (AGL-2621).\n *\n * Complete them, hand them to somebody, move their due date, take them\n * into a spreadsheet, or delete them. Two of these have a side effect\n * outside the document and go through their ROUTE: completing fires\n * `taskCompleted`, which only the server emits, and assigning tells the\n * assignee, which only the server may write into somebody's inbox — so\n * Complete goes through `crm/task-complete` and Assign through\n * `crm/task-save`, exactly as the checkbox and the drawer do for one task.\n * Under a site that is one request per task, each authorized against the\n * site; beneath the organization hub it is ONE request per action, the\n * routes' org-level batch form (AGL-2637), authorized once by the org and\n * answered per task. The due date and the delete have no effect beyond\n * the document, so they are batched client-direct writes under the rules,\n * as reopening is.\n */\n\nimport { CRM_COLLECTIONS, crmMemberPickerLabel } from '@aglyn/aglyn'\nimport { useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, MenuItem, TextField } from '@mui/material'\nimport { doc, serverTimestamp } from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useCrmBulkApply } from '../hooks/use-crm-bulk-apply'\nimport type { CrmTaskRow } from '../hooks/use-crm-tasks'\nimport type { OrgMemberDirectory } from '../hooks/use-org-member-directory'\nimport { downloadTextFile } from '../model/contacts-csv'\nimport { refreshCrmNextActivity } from '../model/next-activity-api'\nimport {\n type CrmBulkPlan,\n type CrmBulkWrite,\n crmBulkWriters,\n runCrmBulkBatch,\n runCrmBulkCalls,\n runCrmBulkWrites,\n} from '../model/crm-bulk-writes'\nimport {\n completeCrmTask,\n completeCrmTasks,\n saveCrmTask,\n saveCrmTasks,\n} from '../model/task-api'\nimport { crmTaskCallScope, crmTaskFieldsOf } from '../model/task-routes'\nimport { dueAtToLocalInput, localInputToDueAt } from '../model/task-views'\nimport { type TaskCsvOptions, tasksCsv } from '../model/tasks-csv'\nimport {\n type CrmBulkNoun,\n CrmBulkBarFrame,\n CrmBulkValueDialog,\n countNoun,\n} from './crm-bulk-bar-frame'\nimport CrmExportAllButton from './crm-export-all-button'\n\nexport interface TasksBulkBarProps {\n /**\n * The site the task routes run as, or `null` at the organization level\n * (AGL-2630), where the routes run as the org — their batch form, one\n * request per action (AGL-2637).\n */\n hostId: string | null\n /** `['orgs', orgId]`, or `null` while the org is unresolved. */\n scope: readonly [string, string] | null\n rows: readonly CrmTaskRow[]\n selected: readonly string[]\n onSelectedChange: (ids: string[]) => void\n /** The section's roster — already read for the Assignee column. */\n directory: OrgMemberDirectory\n /** How the export names the assignee and the linked records — the list's own. */\n csv?: TaskCsvOptions\n}\n\nconst NOUN: CrmBulkNoun = { singular: 'task', plural: 'tasks' }\n\ntype PendingAction = 'assign' | 'due'\n\nconst ACTION_TITLES: Record<PendingAction, string> = {\n assign: 'Assign to',\n due: 'Set the due date',\n}\n\nconst labelOf = (task: CrmTaskRow): string => task.title || task.$id\n\nexport function TasksBulkBar(props: TasksBulkBarProps) {\n if (!props.selected.length) return null\n return <TasksBulkBarBody {...props} />\n}\nTasksBulkBar.displayName = 'TasksBulkBar'\n\nfunction TasksBulkBarBody(props: TasksBulkBarProps) {\n const { hostId, scope, rows, selected, onSelectedChange, directory, csv } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { confirm } = useConfirmationContext()\n const { busy, report, apply, dismissReport } = useCrmBulkApply({ recordKind: 'task' })\n\n const selectedRows = useMemo(() => {\n const chosen = new Set(selected)\n return rows.filter((row) => chosen.has(row.$id))\n }, [rows, selected])\n\n const [pending, setPending] = useState<PendingAction | null>(null)\n const [value, setValue] = useState('')\n\n const writers = useMemo(\n () =>\n crmBulkWriters(firestore, (id) =>\n doc(firestore, scope?.[0] ?? 'orgs', scope?.[1] ?? '', CRM_COLLECTIONS.tasks, id),\n ),\n [firestore, scope],\n )\n\n const openAction = (action: PendingAction) => {\n setValue('')\n setPending(action)\n }\n\n // The org the batch form names — the scope's, which is the org's root.\n const orgId = scope?.[1] ?? null\n\n const runPlan = useCallback(\n async (plan: CrmBulkPlan, done: (count: number) => string) => {\n const outcome = await apply({\n attempted: plan.writes.length,\n skipped: plan.skipped,\n job: () => runCrmBulkWrites(writers, plan.writes, (write) => write.label),\n done,\n })\n // Client-direct writes: the records the selection names are told\n // (AGL-2661), once each however many tasks named them.\n const written = new Set(plan.writes.map((write) => write.id))\n await refreshCrmNextActivity(\n user,\n crmTaskCallScope(hostId, orgId),\n selectedRows.filter((task) => written.has(task.$id)),\n )\n return outcome\n },\n [apply, writers, user, hostId, orgId, selectedRows],\n )\n\n /**\n * The routes over the rows: under a site one request per task, in\n * order, named by title; beneath the org hub one request for them all,\n * tallied off its per-task answers.\n */\n const runCalls = useCallback(\n (\n tasks: readonly CrmTaskRow[],\n call: {\n each: (task: CrmTaskRow) => Promise<unknown>\n batch: (tasks: readonly CrmTaskRow[]) => Promise<ReadonlyArray<{ taskId: string; ok: boolean; error?: string }>>\n },\n done: (count: number) => string,\n ) =>\n apply({\n attempted: tasks.length,\n skipped: [],\n job: () =>\n hostId\n ? runCrmBulkCalls(tasks, labelOf, call.each)\n : runCrmBulkBatch(\n tasks,\n (task) => task.$id,\n labelOf,\n async (rows) =>\n (await call.batch(rows)).map(({ taskId, ...rest }) => ({ id: taskId, ...rest })),\n ),\n done,\n }),\n [apply, hostId],\n )\n\n /*\n * A task already done is left alone rather than sent: the route would\n * answer `alreadyDone` and write nothing, and a request that can only\n * answer nothing is not worth its round trip.\n */\n const handleComplete = useCallback(async () => {\n if (!orgId) return\n const open = selectedRows.filter((task) => task.status !== 'done')\n await runCalls(\n open,\n {\n each: (task) => completeCrmTask(user, { hostId: hostId as string, taskId: task.$id }),\n batch: async (tasks) =>\n (await completeCrmTasks(user, { orgId, taskIds: tasks.map((task) => task.$id) }))\n .results,\n },\n (count) => `Completed ${countNoun(count, NOUN)}`,\n )\n }, [selectedRows, runCalls, user, hostId, orgId])\n\n const handleApply = useCallback(async () => {\n if (!pending || !scope) return\n const action = pending\n setPending(null)\n if (action === 'assign') {\n const assigneeUid = value || null\n const who = assigneeUid ? directory.nameOf(assigneeUid) : 'nobody'\n const reassigned = (task: CrmTaskRow) => ({\n taskId: task.$id,\n task: { ...crmTaskFieldsOf(task), assigneeUid },\n })\n await runCalls(\n selectedRows,\n {\n each: (task) => saveCrmTask(user, { hostId: hostId as string, ...reassigned(task) }),\n batch: async (tasks) =>\n (await saveCrmTasks(user, { orgId: scope[1], tasks: tasks.map(reassigned) })).results,\n },\n (count) => `Assigned ${countNoun(count, NOUN)} to ${who}`,\n )\n return\n }\n const dueAtMs = localInputToDueAt(value)\n const writes: CrmBulkWrite[] = selectedRows.map((task) => ({\n id: task.$id,\n label: labelOf(task),\n kind: 'update',\n // `null` rather than a deleted field: every view orders by `dueAtMs`,\n // and a document missing it drops out of the `orderBy` entirely.\n data: { dueAtMs, updatedAt: serverTimestamp() },\n }))\n await runPlan(\n { writes, skipped: [] },\n (count) =>\n dueAtMs === null\n ? `Due date cleared on ${countNoun(count, NOUN)}`\n : `Due date set on ${countNoun(count, NOUN)}`,\n )\n }, [pending, scope, value, directory, selectedRows, runCalls, user, hostId, runPlan])\n\n const handleExport = useCallback(() => {\n downloadTextFile('tasks-selected.csv', 'text/csv', tasksCsv(selectedRows, csv))\n }, [selectedRows, csv])\n\n const handleDelete = useCallback(async () => {\n if (!scope || !selectedRows.length) return\n const count = selectedRows.length\n const confirmed = await confirm({\n title: count === 1 ? 'Delete this task?' : `Delete ${count} tasks?`,\n description:\n `${count === 1 ? 'It is' : 'They are'} removed for everyone who can ` +\n 'see them. A finished task is better ticked done, which keeps it in ' +\n 'the Done view.',\n confirmationText: count === 1 ? 'Delete task' : 'Delete tasks',\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n const writes: CrmBulkWrite[] = selectedRows.map((task) => ({\n id: task.$id,\n label: labelOf(task),\n kind: 'delete',\n }))\n const outcome = await runPlan(\n { writes, skipped: [] },\n (done) => `Deleted ${countNoun(done, NOUN)}`,\n )\n const refused = new Set(outcome.refused.map((row) => row.label))\n onSelectedChange(\n selectedRows.filter((task) => refused.has(labelOf(task))).map((task) => task.$id),\n )\n }, [scope, selectedRows, confirm, runPlan, onSelectedChange])\n\n return (\n <CrmBulkBarFrame\n count={selected.length}\n noun={NOUN}\n busy={busy}\n onClear={() => onSelectedChange([])}\n report={report}\n onDismissReport={dismissReport}\n extras={\n <CrmBulkValueDialog\n open={pending !== null}\n title={pending ? ACTION_TITLES[pending] : ''}\n count={selected.length}\n noun={NOUN}\n busy={busy}\n canApply\n onClose={() => setPending(null)}\n onApply={() => void handleApply()}\n >\n {pending === 'assign' ? (\n <TextField\n select\n size=\"small\"\n label=\"Assignee\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n disabled={directory.loading}\n error={Boolean(directory.error)}\n helperText={\n directory.error ??\n (directory.loading\n ? 'Loading the team…'\n : 'Somebody other than you is told, as the drawer tells them')\n }\n >\n <MenuItem value=\"\">{'Nobody — clear the assignee'}</MenuItem>\n {directory.members.map((member) => (\n <MenuItem key={member.uid} value={member.uid}>\n {crmMemberPickerLabel(member)}\n </MenuItem>\n ))}\n </TextField>\n ) : pending === 'due' ? (\n <TextField\n size=\"small\"\n type=\"datetime-local\"\n label=\"Due\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n helperText=\"Leave it empty to clear the due date\"\n slotProps={{ inputLabel: { shrink: true } }}\n />\n ) : null}\n </CrmBulkValueDialog>\n }\n >\n <Button size=\"small\" disabled={busy || !scope} onClick={() => void handleComplete()}>\n {'Complete'}\n </Button>\n <Button size=\"small\" disabled={busy || !scope} onClick={() => openAction('assign')}>\n {'Assign'}\n </Button>\n <Button\n size=\"small\"\n disabled={busy || !scope}\n onClick={() => {\n openAction('due')\n // Start from the first task's own date, so a nudge is an edit\n // rather than a retype.\n setValue(dueAtToLocalInput(selectedRows[0]?.dueAtMs))\n }}\n >\n {'Set due'}\n </Button>\n <Button size=\"small\" disabled={busy} onClick={handleExport}>\n {'Export CSV'}\n </Button>\n {/*\n The selection's file is the rows on screen; this one is the whole\n collection, streamed by the server (AGL-2662).\n */}\n <CrmExportAllButton\n resource=\"tasks\"\n orgId={scope?.[1] ?? null}\n hostId={hostId}\n disabled={busy}\n />\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy || !scope}\n onClick={() => void handleDelete()}\n >\n {'Delete'}\n </Button>\n </CrmBulkBarFrame>\n )\n}\nTasksBulkBarBody.displayName = 'TasksBulkBarBody'\n\nexport default TasksBulkBar\n"],"names":["CRM_COLLECTIONS","crmMemberPickerLabel","useConfirmationContext","useFirestore","useUser","Button","MenuItem","TextField","doc","serverTimestamp","useCallback","useMemo","useState","useCrmBulkApply","downloadTextFile","refreshCrmNextActivity","crmBulkWriters","runCrmBulkBatch","runCrmBulkCalls","runCrmBulkWrites","completeCrmTask","completeCrmTasks","saveCrmTask","saveCrmTasks","crmTaskCallScope","crmTaskFieldsOf","dueAtToLocalInput","localInputToDueAt","tasksCsv","CrmBulkBarFrame","CrmBulkValueDialog","countNoun","CrmExportAllButton","NOUN","singular","plural","ACTION_TITLES","assign","due","labelOf","task","title","$id","TasksBulkBar","props","selected","length","TasksBulkBarBody","displayName","directory","hostId","scope","rows","onSelectedChange","csv","firestore","data","user","confirm","busy","report","apply","dismissReport","recordKind","selectedRows","chosen","Set","filter","row","has","pending","setPending","value","setValue","writers","id","tasks","openAction","action","orgId","runPlan","plan","done","outcome","attempted","writes","skipped","job","write","label","written","map","runCalls","call","each","batch","taskId","rest","handleComplete","open","status","taskIds","results","count","handleApply","assigneeUid","who","nameOf","reassigned","dueAtMs","kind","updatedAt","handleExport","handleDelete","confirmed","description","confirmationText","confirmationButtonProps","color","then","catch","refused","noun","onClear","onDismissReport","extras","canApply","onClose","onApply","select","size","onChange","event","target","disabled","loading","error","Boolean","helperText","members","member","uid","type","slotProps","inputLabel","shrink","onClick","resource"],"mappings":"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"}
|