@aglyn/plugins-workflows 1.0.0-beta.234 → 1.0.0-beta.235

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-workflows",
3
- "version": "1.0.0-beta.234",
3
+ "version": "1.0.0-beta.235",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,17 +25,17 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.234",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.234",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.234",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.234",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.234",
33
- "@aglyn/shared-util-email": "1.0.0-beta.234",
34
- "@aglyn/shared-util-http": "1.0.0-beta.234",
35
- "@aglyn/shared-util-timestamp": "1.0.0-beta.234",
36
- "@aglyn/tenant-data-admin": "1.0.0-beta.234",
37
- "@aglyn/tenant-feature-instance": "1.0.0-beta.234",
38
- "@aglyn/tenant-runtime": "1.0.0-beta.234",
28
+ "@aglyn/aglyn": "1.0.0-beta.235",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.235",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.235",
31
+ "@aglyn/shared-ui-next": "1.0.0-beta.235",
32
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.235",
33
+ "@aglyn/shared-util-email": "1.0.0-beta.235",
34
+ "@aglyn/shared-util-http": "1.0.0-beta.235",
35
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.235",
36
+ "@aglyn/tenant-data-admin": "1.0.0-beta.235",
37
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.235",
38
+ "@aglyn/tenant-runtime": "1.0.0-beta.235",
39
39
  "@swc/helpers": "0.5.23"
40
40
  },
41
41
  "peerDependencies": {
@@ -14,6 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
17
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
19
  import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
19
20
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
@@ -24,8 +25,10 @@ import { useParams } from "next/navigation";
24
25
  import { useEffect, useMemo, useState } from "react";
25
26
  import { ceilingedWindow, useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
26
27
  import { useResolvedActivityActors } from "@aglyn/tenant-feature-instance/hooks/use-resolved-activity-actors";
27
- import { activityActorLabel, activityHref, activityPrimaryText } from "@aglyn/aglyn/app-utils/activity-presenter";
28
+ import { activityActorLabel, activityHref } from "@aglyn/aglyn/app-utils/activity-presenter";
28
29
  import { pluginDocsHelp } from "@aglyn/aglyn";
30
+ import { activityRowText, describeActivity } from "@aglyn/aglyn/app-utils/activity-labels";
31
+ import ActivityDetailsDialog from "@aglyn/shared-ui-jsx/components/activity-details-dialog.component";
29
32
  /**
30
33
  * Rows fetched before the client-side sort and page slice.
31
34
  *
@@ -108,6 +111,7 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
108
111
  const { orgSlug, host } = useParams();
109
112
  const firestore = useFirestore();
110
113
  const [attempt, setAttempt] = useState(0);
114
+ const [opened, setOpened] = useState(null);
111
115
  const { data: entries, status } = useFirestoreCollection(()=>{
112
116
  // `strictNullChecks` is off repo-wide, so an absent hostId would reach
113
117
  // `collection()` as a falsy path segment. Refuse to build the query
@@ -223,15 +227,48 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
223
227
  orgSlug,
224
228
  host
225
229
  });
226
- const label = activityPrimaryText(entry);
230
+ const label = activityRowText(entry);
231
+ // A row opens the shared details dialog (AGL-3660); its link
232
+ // still goes where the row points.
233
+ const open = ()=>{
234
+ var _ref;
235
+ var _entry_createdAt_toDate, _entry_createdAt;
236
+ return setOpened({
237
+ description: describeActivity(_extends({}, entry, {
238
+ scopeType: 'host',
239
+ scopeId: hostId
240
+ })),
241
+ who: activityActorLabel(entry),
242
+ when: (_ref = (_entry_createdAt = entry.createdAt) == null ? void 0 : (_entry_createdAt_toDate = _entry_createdAt.toDate) == null ? void 0 : _entry_createdAt_toDate.call(_entry_createdAt).toLocaleString()) != null ? _ref : '—',
243
+ links: href ? [
244
+ {
245
+ label: 'Open',
246
+ href
247
+ }
248
+ ] : []
249
+ });
250
+ };
227
251
  return /*#__PURE__*/ _jsx(ListItem, {
228
252
  disableGutters: true,
229
253
  dense: true,
254
+ role: "button",
255
+ tabIndex: 0,
256
+ onClick: open,
257
+ onKeyDown: (event)=>{
258
+ if (event.key === 'Enter') open();
259
+ },
260
+ sx: {
261
+ cursor: 'pointer',
262
+ '&:hover': {
263
+ bgcolor: 'action.hover'
264
+ }
265
+ },
230
266
  children: /*#__PURE__*/ _jsx(ListItemText, {
231
267
  primary: href ? /*#__PURE__*/ _jsx(AppLink, {
232
268
  href: href,
233
269
  color: "inherit",
234
270
  underline: "hover",
271
+ onClick: (event)=>event.stopPropagation(),
235
272
  children: label
236
273
  }) : label,
237
274
  secondary: `${activityActorLabel(entry)} · ${(_ref = (_entry_createdAt = entry.createdAt) == null ? void 0 : (_entry_createdAt_toDate = _entry_createdAt.toDate) == null ? void 0 : _entry_createdAt_toDate.call(_entry_createdAt).toLocaleString()) != null ? _ref : ''}` + // Run-log entries carry duration (wave v6).
@@ -273,7 +310,11 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
273
310
  underline: "hover",
274
311
  children: 'View all activity'
275
312
  })
276
- }) : null
313
+ }) : null,
314
+ /*#__PURE__*/ _jsx(ActivityDetailsDialog, {
315
+ details: opened,
316
+ onClose: ()=>setOpened(null)
317
+ })
277
318
  ]
278
319
  });
279
320
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-activity-card.component.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 { AppLink, CardDisplay, type HelpTipContent } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport {\n Alert,\n Button,\n Link,\n List,\n ListItem,\n ListItemText,\n Stack,\n Typography,\n} from '@mui/material'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useParams } from 'next/navigation'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n ceilingedWindow,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { useResolvedActivityActors } from '@aglyn/tenant-feature-instance/hooks/use-resolved-activity-actors'\nimport {\n activityActorLabel,\n activityHref,\n activityPrimaryText,\n} from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface HostActivityCardProps {\n hostId: string\n /** Show only entries for this target id (e.g. a screen detail page). */\n targetId?: string\n header?: string\n /** When set, a \"View all activity\" link renders under the list (AGL-249). */\n viewAllHref?: string\n /** Overrides the default help affordance on the card header. */\n help?: HelpTipContent\n}\n\n/**\n * Rows fetched before the client-side sort and page slice.\n *\n * A CEILING, and the read asks for one document more than it so the card can\n * say when the window was not the whole story — `length >= WINDOW` is wrong at\n * exactly the collection size that equals the ceiling.\n */\nconst WINDOW = 200\n\n/**\n * Recent user activity (AGL-118): newest-first feed from\n * `hosts/{hostId}/activity`, optionally filtered to one target so detail\n * pages show just their own history.\n *\n * ## The \"Page Activity\" card reported nothing on a page that had changed\n *\n * This is AGL-2292 — fixed in the sibling `OrgActivityCard`, never applied\n * here. The query was `limit(200)` with NO `orderBy`. Firestore then returns\n * documents in DOCUMENT-ID order, `useHostActivityLogger` appends with\n * `addDoc()`, so the ids are effectively random: the window was a\n * pseudo-random SAMPLE of the collection rather than its newest page, and the\n * client sort below dutifully ordered that sample and sliced the top off it —\n * which made the result look right and be wrong.\n *\n * Measured on a production host: 996 activity documents, so a 200-row window\n * covers the lexicographically-lowest ids and ends at `ClSZ…`. A screen whose\n * five entries have ids beginning `Y`, `h`, `j`, `k`, `t` has every one of\n * them past that boundary — the card fetches 200 rows, matches 0, and says\n * \"No activity yet\" about a page edited that morning.\n *\n * ## Why the targeted query filters SERVER-side\n *\n * Adding `orderBy` alone is not the fix here, and the difference only shows up\n * on the per-target card. The window is host-wide, so 200 newest host-wide\n * entries still have to contain the target's own. On that same host it would\n * have surfaced 1 of the screen's 5 entries, and 40 of the 108 targets with\n * real activity would STILL have rendered \"No activity yet\" — a quieter\n * version of the same lie. A target that has not been touched recently is\n * exactly the one whose history you open the card to read.\n *\n * So `targetId` becomes a `where` and spends the window on the target instead\n * of on the host. That predicate needs no new index: an equality filter with\n * no `orderBy` is served by the automatic single-field index, which is\n * deliberate — a composite (`target.id` ASC, `createdAt` DESC) would be\n * FILE-ONLY until someone ran `firebase deploy --only firestore:indexes` by\n * hand, and every read in between would throw `failed-precondition`. The\n * ordering is done by the client sort below, which is safe at this size: the\n * busiest target in production holds 55 entries against a 200-row window.\n *\n * The un-targeted (dashboard) query has no such excuse and takes the AGL-2292\n * one-liner: `orderBy('createdAt', 'desc')`, single-field, no config change.\n *\n * ## \"Found nothing\" vs \"could not look\"\n *\n * Both used to render the empty state, and so did the first paint before any\n * snapshot arrived. A refused or failed listen now says so and offers a retry\n * (the `used-by-card` shape), because an audit feed that invents a confident\n * zero is worse than one that admits it is blind.\n *\n * ## Why the \"Show N more\" expander became the shared footer\n *\n * It was a FOURTH pagination grammar for the same act — beside the `DataGrid`\n * footer, the shared `ListPagination`, and the \"Load more\" the DAM grid keeps —\n * and it escaped the guard that exists to stop exactly that, on spelling: the\n * check looks for the literal `'Load more'` and this button said\n * `Show ${more} more`.\n *\n * On its own terms it was also the weakest of the four. It only ever grew, so\n * a reader who opened forty rows could not get back to ten without remounting\n * the card; it offered no size control, which made it one of the two grammars\n * that never let a reader choose; it could not state a total even though every\n * row was already in hand; and it said nothing when `WINDOW` bit, so a busy\n * target read as though two hundred entries were all there were.\n *\n * The footer answers all four, and the rows stay a client SLICE — see `sorted`,\n * where paging is free because the window is already fetched.\n */\nexport function HostActivityCard(props: HostActivityCardProps) {\n const {\n hostId,\n targetId,\n header = 'Recent Activity',\n viewAllHref,\n help = pluginDocsHelp('consoleTour', {\n anchor: '#a-sites-dashboard',\n excerpt:\n 'Changes made to this site from the console — publishes, media ' +\n 'saves, member changes — newest first.',\n }),\n } = props\n const { orgSlug, host } = useParams<{ orgSlug: string; host: string }>()\n const firestore = useFirestore()\n const [attempt, setAttempt] = useState(0)\n const { data: entries, status } = useFirestoreCollection<any>(\n () => {\n // `strictNullChecks` is off repo-wide, so an absent hostId would reach\n // `collection()` as a falsy path segment. Refuse to build the query\n // instead, and report it as unreadable below — never as \"no activity\".\n if (!hostId) return null\n const base = collection(firestore, 'hosts', hostId, 'activity')\n // `WINDOW + 1` on both: the extra document is the PROBE, and it is what\n // turns \"there may be more\" into a fact the card can state. It is never\n // rendered — `ceilingedWindow` drops it below.\n return targetId\n ? query(base, where('target.id', '==', targetId), limit(WINDOW + 1))\n : query(base, orderBy('createdAt', 'desc'), limit(WINDOW + 1))\n },\n [firestore, hostId, targetId, attempt],\n { idField: '$id' },\n )\n /*\n * The client sort survives the server-side ordering rather than being\n * replaced by it. It is what orders the TARGETED query (which cannot carry\n * an `orderBy` without a composite index — see above), and on the\n * un-targeted one it still catches a just-written entry whose timestamp the\n * local snapshot has not resolved. Sorting 200 items costs nothing.\n */\n /**\n * Paging is FREE here, which is why it is a slice and not a second query\n * (AGL-2486). The read above already fetches `WINDOW` rows and this then\n * threw all but a page of them away — 190 documents paid for and discarded\n * on every render of this card. Showing more of what is already in hand\n * costs nothing; only running past `WINDOW` would cost a read, and the\n * \"View all\" link exists for that.\n *\n * Sorting is safe here in a way it is not on a server-paged list: these rows\n * are the whole window, not a slice of one.\n */\n const { rows: windowRows, truncated } = ceilingedWindow<any>(entries, WINDOW)\n const sorted = useMemo(\n () =>\n [...windowRows].sort(\n (a, b) => (b.createdAt?.seconds ?? 0) - (a.createdAt?.seconds ?? 0),\n ),\n // `windowRows` is a fresh array each render; the snapshot behind it is not.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [entries],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n // Back to the first page when the card is pointed at a different subject —\n // otherwise a previous screen's page three opens the next one three pages\n // in, over a history that may not have three.\n useEffect(() => setPage(0), [targetId, hostId])\n const pageItems = useMemo(\n () => sorted.slice(page * pageSize, page * pageSize + pageSize),\n [sorted, page, pageSize],\n )\n // The page's rows only: a uid off the page is not asked about.\n const items = useResolvedActivityActors(hostId, pageItems)\n // Three states, not two: a read that never happened must not be reported as\n // an empty history.\n const unreadable = status === 'error' || !hostId\n\n return (\n <CardDisplay\n header={header}\n help={help}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {unreadable ? (\n <Stack spacing={1.5} sx={{ alignItems: 'flex-start' }}>\n <Alert severity=\"warning\" sx={{ width: '100%' }}>\n {'Could not read the activity log. This is NOT the same as ' +\n 'nothing having happened — do not treat this as a record ' +\n 'that the page is unchanged.'}\n </Alert>\n <Button size=\"small\" onClick={() => setAttempt((n) => n + 1)}>\n {'Try again'}\n </Button>\n </Stack>\n ) : status === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading activity…'}\n </Typography>\n ) : sorted.length === 0 ? (\n /*\n * The WINDOW's length, not the page's. A page past the end of a\n * history that shrank while it was open holds no rows and is not an\n * empty history, and this card must never confuse the two.\n */\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No activity yet — changes made in the console appear here.'}\n </Typography>\n ) : (\n <>\n <List dense disablePadding>\n {items.map((entry) => {\n const href = activityHref(entry, { orgSlug, host })\n const label = activityPrimaryText(entry)\n return (\n <ListItem key={entry.$id} disableGutters dense>\n <ListItemText\n primary={\n href ? (\n <AppLink href={href} color=\"inherit\" underline=\"hover\">\n {label}\n </AppLink>\n ) : (\n label\n )\n }\n secondary={\n `${activityActorLabel(entry)} · ${\n entry.createdAt?.toDate?.().toLocaleString() ?? ''\n }` +\n // Run-log entries carry duration (wave v6).\n (entry.durationMs != null\n ? ` · ${entry.durationMs}ms`\n : '')\n }\n slotProps={\n entry.status === 'error'\n ? { primary: { color: 'error' } }\n : undefined\n }\n />\n </ListItem>\n )\n })}\n </List>\n {/* Free paging: these rows are already fetched — see `sorted`. */}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={items.length}\n // The window the card HOLDS, which it knows exactly. `truncated`\n // below is what says the history is larger than that.\n count={sorted.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n {truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {/*\n * Two different sentences because the two queries answer\n * differently. The un-targeted one carries `orderBy('createdAt')`\n * and its window really is the newest entries; the targeted one\n * has no `orderBy` — deliberately, so it needs no composite\n * index — so its window is a document-id SAMPLE of the target's\n * history, and calling it \"most recent\" would be the same lie\n * AGL-2292 was.\n */}\n {targetId\n ? `Showing ${WINDOW} entries for this item. It has more — ` +\n 'these are not necessarily the newest; open the full ' +\n 'activity log for the ordered history.'\n : `Showing this site’s ${WINDOW} most recent entries. There ` +\n 'is more history than that — open the full activity log to ' +\n 'read further back.'}\n </Typography>\n ) : null}\n </>\n )}\n {viewAllHref ? (\n <Typography variant=\"body2\" sx={{ mt: 1 }}>\n <Link href={viewAllHref} color=\"primary\" underline=\"hover\">\n {'View all activity'}\n </Link>\n </Typography>\n ) : null}\n </CardDisplay>\n )\n}\nHostActivityCard.displayName = 'HostActivityCard'\n\nexport default HostActivityCard\n"],"names":["AppLink","CardDisplay","ListPagination","TABLE_PAGE_SIZE_DEFAULT","Alert","Button","Link","List","ListItem","ListItemText","Stack","Typography","collection","limit","orderBy","query","where","useParams","useEffect","useMemo","useState","ceilingedWindow","useFirestore","useFirestoreCollection","useResolvedActivityActors","activityActorLabel","activityHref","activityPrimaryText","pluginDocsHelp","WINDOW","HostActivityCard","props","hostId","targetId","header","viewAllHref","help","anchor","excerpt","orgSlug","host","firestore","attempt","setAttempt","data","entries","status","base","idField","rows","windowRows","truncated","sorted","sort","a","b","createdAt","seconds","page","setPage","pageSize","setPageSize","pageItems","slice","items","unreadable","contentGutterX","contentGutterY","contentBordered","spacing","sx","alignItems","severity","width","size","onClick","n","variant","color","length","dense","disablePadding","map","entry","href","label","disableGutters","primary","underline","secondary","toDate","toLocaleString","durationMs","slotProps","undefined","$id","rowCount","count","onPageChange","onPageSizeChange","mt","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-activity-card.component.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 { AppLink, CardDisplay, type HelpTipContent } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport {\n Alert,\n Button,\n Link,\n List,\n ListItem,\n ListItemText,\n Stack,\n Typography,\n} from '@mui/material'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useParams } from 'next/navigation'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n ceilingedWindow,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { useResolvedActivityActors } from '@aglyn/tenant-feature-instance/hooks/use-resolved-activity-actors'\nimport {\n activityActorLabel,\n activityHref,\n} from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { activityRowText, describeActivity } from '@aglyn/aglyn/app-utils/activity-labels'\nimport ActivityDetailsDialog, {\n type ActivityDetails,\n} from '@aglyn/shared-ui-jsx/components/activity-details-dialog.component'\n\nexport interface HostActivityCardProps {\n hostId: string\n /** Show only entries for this target id (e.g. a screen detail page). */\n targetId?: string\n header?: string\n /** When set, a \"View all activity\" link renders under the list (AGL-249). */\n viewAllHref?: string\n /** Overrides the default help affordance on the card header. */\n help?: HelpTipContent\n}\n\n/**\n * Rows fetched before the client-side sort and page slice.\n *\n * A CEILING, and the read asks for one document more than it so the card can\n * say when the window was not the whole story — `length >= WINDOW` is wrong at\n * exactly the collection size that equals the ceiling.\n */\nconst WINDOW = 200\n\n/**\n * Recent user activity (AGL-118): newest-first feed from\n * `hosts/{hostId}/activity`, optionally filtered to one target so detail\n * pages show just their own history.\n *\n * ## The \"Page Activity\" card reported nothing on a page that had changed\n *\n * This is AGL-2292 — fixed in the sibling `OrgActivityCard`, never applied\n * here. The query was `limit(200)` with NO `orderBy`. Firestore then returns\n * documents in DOCUMENT-ID order, `useHostActivityLogger` appends with\n * `addDoc()`, so the ids are effectively random: the window was a\n * pseudo-random SAMPLE of the collection rather than its newest page, and the\n * client sort below dutifully ordered that sample and sliced the top off it —\n * which made the result look right and be wrong.\n *\n * Measured on a production host: 996 activity documents, so a 200-row window\n * covers the lexicographically-lowest ids and ends at `ClSZ…`. A screen whose\n * five entries have ids beginning `Y`, `h`, `j`, `k`, `t` has every one of\n * them past that boundary — the card fetches 200 rows, matches 0, and says\n * \"No activity yet\" about a page edited that morning.\n *\n * ## Why the targeted query filters SERVER-side\n *\n * Adding `orderBy` alone is not the fix here, and the difference only shows up\n * on the per-target card. The window is host-wide, so 200 newest host-wide\n * entries still have to contain the target's own. On that same host it would\n * have surfaced 1 of the screen's 5 entries, and 40 of the 108 targets with\n * real activity would STILL have rendered \"No activity yet\" — a quieter\n * version of the same lie. A target that has not been touched recently is\n * exactly the one whose history you open the card to read.\n *\n * So `targetId` becomes a `where` and spends the window on the target instead\n * of on the host. That predicate needs no new index: an equality filter with\n * no `orderBy` is served by the automatic single-field index, which is\n * deliberate — a composite (`target.id` ASC, `createdAt` DESC) would be\n * FILE-ONLY until someone ran `firebase deploy --only firestore:indexes` by\n * hand, and every read in between would throw `failed-precondition`. The\n * ordering is done by the client sort below, which is safe at this size: the\n * busiest target in production holds 55 entries against a 200-row window.\n *\n * The un-targeted (dashboard) query has no such excuse and takes the AGL-2292\n * one-liner: `orderBy('createdAt', 'desc')`, single-field, no config change.\n *\n * ## \"Found nothing\" vs \"could not look\"\n *\n * Both used to render the empty state, and so did the first paint before any\n * snapshot arrived. A refused or failed listen now says so and offers a retry\n * (the `used-by-card` shape), because an audit feed that invents a confident\n * zero is worse than one that admits it is blind.\n *\n * ## Why the \"Show N more\" expander became the shared footer\n *\n * It was a FOURTH pagination grammar for the same act — beside the `DataGrid`\n * footer, the shared `ListPagination`, and the \"Load more\" the DAM grid keeps —\n * and it escaped the guard that exists to stop exactly that, on spelling: the\n * check looks for the literal `'Load more'` and this button said\n * `Show ${more} more`.\n *\n * On its own terms it was also the weakest of the four. It only ever grew, so\n * a reader who opened forty rows could not get back to ten without remounting\n * the card; it offered no size control, which made it one of the two grammars\n * that never let a reader choose; it could not state a total even though every\n * row was already in hand; and it said nothing when `WINDOW` bit, so a busy\n * target read as though two hundred entries were all there were.\n *\n * The footer answers all four, and the rows stay a client SLICE — see `sorted`,\n * where paging is free because the window is already fetched.\n */\nexport function HostActivityCard(props: HostActivityCardProps) {\n const {\n hostId,\n targetId,\n header = 'Recent Activity',\n viewAllHref,\n help = pluginDocsHelp('consoleTour', {\n anchor: '#a-sites-dashboard',\n excerpt:\n 'Changes made to this site from the console — publishes, media ' +\n 'saves, member changes — newest first.',\n }),\n } = props\n const { orgSlug, host } = useParams<{ orgSlug: string; host: string }>()\n const firestore = useFirestore()\n const [attempt, setAttempt] = useState(0)\n const [opened, setOpened] = useState<ActivityDetails | null>(null)\n const { data: entries, status } = useFirestoreCollection<any>(\n () => {\n // `strictNullChecks` is off repo-wide, so an absent hostId would reach\n // `collection()` as a falsy path segment. Refuse to build the query\n // instead, and report it as unreadable below — never as \"no activity\".\n if (!hostId) return null\n const base = collection(firestore, 'hosts', hostId, 'activity')\n // `WINDOW + 1` on both: the extra document is the PROBE, and it is what\n // turns \"there may be more\" into a fact the card can state. It is never\n // rendered — `ceilingedWindow` drops it below.\n return targetId\n ? query(base, where('target.id', '==', targetId), limit(WINDOW + 1))\n : query(base, orderBy('createdAt', 'desc'), limit(WINDOW + 1))\n },\n [firestore, hostId, targetId, attempt],\n { idField: '$id' },\n )\n /*\n * The client sort survives the server-side ordering rather than being\n * replaced by it. It is what orders the TARGETED query (which cannot carry\n * an `orderBy` without a composite index — see above), and on the\n * un-targeted one it still catches a just-written entry whose timestamp the\n * local snapshot has not resolved. Sorting 200 items costs nothing.\n */\n /**\n * Paging is FREE here, which is why it is a slice and not a second query\n * (AGL-2486). The read above already fetches `WINDOW` rows and this then\n * threw all but a page of them away — 190 documents paid for and discarded\n * on every render of this card. Showing more of what is already in hand\n * costs nothing; only running past `WINDOW` would cost a read, and the\n * \"View all\" link exists for that.\n *\n * Sorting is safe here in a way it is not on a server-paged list: these rows\n * are the whole window, not a slice of one.\n */\n const { rows: windowRows, truncated } = ceilingedWindow<any>(entries, WINDOW)\n const sorted = useMemo(\n () =>\n [...windowRows].sort(\n (a, b) => (b.createdAt?.seconds ?? 0) - (a.createdAt?.seconds ?? 0),\n ),\n // `windowRows` is a fresh array each render; the snapshot behind it is not.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [entries],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n // Back to the first page when the card is pointed at a different subject —\n // otherwise a previous screen's page three opens the next one three pages\n // in, over a history that may not have three.\n useEffect(() => setPage(0), [targetId, hostId])\n const pageItems = useMemo(\n () => sorted.slice(page * pageSize, page * pageSize + pageSize),\n [sorted, page, pageSize],\n )\n // The page's rows only: a uid off the page is not asked about.\n const items = useResolvedActivityActors(hostId, pageItems)\n // Three states, not two: a read that never happened must not be reported as\n // an empty history.\n const unreadable = status === 'error' || !hostId\n\n return (\n <CardDisplay\n header={header}\n help={help}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {unreadable ? (\n <Stack spacing={1.5} sx={{ alignItems: 'flex-start' }}>\n <Alert severity=\"warning\" sx={{ width: '100%' }}>\n {'Could not read the activity log. This is NOT the same as ' +\n 'nothing having happened — do not treat this as a record ' +\n 'that the page is unchanged.'}\n </Alert>\n <Button size=\"small\" onClick={() => setAttempt((n) => n + 1)}>\n {'Try again'}\n </Button>\n </Stack>\n ) : status === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading activity…'}\n </Typography>\n ) : sorted.length === 0 ? (\n /*\n * The WINDOW's length, not the page's. A page past the end of a\n * history that shrank while it was open holds no rows and is not an\n * empty history, and this card must never confuse the two.\n */\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No activity yet — changes made in the console appear here.'}\n </Typography>\n ) : (\n <>\n <List dense disablePadding>\n {items.map((entry) => {\n const href = activityHref(entry, { orgSlug, host })\n const label = activityRowText(entry)\n // A row opens the shared details dialog (AGL-3660); its link\n // still goes where the row points.\n const open = () =>\n setOpened({\n description: describeActivity({ ...entry, scopeType: 'host', scopeId: hostId }),\n who: activityActorLabel(entry),\n when: entry.createdAt?.toDate?.().toLocaleString() ?? '—',\n links: href ? [{ label: 'Open', href }] : [],\n })\n return (\n <ListItem\n key={entry.$id}\n disableGutters\n dense\n role=\"button\"\n tabIndex={0}\n onClick={open}\n onKeyDown={(event) => {\n if (event.key === 'Enter') open()\n }}\n sx={{ cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }}\n >\n <ListItemText\n primary={\n href ? (\n <AppLink\n href={href}\n color=\"inherit\"\n underline=\"hover\"\n onClick={(event: { stopPropagation: () => void }) => event.stopPropagation()}\n >\n {label}\n </AppLink>\n ) : (\n label\n )\n }\n secondary={\n `${activityActorLabel(entry)} · ${\n entry.createdAt?.toDate?.().toLocaleString() ?? ''\n }` +\n // Run-log entries carry duration (wave v6).\n (entry.durationMs != null\n ? ` · ${entry.durationMs}ms`\n : '')\n }\n slotProps={\n entry.status === 'error'\n ? { primary: { color: 'error' } }\n : undefined\n }\n />\n </ListItem>\n )\n })}\n </List>\n {/* Free paging: these rows are already fetched — see `sorted`. */}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={items.length}\n // The window the card HOLDS, which it knows exactly. `truncated`\n // below is what says the history is larger than that.\n count={sorted.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n {truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {/*\n * Two different sentences because the two queries answer\n * differently. The un-targeted one carries `orderBy('createdAt')`\n * and its window really is the newest entries; the targeted one\n * has no `orderBy` — deliberately, so it needs no composite\n * index — so its window is a document-id SAMPLE of the target's\n * history, and calling it \"most recent\" would be the same lie\n * AGL-2292 was.\n */}\n {targetId\n ? `Showing ${WINDOW} entries for this item. It has more — ` +\n 'these are not necessarily the newest; open the full ' +\n 'activity log for the ordered history.'\n : `Showing this site’s ${WINDOW} most recent entries. There ` +\n 'is more history than that — open the full activity log to ' +\n 'read further back.'}\n </Typography>\n ) : null}\n </>\n )}\n {viewAllHref ? (\n <Typography variant=\"body2\" sx={{ mt: 1 }}>\n <Link href={viewAllHref} color=\"primary\" underline=\"hover\">\n {'View all activity'}\n </Link>\n </Typography>\n ) : null}\n <ActivityDetailsDialog details={opened} onClose={() => setOpened(null)} />\n </CardDisplay>\n )\n}\nHostActivityCard.displayName = 'HostActivityCard'\n\nexport default HostActivityCard\n"],"names":["AppLink","CardDisplay","ListPagination","TABLE_PAGE_SIZE_DEFAULT","Alert","Button","Link","List","ListItem","ListItemText","Stack","Typography","collection","limit","orderBy","query","where","useParams","useEffect","useMemo","useState","ceilingedWindow","useFirestore","useFirestoreCollection","useResolvedActivityActors","activityActorLabel","activityHref","pluginDocsHelp","activityRowText","describeActivity","ActivityDetailsDialog","WINDOW","HostActivityCard","props","hostId","targetId","header","viewAllHref","help","anchor","excerpt","orgSlug","host","firestore","attempt","setAttempt","opened","setOpened","data","entries","status","base","idField","rows","windowRows","truncated","sorted","sort","a","b","createdAt","seconds","page","setPage","pageSize","setPageSize","pageItems","slice","items","unreadable","contentGutterX","contentGutterY","contentBordered","spacing","sx","alignItems","severity","width","size","onClick","n","variant","color","length","dense","disablePadding","map","entry","href","label","open","description","scopeType","scopeId","who","when","toDate","toLocaleString","links","disableGutters","role","tabIndex","onKeyDown","event","key","cursor","bgcolor","primary","underline","stopPropagation","secondary","durationMs","slotProps","undefined","$id","rowCount","count","onPageChange","onPageSizeChange","mt","details","onClose","displayName"],"mappings":"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"}
@@ -338,8 +338,7 @@ const inIdOrder = (source, pageLimit)=>query(source, orderBy(documentId()), limi
338
338
  /*#__PURE__*/ _jsx(CardDisplay, {
339
339
  header: 'Organization automations',
340
340
  help: pluginDocsHelp('staffConsole', {
341
- anchor: '#staff-automations',
342
- excerpt: "The organization's automations, whether each is on and where it is paused — read-only, each opening its trigger and steps."
341
+ anchor: '#staff-org-automations'
343
342
  }),
344
343
  contentGutterX: true,
345
344
  contentGutterY: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/staff-automations-card.component.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiEyeOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport { useFirestore, usePagedCollection } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n documentId,\n type Firestore,\n limit,\n orderBy,\n query,\n type Query,\n where,\n} from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\n\n/*\n * AUTOMATIONS, AS STAFF SEE THEM (AGL-3379).\n *\n * The staff site and organization pages are about a customer the staffer is\n * not a member of, so nothing here edits, runs or pauses: each table is a\n * paged walk of one collection in document-id order, and a row opens a\n * read-only view of the trigger and the steps. The rules admit staff reads on\n * every site and organization subcollection.\n */\n\ntype AutomationRow = { $id: string } & Record<string, any>\n\nconst nameOf = (row: AutomationRow): string => String(row['name'] || row['displayName'] || row.$id)\n\nconst whenOf = (value: any): string =>\n typeof value?.toDate === 'function' ? value.toDate().toLocaleString() : '—'\n\n/** The trigger and the steps of one automation, read-only. */\nfunction StepsDialog(props: { row: AutomationRow | null; onClose: () => void }) {\n const { row, onClose } = props\n const steps: unknown[] = Array.isArray(row?.['steps']) ? row['steps'] : []\n return (\n <Dialog open={Boolean(row)} onClose={onClose} maxWidth=\"md\" fullWidth>\n <DialogTitle>{row ? nameOf(row) : ''}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Box>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Trigger'}\n </Typography>\n <Typography variant=\"body2\">\n {row?.['trigger']?.event ?? 'Manual — runs only when called'}\n </Typography>\n {row?.['trigger']?.filter ? (\n <Typography variant=\"caption\" sx={{ fontFamily: 'monospace' }}>\n {`when ${row['trigger'].filter}`}\n </Typography>\n ) : null}\n </Box>\n <Box>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Steps (${steps.length})`}\n </Typography>\n <Box\n component=\"pre\"\n sx={{\n m: 0,\n p: 1.5,\n borderRadius: 1,\n bgcolor: 'action.hover',\n fontSize: 12,\n overflow: 'auto',\n maxHeight: 420,\n }}\n >\n {JSON.stringify(steps, null, 2)}\n </Box>\n </Box>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\n\n/** One paged, read-only table of automations. */\nconst millisOf = (value: any): number | null =>\n typeof value?.toMillis === 'function' ? value.toMillis() : null\n\n/*\n * EVERY HEADER SORTS, OVER THE PAGE (AGL-3680). These read-only staff tables\n * walk each collection by document id. Their name falls back across fields\n * that differ by collection (`name`, `displayName`, the id), their status is\n * drawn from the trigger and the pause lists, and `updatedAt` is not stamped\n * by every writer (an imported automation carries none) — so no stored field\n * orders all three, and each header sorts the page on screen and says so.\n */\nconst AUTOMATION_PAGE_SORTS = {\n name: (row: AutomationRow) => nameOf(row),\n status: (row: AutomationRow) => String(row['trigger']?.event ?? 'manual'),\n updatedAt: (row: AutomationRow) => millisOf(row['updatedAt']),\n}\nconst AUTOMATION_PAGE_SORT_HEADERS = { name: 'Automation', status: 'Status', updatedAt: 'Updated' }\nconst NO_QUERY_SORTS = [] as const\n\nfunction AutomationTable(props: {\n title: string\n buildQuery: (firestore: Firestore, pageLimit: number) => Query | null\n deps: readonly unknown[]\n /** The site the table is read for, to mark what it has paused. */\n hostId?: string\n emptyLabel: string\n onOpen: (row: AutomationRow) => void\n}) {\n const { title, buildQuery, deps, hostId, emptyLabel, onOpen } = props\n const firestore = useFirestore()\n const paged = usePagedCollection<AutomationRow>(\n (pageLimit) => buildQuery(firestore, pageLimit),\n [firestore, ...deps],\n { idField: '$id' },\n )\n const byId = useMemo(() => new Map(paged.rows.map((row) => [row.$id, row])), [paged.rows])\n const columnSort = useListColumnSort<AutomationRow>({\n sorts: NO_QUERY_SORTS,\n rows: paged.rows,\n pageSorts: AUTOMATION_PAGE_SORTS,\n headers: AUTOMATION_PAGE_SORT_HEADERS,\n })\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Automation',\n flex: 1.4,\n minWidth: 200,\n valueGetter: (_value, row: AutomationRow) => nameOf(row),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 1.2,\n minWidth: 200,\n valueGetter: (_value, row: AutomationRow) => String(row['trigger']?.event ?? 'manual'),\n renderCell: ({ row }: { row: AutomationRow }) => {\n const paused: string[] = Array.isArray(row['pausedHostIds']) ? row['pausedHostIds'] : []\n return (\n <Stack\n direction=\"row\"\n spacing={0.5}\n useFlexGap\n sx={{ alignItems: 'center', height: '100%', flexWrap: 'wrap' }}\n >\n <Chip size=\"small\" variant=\"outlined\" label={row['trigger']?.event ?? 'manual'} />\n {row['enabled'] === false ? (\n <Chip size=\"small\" color=\"warning\" label=\"off\" />\n ) : (\n <Chip size=\"small\" color=\"success\" label=\"on\" />\n )}\n {hostId && paused.includes(hostId) ? (\n <Chip size=\"small\" color=\"warning\" label=\"paused here\" />\n ) : !hostId && paused.length ? (\n <Chip size=\"small\" color=\"warning\" label={`paused on ${paused.length}`} />\n ) : null}\n {row['deletedAt'] ? <Chip size=\"small\" color=\"error\" label=\"deleted\" /> : null}\n </Stack>\n )\n },\n },\n {\n field: 'updatedAt',\n headerName: 'Updated',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: AutomationRow) => whenOf(row['updatedAt']),\n },\n listActionsColumn(\n (row: AutomationRow) => (\n <ListRowActions\n label={nameOf(row)}\n quick={{ icon: mdiEyeOutline.path, label: 'View steps', onClick: () => onOpen(row) }}\n items={[]}\n />\n ),\n { width: 110 },\n ),\n ],\n [hostId, onOpen],\n )\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{title}</Typography>\n <ListQueryNotices refused={[]} notices={columnSort.notices} />\n <ListTable\n rows={columnSort.rows}\n columns={columns}\n loading={paged.status === 'loading'}\n columnSort={columnSort}\n noRowsLabel={emptyLabel}\n onOpen={(id) => {\n const row = byId.get(String(id))\n if (row) onOpen(row)\n }}\n hideFooter\n rowHeight={TABLE_ROW_HEIGHT}\n />\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n )\n}\n\nconst inIdOrder = (source: Query, pageLimit: number) =>\n query(source, orderBy(documentId()), limit(pageLimit))\n\n/**\n * The staff site page's automations (`staffSite` zone): the site's own\n * automations, the organization automations that run on it, and its\n * workflows.\n */\nexport function StaffSiteAutomationsCard(props: { hostId: string; orgId?: string }) {\n const { hostId, orgId } = props\n const [open, setOpen] = useState<AutomationRow | null>(null)\n return (\n <>\n <CardDisplay\n header={'Automations'}\n help={pluginDocsHelp('staffConsole', {\n anchor: '#staff-automations',\n excerpt:\n \"The site's automations, the organization automations that run on it, and its workflows — read-only, each opening its trigger and steps.\",\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={3}>\n <AutomationTable\n title=\"Site automations\"\n buildQuery={(firestore, pageLimit) =>\n hostId ? inIdOrder(collection(firestore, 'hosts', hostId, 'actions'), pageLimit) : null\n }\n deps={[hostId]}\n hostId={hostId}\n emptyLabel=\"No automations on this site\"\n onOpen={setOpen}\n />\n <AutomationTable\n title=\"Organization automations that run here\"\n buildQuery={(firestore, pageLimit) =>\n orgId && hostId\n ? inIdOrder(\n query(\n collection(firestore, 'orgs', orgId, 'automations'),\n where('visibleTo', 'array-contains-any', ['org', `host:${hostId}`]),\n ),\n pageLimit,\n )\n : null\n }\n deps={[orgId, hostId]}\n hostId={hostId}\n emptyLabel=\"No organization automation runs on this site\"\n onOpen={setOpen}\n />\n <AutomationTable\n title=\"Workflows\"\n buildQuery={(firestore, pageLimit) =>\n hostId ? inIdOrder(collection(firestore, 'hosts', hostId, 'workflows'), pageLimit) : null\n }\n deps={[hostId]}\n emptyLabel=\"No workflows on this site\"\n onOpen={setOpen}\n />\n </Stack>\n </CardDisplay>\n <StepsDialog row={open} onClose={() => setOpen(null)} />\n </>\n )\n}\n\n/** The staff organization page's automations (`adminOrgDetail` zone). */\nexport function StaffOrgAutomationsCard(props: { orgId: string }) {\n const { orgId } = props\n const [open, setOpen] = useState<AutomationRow | null>(null)\n return (\n <>\n <CardDisplay\n header={'Organization automations'}\n help={pluginDocsHelp('staffConsole', {\n anchor: '#staff-automations',\n excerpt:\n \"The organization's automations, whether each is on and where it is paused — read-only, each opening its trigger and steps.\",\n })}\n contentGutterX\n contentGutterY\n >\n <AutomationTable\n title=\"Automations\"\n buildQuery={(firestore, pageLimit) =>\n orgId ? inIdOrder(collection(firestore, 'orgs', orgId, 'automations'), pageLimit) : null\n }\n deps={[orgId]}\n emptyLabel=\"This organization has no automations\"\n onOpen={setOpen}\n />\n </CardDisplay>\n <StepsDialog row={open} onClose={() => setOpen(null)} />\n </>\n )\n}\n"],"names":["pluginDocsHelp","mdiEyeOutline","CardDisplay","ListPagination","ListQueryNotices","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useListColumnSort","useFirestore","usePagedCollection","Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","collection","documentId","limit","orderBy","query","where","useMemo","useState","nameOf","row","String","$id","whenOf","value","toDate","toLocaleString","StepsDialog","props","onClose","steps","Array","isArray","open","Boolean","maxWidth","fullWidth","spacing","variant","color","event","filter","sx","fontFamily","length","component","m","p","borderRadius","bgcolor","fontSize","overflow","maxHeight","JSON","stringify","onClick","millisOf","toMillis","AUTOMATION_PAGE_SORTS","name","status","updatedAt","AUTOMATION_PAGE_SORT_HEADERS","NO_QUERY_SORTS","AutomationTable","title","buildQuery","deps","hostId","emptyLabel","onOpen","firestore","paged","pageLimit","idField","byId","Map","rows","map","columnSort","sorts","pageSorts","headers","columns","field","headerName","flex","minWidth","valueGetter","_value","renderCell","paused","direction","useFlexGap","alignItems","height","flexWrap","size","label","includes","quick","icon","path","items","width","refused","notices","loading","noRowsLabel","id","get","hideFooter","rowHeight","page","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","inIdOrder","source","StaffSiteAutomationsCard","orgId","setOpen","header","help","anchor","excerpt","contentGutterX","contentGutterY","StaffOrgAutomationsCard"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/staff-automations-card.component.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiEyeOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport { useFirestore, usePagedCollection } from '@aglyn/tenant-feature-instance'\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n documentId,\n type Firestore,\n limit,\n orderBy,\n query,\n type Query,\n where,\n} from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\n\n/*\n * AUTOMATIONS, AS STAFF SEE THEM (AGL-3379).\n *\n * The staff site and organization pages are about a customer the staffer is\n * not a member of, so nothing here edits, runs or pauses: each table is a\n * paged walk of one collection in document-id order, and a row opens a\n * read-only view of the trigger and the steps. The rules admit staff reads on\n * every site and organization subcollection.\n */\n\ntype AutomationRow = { $id: string } & Record<string, any>\n\nconst nameOf = (row: AutomationRow): string => String(row['name'] || row['displayName'] || row.$id)\n\nconst whenOf = (value: any): string =>\n typeof value?.toDate === 'function' ? value.toDate().toLocaleString() : '—'\n\n/** The trigger and the steps of one automation, read-only. */\nfunction StepsDialog(props: { row: AutomationRow | null; onClose: () => void }) {\n const { row, onClose } = props\n const steps: unknown[] = Array.isArray(row?.['steps']) ? row['steps'] : []\n return (\n <Dialog open={Boolean(row)} onClose={onClose} maxWidth=\"md\" fullWidth>\n <DialogTitle>{row ? nameOf(row) : ''}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Box>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Trigger'}\n </Typography>\n <Typography variant=\"body2\">\n {row?.['trigger']?.event ?? 'Manual — runs only when called'}\n </Typography>\n {row?.['trigger']?.filter ? (\n <Typography variant=\"caption\" sx={{ fontFamily: 'monospace' }}>\n {`when ${row['trigger'].filter}`}\n </Typography>\n ) : null}\n </Box>\n <Box>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Steps (${steps.length})`}\n </Typography>\n <Box\n component=\"pre\"\n sx={{\n m: 0,\n p: 1.5,\n borderRadius: 1,\n bgcolor: 'action.hover',\n fontSize: 12,\n overflow: 'auto',\n maxHeight: 420,\n }}\n >\n {JSON.stringify(steps, null, 2)}\n </Box>\n </Box>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\n\n/** One paged, read-only table of automations. */\nconst millisOf = (value: any): number | null =>\n typeof value?.toMillis === 'function' ? value.toMillis() : null\n\n/*\n * EVERY HEADER SORTS, OVER THE PAGE (AGL-3680). These read-only staff tables\n * walk each collection by document id. Their name falls back across fields\n * that differ by collection (`name`, `displayName`, the id), their status is\n * drawn from the trigger and the pause lists, and `updatedAt` is not stamped\n * by every writer (an imported automation carries none) — so no stored field\n * orders all three, and each header sorts the page on screen and says so.\n */\nconst AUTOMATION_PAGE_SORTS = {\n name: (row: AutomationRow) => nameOf(row),\n status: (row: AutomationRow) => String(row['trigger']?.event ?? 'manual'),\n updatedAt: (row: AutomationRow) => millisOf(row['updatedAt']),\n}\nconst AUTOMATION_PAGE_SORT_HEADERS = { name: 'Automation', status: 'Status', updatedAt: 'Updated' }\nconst NO_QUERY_SORTS = [] as const\n\nfunction AutomationTable(props: {\n title: string\n buildQuery: (firestore: Firestore, pageLimit: number) => Query | null\n deps: readonly unknown[]\n /** The site the table is read for, to mark what it has paused. */\n hostId?: string\n emptyLabel: string\n onOpen: (row: AutomationRow) => void\n}) {\n const { title, buildQuery, deps, hostId, emptyLabel, onOpen } = props\n const firestore = useFirestore()\n const paged = usePagedCollection<AutomationRow>(\n (pageLimit) => buildQuery(firestore, pageLimit),\n [firestore, ...deps],\n { idField: '$id' },\n )\n const byId = useMemo(() => new Map(paged.rows.map((row) => [row.$id, row])), [paged.rows])\n const columnSort = useListColumnSort<AutomationRow>({\n sorts: NO_QUERY_SORTS,\n rows: paged.rows,\n pageSorts: AUTOMATION_PAGE_SORTS,\n headers: AUTOMATION_PAGE_SORT_HEADERS,\n })\n const columns: GridColDef[] = useMemo(\n () => [\n {\n field: 'name',\n headerName: 'Automation',\n flex: 1.4,\n minWidth: 200,\n valueGetter: (_value, row: AutomationRow) => nameOf(row),\n },\n {\n field: 'status',\n headerName: 'Status',\n flex: 1.2,\n minWidth: 200,\n valueGetter: (_value, row: AutomationRow) => String(row['trigger']?.event ?? 'manual'),\n renderCell: ({ row }: { row: AutomationRow }) => {\n const paused: string[] = Array.isArray(row['pausedHostIds']) ? row['pausedHostIds'] : []\n return (\n <Stack\n direction=\"row\"\n spacing={0.5}\n useFlexGap\n sx={{ alignItems: 'center', height: '100%', flexWrap: 'wrap' }}\n >\n <Chip size=\"small\" variant=\"outlined\" label={row['trigger']?.event ?? 'manual'} />\n {row['enabled'] === false ? (\n <Chip size=\"small\" color=\"warning\" label=\"off\" />\n ) : (\n <Chip size=\"small\" color=\"success\" label=\"on\" />\n )}\n {hostId && paused.includes(hostId) ? (\n <Chip size=\"small\" color=\"warning\" label=\"paused here\" />\n ) : !hostId && paused.length ? (\n <Chip size=\"small\" color=\"warning\" label={`paused on ${paused.length}`} />\n ) : null}\n {row['deletedAt'] ? <Chip size=\"small\" color=\"error\" label=\"deleted\" /> : null}\n </Stack>\n )\n },\n },\n {\n field: 'updatedAt',\n headerName: 'Updated',\n flex: 0.9,\n minWidth: 160,\n valueGetter: (_value, row: AutomationRow) => whenOf(row['updatedAt']),\n },\n listActionsColumn(\n (row: AutomationRow) => (\n <ListRowActions\n label={nameOf(row)}\n quick={{ icon: mdiEyeOutline.path, label: 'View steps', onClick: () => onOpen(row) }}\n items={[]}\n />\n ),\n { width: 110 },\n ),\n ],\n [hostId, onOpen],\n )\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{title}</Typography>\n <ListQueryNotices refused={[]} notices={columnSort.notices} />\n <ListTable\n rows={columnSort.rows}\n columns={columns}\n loading={paged.status === 'loading'}\n columnSort={columnSort}\n noRowsLabel={emptyLabel}\n onOpen={(id) => {\n const row = byId.get(String(id))\n if (row) onOpen(row)\n }}\n hideFooter\n rowHeight={TABLE_ROW_HEIGHT}\n />\n <ListPagination\n page={paged.page}\n pageSize={paged.pageSize}\n rowCount={paged.rows.length}\n hasMore={paged.hasMore}\n onPageChange={paged.setPage}\n onPageSizeChange={paged.setPageSize}\n />\n </Stack>\n )\n}\n\nconst inIdOrder = (source: Query, pageLimit: number) =>\n query(source, orderBy(documentId()), limit(pageLimit))\n\n/**\n * The staff site page's automations (`staffSite` zone): the site's own\n * automations, the organization automations that run on it, and its\n * workflows.\n */\nexport function StaffSiteAutomationsCard(props: { hostId: string; orgId?: string }) {\n const { hostId, orgId } = props\n const [open, setOpen] = useState<AutomationRow | null>(null)\n return (\n <>\n <CardDisplay\n header={'Automations'}\n help={pluginDocsHelp('staffConsole', {\n anchor: '#staff-automations',\n excerpt:\n \"The site's automations, the organization automations that run on it, and its workflows — read-only, each opening its trigger and steps.\",\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={3}>\n <AutomationTable\n title=\"Site automations\"\n buildQuery={(firestore, pageLimit) =>\n hostId ? inIdOrder(collection(firestore, 'hosts', hostId, 'actions'), pageLimit) : null\n }\n deps={[hostId]}\n hostId={hostId}\n emptyLabel=\"No automations on this site\"\n onOpen={setOpen}\n />\n <AutomationTable\n title=\"Organization automations that run here\"\n buildQuery={(firestore, pageLimit) =>\n orgId && hostId\n ? inIdOrder(\n query(\n collection(firestore, 'orgs', orgId, 'automations'),\n where('visibleTo', 'array-contains-any', ['org', `host:${hostId}`]),\n ),\n pageLimit,\n )\n : null\n }\n deps={[orgId, hostId]}\n hostId={hostId}\n emptyLabel=\"No organization automation runs on this site\"\n onOpen={setOpen}\n />\n <AutomationTable\n title=\"Workflows\"\n buildQuery={(firestore, pageLimit) =>\n hostId ? inIdOrder(collection(firestore, 'hosts', hostId, 'workflows'), pageLimit) : null\n }\n deps={[hostId]}\n emptyLabel=\"No workflows on this site\"\n onOpen={setOpen}\n />\n </Stack>\n </CardDisplay>\n <StepsDialog row={open} onClose={() => setOpen(null)} />\n </>\n )\n}\n\n/** The staff organization page's automations (`adminOrgDetail` zone). */\nexport function StaffOrgAutomationsCard(props: { orgId: string }) {\n const { orgId } = props\n const [open, setOpen] = useState<AutomationRow | null>(null)\n return (\n <>\n <CardDisplay\n header={'Organization automations'}\n help={pluginDocsHelp('staffConsole', {\n anchor: '#staff-org-automations',\n })}\n contentGutterX\n contentGutterY\n >\n <AutomationTable\n title=\"Automations\"\n buildQuery={(firestore, pageLimit) =>\n orgId ? inIdOrder(collection(firestore, 'orgs', orgId, 'automations'), pageLimit) : null\n }\n deps={[orgId]}\n emptyLabel=\"This organization has no automations\"\n onOpen={setOpen}\n />\n </CardDisplay>\n <StepsDialog row={open} onClose={() => setOpen(null)} />\n </>\n )\n}\n"],"names":["pluginDocsHelp","mdiEyeOutline","CardDisplay","ListPagination","ListQueryNotices","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useListColumnSort","useFirestore","usePagedCollection","Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","collection","documentId","limit","orderBy","query","where","useMemo","useState","nameOf","row","String","$id","whenOf","value","toDate","toLocaleString","StepsDialog","props","onClose","steps","Array","isArray","open","Boolean","maxWidth","fullWidth","spacing","variant","color","event","filter","sx","fontFamily","length","component","m","p","borderRadius","bgcolor","fontSize","overflow","maxHeight","JSON","stringify","onClick","millisOf","toMillis","AUTOMATION_PAGE_SORTS","name","status","updatedAt","AUTOMATION_PAGE_SORT_HEADERS","NO_QUERY_SORTS","AutomationTable","title","buildQuery","deps","hostId","emptyLabel","onOpen","firestore","paged","pageLimit","idField","byId","Map","rows","map","columnSort","sorts","pageSorts","headers","columns","field","headerName","flex","minWidth","valueGetter","_value","renderCell","paused","direction","useFlexGap","alignItems","height","flexWrap","size","label","includes","quick","icon","path","items","width","refused","notices","loading","noRowsLabel","id","get","hideFooter","rowHeight","page","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","inIdOrder","source","StaffSiteAutomationsCard","orgId","setOpen","header","help","anchor","excerpt","contentGutterX","contentGutterY","StaffOrgAutomationsCard"],"mappings":"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"}