@modern-admin/web 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/config/runtime-config.d.ts +84 -0
- package/dist/config/runtime-config.js +19 -0
- package/dist/config/vite.d.ts +84 -0
- package/dist/config/vite.js +166 -0
- package/dist/lib/index.js +0 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/mount.d.ts.map +1 -1
- package/dist/standalone/assets/{action-component-host-CXIbN-8V.js → action-component-host-CuzX-99S.js} +2 -2
- package/dist/standalone/assets/action-component-host-CuzX-99S.js.br +0 -0
- package/dist/standalone/assets/action-component-host-CuzX-99S.js.gz +0 -0
- package/dist/standalone/assets/{action-component-host-CXIbN-8V.js.map → action-component-host-CuzX-99S.js.map} +1 -1
- package/dist/standalone/assets/{action-page-OsWfKQ6C.js → action-page-BUTCUZ7e.js} +2 -2
- package/dist/standalone/assets/action-page-BUTCUZ7e.js.br +0 -0
- package/dist/standalone/assets/action-page-BUTCUZ7e.js.gz +0 -0
- package/dist/standalone/assets/{action-page-OsWfKQ6C.js.map → action-page-BUTCUZ7e.js.map} +1 -1
- package/dist/standalone/assets/{ai-assistant-widget-Bt4Z7Fmr.js → ai-assistant-widget-B3Xy7bCi.js} +2 -2
- package/dist/standalone/assets/ai-assistant-widget-B3Xy7bCi.js.br +0 -0
- package/dist/standalone/assets/ai-assistant-widget-B3Xy7bCi.js.gz +0 -0
- package/dist/standalone/assets/{ai-assistant-widget-Bt4Z7Fmr.js.map → ai-assistant-widget-B3Xy7bCi.js.map} +1 -1
- package/dist/standalone/assets/{audit-log-page-2wuGiWg-.js → audit-log-page-GHzf7mLS.js} +2 -2
- package/dist/standalone/assets/audit-log-page-GHzf7mLS.js.br +0 -0
- package/dist/standalone/assets/audit-log-page-GHzf7mLS.js.gz +0 -0
- package/dist/standalone/assets/{audit-log-page-2wuGiWg-.js.map → audit-log-page-GHzf7mLS.js.map} +1 -1
- package/dist/standalone/assets/{calendar-BZVUWakC.js → calendar-DxUwrP2r.js} +2 -2
- package/dist/standalone/assets/calendar-DxUwrP2r.js.br +0 -0
- package/dist/standalone/assets/calendar-DxUwrP2r.js.gz +0 -0
- package/dist/standalone/assets/{calendar-BZVUWakC.js.map → calendar-DxUwrP2r.js.map} +1 -1
- package/dist/standalone/assets/{chart-widget-BtJb6YXV.js → chart-widget-DnK78Joj.js} +2 -2
- package/dist/standalone/assets/chart-widget-DnK78Joj.js.br +0 -0
- package/dist/standalone/assets/chart-widget-DnK78Joj.js.gz +0 -0
- package/dist/standalone/assets/{chart-widget-BtJb6YXV.js.map → chart-widget-DnK78Joj.js.map} +1 -1
- package/dist/standalone/assets/{command-BNF_evMy.js → command-Df4Effbo.js} +2 -2
- package/dist/standalone/assets/command-Df4Effbo.js.br +0 -0
- package/dist/standalone/assets/command-Df4Effbo.js.gz +0 -0
- package/dist/standalone/assets/{command-BNF_evMy.js.map → command-Df4Effbo.js.map} +1 -1
- package/dist/standalone/assets/{date-picker-LfbUehd1.js → date-picker-BM6tYOT1.js} +2 -2
- package/dist/standalone/assets/date-picker-BM6tYOT1.js.br +0 -0
- package/dist/standalone/assets/date-picker-BM6tYOT1.js.gz +0 -0
- package/dist/standalone/assets/{date-picker-LfbUehd1.js.map → date-picker-BM6tYOT1.js.map} +1 -1
- package/dist/standalone/assets/{edit-page-DCRB9Y_q.js → edit-page-C_QYbzrq.js} +2 -2
- package/dist/standalone/assets/edit-page-C_QYbzrq.js.br +0 -0
- package/dist/standalone/assets/edit-page-C_QYbzrq.js.gz +0 -0
- package/dist/standalone/assets/{edit-page-DCRB9Y_q.js.map → edit-page-C_QYbzrq.js.map} +1 -1
- package/dist/standalone/assets/{empty-DAEAjFd3.js → empty-BQFNUnfh.js} +2 -2
- package/dist/standalone/assets/empty-BQFNUnfh.js.br +0 -0
- package/dist/standalone/assets/empty-BQFNUnfh.js.gz +0 -0
- package/dist/standalone/assets/{empty-DAEAjFd3.js.map → empty-BQFNUnfh.js.map} +1 -1
- package/dist/standalone/assets/{global-search-dialog-HMLztOho.js → global-search-dialog-DMgCfJpF.js} +2 -2
- package/dist/standalone/assets/global-search-dialog-DMgCfJpF.js.br +0 -0
- package/dist/standalone/assets/global-search-dialog-DMgCfJpF.js.gz +0 -0
- package/dist/standalone/assets/{global-search-dialog-HMLztOho.js.map → global-search-dialog-DMgCfJpF.js.map} +1 -1
- package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js → heavy-fields-Do2qd-Wi.js} +2 -2
- package/dist/standalone/assets/heavy-fields-Do2qd-Wi.js.br +0 -0
- package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js.gz → heavy-fields-Do2qd-Wi.js.gz} +0 -0
- package/dist/standalone/assets/{heavy-fields-ZuqfOFMv.js.map → heavy-fields-Do2qd-Wi.js.map} +1 -1
- package/dist/standalone/assets/{home-page-HmheewiP.js → home-page-eGVeZnKR.js} +3 -3
- package/dist/standalone/assets/home-page-eGVeZnKR.js.br +0 -0
- package/dist/standalone/assets/home-page-eGVeZnKR.js.gz +0 -0
- package/dist/standalone/assets/{home-page-HmheewiP.js.map → home-page-eGVeZnKR.js.map} +1 -1
- package/dist/standalone/assets/{icon-registry-BV44--RL.js → icon-registry-BroqBCns.js} +2 -2
- package/dist/standalone/assets/icon-registry-BroqBCns.js.br +0 -0
- package/dist/standalone/assets/{icon-registry-BV44--RL.js.gz → icon-registry-BroqBCns.js.gz} +0 -0
- package/dist/standalone/assets/{icon-registry-BV44--RL.js.map → icon-registry-BroqBCns.js.map} +1 -1
- package/dist/standalone/assets/index-C3MuojaM.js +15 -0
- package/dist/standalone/assets/index-C3MuojaM.js.br +0 -0
- package/dist/standalone/assets/index-C3MuojaM.js.gz +0 -0
- package/dist/standalone/assets/{index-t4woXGa-.js.map → index-C3MuojaM.js.map} +1 -1
- package/dist/standalone/assets/{json-editor-sGw4fDoj.js → json-editor-BQLKoybQ.js} +2 -2
- package/dist/standalone/assets/json-editor-BQLKoybQ.js.br +0 -0
- package/dist/standalone/assets/json-editor-BQLKoybQ.js.gz +0 -0
- package/dist/standalone/assets/{json-editor-sGw4fDoj.js.map → json-editor-BQLKoybQ.js.map} +1 -1
- package/dist/standalone/assets/{property-renderer-DyCwdVkh.js → property-renderer--LvNtRJE.js} +4 -4
- package/dist/standalone/assets/property-renderer--LvNtRJE.js.br +0 -0
- package/dist/standalone/assets/property-renderer--LvNtRJE.js.gz +0 -0
- package/dist/standalone/assets/{property-renderer-DyCwdVkh.js.map → property-renderer--LvNtRJE.js.map} +1 -1
- package/dist/standalone/assets/{reference-Dfd804y3.js → reference-UuQ6Jts1.js} +2 -2
- package/dist/standalone/assets/reference-UuQ6Jts1.js.br +0 -0
- package/dist/standalone/assets/reference-UuQ6Jts1.js.gz +0 -0
- package/dist/standalone/assets/{reference-Dfd804y3.js.map → reference-UuQ6Jts1.js.map} +1 -1
- package/dist/standalone/assets/{revisions-button-DXZAh2Lu.js → revisions-button-DHcQjj4r.js} +2 -2
- package/dist/standalone/assets/revisions-button-DHcQjj4r.js.br +0 -0
- package/dist/standalone/assets/revisions-button-DHcQjj4r.js.gz +0 -0
- package/dist/standalone/assets/{revisions-button-DXZAh2Lu.js.map → revisions-button-DHcQjj4r.js.map} +1 -1
- package/dist/standalone/assets/{select-BWf6walM.js → select-B30EsRTA.js} +2 -2
- package/dist/standalone/assets/select-B30EsRTA.js.br +0 -0
- package/dist/standalone/assets/select-B30EsRTA.js.gz +0 -0
- package/dist/standalone/assets/{select-BWf6walM.js.map → select-B30EsRTA.js.map} +1 -1
- package/dist/standalone/assets/{settings-page-Co7sWbbL.js → settings-page-3r5atVKG.js} +2 -2
- package/dist/standalone/assets/settings-page-3r5atVKG.js.br +0 -0
- package/dist/standalone/assets/settings-page-3r5atVKG.js.gz +0 -0
- package/dist/standalone/assets/{settings-page-Co7sWbbL.js.map → settings-page-3r5atVKG.js.map} +1 -1
- package/dist/standalone/assets/{show-page-D-_vldWV.js → show-page-CmRDBZRg.js} +2 -2
- package/dist/standalone/assets/show-page-CmRDBZRg.js.br +0 -0
- package/dist/standalone/assets/show-page-CmRDBZRg.js.gz +0 -0
- package/dist/standalone/assets/{show-page-D-_vldWV.js.map → show-page-CmRDBZRg.js.map} +1 -1
- package/dist/standalone/assets/{tabs-khcHka8W.js → tabs-BXeUKAN4.js} +2 -2
- package/dist/standalone/assets/tabs-BXeUKAN4.js.br +0 -0
- package/dist/standalone/assets/tabs-BXeUKAN4.js.gz +0 -0
- package/dist/standalone/assets/{tabs-khcHka8W.js.map → tabs-BXeUKAN4.js.map} +1 -1
- package/dist/standalone/assets/{value-format-CPnbnlE6.js → value-format-BikdS7In.js} +2 -2
- package/dist/standalone/assets/value-format-BikdS7In.js.br +0 -0
- package/dist/standalone/assets/value-format-BikdS7In.js.gz +0 -0
- package/dist/standalone/assets/{value-format-CPnbnlE6.js.map → value-format-BikdS7In.js.map} +1 -1
- package/dist/standalone/assets/{wizard-create-page-CFwMs8E3.js → wizard-create-page-ChrGqxlO.js} +2 -2
- package/dist/standalone/assets/wizard-create-page-ChrGqxlO.js.br +0 -0
- package/dist/standalone/assets/wizard-create-page-ChrGqxlO.js.gz +0 -0
- package/dist/standalone/assets/{wizard-create-page-CFwMs8E3.js.map → wizard-create-page-ChrGqxlO.js.map} +1 -1
- package/dist/standalone/assets/{zod-BbfEBA4A.js → zod-CF8JsamI.js} +2 -2
- package/dist/standalone/assets/zod-CF8JsamI.js.br +0 -0
- package/dist/standalone/assets/zod-CF8JsamI.js.gz +0 -0
- package/dist/standalone/assets/{zod-BbfEBA4A.js.map → zod-CF8JsamI.js.map} +1 -1
- package/dist/standalone/index.html +4 -4
- package/dist/standalone/index.html.br +0 -0
- package/dist/standalone/index.html.gz +0 -0
- package/package.json +27 -5
- package/src/mount.tsx +8 -2
- package/src/standalone.tsx +1 -0
- package/src/vite.ts +219 -0
- package/vite.config.ts +13 -104
- package/dist/standalone/assets/action-component-host-CXIbN-8V.js.br +0 -0
- package/dist/standalone/assets/action-component-host-CXIbN-8V.js.gz +0 -0
- package/dist/standalone/assets/action-page-OsWfKQ6C.js.br +0 -0
- package/dist/standalone/assets/action-page-OsWfKQ6C.js.gz +0 -0
- package/dist/standalone/assets/ai-assistant-widget-Bt4Z7Fmr.js.br +0 -0
- package/dist/standalone/assets/ai-assistant-widget-Bt4Z7Fmr.js.gz +0 -0
- package/dist/standalone/assets/audit-log-page-2wuGiWg-.js.br +0 -0
- package/dist/standalone/assets/audit-log-page-2wuGiWg-.js.gz +0 -0
- package/dist/standalone/assets/calendar-BZVUWakC.js.br +0 -0
- package/dist/standalone/assets/calendar-BZVUWakC.js.gz +0 -0
- package/dist/standalone/assets/chart-widget-BtJb6YXV.js.br +0 -0
- package/dist/standalone/assets/chart-widget-BtJb6YXV.js.gz +0 -0
- package/dist/standalone/assets/command-BNF_evMy.js.br +0 -0
- package/dist/standalone/assets/command-BNF_evMy.js.gz +0 -0
- package/dist/standalone/assets/date-picker-LfbUehd1.js.br +0 -0
- package/dist/standalone/assets/date-picker-LfbUehd1.js.gz +0 -0
- package/dist/standalone/assets/edit-page-DCRB9Y_q.js.br +0 -0
- package/dist/standalone/assets/edit-page-DCRB9Y_q.js.gz +0 -0
- package/dist/standalone/assets/empty-DAEAjFd3.js.br +0 -0
- package/dist/standalone/assets/empty-DAEAjFd3.js.gz +0 -0
- package/dist/standalone/assets/global-search-dialog-HMLztOho.js.br +0 -0
- package/dist/standalone/assets/global-search-dialog-HMLztOho.js.gz +0 -0
- package/dist/standalone/assets/heavy-fields-ZuqfOFMv.js.br +0 -0
- package/dist/standalone/assets/home-page-HmheewiP.js.br +0 -0
- package/dist/standalone/assets/home-page-HmheewiP.js.gz +0 -0
- package/dist/standalone/assets/icon-registry-BV44--RL.js.br +0 -0
- package/dist/standalone/assets/index-t4woXGa-.js +0 -15
- package/dist/standalone/assets/index-t4woXGa-.js.br +0 -0
- package/dist/standalone/assets/index-t4woXGa-.js.gz +0 -0
- package/dist/standalone/assets/json-editor-sGw4fDoj.js.br +0 -0
- package/dist/standalone/assets/json-editor-sGw4fDoj.js.gz +0 -0
- package/dist/standalone/assets/property-renderer-DyCwdVkh.js.br +0 -0
- package/dist/standalone/assets/property-renderer-DyCwdVkh.js.gz +0 -0
- package/dist/standalone/assets/reference-Dfd804y3.js.br +0 -0
- package/dist/standalone/assets/reference-Dfd804y3.js.gz +0 -0
- package/dist/standalone/assets/revisions-button-DXZAh2Lu.js.br +0 -0
- package/dist/standalone/assets/revisions-button-DXZAh2Lu.js.gz +0 -0
- package/dist/standalone/assets/select-BWf6walM.js.br +0 -0
- package/dist/standalone/assets/select-BWf6walM.js.gz +0 -0
- package/dist/standalone/assets/settings-page-Co7sWbbL.js.br +0 -0
- package/dist/standalone/assets/settings-page-Co7sWbbL.js.gz +0 -0
- package/dist/standalone/assets/show-page-D-_vldWV.js.br +0 -0
- package/dist/standalone/assets/show-page-D-_vldWV.js.gz +0 -0
- package/dist/standalone/assets/tabs-khcHka8W.js.br +0 -0
- package/dist/standalone/assets/tabs-khcHka8W.js.gz +0 -0
- package/dist/standalone/assets/value-format-CPnbnlE6.js.br +0 -0
- package/dist/standalone/assets/value-format-CPnbnlE6.js.gz +0 -0
- package/dist/standalone/assets/wizard-create-page-CFwMs8E3.js.br +0 -0
- package/dist/standalone/assets/wizard-create-page-CFwMs8E3.js.gz +0 -0
- package/dist/standalone/assets/zod-BbfEBA4A.js.br +0 -0
- package/dist/standalone/assets/zod-BbfEBA4A.js.gz +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n}from"./with-selector-NDLhu4dT.js";import{t as r}from"./arrow-left-CI9hUPwJ.js";import{s as i}from"./dist-Dpg8xy_r.js";import{K as a,U as o,et as s,i as c}from"./router-wFvtgo77.js";import{a as l,c as u,l as d,t as f}from"./action-component-host-
|
|
2
|
-
//# sourceMappingURL=action-page-
|
|
1
|
+
import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n}from"./with-selector-NDLhu4dT.js";import{t as r}from"./arrow-left-CI9hUPwJ.js";import{s as i}from"./dist-Dpg8xy_r.js";import{K as a,U as o,et as s,i as c}from"./router-wFvtgo77.js";import{a as l,c as u,l as d,t as f}from"./action-component-host-CuzX-99S.js";import{a as p,i as m,n as h,t as g}from"./card-4cS4uWCP.js";var _=e(t(),1),v=n();function y({resourceId:e,actionName:t,recordId:n,recordIds:y}){let b=a(e),x=o(e,n),{t:S}=s(),C=c(),w=b?.actions?.find(e=>e.name===t),T=w?l(w):t,E=_.useCallback(()=>{C(n?{name:`show`,resourceId:e,recordId:n}:{name:`list`,resourceId:e})},[C,e,n]),D=x.data?.record?.title||n;return(0,v.jsxs)(`div`,{className:`space-y-2 sm:space-y-4`,children:[(0,v.jsx)(u,{items:[d(S(`common:home`)),{label:b?.name??e,to:{name:`list`,resourceId:e}},...n?[{label:D??n,to:{name:`show`,resourceId:e,recordId:n}}]:[],{label:T}]}),(0,v.jsxs)(g,{children:[(0,v.jsxs)(m,{className:`flex flex-row items-center justify-between gap-3`,children:[(0,v.jsx)(p,{className:`truncate`,children:T}),(0,v.jsxs)(i,{variant:`outline`,size:`sm`,onClick:E,className:`shrink-0`,children:[(0,v.jsx)(r,{className:`size-4`}),(0,v.jsx)(`span`,{className:`hidden sm:inline`,children:S(`common:back`)})]})]}),(0,v.jsx)(h,{children:(0,v.jsx)(f,{resourceId:e,actionName:t,...n===void 0?{}:{recordId:n},...y===void 0?{}:{recordIds:y},onClose:E})})]})]})}export{y as ResourceActionPage};
|
|
2
|
+
//# sourceMappingURL=action-page-BUTCUZ7e.js.map
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-page-
|
|
1
|
+
{"version":3,"file":"action-page-BUTCUZ7e.js","names":[],"sources":["../../../../react/src/pages/action-page.tsx"],"sourcesContent":["// Full-page surface for a custom action that declares a `component`.\n//\n// Routed at `/resources/:resourceId/actions/:actionName` (resource-scoped)\n// and `/resources/:resourceId/:recordId/actions/:actionName` (record-scoped).\n// The page owns the chrome — breadcrumbs, title, back button — and delegates\n// the action itself to `<ActionComponentHost/>`, which is also what the\n// dialog presentation (`custom.showAs: 'dialog'`) renders.\n\nimport * as React from 'react'\nimport { Button, Card, CardContent, CardHeader, CardTitle } from '@modern-admin/ui'\nimport { ArrowLeft } from 'lucide-react'\nimport { useRecord, useResource } from '../hooks.js'\nimport { useI18n } from '../i18n.js'\nimport { useNavigate } from '../router.js'\nimport { PageBreadcrumbs, homeCrumb } from '../breadcrumbs.js'\nimport { getActionLabel } from '../action-menu.js'\nimport { ActionComponentHost } from '../components/action-component-host.js'\n\nexport interface ResourceActionPageProps {\n resourceId: string\n actionName: string\n /** Present for record-scoped actions. */\n recordId?: string\n /** Present for bulk-scoped actions — read from `?recordIds=a,b,c`. */\n recordIds?: string[]\n}\n\nexport function ResourceActionPage({\n resourceId,\n actionName,\n recordId,\n recordIds,\n}: ResourceActionPageProps): React.ReactElement {\n const resource = useResource(resourceId)\n const record = useRecord(resourceId, recordId)\n const { t } = useI18n()\n const navigate = useNavigate()\n\n const action = resource?.actions?.find((a) => a.name === actionName)\n const title = action ? getActionLabel(action) : actionName\n\n // Dismissing returns to wherever the action was launched from: the record\n // for record-scoped actions, the list for resource-scoped ones.\n const close = React.useCallback(() => {\n if (recordId) navigate({ name: 'show', resourceId, recordId })\n else navigate({ name: 'list', resourceId })\n }, [navigate, resourceId, recordId])\n\n const recordLabel = record.data?.record?.title || recordId\n\n return (\n <div className=\"space-y-2 sm:space-y-4\">\n <PageBreadcrumbs\n items={[\n homeCrumb(t('common:home')),\n { label: resource?.name ?? resourceId, to: { name: 'list', resourceId } },\n ...(recordId\n ? [{ label: recordLabel ?? recordId, to: { name: 'show' as const, resourceId, recordId } }]\n : []),\n { label: title },\n ]}\n />\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3\">\n <CardTitle className=\"truncate\">{title}</CardTitle>\n <Button variant=\"outline\" size=\"sm\" onClick={close} className=\"shrink-0\">\n <ArrowLeft className=\"size-4\" />\n <span className=\"hidden sm:inline\">{t('common:back')}</span>\n </Button>\n </CardHeader>\n <CardContent>\n <ActionComponentHost\n resourceId={resourceId}\n actionName={actionName}\n {...(recordId !== undefined ? { recordId } : {})}\n {...(recordIds !== undefined ? { recordIds } : {})}\n onClose={close}\n />\n </CardContent>\n </Card>\n </div>\n )\n}\n"],"mappings":"2ZA2BA,SAAgB,EAAmB,CACjC,aACA,aACA,WACA,aAC8C,CAC9C,IAAM,EAAW,EAAY,CAAU,EACjC,EAAS,EAAU,EAAY,CAAQ,EACvC,CAAE,KAAM,EAAQ,EAChB,EAAW,EAAY,EAEvB,EAAS,GAAU,SAAS,KAAM,GAAM,EAAE,OAAS,CAAU,EAC7D,EAAQ,EAAS,EAAe,CAAM,EAAI,EAI1C,EAAA,EAAc,gBAAkB,CACtB,EAAV,EAAmB,CAAE,KAAM,OAAQ,aAAY,UAAS,EAC9C,CAAE,KAAM,OAAQ,YAAW,CAAC,CAC5C,EAAG,CAAC,EAAU,EAAY,CAAQ,CAAC,EAE7B,EAAc,EAAO,MAAM,QAAQ,OAAS,EAElD,OACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,kCAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MAAO,CACL,EAAU,EAAE,aAAa,CAAC,EAC1B,CAAE,MAAO,GAAU,MAAQ,EAAY,GAAI,CAAE,KAAM,OAAQ,YAAW,CAAE,EACxE,GAAI,EACA,CAAC,CAAE,MAAO,GAAe,EAAU,GAAI,CAAE,KAAM,OAAiB,aAAY,UAAS,CAAE,CAAC,EACxF,CAAC,EACL,CAAE,MAAO,CAAM,CACjB,CACD,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,EAAD,CAAA,SAAA,EACE,EAAA,EAAA,KAAA,CAAC,EAAD,CAAY,UAAU,4DAAtB,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAW,UAAU,oBAAY,CAAiB,CAAA,GAClD,EAAA,EAAA,KAAA,CAAC,EAAD,CAAQ,QAAQ,UAAU,KAAK,KAAK,QAAS,EAAO,UAAU,oBAA9D,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAW,UAAU,QAAU,CAAA,GAC/B,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,4BAAoB,EAAE,aAAa,CAAQ,CAAA,CACrD,GACE,KACZ,EAAA,EAAA,IAAA,CAAC,EAAD,CAAA,UACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACc,aACA,aACZ,GAAK,IAAa,IAAA,GAA2B,CAAC,EAAhB,CAAE,UAAS,EACzC,GAAK,IAAc,IAAA,GAA4B,CAAC,EAAjB,CAAE,WAAU,EAC3C,QAAS,CACV,CAAA,CACU,CAAA,CACT,CAAA,CAAA,CACH,GAET"}
|
package/dist/standalone/assets/{ai-assistant-widget-Bt4Z7Fmr.js → ai-assistant-widget-B3Xy7bCi.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n}from"./with-selector-NDLhu4dT.js";import{t as r}from"./bot-DFDgGfNj.js";import{n as ee,t as te}from"./send-D4KHQGYj.js";import{t as i}from"./plus-Dk-FzitW.js";import{t as a}from"./x-C3kLunD6.js";import{s as o}from"./dist-Dpg8xy_r.js";import{t as s}from"./textarea-CX_0QVSz.js";import{F as c,at as l,et as u,ht as d,i as f,ot as p,r as m,rt as h}from"./router-wFvtgo77.js";import{t as g}from"./badge-6nyM5Jft.js";import{n as ne,t as re}from"./card-4cS4uWCP.js";import{t as ie}from"./richtext-render-DnVcQ34I.js";import{t as _}from"./uuid-DLSnbsz2.js";import{n as ae}from"./use-dashboard-charts-Cr3OUSL5.js";import{E as oe,M as se,O as ce,Q as le,S as ue,T as v,U as y,c as de,d as b,f as x,i as S,l as fe,ot as pe,s as me,u as he,ut as ge,w as _e}from"./index-t4woXGa-.js";var C=e(t(),1),w=n(),T=()=>_(),E=5,D=(e,t)=>{let n=(Array.isArray(e.input?.messages)?e.input.messages:[]).map((t,n)=>({id:`${e.id}-input-${n}`,role:t.role,content:t.content}));if(e.output){let r=String(e.output.text??``).trim();n.push({id:`task-${e.id}`,role:`assistant`,content:r||t,citations:Array.isArray(e.output.citations)?e.output.citations:void 0})}return n};function O(){let e=h(),t=d(),n=S(),_=f(),O=m(),{locale:k,t:A}=u(),[j,M]=C.useState(!1),[N,P]=C.useState(``),[F,I]=C.useState([]),[L,R]=C.useState(()=>T()),[z,B]=C.useState(null),[V,H]=C.useState([]),U=c().aiAssistant,W=p({queryKey:[`modern-admin`,`ai-assistant`,`settings`],queryFn:()=>e.getAiAssistantSettings(),enabled:U}),G=l({mutationFn:async t=>{let n=typeof window<`u`?window.location.pathname:void 0;return n&&O&&(n===O?n=`/`:n.startsWith(`${O}/`)&&(n=n.slice(O.length))),e.sendAiAssistantChat(t.messages,t.requestId,k,t.conversationId,n?{pathname:n}:void 0)},onSuccess:e=>{B(e.taskId),t.invalidateQueries({queryKey:[`modern-admin`,`ai-assistant`,`chats`]})},onError:e=>{n.error({message:e instanceof Error?e.message:String(e)})}}),K=p({queryKey:[`modern-admin`,`ai-assistant`,`chats`],queryFn:()=>e.listAiAssistantChats(),enabled:U&&j&&W.data?.configured===!0}),q=p({queryKey:[`modern-admin`,`ai-assistant`,`task`,z],queryFn:()=>e.getAiAssistantTask(z),enabled:!!z,refetchInterval:e=>{let t=e.state.data?.status;return t===`pending`||t===`running`?1200:!1}});C.useEffect(()=>{let e=q.data;if(!(!e||!z)){if(e.status===`succeeded`&&e.output!=null){I(t=>{if(t.some(t=>t.id===`task-${e.id}`))return t;let n=String(e.output?.text??``).trim();return[...t,{id:`task-${e.id}`,role:`assistant`,content:n||A(`aiAssistant:noText`),citations:Array.isArray(e.output?.citations)?e.output.citations:void 0}]}),B(null),t.invalidateQueries({queryKey:[`modern-admin`,`ai-assistant`,`chats`]});let n=Array.isArray(e.output?.uiActions)?e.output.uiActions:[];for(let e of n){if(e.kind===`refresh`&&e.target===`dashboard`){ae();continue}e.kind===`navigate`&&(M(!1),_(e.route))}return}(e.status===`failed`||e.status===`cancelled`)&&(n.error({message:e.error??A(`aiAssistant:taskFailed`)}),B(null))}},[z,_,n,t,q.data,A]);let J=C.useCallback(e=>{let t={id:T(),role:`user`,content:e},n=[...F,t];I(n),G.mutate({requestId:t.id,conversationId:L,messages:n.map(e=>({role:e.role,content:e.content}))})},[G,L,F]),ve=C.useCallback(()=>{R(T()),I([]),P(``),H([]),B(null)},[]),ye=C.useCallback(async t=>{try{let n=await e.getAiAssistantTask(t.taskId);R(t.conversationId),I(D(n,A(`aiAssistant:noText`))),H([]),P(``),B(n.status===`pending`||n.status===`running`?n.id:null)}catch(e){n.error({message:e instanceof Error?e.message:String(e)})}},[e,n,A]);if(C.useEffect(()=>{if(z!==null||G.isPending)return;let e=V[0];e&&(H(e=>e.slice(1)),J(e.content))},[z,G.isPending,V,J]),!U||W.isLoading||W.error||!W.data?.enabled||!W.data.canChat)return null;let Y=G.isPending||z!=null,X=V.length>=E,Z=()=>{let e=N.trim();if(e){if(Y){if(X)return;H(t=>[...t,{id:T(),content:e}]),P(``);return}P(``),J(e)}},Q=W.data.configured,be=Y||q.data?.status===`running`||q.data?.status===`pending`,$=typeof q.data?.progress==`number`?q.data.progress:null,xe=N.trim().length===0||Y&&X;return(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(`div`,{className:`fixed bottom-4 right-4 z-40`,children:(0,w.jsxs)(o,{onClick:()=>M(!0),"aria-label":A(`aiAssistant:fab.label`),className:`group h-10 min-w-10 max-w-10 rounded-full pl-3 pr-3 shadow-lg justify-end gap-0 overflow-hidden opacity-70 hover:opacity-100 transition-[max-width,padding-left,opacity] duration-200 ease-in-out hover:max-w-36 hover:pl-4`,children:[(0,w.jsx)(`span`,{className:`max-w-0 overflow-hidden whitespace-nowrap text-sm font-medium transition-[max-width,padding-right] duration-200 ease-in-out group-hover:max-w-[5rem] group-hover:pr-2`,children:A(`aiAssistant:fab.short`)}),(0,w.jsx)(ee,{className:`size-4 shrink-0`})]})}),(0,w.jsx)(me,{open:j,onOpenChange:M,children:(0,w.jsxs)(fe,{hideCloseButton:!0,className:`flex w-full flex-col gap-0 sm:max-w-xl`,children:[(0,w.jsx)(b,{className:`border-b border-border pb-4`,children:(0,w.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,w.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,w.jsxs)(x,{className:`flex items-center gap-2`,children:[(0,w.jsx)(r,{className:`size-5`}),A(`aiAssistant:title`)]}),(0,w.jsx)(he,{children:A(`aiAssistant:description`)})]}),(0,w.jsxs)(`div`,{className:`flex shrink-0 items-center gap-0.5`,children:[Q&&(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(ue,{children:[(0,w.jsx)(se,{asChild:!0,children:(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":A(`aiAssistant:history`),children:(0,w.jsx)(ge,{className:`size-4`})})}),(0,w.jsxs)(_e,{align:`end`,className:`w-72`,children:[(0,w.jsx)(oe,{children:A(`aiAssistant:history`)}),(0,w.jsx)(ce,{}),K.isLoading&&(0,w.jsx)(v,{disabled:!0,children:A(`common:loading`)}),!K.isLoading&&(K.data?.length??0)===0&&(0,w.jsx)(v,{disabled:!0,children:A(`aiAssistant:history.empty`)}),K.data?.map(e=>(0,w.jsxs)(v,{className:`flex-col items-start gap-0 py-2`,onSelect:()=>{ye(e)},children:[(0,w.jsx)(`span`,{className:`line-clamp-1 w-full text-sm font-medium`,children:e.title}),(0,w.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:new Intl.DateTimeFormat(k,{dateStyle:`short`,timeStyle:`short`}).format(new Date(e.updatedAt))})]},e.conversationId))]})]}),(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,onClick:ve,"aria-label":A(`aiAssistant:newChat`),children:(0,w.jsx)(i,{className:`size-4`})})]}),(0,w.jsx)(de,{asChild:!0,children:(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":A(`common:close`),children:(0,w.jsx)(a,{className:`size-4`})})})]})]})}),Q?(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(y,{className:`flex-1 px-4 py-4`,children:(0,w.jsxs)(`div`,{className:`space-y-3`,children:[F.length===0&&(0,w.jsx)(re,{children:(0,w.jsxs)(ne,{className:`space-y-2 p-4 text-sm text-muted-foreground`,children:[(0,w.jsx)(`div`,{className:`font-medium text-foreground`,children:A(`aiAssistant:tryAsking`)}),(0,w.jsxs)(`div`,{children:[`- `,A(`aiAssistant:tryAsking.example1`)]}),(0,w.jsxs)(`div`,{children:[`- `,A(`aiAssistant:tryAsking.example2`)]}),(0,w.jsxs)(`div`,{children:[`- `,A(`aiAssistant:tryAsking.example3`)]})]})}),F.map(e=>(0,w.jsx)(`div`,{className:e.role===`user`?`flex justify-end`:`flex justify-start`,children:(0,w.jsxs)(`div`,{className:e.role===`user`?`max-w-[85%] rounded-2xl bg-primary px-4 py-3 text-sm text-primary-foreground`:`max-w-[85%] rounded-2xl border border-border bg-card px-4 py-3 text-sm`,children:[e.role===`assistant`?(0,w.jsx)(ie,{value:e.content,format:`markdown`,className:`text-sm [&_pre]:overflow-x-auto [&_pre]:text-xs`}):(0,w.jsx)(`div`,{className:`whitespace-pre-wrap`,children:e.content}),e.citations&&e.citations.length>0&&(0,w.jsx)(`div`,{className:`mt-3 flex flex-wrap gap-2`,children:e.citations.slice(0,6).map((e,t)=>{let n=`${e.resourceId}${e.recordId?`#${e.recordId}`:``}`,r=`${e.resourceId}-${e.recordId??`resource`}-${t}`;return e.recordId?(0,w.jsx)(`button`,{type:`button`,className:`cursor-pointer`,onClick:()=>{M(!1),_({name:`show`,resourceId:e.resourceId,recordId:e.recordId})},children:(0,w.jsx)(g,{variant:`outline`,children:n})},r):(0,w.jsx)(`button`,{type:`button`,className:`cursor-pointer`,onClick:()=>{M(!1),_({name:`list`,resourceId:e.resourceId})},children:(0,w.jsx)(g,{variant:`outline`,children:n})},r)})})]})},e.id)),be&&(0,w.jsx)(`div`,{className:`flex justify-start`,children:(0,w.jsxs)(`div`,{className:`inline-flex items-center gap-2 rounded-2xl border border-border bg-card px-4 py-3 text-sm text-muted-foreground`,children:[(0,w.jsx)(pe,{className:`size-4 animate-spin`}),$==null?A(`aiAssistant:thinking`):A(`aiAssistant:thinkingProgress`,{progress:$})]})})]})}),(0,w.jsxs)(`div`,{className:`border-t border-border bg-background/95 px-3 py-2`,children:[V.length>0&&(0,w.jsx)(`div`,{className:`mb-2 space-y-1`,children:V.map((e,t)=>(0,w.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md bg-muted/50 px-2 py-1 text-sm`,children:[(0,w.jsxs)(`span`,{className:`shrink-0 tabular-nums text-xs text-muted-foreground`,children:[`(`,t+1,`)`]}),(0,w.jsx)(`span`,{className:`flex-1 truncate`,title:e.content,children:e.content}),(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,className:`size-6 shrink-0`,onClick:()=>H(t=>t.filter(t=>t.id!==e.id)),"aria-label":A(`aiAssistant:queue.cancel`),children:(0,w.jsx)(a,{className:`size-3.5`})})]},e.id))}),(0,w.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),Z()},children:[(0,w.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,w.jsx)(s,{value:N,onChange:e=>P(e.target.value),onKeyDown:e=>{e.key===`Enter`&&!e.shiftKey&&!e.nativeEvent.isComposing&&(e.preventDefault(),Z())},rows:1,placeholder:A(`aiAssistant:input.placeholder`),"aria-label":A(`aiAssistant:input.placeholder`),className:`flex-1 max-h-[10rem] min-h-0 resize-none border-0 bg-transparent px-1 py-2 text-sm shadow-none outline-none [field-sizing:content] focus-visible:ring-0 focus-visible:ring-offset-0`}),(0,w.jsx)(o,{type:`submit`,disabled:xe,"aria-label":A(`aiAssistant:send`),className:`size-8 shrink-0 rounded-full p-0`,children:(0,w.jsx)(te,{className:`size-4`})})]}),(0,w.jsx)(`p`,{className:`px-1 pb-1 text-xs text-muted-foreground`,children:A(`aiAssistant:input.hint`)})]})]})]}):(0,w.jsxs)(`div`,{className:`flex flex-1 flex-col items-center justify-center gap-4 px-4 text-center`,children:[(0,w.jsxs)(`div`,{className:`space-y-2`,children:[(0,w.jsx)(`div`,{className:`text-lg font-semibold`,children:A(`aiAssistant:notConfigured.title`)}),(0,w.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:A(`aiAssistant:notConfigured.description`)})]}),W.data.canManage&&(0,w.jsxs)(o,{variant:`outline`,onClick:()=>{M(!1),_({name:`settings`,section:`ai-assistant`})},children:[(0,w.jsx)(le,{className:`size-4`}),A(`aiAssistant:notConfigured.openSettings`)]})]})]})})]})}export{O as AiAssistantWidget};
|
|
2
|
-
//# sourceMappingURL=ai-assistant-widget-
|
|
1
|
+
import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n}from"./with-selector-NDLhu4dT.js";import{t as r}from"./bot-DFDgGfNj.js";import{n as ee,t as te}from"./send-D4KHQGYj.js";import{t as i}from"./plus-Dk-FzitW.js";import{t as a}from"./x-C3kLunD6.js";import{s as o}from"./dist-Dpg8xy_r.js";import{t as s}from"./textarea-CX_0QVSz.js";import{F as c,at as l,et as u,ht as d,i as f,ot as p,r as m,rt as h}from"./router-wFvtgo77.js";import{t as g}from"./badge-6nyM5Jft.js";import{n as ne,t as re}from"./card-4cS4uWCP.js";import{t as ie}from"./richtext-render-DnVcQ34I.js";import{t as _}from"./uuid-DLSnbsz2.js";import{n as ae}from"./use-dashboard-charts-Cr3OUSL5.js";import{E as oe,M as se,O as ce,Q as le,S as ue,T as v,U as y,c as de,d as b,f as x,i as S,l as fe,ot as pe,s as me,u as he,ut as ge,w as _e}from"./index-C3MuojaM.js";var C=e(t(),1),w=n(),T=()=>_(),E=5,D=(e,t)=>{let n=(Array.isArray(e.input?.messages)?e.input.messages:[]).map((t,n)=>({id:`${e.id}-input-${n}`,role:t.role,content:t.content}));if(e.output){let r=String(e.output.text??``).trim();n.push({id:`task-${e.id}`,role:`assistant`,content:r||t,citations:Array.isArray(e.output.citations)?e.output.citations:void 0})}return n};function O(){let e=h(),t=d(),n=S(),_=f(),O=m(),{locale:k,t:A}=u(),[j,M]=C.useState(!1),[N,P]=C.useState(``),[F,I]=C.useState([]),[L,R]=C.useState(()=>T()),[z,B]=C.useState(null),[V,H]=C.useState([]),U=c().aiAssistant,W=p({queryKey:[`modern-admin`,`ai-assistant`,`settings`],queryFn:()=>e.getAiAssistantSettings(),enabled:U}),G=l({mutationFn:async t=>{let n=typeof window<`u`?window.location.pathname:void 0;return n&&O&&(n===O?n=`/`:n.startsWith(`${O}/`)&&(n=n.slice(O.length))),e.sendAiAssistantChat(t.messages,t.requestId,k,t.conversationId,n?{pathname:n}:void 0)},onSuccess:e=>{B(e.taskId),t.invalidateQueries({queryKey:[`modern-admin`,`ai-assistant`,`chats`]})},onError:e=>{n.error({message:e instanceof Error?e.message:String(e)})}}),K=p({queryKey:[`modern-admin`,`ai-assistant`,`chats`],queryFn:()=>e.listAiAssistantChats(),enabled:U&&j&&W.data?.configured===!0}),q=p({queryKey:[`modern-admin`,`ai-assistant`,`task`,z],queryFn:()=>e.getAiAssistantTask(z),enabled:!!z,refetchInterval:e=>{let t=e.state.data?.status;return t===`pending`||t===`running`?1200:!1}});C.useEffect(()=>{let e=q.data;if(!(!e||!z)){if(e.status===`succeeded`&&e.output!=null){I(t=>{if(t.some(t=>t.id===`task-${e.id}`))return t;let n=String(e.output?.text??``).trim();return[...t,{id:`task-${e.id}`,role:`assistant`,content:n||A(`aiAssistant:noText`),citations:Array.isArray(e.output?.citations)?e.output.citations:void 0}]}),B(null),t.invalidateQueries({queryKey:[`modern-admin`,`ai-assistant`,`chats`]});let n=Array.isArray(e.output?.uiActions)?e.output.uiActions:[];for(let e of n){if(e.kind===`refresh`&&e.target===`dashboard`){ae();continue}e.kind===`navigate`&&(M(!1),_(e.route))}return}(e.status===`failed`||e.status===`cancelled`)&&(n.error({message:e.error??A(`aiAssistant:taskFailed`)}),B(null))}},[z,_,n,t,q.data,A]);let J=C.useCallback(e=>{let t={id:T(),role:`user`,content:e},n=[...F,t];I(n),G.mutate({requestId:t.id,conversationId:L,messages:n.map(e=>({role:e.role,content:e.content}))})},[G,L,F]),ve=C.useCallback(()=>{R(T()),I([]),P(``),H([]),B(null)},[]),ye=C.useCallback(async t=>{try{let n=await e.getAiAssistantTask(t.taskId);R(t.conversationId),I(D(n,A(`aiAssistant:noText`))),H([]),P(``),B(n.status===`pending`||n.status===`running`?n.id:null)}catch(e){n.error({message:e instanceof Error?e.message:String(e)})}},[e,n,A]);if(C.useEffect(()=>{if(z!==null||G.isPending)return;let e=V[0];e&&(H(e=>e.slice(1)),J(e.content))},[z,G.isPending,V,J]),!U||W.isLoading||W.error||!W.data?.enabled||!W.data.canChat)return null;let Y=G.isPending||z!=null,X=V.length>=E,Z=()=>{let e=N.trim();if(e){if(Y){if(X)return;H(t=>[...t,{id:T(),content:e}]),P(``);return}P(``),J(e)}},Q=W.data.configured,be=Y||q.data?.status===`running`||q.data?.status===`pending`,$=typeof q.data?.progress==`number`?q.data.progress:null,xe=N.trim().length===0||Y&&X;return(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(`div`,{className:`fixed bottom-4 right-4 z-40`,children:(0,w.jsxs)(o,{onClick:()=>M(!0),"aria-label":A(`aiAssistant:fab.label`),className:`group h-10 min-w-10 max-w-10 rounded-full pl-3 pr-3 shadow-lg justify-end gap-0 overflow-hidden opacity-70 hover:opacity-100 transition-[max-width,padding-left,opacity] duration-200 ease-in-out hover:max-w-36 hover:pl-4`,children:[(0,w.jsx)(`span`,{className:`max-w-0 overflow-hidden whitespace-nowrap text-sm font-medium transition-[max-width,padding-right] duration-200 ease-in-out group-hover:max-w-[5rem] group-hover:pr-2`,children:A(`aiAssistant:fab.short`)}),(0,w.jsx)(ee,{className:`size-4 shrink-0`})]})}),(0,w.jsx)(me,{open:j,onOpenChange:M,children:(0,w.jsxs)(fe,{hideCloseButton:!0,className:`flex w-full flex-col gap-0 sm:max-w-xl`,children:[(0,w.jsx)(b,{className:`border-b border-border pb-4`,children:(0,w.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,w.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,w.jsxs)(x,{className:`flex items-center gap-2`,children:[(0,w.jsx)(r,{className:`size-5`}),A(`aiAssistant:title`)]}),(0,w.jsx)(he,{children:A(`aiAssistant:description`)})]}),(0,w.jsxs)(`div`,{className:`flex shrink-0 items-center gap-0.5`,children:[Q&&(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(ue,{children:[(0,w.jsx)(se,{asChild:!0,children:(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":A(`aiAssistant:history`),children:(0,w.jsx)(ge,{className:`size-4`})})}),(0,w.jsxs)(_e,{align:`end`,className:`w-72`,children:[(0,w.jsx)(oe,{children:A(`aiAssistant:history`)}),(0,w.jsx)(ce,{}),K.isLoading&&(0,w.jsx)(v,{disabled:!0,children:A(`common:loading`)}),!K.isLoading&&(K.data?.length??0)===0&&(0,w.jsx)(v,{disabled:!0,children:A(`aiAssistant:history.empty`)}),K.data?.map(e=>(0,w.jsxs)(v,{className:`flex-col items-start gap-0 py-2`,onSelect:()=>{ye(e)},children:[(0,w.jsx)(`span`,{className:`line-clamp-1 w-full text-sm font-medium`,children:e.title}),(0,w.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:new Intl.DateTimeFormat(k,{dateStyle:`short`,timeStyle:`short`}).format(new Date(e.updatedAt))})]},e.conversationId))]})]}),(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,onClick:ve,"aria-label":A(`aiAssistant:newChat`),children:(0,w.jsx)(i,{className:`size-4`})})]}),(0,w.jsx)(de,{asChild:!0,children:(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,"aria-label":A(`common:close`),children:(0,w.jsx)(a,{className:`size-4`})})})]})]})}),Q?(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(y,{className:`flex-1 px-4 py-4`,children:(0,w.jsxs)(`div`,{className:`space-y-3`,children:[F.length===0&&(0,w.jsx)(re,{children:(0,w.jsxs)(ne,{className:`space-y-2 p-4 text-sm text-muted-foreground`,children:[(0,w.jsx)(`div`,{className:`font-medium text-foreground`,children:A(`aiAssistant:tryAsking`)}),(0,w.jsxs)(`div`,{children:[`- `,A(`aiAssistant:tryAsking.example1`)]}),(0,w.jsxs)(`div`,{children:[`- `,A(`aiAssistant:tryAsking.example2`)]}),(0,w.jsxs)(`div`,{children:[`- `,A(`aiAssistant:tryAsking.example3`)]})]})}),F.map(e=>(0,w.jsx)(`div`,{className:e.role===`user`?`flex justify-end`:`flex justify-start`,children:(0,w.jsxs)(`div`,{className:e.role===`user`?`max-w-[85%] rounded-2xl bg-primary px-4 py-3 text-sm text-primary-foreground`:`max-w-[85%] rounded-2xl border border-border bg-card px-4 py-3 text-sm`,children:[e.role===`assistant`?(0,w.jsx)(ie,{value:e.content,format:`markdown`,className:`text-sm [&_pre]:overflow-x-auto [&_pre]:text-xs`}):(0,w.jsx)(`div`,{className:`whitespace-pre-wrap`,children:e.content}),e.citations&&e.citations.length>0&&(0,w.jsx)(`div`,{className:`mt-3 flex flex-wrap gap-2`,children:e.citations.slice(0,6).map((e,t)=>{let n=`${e.resourceId}${e.recordId?`#${e.recordId}`:``}`,r=`${e.resourceId}-${e.recordId??`resource`}-${t}`;return e.recordId?(0,w.jsx)(`button`,{type:`button`,className:`cursor-pointer`,onClick:()=>{M(!1),_({name:`show`,resourceId:e.resourceId,recordId:e.recordId})},children:(0,w.jsx)(g,{variant:`outline`,children:n})},r):(0,w.jsx)(`button`,{type:`button`,className:`cursor-pointer`,onClick:()=>{M(!1),_({name:`list`,resourceId:e.resourceId})},children:(0,w.jsx)(g,{variant:`outline`,children:n})},r)})})]})},e.id)),be&&(0,w.jsx)(`div`,{className:`flex justify-start`,children:(0,w.jsxs)(`div`,{className:`inline-flex items-center gap-2 rounded-2xl border border-border bg-card px-4 py-3 text-sm text-muted-foreground`,children:[(0,w.jsx)(pe,{className:`size-4 animate-spin`}),$==null?A(`aiAssistant:thinking`):A(`aiAssistant:thinkingProgress`,{progress:$})]})})]})}),(0,w.jsxs)(`div`,{className:`border-t border-border bg-background/95 px-3 py-2`,children:[V.length>0&&(0,w.jsx)(`div`,{className:`mb-2 space-y-1`,children:V.map((e,t)=>(0,w.jsxs)(`div`,{className:`flex items-center gap-2 rounded-md bg-muted/50 px-2 py-1 text-sm`,children:[(0,w.jsxs)(`span`,{className:`shrink-0 tabular-nums text-xs text-muted-foreground`,children:[`(`,t+1,`)`]}),(0,w.jsx)(`span`,{className:`flex-1 truncate`,title:e.content,children:e.content}),(0,w.jsx)(o,{type:`button`,variant:`ghost`,size:`icon`,className:`size-6 shrink-0`,onClick:()=>H(t=>t.filter(t=>t.id!==e.id)),"aria-label":A(`aiAssistant:queue.cancel`),children:(0,w.jsx)(a,{className:`size-3.5`})})]},e.id))}),(0,w.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),Z()},children:[(0,w.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,w.jsx)(s,{value:N,onChange:e=>P(e.target.value),onKeyDown:e=>{e.key===`Enter`&&!e.shiftKey&&!e.nativeEvent.isComposing&&(e.preventDefault(),Z())},rows:1,placeholder:A(`aiAssistant:input.placeholder`),"aria-label":A(`aiAssistant:input.placeholder`),className:`flex-1 max-h-[10rem] min-h-0 resize-none border-0 bg-transparent px-1 py-2 text-sm shadow-none outline-none [field-sizing:content] focus-visible:ring-0 focus-visible:ring-offset-0`}),(0,w.jsx)(o,{type:`submit`,disabled:xe,"aria-label":A(`aiAssistant:send`),className:`size-8 shrink-0 rounded-full p-0`,children:(0,w.jsx)(te,{className:`size-4`})})]}),(0,w.jsx)(`p`,{className:`px-1 pb-1 text-xs text-muted-foreground`,children:A(`aiAssistant:input.hint`)})]})]})]}):(0,w.jsxs)(`div`,{className:`flex flex-1 flex-col items-center justify-center gap-4 px-4 text-center`,children:[(0,w.jsxs)(`div`,{className:`space-y-2`,children:[(0,w.jsx)(`div`,{className:`text-lg font-semibold`,children:A(`aiAssistant:notConfigured.title`)}),(0,w.jsx)(`div`,{className:`text-sm text-muted-foreground`,children:A(`aiAssistant:notConfigured.description`)})]}),W.data.canManage&&(0,w.jsxs)(o,{variant:`outline`,onClick:()=>{M(!1),_({name:`settings`,section:`ai-assistant`})},children:[(0,w.jsx)(le,{className:`size-4`}),A(`aiAssistant:notConfigured.openSettings`)]})]})]})})]})}export{O as AiAssistantWidget};
|
|
2
|
+
//# sourceMappingURL=ai-assistant-widget-B3Xy7bCi.js.map
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-assistant-widget-Bt4Z7Fmr.js","names":[],"sources":["../../../../react/src/components/ai-assistant-widget.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n RichtextRender,\n ScrollArea,\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n Textarea,\n} from '@modern-admin/ui'\nimport { Bot, History, Loader2, MessageSquare, Plus, Send, Settings, X } from 'lucide-react'\nimport { uuidv7 } from '@modern-admin/core'\nimport { useAdminClient } from '../provider.js'\nimport { useFeatures } from '../hooks.js'\nimport { useNotify } from '../notify.js'\nimport { useBasepath, useNavigate } from '../router.js'\nimport { useI18n } from '../i18n.js'\nimport type {\n AiAssistantChatHistoryItem,\n AiAssistantChatMessage,\n AiAssistantCitation,\n AiAssistantTask,\n AiUiAction,\n} from '../client.js'\nimport { emitDashboardReload } from '../use-dashboard-charts.js'\n\ninterface ChatItem {\n id: string\n role: 'user' | 'assistant'\n content: string\n citations?: AiAssistantCitation[]\n}\n\ninterface QueuedItem {\n id: string\n content: string\n}\n\nconst randomId = (): string => uuidv7()\n\nconst MAX_QUEUE = 5\n\nconst messagesFromTask = (task: AiAssistantTask, fallbackText: string): ChatItem[] => {\n const inputMessages = Array.isArray(task.input?.messages)\n ? task.input.messages as AiAssistantChatMessage[]\n : []\n const items: ChatItem[] = inputMessages.map((message, index) => ({\n id: `${task.id}-input-${index}`,\n role: message.role,\n content: message.content,\n }))\n if (task.output) {\n const text = String(task.output.text ?? '').trim()\n items.push({\n id: `task-${task.id}`,\n role: 'assistant',\n content: text || fallbackText,\n citations: Array.isArray(task.output.citations) ? task.output.citations : undefined,\n })\n }\n return items\n}\n\nexport function AiAssistantWidget(): React.ReactElement | null {\n const client = useAdminClient()\n const queryClient = useQueryClient()\n const notify = useNotify()\n const navigate = useNavigate()\n const basepath = useBasepath()\n const { locale, t } = useI18n()\n const [open, setOpen] = React.useState(false)\n const [input, setInput] = React.useState('')\n const [messages, setMessages] = React.useState<ChatItem[]>([])\n const [conversationId, setConversationId] = React.useState(() => randomId())\n const [activeTaskId, setActiveTaskId] = React.useState<string | null>(null)\n const [queue, setQueue] = React.useState<QueuedItem[]>([])\n\n // Capability gate: when the host hasn't wired `aiAssistant` in\n // `ModernAdminModule.forRoot`, the `/admin/api/ai-assistant/*`\n // controllers aren't even registered — issuing the settings query would\n // 404 every page load. Skip the query, the widget, and the chat sheet\n // entirely in that case.\n const features = useFeatures()\n const aiAvailable = features.aiAssistant\n\n const settings = useQuery({\n queryKey: ['modern-admin', 'ai-assistant', 'settings'],\n queryFn: () => client.getAiAssistantSettings(),\n enabled: aiAvailable,\n })\n\n const chat = useMutation({\n mutationFn: async (input: {\n requestId: string\n conversationId: string\n messages: AiAssistantChatMessage[]\n }) => {\n // Strip the admin's basepath so the backend receives a basepath-relative\n // path (e.g. `/resources/posts/123`, not `/admin/resources/posts/123`) —\n // the grounding prompt matches against the relative `/resources/...` form.\n // Boundary-aware: only strip a full segment match so basepath `/admin`\n // never mangles an unrelated path like `/administrators`.\n let pathname = typeof window !== 'undefined' ? window.location.pathname : undefined\n if (pathname && basepath) {\n if (pathname === basepath) {\n pathname = '/'\n } else if (pathname.startsWith(`${basepath}/`)) {\n pathname = pathname.slice(basepath.length)\n }\n }\n return client.sendAiAssistantChat(\n input.messages,\n input.requestId,\n locale,\n input.conversationId,\n pathname ? { pathname } : undefined,\n )\n },\n onSuccess: (response) => {\n setActiveTaskId(response.taskId)\n void queryClient.invalidateQueries({ queryKey: ['modern-admin', 'ai-assistant', 'chats'] })\n },\n onError: (err) => {\n notify.error({ message: err instanceof Error ? err.message : String(err) })\n },\n })\n\n const history = useQuery<AiAssistantChatHistoryItem[]>({\n queryKey: ['modern-admin', 'ai-assistant', 'chats'],\n queryFn: () => client.listAiAssistantChats(),\n enabled: aiAvailable && open && settings.data?.configured === true,\n })\n\n const task = useQuery<AiAssistantTask>({\n queryKey: ['modern-admin', 'ai-assistant', 'task', activeTaskId],\n queryFn: () => client.getAiAssistantTask(activeTaskId!),\n enabled: !!activeTaskId,\n refetchInterval: (query) => {\n const state = query.state.data?.status\n return state === 'pending' || state === 'running' ? 1200 : false\n },\n })\n\n React.useEffect(() => {\n const data = task.data\n if (!data || !activeTaskId) return\n if (data.status === 'succeeded' && data.output != null) {\n setMessages((prev) => {\n const alreadyExists = prev.some((item) => item.id === `task-${data.id}`)\n if (alreadyExists) return prev\n const text = String(data.output?.text ?? '').trim()\n return [\n ...prev,\n {\n id: `task-${data.id}`,\n role: 'assistant',\n content: text || t('aiAssistant:noText'),\n citations: Array.isArray(data.output?.citations) ? data.output.citations : undefined,\n },\n ]\n })\n setActiveTaskId(null)\n void queryClient.invalidateQueries({ queryKey: ['modern-admin', 'ai-assistant', 'chats'] })\n const uiActions = Array.isArray(data.output?.uiActions)\n ? (data.output.uiActions as AiUiAction[])\n : []\n for (const action of uiActions) {\n if (action.kind === 'refresh' && action.target === 'dashboard') {\n emitDashboardReload()\n continue\n }\n if (action.kind === 'navigate') {\n setOpen(false)\n navigate(action.route)\n }\n }\n return\n }\n if (data.status === 'failed' || data.status === 'cancelled') {\n notify.error({ message: data.error ?? t('aiAssistant:taskFailed') })\n setActiveTaskId(null)\n }\n }, [activeTaskId, navigate, notify, queryClient, task.data, t])\n\n // Send a user message immediately (assumes nothing is in flight).\n const sendChat = React.useCallback(\n (content: string): void => {\n const userMsg: ChatItem = { id: randomId(), role: 'user', content }\n const updated = [...messages, userMsg]\n setMessages(updated)\n chat.mutate({\n requestId: userMsg.id,\n conversationId,\n messages: updated.map((m) => ({ role: m.role, content: m.content })),\n })\n },\n [chat, conversationId, messages],\n )\n\n const startNewChat = React.useCallback((): void => {\n setConversationId(randomId())\n setMessages([])\n setInput('')\n setQueue([])\n setActiveTaskId(null)\n }, [])\n\n const selectChat = React.useCallback(\n async (item: AiAssistantChatHistoryItem): Promise<void> => {\n try {\n const selected = await client.getAiAssistantTask(item.taskId)\n setConversationId(item.conversationId)\n setMessages(messagesFromTask(selected, t('aiAssistant:noText')))\n setQueue([])\n setInput('')\n setActiveTaskId(\n selected.status === 'pending' || selected.status === 'running' ? selected.id : null,\n )\n } catch (err) {\n notify.error({ message: err instanceof Error ? err.message : String(err) })\n }\n },\n [client, notify, t],\n )\n\n // Auto-dequeue: when nothing is in flight and queue has items, send the head.\n React.useEffect(() => {\n if (activeTaskId !== null) return\n if (chat.isPending) return\n const next = queue[0]\n if (!next) return\n setQueue((q) => q.slice(1))\n sendChat(next.content)\n }, [activeTaskId, chat.isPending, queue, sendChat])\n\n if (!aiAvailable) return null\n if (settings.isLoading) return null\n if (settings.error) return null\n if (!settings.data?.enabled) return null\n if (!settings.data.canChat) return null\n\n const isProcessing = chat.isPending || activeTaskId != null\n const queueFull = queue.length >= MAX_QUEUE\n\n const submit = (): void => {\n const text = input.trim()\n if (!text) return\n if (isProcessing) {\n if (queueFull) return\n setQueue((q) => [...q, { id: randomId(), content: text }])\n setInput('')\n return\n }\n setInput('')\n sendChat(text)\n }\n\n const configured = settings.data.configured\n const isThinking =\n isProcessing ||\n task.data?.status === 'running' ||\n task.data?.status === 'pending'\n const progress = typeof task.data?.progress === 'number' ? task.data.progress : null\n const sendDisabled = input.trim().length === 0 || (isProcessing && queueFull)\n\n return (\n <>\n <div className=\"fixed bottom-4 right-4 z-40\">\n <Button\n onClick={() => setOpen(true)}\n aria-label={t('aiAssistant:fab.label')}\n className={\n 'group h-10 min-w-10 max-w-10 rounded-full pl-3 pr-3 shadow-lg ' +\n 'justify-end gap-0 overflow-hidden opacity-70 hover:opacity-100 ' +\n 'transition-[max-width,padding-left,opacity] duration-200 ease-in-out ' +\n 'hover:max-w-36 hover:pl-4'\n }\n >\n <span className=\"max-w-0 overflow-hidden whitespace-nowrap text-sm font-medium transition-[max-width,padding-right] duration-200 ease-in-out group-hover:max-w-[5rem] group-hover:pr-2\">\n {t('aiAssistant:fab.short')}\n </span>\n <MessageSquare className=\"size-4 shrink-0\" />\n </Button>\n </div>\n <Sheet open={open} onOpenChange={setOpen}>\n <SheetContent hideCloseButton className=\"flex w-full flex-col gap-0 sm:max-w-xl\">\n <SheetHeader className=\"border-b border-border pb-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <SheetTitle className=\"flex items-center gap-2\">\n <Bot className=\"size-5\" />\n {t('aiAssistant:title')}\n </SheetTitle>\n <SheetDescription>{t('aiAssistant:description')}</SheetDescription>\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {configured && (\n <>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('aiAssistant:history')}\n >\n <History className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-72\">\n <DropdownMenuLabel>{t('aiAssistant:history')}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {history.isLoading && (\n <DropdownMenuItem disabled>{t('common:loading')}</DropdownMenuItem>\n )}\n {!history.isLoading && (history.data?.length ?? 0) === 0 && (\n <DropdownMenuItem disabled>{t('aiAssistant:history.empty')}</DropdownMenuItem>\n )}\n {history.data?.map((item) => (\n <DropdownMenuItem\n key={item.conversationId}\n className=\"flex-col items-start gap-0 py-2\"\n onSelect={() => {\n void selectChat(item)\n }}\n >\n <span className=\"line-clamp-1 w-full text-sm font-medium\">{item.title}</span>\n <span className=\"text-xs text-muted-foreground\">\n {new Intl.DateTimeFormat(locale, {\n dateStyle: 'short',\n timeStyle: 'short',\n }).format(new Date(item.updatedAt))}\n </span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={startNewChat}\n aria-label={t('aiAssistant:newChat')}\n >\n <Plus className=\"size-4\" />\n </Button>\n </>\n )}\n <SheetClose asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('common:close')}\n >\n <X className=\"size-4\" />\n </Button>\n </SheetClose>\n </div>\n </div>\n </SheetHeader>\n\n {!configured ? (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-4 px-4 text-center\">\n <div className=\"space-y-2\">\n <div className=\"text-lg font-semibold\">\n {t('aiAssistant:notConfigured.title')}\n </div>\n <div className=\"text-sm text-muted-foreground\">\n {t('aiAssistant:notConfigured.description')}\n </div>\n </div>\n {settings.data.canManage && (\n <Button\n variant=\"outline\"\n onClick={() => {\n setOpen(false)\n navigate({ name: 'settings', section: 'ai-assistant' })\n }}\n >\n <Settings className=\"size-4\" />\n {t('aiAssistant:notConfigured.openSettings')}\n </Button>\n )}\n </div>\n ) : (\n <>\n <ScrollArea className=\"flex-1 px-4 py-4\">\n <div className=\"space-y-3\">\n {messages.length === 0 && (\n <Card>\n <CardContent className=\"space-y-2 p-4 text-sm text-muted-foreground\">\n <div className=\"font-medium text-foreground\">\n {t('aiAssistant:tryAsking')}\n </div>\n <div>- {t('aiAssistant:tryAsking.example1')}</div>\n <div>- {t('aiAssistant:tryAsking.example2')}</div>\n <div>- {t('aiAssistant:tryAsking.example3')}</div>\n </CardContent>\n </Card>\n )}\n {messages.map((message) => (\n <div\n key={message.id}\n className={message.role === 'user' ? 'flex justify-end' : 'flex justify-start'}\n >\n <div\n className={message.role === 'user'\n ? 'max-w-[85%] rounded-2xl bg-primary px-4 py-3 text-sm text-primary-foreground'\n : 'max-w-[85%] rounded-2xl border border-border bg-card px-4 py-3 text-sm'}\n >\n {message.role === 'assistant' ? (\n <RichtextRender\n value={message.content}\n format=\"markdown\"\n className=\"text-sm [&_pre]:overflow-x-auto [&_pre]:text-xs\"\n />\n ) : (\n <div className=\"whitespace-pre-wrap\">{message.content}</div>\n )}\n {message.citations && message.citations.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-2\">\n {message.citations.slice(0, 6).map((citation, index) => {\n const label = `${citation.resourceId}${citation.recordId ? `#${citation.recordId}` : ''}`\n const key = `${citation.resourceId}-${citation.recordId ?? 'resource'}-${index}`\n if (citation.recordId) {\n return (\n <button\n key={key}\n type=\"button\"\n className=\"cursor-pointer\"\n onClick={() => {\n setOpen(false)\n navigate({\n name: 'show',\n resourceId: citation.resourceId,\n recordId: citation.recordId!,\n })\n }}\n >\n <Badge variant=\"outline\">{label}</Badge>\n </button>\n )\n }\n return (\n <button\n key={key}\n type=\"button\"\n className=\"cursor-pointer\"\n onClick={() => {\n setOpen(false)\n navigate({ name: 'list', resourceId: citation.resourceId })\n }}\n >\n <Badge variant=\"outline\">{label}</Badge>\n </button>\n )\n })}\n </div>\n )}\n </div>\n </div>\n ))}\n {isThinking && (\n <div className=\"flex justify-start\">\n <div className=\"inline-flex items-center gap-2 rounded-2xl border border-border bg-card px-4 py-3 text-sm text-muted-foreground\">\n <Loader2 className=\"size-4 animate-spin\" />\n {progress != null\n ? t('aiAssistant:thinkingProgress', { progress })\n : t('aiAssistant:thinking')}\n </div>\n </div>\n )}\n </div>\n </ScrollArea>\n\n <div className=\"border-t border-border bg-background/95 px-3 py-2\">\n {queue.length > 0 && (\n <div className=\"mb-2 space-y-1\">\n {queue.map((item, index) => (\n <div\n key={item.id}\n className=\"flex items-center gap-2 rounded-md bg-muted/50 px-2 py-1 text-sm\"\n >\n <span className=\"shrink-0 tabular-nums text-xs text-muted-foreground\">\n ({index + 1})\n </span>\n <span className=\"flex-1 truncate\" title={item.content}>\n {item.content}\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-6 shrink-0\"\n onClick={() =>\n setQueue((q) => q.filter((entry) => entry.id !== item.id))\n }\n aria-label={t('aiAssistant:queue.cancel')}\n >\n <X className=\"size-3.5\" />\n </Button>\n </div>\n ))}\n </div>\n )}\n <form\n onSubmit={(e) => {\n e.preventDefault()\n submit()\n }}\n >\n <div className=\"flex items-center gap-1\">\n <Textarea\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault()\n submit()\n }\n }}\n rows={1}\n placeholder={t('aiAssistant:input.placeholder')}\n aria-label={t('aiAssistant:input.placeholder')}\n className={\n 'flex-1 max-h-[10rem] min-h-0 resize-none border-0 bg-transparent px-1 py-2 ' +\n 'text-sm shadow-none outline-none [field-sizing:content] ' +\n 'focus-visible:ring-0 focus-visible:ring-offset-0'\n }\n />\n <Button\n type=\"submit\"\n disabled={sendDisabled}\n aria-label={t('aiAssistant:send')}\n className=\"size-8 shrink-0 rounded-full p-0\"\n >\n <Send className=\"size-4\" />\n </Button>\n </div>\n <p className=\"px-1 pb-1 text-xs text-muted-foreground\">\n {t('aiAssistant:input.hint')}\n </p>\n </form>\n </div>\n </>\n )}\n </SheetContent>\n </Sheet>\n </>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai-assistant-widget-B3Xy7bCi.js","names":[],"sources":["../../../../react/src/components/ai-assistant-widget.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n RichtextRender,\n ScrollArea,\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n Textarea,\n} from '@modern-admin/ui'\nimport { Bot, History, Loader2, MessageSquare, Plus, Send, Settings, X } from 'lucide-react'\nimport { uuidv7 } from '@modern-admin/core'\nimport { useAdminClient } from '../provider.js'\nimport { useFeatures } from '../hooks.js'\nimport { useNotify } from '../notify.js'\nimport { useBasepath, useNavigate } from '../router.js'\nimport { useI18n } from '../i18n.js'\nimport type {\n AiAssistantChatHistoryItem,\n AiAssistantChatMessage,\n AiAssistantCitation,\n AiAssistantTask,\n AiUiAction,\n} from '../client.js'\nimport { emitDashboardReload } from '../use-dashboard-charts.js'\n\ninterface ChatItem {\n id: string\n role: 'user' | 'assistant'\n content: string\n citations?: AiAssistantCitation[]\n}\n\ninterface QueuedItem {\n id: string\n content: string\n}\n\nconst randomId = (): string => uuidv7()\n\nconst MAX_QUEUE = 5\n\nconst messagesFromTask = (task: AiAssistantTask, fallbackText: string): ChatItem[] => {\n const inputMessages = Array.isArray(task.input?.messages)\n ? task.input.messages as AiAssistantChatMessage[]\n : []\n const items: ChatItem[] = inputMessages.map((message, index) => ({\n id: `${task.id}-input-${index}`,\n role: message.role,\n content: message.content,\n }))\n if (task.output) {\n const text = String(task.output.text ?? '').trim()\n items.push({\n id: `task-${task.id}`,\n role: 'assistant',\n content: text || fallbackText,\n citations: Array.isArray(task.output.citations) ? task.output.citations : undefined,\n })\n }\n return items\n}\n\nexport function AiAssistantWidget(): React.ReactElement | null {\n const client = useAdminClient()\n const queryClient = useQueryClient()\n const notify = useNotify()\n const navigate = useNavigate()\n const basepath = useBasepath()\n const { locale, t } = useI18n()\n const [open, setOpen] = React.useState(false)\n const [input, setInput] = React.useState('')\n const [messages, setMessages] = React.useState<ChatItem[]>([])\n const [conversationId, setConversationId] = React.useState(() => randomId())\n const [activeTaskId, setActiveTaskId] = React.useState<string | null>(null)\n const [queue, setQueue] = React.useState<QueuedItem[]>([])\n\n // Capability gate: when the host hasn't wired `aiAssistant` in\n // `ModernAdminModule.forRoot`, the `/admin/api/ai-assistant/*`\n // controllers aren't even registered — issuing the settings query would\n // 404 every page load. Skip the query, the widget, and the chat sheet\n // entirely in that case.\n const features = useFeatures()\n const aiAvailable = features.aiAssistant\n\n const settings = useQuery({\n queryKey: ['modern-admin', 'ai-assistant', 'settings'],\n queryFn: () => client.getAiAssistantSettings(),\n enabled: aiAvailable,\n })\n\n const chat = useMutation({\n mutationFn: async (input: {\n requestId: string\n conversationId: string\n messages: AiAssistantChatMessage[]\n }) => {\n // Strip the admin's basepath so the backend receives a basepath-relative\n // path (e.g. `/resources/posts/123`, not `/admin/resources/posts/123`) —\n // the grounding prompt matches against the relative `/resources/...` form.\n // Boundary-aware: only strip a full segment match so basepath `/admin`\n // never mangles an unrelated path like `/administrators`.\n let pathname = typeof window !== 'undefined' ? window.location.pathname : undefined\n if (pathname && basepath) {\n if (pathname === basepath) {\n pathname = '/'\n } else if (pathname.startsWith(`${basepath}/`)) {\n pathname = pathname.slice(basepath.length)\n }\n }\n return client.sendAiAssistantChat(\n input.messages,\n input.requestId,\n locale,\n input.conversationId,\n pathname ? { pathname } : undefined,\n )\n },\n onSuccess: (response) => {\n setActiveTaskId(response.taskId)\n void queryClient.invalidateQueries({ queryKey: ['modern-admin', 'ai-assistant', 'chats'] })\n },\n onError: (err) => {\n notify.error({ message: err instanceof Error ? err.message : String(err) })\n },\n })\n\n const history = useQuery<AiAssistantChatHistoryItem[]>({\n queryKey: ['modern-admin', 'ai-assistant', 'chats'],\n queryFn: () => client.listAiAssistantChats(),\n enabled: aiAvailable && open && settings.data?.configured === true,\n })\n\n const task = useQuery<AiAssistantTask>({\n queryKey: ['modern-admin', 'ai-assistant', 'task', activeTaskId],\n queryFn: () => client.getAiAssistantTask(activeTaskId!),\n enabled: !!activeTaskId,\n refetchInterval: (query) => {\n const state = query.state.data?.status\n return state === 'pending' || state === 'running' ? 1200 : false\n },\n })\n\n React.useEffect(() => {\n const data = task.data\n if (!data || !activeTaskId) return\n if (data.status === 'succeeded' && data.output != null) {\n setMessages((prev) => {\n const alreadyExists = prev.some((item) => item.id === `task-${data.id}`)\n if (alreadyExists) return prev\n const text = String(data.output?.text ?? '').trim()\n return [\n ...prev,\n {\n id: `task-${data.id}`,\n role: 'assistant',\n content: text || t('aiAssistant:noText'),\n citations: Array.isArray(data.output?.citations) ? data.output.citations : undefined,\n },\n ]\n })\n setActiveTaskId(null)\n void queryClient.invalidateQueries({ queryKey: ['modern-admin', 'ai-assistant', 'chats'] })\n const uiActions = Array.isArray(data.output?.uiActions)\n ? (data.output.uiActions as AiUiAction[])\n : []\n for (const action of uiActions) {\n if (action.kind === 'refresh' && action.target === 'dashboard') {\n emitDashboardReload()\n continue\n }\n if (action.kind === 'navigate') {\n setOpen(false)\n navigate(action.route)\n }\n }\n return\n }\n if (data.status === 'failed' || data.status === 'cancelled') {\n notify.error({ message: data.error ?? t('aiAssistant:taskFailed') })\n setActiveTaskId(null)\n }\n }, [activeTaskId, navigate, notify, queryClient, task.data, t])\n\n // Send a user message immediately (assumes nothing is in flight).\n const sendChat = React.useCallback(\n (content: string): void => {\n const userMsg: ChatItem = { id: randomId(), role: 'user', content }\n const updated = [...messages, userMsg]\n setMessages(updated)\n chat.mutate({\n requestId: userMsg.id,\n conversationId,\n messages: updated.map((m) => ({ role: m.role, content: m.content })),\n })\n },\n [chat, conversationId, messages],\n )\n\n const startNewChat = React.useCallback((): void => {\n setConversationId(randomId())\n setMessages([])\n setInput('')\n setQueue([])\n setActiveTaskId(null)\n }, [])\n\n const selectChat = React.useCallback(\n async (item: AiAssistantChatHistoryItem): Promise<void> => {\n try {\n const selected = await client.getAiAssistantTask(item.taskId)\n setConversationId(item.conversationId)\n setMessages(messagesFromTask(selected, t('aiAssistant:noText')))\n setQueue([])\n setInput('')\n setActiveTaskId(\n selected.status === 'pending' || selected.status === 'running' ? selected.id : null,\n )\n } catch (err) {\n notify.error({ message: err instanceof Error ? err.message : String(err) })\n }\n },\n [client, notify, t],\n )\n\n // Auto-dequeue: when nothing is in flight and queue has items, send the head.\n React.useEffect(() => {\n if (activeTaskId !== null) return\n if (chat.isPending) return\n const next = queue[0]\n if (!next) return\n setQueue((q) => q.slice(1))\n sendChat(next.content)\n }, [activeTaskId, chat.isPending, queue, sendChat])\n\n if (!aiAvailable) return null\n if (settings.isLoading) return null\n if (settings.error) return null\n if (!settings.data?.enabled) return null\n if (!settings.data.canChat) return null\n\n const isProcessing = chat.isPending || activeTaskId != null\n const queueFull = queue.length >= MAX_QUEUE\n\n const submit = (): void => {\n const text = input.trim()\n if (!text) return\n if (isProcessing) {\n if (queueFull) return\n setQueue((q) => [...q, { id: randomId(), content: text }])\n setInput('')\n return\n }\n setInput('')\n sendChat(text)\n }\n\n const configured = settings.data.configured\n const isThinking =\n isProcessing ||\n task.data?.status === 'running' ||\n task.data?.status === 'pending'\n const progress = typeof task.data?.progress === 'number' ? task.data.progress : null\n const sendDisabled = input.trim().length === 0 || (isProcessing && queueFull)\n\n return (\n <>\n <div className=\"fixed bottom-4 right-4 z-40\">\n <Button\n onClick={() => setOpen(true)}\n aria-label={t('aiAssistant:fab.label')}\n className={\n 'group h-10 min-w-10 max-w-10 rounded-full pl-3 pr-3 shadow-lg ' +\n 'justify-end gap-0 overflow-hidden opacity-70 hover:opacity-100 ' +\n 'transition-[max-width,padding-left,opacity] duration-200 ease-in-out ' +\n 'hover:max-w-36 hover:pl-4'\n }\n >\n <span className=\"max-w-0 overflow-hidden whitespace-nowrap text-sm font-medium transition-[max-width,padding-right] duration-200 ease-in-out group-hover:max-w-[5rem] group-hover:pr-2\">\n {t('aiAssistant:fab.short')}\n </span>\n <MessageSquare className=\"size-4 shrink-0\" />\n </Button>\n </div>\n <Sheet open={open} onOpenChange={setOpen}>\n <SheetContent hideCloseButton className=\"flex w-full flex-col gap-0 sm:max-w-xl\">\n <SheetHeader className=\"border-b border-border pb-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <SheetTitle className=\"flex items-center gap-2\">\n <Bot className=\"size-5\" />\n {t('aiAssistant:title')}\n </SheetTitle>\n <SheetDescription>{t('aiAssistant:description')}</SheetDescription>\n </div>\n <div className=\"flex shrink-0 items-center gap-0.5\">\n {configured && (\n <>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('aiAssistant:history')}\n >\n <History className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-72\">\n <DropdownMenuLabel>{t('aiAssistant:history')}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {history.isLoading && (\n <DropdownMenuItem disabled>{t('common:loading')}</DropdownMenuItem>\n )}\n {!history.isLoading && (history.data?.length ?? 0) === 0 && (\n <DropdownMenuItem disabled>{t('aiAssistant:history.empty')}</DropdownMenuItem>\n )}\n {history.data?.map((item) => (\n <DropdownMenuItem\n key={item.conversationId}\n className=\"flex-col items-start gap-0 py-2\"\n onSelect={() => {\n void selectChat(item)\n }}\n >\n <span className=\"line-clamp-1 w-full text-sm font-medium\">{item.title}</span>\n <span className=\"text-xs text-muted-foreground\">\n {new Intl.DateTimeFormat(locale, {\n dateStyle: 'short',\n timeStyle: 'short',\n }).format(new Date(item.updatedAt))}\n </span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={startNewChat}\n aria-label={t('aiAssistant:newChat')}\n >\n <Plus className=\"size-4\" />\n </Button>\n </>\n )}\n <SheetClose asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={t('common:close')}\n >\n <X className=\"size-4\" />\n </Button>\n </SheetClose>\n </div>\n </div>\n </SheetHeader>\n\n {!configured ? (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-4 px-4 text-center\">\n <div className=\"space-y-2\">\n <div className=\"text-lg font-semibold\">\n {t('aiAssistant:notConfigured.title')}\n </div>\n <div className=\"text-sm text-muted-foreground\">\n {t('aiAssistant:notConfigured.description')}\n </div>\n </div>\n {settings.data.canManage && (\n <Button\n variant=\"outline\"\n onClick={() => {\n setOpen(false)\n navigate({ name: 'settings', section: 'ai-assistant' })\n }}\n >\n <Settings className=\"size-4\" />\n {t('aiAssistant:notConfigured.openSettings')}\n </Button>\n )}\n </div>\n ) : (\n <>\n <ScrollArea className=\"flex-1 px-4 py-4\">\n <div className=\"space-y-3\">\n {messages.length === 0 && (\n <Card>\n <CardContent className=\"space-y-2 p-4 text-sm text-muted-foreground\">\n <div className=\"font-medium text-foreground\">\n {t('aiAssistant:tryAsking')}\n </div>\n <div>- {t('aiAssistant:tryAsking.example1')}</div>\n <div>- {t('aiAssistant:tryAsking.example2')}</div>\n <div>- {t('aiAssistant:tryAsking.example3')}</div>\n </CardContent>\n </Card>\n )}\n {messages.map((message) => (\n <div\n key={message.id}\n className={message.role === 'user' ? 'flex justify-end' : 'flex justify-start'}\n >\n <div\n className={message.role === 'user'\n ? 'max-w-[85%] rounded-2xl bg-primary px-4 py-3 text-sm text-primary-foreground'\n : 'max-w-[85%] rounded-2xl border border-border bg-card px-4 py-3 text-sm'}\n >\n {message.role === 'assistant' ? (\n <RichtextRender\n value={message.content}\n format=\"markdown\"\n className=\"text-sm [&_pre]:overflow-x-auto [&_pre]:text-xs\"\n />\n ) : (\n <div className=\"whitespace-pre-wrap\">{message.content}</div>\n )}\n {message.citations && message.citations.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-2\">\n {message.citations.slice(0, 6).map((citation, index) => {\n const label = `${citation.resourceId}${citation.recordId ? `#${citation.recordId}` : ''}`\n const key = `${citation.resourceId}-${citation.recordId ?? 'resource'}-${index}`\n if (citation.recordId) {\n return (\n <button\n key={key}\n type=\"button\"\n className=\"cursor-pointer\"\n onClick={() => {\n setOpen(false)\n navigate({\n name: 'show',\n resourceId: citation.resourceId,\n recordId: citation.recordId!,\n })\n }}\n >\n <Badge variant=\"outline\">{label}</Badge>\n </button>\n )\n }\n return (\n <button\n key={key}\n type=\"button\"\n className=\"cursor-pointer\"\n onClick={() => {\n setOpen(false)\n navigate({ name: 'list', resourceId: citation.resourceId })\n }}\n >\n <Badge variant=\"outline\">{label}</Badge>\n </button>\n )\n })}\n </div>\n )}\n </div>\n </div>\n ))}\n {isThinking && (\n <div className=\"flex justify-start\">\n <div className=\"inline-flex items-center gap-2 rounded-2xl border border-border bg-card px-4 py-3 text-sm text-muted-foreground\">\n <Loader2 className=\"size-4 animate-spin\" />\n {progress != null\n ? t('aiAssistant:thinkingProgress', { progress })\n : t('aiAssistant:thinking')}\n </div>\n </div>\n )}\n </div>\n </ScrollArea>\n\n <div className=\"border-t border-border bg-background/95 px-3 py-2\">\n {queue.length > 0 && (\n <div className=\"mb-2 space-y-1\">\n {queue.map((item, index) => (\n <div\n key={item.id}\n className=\"flex items-center gap-2 rounded-md bg-muted/50 px-2 py-1 text-sm\"\n >\n <span className=\"shrink-0 tabular-nums text-xs text-muted-foreground\">\n ({index + 1})\n </span>\n <span className=\"flex-1 truncate\" title={item.content}>\n {item.content}\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-6 shrink-0\"\n onClick={() =>\n setQueue((q) => q.filter((entry) => entry.id !== item.id))\n }\n aria-label={t('aiAssistant:queue.cancel')}\n >\n <X className=\"size-3.5\" />\n </Button>\n </div>\n ))}\n </div>\n )}\n <form\n onSubmit={(e) => {\n e.preventDefault()\n submit()\n }}\n >\n <div className=\"flex items-center gap-1\">\n <Textarea\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault()\n submit()\n }\n }}\n rows={1}\n placeholder={t('aiAssistant:input.placeholder')}\n aria-label={t('aiAssistant:input.placeholder')}\n className={\n 'flex-1 max-h-[10rem] min-h-0 resize-none border-0 bg-transparent px-1 py-2 ' +\n 'text-sm shadow-none outline-none [field-sizing:content] ' +\n 'focus-visible:ring-0 focus-visible:ring-offset-0'\n }\n />\n <Button\n type=\"submit\"\n disabled={sendDisabled}\n aria-label={t('aiAssistant:send')}\n className=\"size-8 shrink-0 rounded-full p-0\"\n >\n <Send className=\"size-4\" />\n </Button>\n </div>\n <p className=\"px-1 pb-1 text-xs text-muted-foreground\">\n {t('aiAssistant:input.hint')}\n </p>\n </form>\n </div>\n </>\n )}\n </SheetContent>\n </Sheet>\n </>\n )\n}\n"],"mappings":"i2BAmDM,MAAyB,EAAO,EAEhC,EAAY,EAEZ,GAAoB,EAAuB,IAAqC,CAIpF,IAAM,GAHgB,MAAM,QAAQ,EAAK,OAAO,QAAQ,EACpD,EAAK,MAAM,SACX,CAAC,EAAA,CACmC,KAAK,EAAS,KAAW,CAC/D,GAAI,GAAG,EAAK,GAAG,SAAS,IACxB,KAAM,EAAQ,KACd,QAAS,EAAQ,OACnB,EAAE,EACF,GAAI,EAAK,OAAQ,CACf,IAAM,EAAO,OAAO,EAAK,OAAO,MAAQ,EAAE,CAAC,CAAC,KAAK,EACjD,EAAM,KAAK,CACT,GAAI,QAAQ,EAAK,KACjB,KAAM,YACN,QAAS,GAAQ,EACjB,UAAW,MAAM,QAAQ,EAAK,OAAO,SAAS,EAAI,EAAK,OAAO,UAAY,IAAA,EAC5E,CAAC,CACH,CACA,OAAO,CACT,EAEA,SAAgB,GAA+C,CAC7D,IAAM,EAAS,EAAe,EACxB,EAAc,EAAe,EAC7B,EAAS,EAAU,EACnB,EAAW,EAAY,EACvB,EAAW,EAAY,EACvB,CAAE,SAAQ,KAAM,EAAQ,EACxB,CAAC,EAAM,GAAA,EAAiB,SAAS,EAAK,EACtC,CAAC,EAAO,GAAA,EAAkB,SAAS,EAAE,EACrC,CAAC,EAAU,GAAA,EAAqB,SAAqB,CAAC,CAAC,EACvD,CAAC,EAAgB,GAAA,EAA2B,aAAe,EAAS,CAAC,EACrE,CAAC,EAAc,GAAA,EAAyB,SAAwB,IAAI,EACpE,CAAC,EAAO,GAAA,EAAkB,SAAuB,CAAC,CAAC,EAQnD,EADW,EACG,CAAA,CAAS,YAEvB,EAAW,EAAS,CACxB,SAAU,CAAC,eAAgB,eAAgB,UAAU,EACrD,YAAe,EAAO,uBAAuB,EAC7C,QAAS,CACX,CAAC,EAEK,EAAO,EAAY,CACvB,WAAY,KAAO,IAIb,CAMJ,IAAI,EAAW,OAAO,OAAW,IAAc,OAAO,SAAS,SAAW,IAAA,GAQ1E,OAPI,GAAY,IACV,IAAa,EACf,EAAW,IACF,EAAS,WAAW,GAAG,EAAS,EAAE,IAC3C,EAAW,EAAS,MAAM,EAAS,MAAM,IAGtC,EAAO,oBACZ,EAAM,SACN,EAAM,UACN,EACA,EAAM,eACN,EAAW,CAAE,UAAS,EAAI,IAAA,EAC5B,CACF,EACA,UAAY,GAAa,CACvB,EAAgB,EAAS,MAAM,EAC/B,EAAiB,kBAAkB,CAAE,SAAU,CAAC,eAAgB,eAAgB,OAAO,CAAE,CAAC,CAC5F,EACA,QAAU,GAAQ,CAChB,EAAO,MAAM,CAAE,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAAE,CAAC,CAC5E,CACF,CAAC,EAEK,EAAU,EAAuC,CACrD,SAAU,CAAC,eAAgB,eAAgB,OAAO,EAClD,YAAe,EAAO,qBAAqB,EAC3C,QAAS,GAAe,GAAQ,EAAS,MAAM,aAAe,EAChE,CAAC,EAEK,EAAO,EAA0B,CACrC,SAAU,CAAC,eAAgB,eAAgB,OAAQ,CAAY,EAC/D,YAAe,EAAO,mBAAmB,CAAa,EACtD,QAAS,CAAC,CAAC,EACX,gBAAkB,GAAU,CAC1B,IAAM,EAAQ,EAAM,MAAM,MAAM,OAChC,OAAO,IAAU,WAAa,IAAU,UAAY,KAAO,EAC7D,CACF,CAAC,EAED,EAAM,cAAgB,CACpB,IAAM,EAAO,EAAK,KACd,MAAC,GAAQ,CAAC,GACd,IAAI,EAAK,SAAW,aAAe,EAAK,QAAU,KAAM,CACtD,EAAa,GAAS,CAEpB,GADsB,EAAK,KAAM,GAAS,EAAK,KAAO,QAAQ,EAAK,IAC/D,EAAe,OAAO,EAC1B,IAAM,EAAO,OAAO,EAAK,QAAQ,MAAQ,EAAE,CAAC,CAAC,KAAK,EAClD,MAAO,CACL,GAAG,EACH,CACE,GAAI,QAAQ,EAAK,KACjB,KAAM,YACN,QAAS,GAAQ,EAAE,oBAAoB,EACvC,UAAW,MAAM,QAAQ,EAAK,QAAQ,SAAS,EAAI,EAAK,OAAO,UAAY,IAAA,EAC7E,CACF,CACF,CAAC,EACD,EAAgB,IAAI,EACpB,EAAiB,kBAAkB,CAAE,SAAU,CAAC,eAAgB,eAAgB,OAAO,CAAE,CAAC,EAC1F,IAAM,EAAY,MAAM,QAAQ,EAAK,QAAQ,SAAS,EACjD,EAAK,OAAO,UACb,CAAC,EACL,IAAK,IAAM,KAAU,EAAW,CAC9B,GAAI,EAAO,OAAS,WAAa,EAAO,SAAW,YAAa,CAC9D,GAAoB,EACpB,QACF,CACI,EAAO,OAAS,aAClB,EAAQ,EAAK,EACb,EAAS,EAAO,KAAK,EAEzB,CACA,MACF,EACI,EAAK,SAAW,UAAY,EAAK,SAAW,eAC9C,EAAO,MAAM,CAAE,QAAS,EAAK,OAAS,EAAE,wBAAwB,CAAE,CAAC,EACnE,EAAgB,IAAI,EAHtB,CAKF,EAAG,CAAC,EAAc,EAAU,EAAQ,EAAa,EAAK,KAAM,CAAC,CAAC,EAG9D,IAAM,EAAA,EAAiB,YACpB,GAA0B,CACzB,IAAM,EAAoB,CAAE,GAAI,EAAS,EAAG,KAAM,OAAQ,SAAQ,EAC5D,EAAU,CAAC,GAAG,EAAU,CAAO,EACrC,EAAY,CAAO,EACnB,EAAK,OAAO,CACV,UAAW,EAAQ,GACnB,iBACA,SAAU,EAAQ,IAAK,IAAO,CAAE,KAAM,EAAE,KAAM,QAAS,EAAE,OAAQ,EAAE,CACrE,CAAC,CACH,EACA,CAAC,EAAM,EAAgB,CAAQ,CACjC,EAEM,GAAA,EAAqB,gBAAwB,CACjD,EAAkB,EAAS,CAAC,EAC5B,EAAY,CAAC,CAAC,EACd,EAAS,EAAE,EACX,EAAS,CAAC,CAAC,EACX,EAAgB,IAAI,CACtB,EAAG,CAAC,CAAC,EAEC,GAAA,EAAmB,YACvB,KAAO,IAAoD,CACzD,GAAI,CACF,IAAM,EAAW,MAAM,EAAO,mBAAmB,EAAK,MAAM,EAC5D,EAAkB,EAAK,cAAc,EACrC,EAAY,EAAiB,EAAU,EAAE,oBAAoB,CAAC,CAAC,EAC/D,EAAS,CAAC,CAAC,EACX,EAAS,EAAE,EACX,EACE,EAAS,SAAW,WAAa,EAAS,SAAW,UAAY,EAAS,GAAK,IACjF,CACF,OAAS,EAAK,CACZ,EAAO,MAAM,CAAE,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAAE,CAAC,CAC5E,CACF,EACA,CAAC,EAAQ,EAAQ,CAAC,CACpB,EAgBA,GAbA,EAAM,cAAgB,CAEpB,GADI,IAAiB,MACjB,EAAK,UAAW,OACpB,IAAM,EAAO,EAAM,GACd,IACL,EAAU,GAAM,EAAE,MAAM,CAAC,CAAC,EAC1B,EAAS,EAAK,OAAO,EACvB,EAAG,CAAC,EAAc,EAAK,UAAW,EAAO,CAAQ,CAAC,EAE9C,CAAC,GACD,EAAS,WACT,EAAS,OACT,CAAC,EAAS,MAAM,SAChB,CAAC,EAAS,KAAK,QAAS,OAAO,KAEnC,IAAM,EAAe,EAAK,WAAa,GAAgB,KACjD,EAAY,EAAM,QAAU,EAE5B,MAAqB,CACzB,IAAM,EAAO,EAAM,KAAK,EACnB,KACL,IAAI,EAAc,CAChB,GAAI,EAAW,OACf,EAAU,GAAM,CAAC,GAAG,EAAG,CAAE,GAAI,EAAS,EAAG,QAAS,CAAK,CAAC,CAAC,EACzD,EAAS,EAAE,EACX,MACF,CACA,EAAS,EAAE,EACX,EAAS,CAAI,CAFb,CAGF,EAEM,EAAa,EAAS,KAAK,WAC3B,GACJ,GACA,EAAK,MAAM,SAAW,WACtB,EAAK,MAAM,SAAW,UAClB,EAAW,OAAO,EAAK,MAAM,UAAa,SAAW,EAAK,KAAK,SAAW,KAC1E,GAAe,EAAM,KAAK,CAAC,CAAC,SAAW,GAAM,GAAgB,EAEnE,OACE,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,wCACb,EAAA,EAAA,KAAA,CAAC,EAAD,CACE,YAAe,EAAQ,EAAI,EAC3B,aAAY,EAAE,uBAAuB,EACrC,UACE,uOAJJ,EAUE,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,iLACb,EAAE,uBAAuB,CACtB,CAAA,GACN,EAAA,EAAA,IAAA,CAAC,GAAD,CAAe,UAAU,iBAAmB,CAAA,CACtC,GACL,CAAA,GACL,EAAA,EAAA,IAAA,CAAC,GAAD,CAAa,OAAM,aAAc,YAC/B,EAAA,EAAA,KAAA,CAAC,GAAD,CAAc,gBAAA,GAAgB,UAAU,kDAAxC,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAa,UAAU,wCACrB,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,kCAAf,EACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,0BAAf,EACE,EAAA,EAAA,KAAA,CAAC,EAAD,CAAY,UAAU,mCAAtB,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAK,UAAU,QAAU,CAAA,EACxB,EAAE,mBAAmB,CACZ,KACZ,EAAA,EAAA,IAAA,CAAC,GAAD,CAAA,SAAmB,EAAE,yBAAyB,CAAoB,CAAA,CAC/D,KACL,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,8CAAf,CACG,IACC,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAA,CAAC,GAAD,CAAA,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,GAAD,CAAqB,QAAA,aACnB,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,SACL,QAAQ,QACR,KAAK,OACL,aAAY,EAAE,qBAAqB,YAEnC,EAAA,EAAA,IAAA,CAAC,GAAD,CAAS,UAAU,QAAU,CAAA,CACvB,CAAA,CACW,CAAA,GACrB,EAAA,EAAA,KAAA,CAAC,GAAD,CAAqB,MAAM,MAAM,UAAU,gBAA3C,EACE,EAAA,EAAA,IAAA,CAAC,GAAD,CAAA,SAAoB,EAAE,qBAAqB,CAAqB,CAAA,GAChE,EAAA,EAAA,IAAA,CAAC,GAAD,CAAwB,CAAA,EACvB,EAAQ,YACP,EAAA,EAAA,IAAA,CAAC,EAAD,CAAkB,SAAA,YAAU,EAAE,gBAAgB,CAAoB,CAAA,EAEnE,CAAC,EAAQ,YAAc,EAAQ,MAAM,QAAU,KAAO,IACrD,EAAA,EAAA,IAAA,CAAC,EAAD,CAAkB,SAAA,YAAU,EAAE,2BAA2B,CAAoB,CAAA,EAE9E,EAAQ,MAAM,IAAK,IAClB,EAAA,EAAA,KAAA,CAAC,EAAD,CAEE,UAAU,kCACV,aAAgB,CACd,GAAgB,CAAI,CACtB,WALF,EAOE,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,mDAA2C,EAAK,KAAY,CAAA,GAC5E,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,yCACb,IAAI,KAAK,eAAe,EAAQ,CAC/B,UAAW,QACX,UAAW,OACb,CAAC,CAAC,CAAC,OAAO,IAAI,KAAK,EAAK,SAAS,CAAC,CAC9B,CAAA,CACU,GAbX,EAAK,cAaM,CACnB,CACkB,GACT,CAAA,CAAA,GACd,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,SACL,QAAQ,QACR,KAAK,OACL,QAAS,GACT,aAAY,EAAE,qBAAqB,YAEnC,EAAA,EAAA,IAAA,CAAC,EAAD,CAAM,UAAU,QAAU,CAAA,CACpB,CAAA,CACR,CAAA,CAAA,GAEJ,EAAA,EAAA,IAAA,CAAC,GAAD,CAAY,QAAA,aACV,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,SACL,QAAQ,QACR,KAAK,OACL,aAAY,EAAE,cAAc,YAE5B,EAAA,EAAA,IAAA,CAAC,EAAD,CAAG,UAAU,QAAU,CAAA,CACjB,CAAA,CACE,CAAA,CACT,GACF,GACM,CAAA,EAEX,GAwBA,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,UAAU,6BACpB,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,qBAAf,CACG,EAAS,SAAW,IACnB,EAAA,EAAA,IAAA,CAAC,GAAD,CAAA,UACE,EAAA,EAAA,KAAA,CAAC,GAAD,CAAa,UAAU,uDAAvB,EACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,uCACZ,EAAE,uBAAuB,CACvB,CAAA,GACL,EAAA,EAAA,KAAA,CAAC,MAAD,CAAA,SAAA,CAAK,KAAG,EAAE,gCAAgC,CAAO,CAAA,CAAA,GACjD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAA,SAAA,CAAK,KAAG,EAAE,gCAAgC,CAAO,CAAA,CAAA,GACjD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAA,SAAA,CAAK,KAAG,EAAE,gCAAgC,CAAO,CAAA,CAAA,CACtC,GACT,CAAA,EAEP,EAAS,IAAK,IACb,EAAA,EAAA,IAAA,CAAC,MAAD,CAEE,UAAW,EAAQ,OAAS,OAAS,mBAAqB,+BAE1D,EAAA,EAAA,KAAA,CAAC,MAAD,CACE,UAAW,EAAQ,OAAS,OACxB,+EACA,kFAHN,CAKG,EAAQ,OAAS,aAChB,EAAA,EAAA,IAAA,CAAC,GAAD,CACE,MAAO,EAAQ,QACf,OAAO,WACP,UAAU,iDACX,CAAA,GAED,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,+BAAuB,EAAQ,OAAa,CAAA,EAE5D,EAAQ,WAAa,EAAQ,UAAU,OAAS,IAC/C,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,qCACZ,EAAQ,UAAU,MAAM,EAAG,CAAC,CAAC,CAAC,KAAK,EAAU,IAAU,CACtD,IAAM,EAAQ,GAAG,EAAS,aAAa,EAAS,SAAW,IAAI,EAAS,WAAa,KAC/E,EAAM,GAAG,EAAS,WAAW,GAAG,EAAS,UAAY,WAAW,GAAG,IAoBzE,OAnBI,EAAS,UAET,EAAA,EAAA,IAAA,CAAC,SAAD,CAEE,KAAK,SACL,UAAU,iBACV,YAAe,CACb,EAAQ,EAAK,EACb,EAAS,CACP,KAAM,OACN,WAAY,EAAS,WACrB,SAAU,EAAS,QACrB,CAAC,CACH,YAEA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAO,QAAQ,mBAAW,CAAa,CAAA,CACjC,EAbD,CAaC,GAIV,EAAA,EAAA,IAAA,CAAC,SAAD,CAEE,KAAK,SACL,UAAU,iBACV,YAAe,CACb,EAAQ,EAAK,EACb,EAAS,CAAE,KAAM,OAAQ,WAAY,EAAS,UAAW,CAAC,CAC5D,YAEA,EAAA,EAAA,IAAA,CAAC,EAAD,CAAO,QAAQ,mBAAW,CAAa,CAAA,CACjC,EATD,CASC,CAEZ,CAAC,CACE,CAAA,CAEJ,GACF,EA1DE,EAAQ,EA0DV,CACN,EACA,KACC,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,+BACb,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,2HAAf,EACE,EAAA,EAAA,IAAA,CAAC,GAAD,CAAS,UAAU,qBAAuB,CAAA,EACzC,GAAY,KAET,EAAE,sBAAsB,EADxB,EAAE,+BAAgC,CAAE,UAAS,CAAC,CAE/C,GACF,CAAA,CAEJ,GACK,CAAA,GAEZ,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,6DAAf,CACG,EAAM,OAAS,IACd,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,0BACZ,EAAM,KAAK,EAAM,KAChB,EAAA,EAAA,KAAA,CAAC,MAAD,CAEE,UAAU,4EAFZ,EAIE,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAU,+DAAhB,CAAsE,IAClE,EAAQ,EAAE,GACR,KACN,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAU,kBAAkB,MAAO,EAAK,iBAC3C,EAAK,OACF,CAAA,GACN,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,SACL,QAAQ,QACR,KAAK,OACL,UAAU,kBACV,YACE,EAAU,GAAM,EAAE,OAAQ,GAAU,EAAM,KAAO,EAAK,EAAE,CAAC,EAE3D,aAAY,EAAE,0BAA0B,YAExC,EAAA,EAAA,IAAA,CAAC,EAAD,CAAG,UAAU,UAAY,CAAA,CACnB,CAAA,CACL,GArBE,EAAK,EAqBP,CACN,CACE,CAAA,GAEP,EAAA,EAAA,KAAA,CAAC,OAAD,CACE,SAAW,GAAM,CACf,EAAE,eAAe,EACjB,EAAO,CACT,WAJF,EAME,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MAAO,EACP,SAAW,GAAM,EAAS,EAAE,OAAO,KAAK,EACxC,UAAY,GAAM,CACZ,EAAE,MAAQ,SAAW,CAAC,EAAE,UAAY,CAAC,EAAE,YAAY,cACrD,EAAE,eAAe,EACjB,EAAO,EAEX,EACA,KAAM,EACN,YAAa,EAAE,+BAA+B,EAC9C,aAAY,EAAE,+BAA+B,EAC7C,UACE,qLAIH,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,KAAK,SACL,SAAU,GACV,aAAY,EAAE,kBAAkB,EAChC,UAAU,6CAEV,EAAA,EAAA,IAAA,CAAC,GAAD,CAAM,UAAU,QAAU,CAAA,CACpB,CAAA,CACL,KACL,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAU,mDACV,EAAE,wBAAwB,CAC1B,CAAA,CACC,GACH,GACL,CAAA,CAAA,GAtLF,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,mFAAf,EACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,qBAAf,EACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,iCACZ,EAAE,iCAAiC,CACjC,CAAA,GACL,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,yCACZ,EAAE,uCAAuC,CACvC,CAAA,CACF,IACJ,EAAS,KAAK,YACb,EAAA,EAAA,KAAA,CAAC,EAAD,CACE,QAAQ,UACR,YAAe,CACb,EAAQ,EAAK,EACb,EAAS,CAAE,KAAM,WAAY,QAAS,cAAe,CAAC,CACxD,WALF,EAOE,EAAA,EAAA,IAAA,CAAC,GAAD,CAAU,UAAU,QAAU,CAAA,EAC7B,EAAE,wCAAwC,CACrC,GAEP,GAmKK,GACT,CAAA,CACP,CAAA,CAAA,CAEN"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n,s as r}from"./with-selector-NDLhu4dT.js";import{n as i,r as a,t as o}from"./key-lbuX0yk_.js";import{n as s,t as c}from"./trash-2-DhyKQCKv.js";import{n as l,t as u}from"./pencil-BO-k3yTG.js";import{t as d}from"./file-text-CcLZq8HA.js";import{t as f}from"./key-round-CQLp1Ua9.js";import{t as p}from"./x-C3kLunD6.js";import{s as m}from"./dist-Dpg8xy_r.js";import{a as h,i as g,n as _,r as v,t as y}from"./select-BWf6walM.js";import{a as b,c as x,i as S,n as C,o as w,r as T,s as E,t as D}from"./calendar-BZVUWakC.js";import{K as O,L as k,U as A,W as j,et as M,n as N,q as P}from"./router-wFvtgo77.js";import{a as ee,i as te,n as F}from"./useQueries-BCUhWYX4.js";import{a as I,i as L,n as R,t as z}from"./card-4cS4uWCP.js";import{a as ne,i as re,n as ie,r as ae,t as B}from"./user-directory-9COoW6AO.js";import{H as V,Y as H,at as oe,ot as se,vt as ce}from"./index-t4woXGa-.js";var U=e(t(),1),W=n(),G=`yyyy-MM-dd`,K=`MMM d, yyyy`;function q(e){if(!e)return;let t=e.length<=10?S(e,G,new Date):C(e);return E(t)?t:void 0}function J({from:e,to:t,onChange:n,disabled:i,className:o,labels:s={}}){let c=s.placeholder??`Select date range`,l=s.apply??`Apply`,u=s.clear??`Clear`,[d,f]=U.useState(!1),[h,g]=U.useState(()=>{let n=q(e),r=q(t);return n&&r?{from:n,to:r}:void 0});U.useEffect(()=>{let n=q(e),r=q(t);g(n??r?{from:n,to:r}:void 0)},[e,t]);let[_,v]=U.useState(()=>typeof window<`u`&&window.innerWidth>=640?2:1);U.useEffect(()=>{let e=window.matchMedia(`(min-width: 640px)`),t=e=>v(e.matches?2:1);return v(e.matches?2:1),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]);let[y,S]=U.useState(()=>w(q(e)??new Date)),[C,E]=U.useState(()=>{let n=q(e),r=q(t),i=w(n??new Date);return r&&!T(i,r)?w(r):x(i,1)}),O=n=>{if(n){let n=q(e),r=q(t);g(n&&r?{from:n,to:r}:void 0);let i=w(n??new Date),a=r&&!T(i,r)?w(r):x(i,1);S(i),E(a)}else{let n=q(e),r=q(t);g(n??r?{from:n,to:r}:void 0)}f(n)},k=e=>{S(e),!T(e,C)&&e>C&&E(x(e,1))},A=e=>{E(e),!T(e,y)&&e<y&&S(x(e,-1))},j=e=>{g(e)},M=()=>{h?.from&&n(b(h.from,G),h.to?b(h.to,G):``),f(!1)},N=()=>{g(void 0),n(``,``),f(!1)},P=e=>{e.stopPropagation(),g(void 0),n(``,``)},I=q(e),L=q(t),R=!!(e||t),z=R?[I?b(I,K):`…`,L?b(L,K):`…`].join(` – `):null;return(0,W.jsxs)(F,{open:d,onOpenChange:O,children:[(0,W.jsx)(ee,{asChild:!0,children:(0,W.jsxs)(m,{type:`button`,variant:`outline`,disabled:i,className:r(`h-9 w-full justify-start gap-2 px-3 font-normal`,!R&&`text-muted-foreground`,o),children:[(0,W.jsx)(a,{className:`size-4 shrink-0 opacity-60`}),(0,W.jsx)(`span`,{className:`flex-1 truncate text-left text-sm`,children:z??c}),R&&(0,W.jsx)(`span`,{role:`button`,tabIndex:0,"aria-label":u,onClick:P,onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&P(e)},className:`ml-1 rounded-sm p-0.5 opacity-50 hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring`,children:(0,W.jsx)(p,{className:`size-3.5`})})]})}),(0,W.jsxs)(te,{className:`w-auto p-0`,align:`start`,children:[_===2?(0,W.jsxs)(`div`,{className:`flex flex-col sm:flex-row`,children:[(0,W.jsx)(D,{mode:`range`,selected:h,onSelect:j,numberOfMonths:1,month:y,onMonthChange:k,endMonth:C,autoFocus:!0}),(0,W.jsx)(D,{mode:`range`,selected:h,onSelect:j,numberOfMonths:1,month:C,onMonthChange:A,startMonth:y})]}):(0,W.jsx)(D,{mode:`range`,selected:h,onSelect:j,numberOfMonths:1,defaultMonth:h?.from??h?.to??void 0,autoFocus:!0}),(0,W.jsxs)(`div`,{className:`flex items-center justify-between border-t border-border px-3 py-2`,children:[(0,W.jsx)(m,{variant:`ghost`,size:`sm`,onClick:N,children:u}),(0,W.jsx)(m,{size:`sm`,onClick:M,disabled:!h?.from,children:l})]})]})]})}J.displayName=`DateRangeInput`;var Y=`__all__`,le=[`new`,`edit`,`delete`,`bulkDelete`,`login`,`apiKey.create`,`apiKey.update`,`apiKey.delete`],X={__auth__:`audit:virtualResource.auth`,__api_keys__:`audit:virtualResource.apiKeys`},ue={Icon:d,iconClass:`text-muted-foreground`,bgClass:`bg-muted`,titleKey:``},Z={new:{Icon:i,iconClass:`text-blue-600 dark:text-blue-300`,bgClass:`bg-blue-100 dark:bg-blue-950/40`,titleKey:`audit:action.new`},edit:{Icon:u,iconClass:`text-emerald-600 dark:text-emerald-300`,bgClass:`bg-emerald-100 dark:bg-emerald-950/40`,titleKey:`audit:action.edit`},delete:{Icon:c,iconClass:`text-rose-600 dark:text-rose-300`,bgClass:`bg-rose-100 dark:bg-rose-950/40`,titleKey:`audit:action.delete`},bulkDelete:{Icon:c,iconClass:`text-rose-600 dark:text-rose-300`,bgClass:`bg-rose-100 dark:bg-rose-950/40`,titleKey:`audit:action.bulkDelete`},login:{Icon:oe,iconClass:`text-violet-600 dark:text-violet-300`,bgClass:`bg-violet-100 dark:bg-violet-950/40`,titleKey:`audit:action.login`},"apiKey.create":{Icon:o,iconClass:`text-amber-600 dark:text-amber-300`,bgClass:`bg-amber-100 dark:bg-amber-950/40`,titleKey:`audit:action.apiKeyCreate`},"apiKey.update":{Icon:f,iconClass:`text-amber-600 dark:text-amber-300`,bgClass:`bg-amber-100 dark:bg-amber-950/40`,titleKey:`audit:action.apiKeyUpdate`},"apiKey.delete":{Icon:c,iconClass:`text-rose-600 dark:text-rose-300`,bgClass:`bg-rose-100 dark:bg-rose-950/40`,titleKey:`audit:action.apiKeyDelete`}};function de(e){let t=U.useMemo(()=>new Intl.RelativeTimeFormat(e,{numeric:`auto`}),[e]),n=U.useMemo(()=>new Intl.DateTimeFormat(e,{dateStyle:`medium`,timeStyle:`short`}),[e]);return U.useCallback((e,r)=>{let i=Math.round((r-e)/1e3);return i<45?t.format(-Math.max(i,0),`second`):i<3600?t.format(-Math.round(i/60),`minute`):i<86400?t.format(-Math.round(i/3600),`hour`):i<86400*7?t.format(-Math.round(i/86400),`day`):n.format(new Date(e))},[n,t])}var Q=e=>{let t=e.split(/\s+|[._@-]/).filter(Boolean);return t.length===0?`?`:t.length===1?t[0].slice(0,2).toUpperCase():(t[0][0]+t[1][0]).toUpperCase()},$=25;function fe(){let{t:e,locale:t}=M(),n=P(),[r,i]=U.useState({}),a=k(r,$),o=U.useMemo(()=>(a.data?.pages??[]).flatMap(e=>e.events.slice(0,$)),[a.data]),s=ie(U.useMemo(()=>Array.from(new Set(o.map(e=>e.userId).filter(e=>!!e))),[o])),c=U.useMemo(()=>{let e={};for(let t of n)e[t.id]=t;return e},[n]),l=n.some(e=>e.id===B),u=de(t),d=U.useCallback(e=>new Intl.DateTimeFormat(t,{dateStyle:`medium`,timeStyle:`short`}).format(new Date(e)),[t]),f=U.useMemo(()=>Date.now(),[o]),p=U.useRef(null),{hasNextPage:m,isFetchingNextPage:b,fetchNextPage:x}=a;U.useEffect(()=>{let e=p.current;if(!e)return;let t=new IntersectionObserver(e=>{e[0]?.isIntersecting&&m&&!b&&x()},{rootMargin:`200px`});return t.observe(e),()=>t.disconnect()},[m,b,x]);let S=e=>{i(t=>({...t,...e}))};return(0,W.jsxs)(`div`,{className:`space-y-2 sm:space-y-4`,children:[(0,W.jsxs)(z,{children:[(0,W.jsx)(L,{children:(0,W.jsx)(I,{children:e(`audit:title`)})}),(0,W.jsxs)(R,{className:`grid gap-3 sm:grid-cols-2 md:grid-cols-4`,children:[(0,W.jsxs)(y,{value:r.resourceId??Y,onValueChange:e=>S({resourceId:e===Y?void 0:e}),children:[(0,W.jsx)(g,{"aria-label":e(`audit:resource`),children:(0,W.jsx)(h,{})}),(0,W.jsxs)(_,{children:[(0,W.jsx)(v,{value:Y,children:e(`audit:allResources`)}),Object.entries(X).map(([t,n])=>(0,W.jsx)(v,{value:t,children:e(n)},t)),n.map(e=>(0,W.jsxs)(v,{value:e.id,children:[e.name,e.name!==e.id&&(0,W.jsxs)(`span`,{className:`ml-1.5 text-xs text-muted-foreground`,children:[`(`,e.id,`)`]})]},e.id))]})]}),(0,W.jsxs)(y,{value:r.actions?.[0]??Y,onValueChange:e=>S({actions:e===Y?void 0:[e]}),children:[(0,W.jsx)(g,{"aria-label":e(`audit:action`),children:(0,W.jsx)(h,{})}),(0,W.jsxs)(_,{children:[(0,W.jsx)(v,{value:Y,children:e(`audit:allActions`)}),le.map(t=>{let n=Z[t];return(0,W.jsx)(v,{value:t,children:n?.titleKey?e(n.titleKey):t},t)})]})]}),(0,W.jsx)(H,{value:r.recordId??``,placeholder:e(`audit:recordId`),onChange:e=>S({recordId:e.target.value||void 0})}),(0,W.jsx)(H,{value:r.userId??``,placeholder:e(`audit:userId`),onChange:e=>S({userId:e.target.value||void 0})}),(0,W.jsx)(J,{from:r.from,to:r.to,onChange:(e,t)=>S({from:e||void 0,to:t||void 0}),className:`sm:col-span-2 md:col-span-4`,labels:{placeholder:e(`audit:dateRangePlaceholder`),apply:e(`common:apply`),clear:e(`common:clear`)}})]})]}),(0,W.jsx)(z,{children:(0,W.jsxs)(R,{children:[a.isLoading?(0,W.jsxs)(`div`,{className:`space-y-3`,children:[(0,W.jsx)(V,{className:`h-20 w-full`}),(0,W.jsx)(V,{className:`h-20 w-full`}),(0,W.jsx)(V,{className:`h-20 w-full`})]}):a.isError?(0,W.jsx)(`div`,{className:`rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive`,children:e(`audit:loadError`)}):o.length===0?(0,W.jsx)(`div`,{className:`rounded-md border border-dashed border-border p-6 text-center text-sm text-muted-foreground`,children:e(`audit:noEvents`)}):(0,W.jsx)(`ol`,{className:`space-y-3`,children:o.map((e,t)=>(0,W.jsx)(pe,{entry:e,resource:c[e.resourceId],user:e.userId?s.get(e.userId)??null:null,userResourceId:l?B:void 0,now:f,formatRelative:u,formatAbsolute:d},e.id??`${e.at}:${t}`))}),(0,W.jsx)(`div`,{ref:p,className:`h-1`,"aria-hidden":!0}),a.isFetchingNextPage&&(0,W.jsx)(`div`,{className:`flex justify-center py-4`,children:(0,W.jsx)(se,{className:`size-5 animate-spin text-muted-foreground`})})]})})]})}function pe({entry:e,resource:t,user:n,userResourceId:i,now:a,formatRelative:o,formatAbsolute:c}){let{t:u}=M(),[d,f]=U.useState(!1),p=Z[e.action]??ue,h=p.Icon,g=p.titleKey?u(p.titleKey):e.action,_=ae(n,e.userId??u(`history:unknownUser`)),v=u(`audit:by`,{user:`\0`}).split(`\0`),y=v[0]??``,b=v[1]??``,x=X[e.resourceId],S=x?u(x):t?.name??e.resourceId,C=e.action===`edit`&&!!e.recordId;return(0,W.jsx)(`li`,{className:`rounded-lg border border-border bg-card p-4 shadow-sm`,children:(0,W.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,W.jsx)(`span`,{className:r(`flex size-10 shrink-0 items-center justify-center rounded-full`,p.bgClass),children:(0,W.jsx)(h,{className:r(`size-5`,p.iconClass)})}),(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,W.jsxs)(`div`,{className:`min-w-0`,children:[(0,W.jsx)(`p`,{className:`truncate text-sm font-semibold`,children:g}),(0,W.jsxs)(`div`,{className:`mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground`,children:[(0,W.jsx)(`span`,{className:`font-medium`,children:S}),e.recordId&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`span`,{"aria-hidden":!0,children:`·`}),t?(0,W.jsxs)(N,{to:{name:`show`,resourceId:e.resourceId,recordId:e.recordId},className:`inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground hover:underline`,title:e.recordId,children:[(0,W.jsx)(`span`,{className:`truncate max-w-[12rem]`,children:e.recordTitle??`#${e.recordId}`}),(0,W.jsx)(l,{className:`size-3 shrink-0`})]}):e.recordTitle?(0,W.jsx)(`span`,{className:`truncate max-w-[12rem] rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground`,title:e.recordId,children:e.recordTitle}):null]}),!e.recordId&&e.recordIds?.length?(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`span`,{"aria-hidden":!0,children:`·`}),(0,W.jsxs)(`span`,{children:[e.recordIds.length,` `,u(`audit:records`)]})]}):null]}),(0,W.jsxs)(`p`,{className:`mt-1 flex items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,W.jsx)(`span`,{"aria-hidden":!0,className:`inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary`,children:Q(_)}),(0,W.jsxs)(`span`,{className:`truncate`,children:[y,i&&e.userId?(0,W.jsx)(N,{to:{name:`show`,resourceId:i,recordId:e.userId},className:`font-medium text-foreground hover:underline`,children:_}):_,b]})]})]}),(0,W.jsx)(`time`,{className:`shrink-0 text-xs text-muted-foreground`,dateTime:new Date(e.at).toISOString(),title:c(e.at),children:o(e.at,a)})]}),C&&(0,W.jsxs)(`div`,{className:`mt-3`,children:[(0,W.jsxs)(m,{variant:`ghost`,size:`sm`,className:`-ml-2 h-7 px-2 text-xs`,onClick:()=>f(e=>!e),"aria-expanded":d,children:[d?(0,W.jsx)(s,{className:`size-3.5`}):(0,W.jsx)(ce,{className:`size-3.5`}),u(d?`audit:hideChanges`:`audit:viewChanges`)]}),d&&(0,W.jsx)(`div`,{className:`mt-2`,children:(0,W.jsx)(me,{resourceId:e.resourceId,recordId:e.recordId,atMs:e.at})})]})]})]})})}function me({resourceId:e,recordId:t,atMs:n}){let{t:r}=M(),i=O(e),a=j(e,t,{limit:50}),o=A(e,t),s=U.useMemo(()=>{let e={};for(let t of i?.properties??[])e[t.path]=t.label;return e},[i]);if(a.isLoading)return(0,W.jsx)(V,{className:`h-16 w-full`});if(a.isError)return(0,W.jsx)(`div`,{className:`rounded-md border border-destructive/50 bg-destructive/10 p-3 text-xs text-destructive`,children:r(`audit:loadDiffError`)});let c=ge(a.data?.revisions??[],n);if(!c)return(0,W.jsx)(`div`,{className:`rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground`,children:r(`audit:noDiff`)});let l=re(c.snapshotBefore??{},c.snapshot).map(e=>({...e,label:s[e.path]})),u=o.data?.record?.title;return(0,W.jsxs)(`div`,{className:`space-y-2`,children:[u&&u!==t&&(0,W.jsx)(`p`,{className:`truncate text-xs text-muted-foreground`,title:u,children:u}),(0,W.jsx)(ne,{fields:l,labels:{added:r(`diff:added`),changed:r(`diff:changed`),removed:r(`diff:removed`),before:r(`diff:before`),after:r(`diff:after`),noChanges:r(`diff:noChanges`)}})]})}var he=6e4,ge=(e,t)=>{let n=null,r=1/0;for(let i of e){let e=new Date(i.createdAt).getTime();if(Number.isNaN(e))continue;let a=Math.abs(e-t);a<r&&a<=he&&(r=a,n=i)}return n};export{fe as AuditLogPage};
|
|
2
|
-
//# sourceMappingURL=audit-log-page-
|
|
1
|
+
import{a as e,n as t}from"./createLucideIcon-CUopU_LC.js";import{S as n,s as r}from"./with-selector-NDLhu4dT.js";import{n as i,r as a,t as o}from"./key-lbuX0yk_.js";import{n as s,t as c}from"./trash-2-DhyKQCKv.js";import{n as l,t as u}from"./pencil-BO-k3yTG.js";import{t as d}from"./file-text-CcLZq8HA.js";import{t as f}from"./key-round-CQLp1Ua9.js";import{t as p}from"./x-C3kLunD6.js";import{s as m}from"./dist-Dpg8xy_r.js";import{a as h,i as g,n as _,r as v,t as y}from"./select-B30EsRTA.js";import{a as b,c as x,i as S,n as C,o as w,r as T,s as E,t as D}from"./calendar-DxUwrP2r.js";import{K as O,L as k,U as A,W as j,et as M,n as N,q as P}from"./router-wFvtgo77.js";import{a as ee,i as te,n as F}from"./useQueries-BCUhWYX4.js";import{a as I,i as L,n as R,t as z}from"./card-4cS4uWCP.js";import{a as ne,i as re,n as ie,r as ae,t as B}from"./user-directory-9COoW6AO.js";import{H as V,Y as H,at as oe,ot as se,vt as ce}from"./index-C3MuojaM.js";var U=e(t(),1),W=n(),G=`yyyy-MM-dd`,K=`MMM d, yyyy`;function q(e){if(!e)return;let t=e.length<=10?S(e,G,new Date):C(e);return E(t)?t:void 0}function J({from:e,to:t,onChange:n,disabled:i,className:o,labels:s={}}){let c=s.placeholder??`Select date range`,l=s.apply??`Apply`,u=s.clear??`Clear`,[d,f]=U.useState(!1),[h,g]=U.useState(()=>{let n=q(e),r=q(t);return n&&r?{from:n,to:r}:void 0});U.useEffect(()=>{let n=q(e),r=q(t);g(n??r?{from:n,to:r}:void 0)},[e,t]);let[_,v]=U.useState(()=>typeof window<`u`&&window.innerWidth>=640?2:1);U.useEffect(()=>{let e=window.matchMedia(`(min-width: 640px)`),t=e=>v(e.matches?2:1);return v(e.matches?2:1),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]);let[y,S]=U.useState(()=>w(q(e)??new Date)),[C,E]=U.useState(()=>{let n=q(e),r=q(t),i=w(n??new Date);return r&&!T(i,r)?w(r):x(i,1)}),O=n=>{if(n){let n=q(e),r=q(t);g(n&&r?{from:n,to:r}:void 0);let i=w(n??new Date),a=r&&!T(i,r)?w(r):x(i,1);S(i),E(a)}else{let n=q(e),r=q(t);g(n??r?{from:n,to:r}:void 0)}f(n)},k=e=>{S(e),!T(e,C)&&e>C&&E(x(e,1))},A=e=>{E(e),!T(e,y)&&e<y&&S(x(e,-1))},j=e=>{g(e)},M=()=>{h?.from&&n(b(h.from,G),h.to?b(h.to,G):``),f(!1)},N=()=>{g(void 0),n(``,``),f(!1)},P=e=>{e.stopPropagation(),g(void 0),n(``,``)},I=q(e),L=q(t),R=!!(e||t),z=R?[I?b(I,K):`…`,L?b(L,K):`…`].join(` – `):null;return(0,W.jsxs)(F,{open:d,onOpenChange:O,children:[(0,W.jsx)(ee,{asChild:!0,children:(0,W.jsxs)(m,{type:`button`,variant:`outline`,disabled:i,className:r(`h-9 w-full justify-start gap-2 px-3 font-normal`,!R&&`text-muted-foreground`,o),children:[(0,W.jsx)(a,{className:`size-4 shrink-0 opacity-60`}),(0,W.jsx)(`span`,{className:`flex-1 truncate text-left text-sm`,children:z??c}),R&&(0,W.jsx)(`span`,{role:`button`,tabIndex:0,"aria-label":u,onClick:P,onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&P(e)},className:`ml-1 rounded-sm p-0.5 opacity-50 hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring`,children:(0,W.jsx)(p,{className:`size-3.5`})})]})}),(0,W.jsxs)(te,{className:`w-auto p-0`,align:`start`,children:[_===2?(0,W.jsxs)(`div`,{className:`flex flex-col sm:flex-row`,children:[(0,W.jsx)(D,{mode:`range`,selected:h,onSelect:j,numberOfMonths:1,month:y,onMonthChange:k,endMonth:C,autoFocus:!0}),(0,W.jsx)(D,{mode:`range`,selected:h,onSelect:j,numberOfMonths:1,month:C,onMonthChange:A,startMonth:y})]}):(0,W.jsx)(D,{mode:`range`,selected:h,onSelect:j,numberOfMonths:1,defaultMonth:h?.from??h?.to??void 0,autoFocus:!0}),(0,W.jsxs)(`div`,{className:`flex items-center justify-between border-t border-border px-3 py-2`,children:[(0,W.jsx)(m,{variant:`ghost`,size:`sm`,onClick:N,children:u}),(0,W.jsx)(m,{size:`sm`,onClick:M,disabled:!h?.from,children:l})]})]})]})}J.displayName=`DateRangeInput`;var Y=`__all__`,le=[`new`,`edit`,`delete`,`bulkDelete`,`login`,`apiKey.create`,`apiKey.update`,`apiKey.delete`],X={__auth__:`audit:virtualResource.auth`,__api_keys__:`audit:virtualResource.apiKeys`},ue={Icon:d,iconClass:`text-muted-foreground`,bgClass:`bg-muted`,titleKey:``},Z={new:{Icon:i,iconClass:`text-blue-600 dark:text-blue-300`,bgClass:`bg-blue-100 dark:bg-blue-950/40`,titleKey:`audit:action.new`},edit:{Icon:u,iconClass:`text-emerald-600 dark:text-emerald-300`,bgClass:`bg-emerald-100 dark:bg-emerald-950/40`,titleKey:`audit:action.edit`},delete:{Icon:c,iconClass:`text-rose-600 dark:text-rose-300`,bgClass:`bg-rose-100 dark:bg-rose-950/40`,titleKey:`audit:action.delete`},bulkDelete:{Icon:c,iconClass:`text-rose-600 dark:text-rose-300`,bgClass:`bg-rose-100 dark:bg-rose-950/40`,titleKey:`audit:action.bulkDelete`},login:{Icon:oe,iconClass:`text-violet-600 dark:text-violet-300`,bgClass:`bg-violet-100 dark:bg-violet-950/40`,titleKey:`audit:action.login`},"apiKey.create":{Icon:o,iconClass:`text-amber-600 dark:text-amber-300`,bgClass:`bg-amber-100 dark:bg-amber-950/40`,titleKey:`audit:action.apiKeyCreate`},"apiKey.update":{Icon:f,iconClass:`text-amber-600 dark:text-amber-300`,bgClass:`bg-amber-100 dark:bg-amber-950/40`,titleKey:`audit:action.apiKeyUpdate`},"apiKey.delete":{Icon:c,iconClass:`text-rose-600 dark:text-rose-300`,bgClass:`bg-rose-100 dark:bg-rose-950/40`,titleKey:`audit:action.apiKeyDelete`}};function de(e){let t=U.useMemo(()=>new Intl.RelativeTimeFormat(e,{numeric:`auto`}),[e]),n=U.useMemo(()=>new Intl.DateTimeFormat(e,{dateStyle:`medium`,timeStyle:`short`}),[e]);return U.useCallback((e,r)=>{let i=Math.round((r-e)/1e3);return i<45?t.format(-Math.max(i,0),`second`):i<3600?t.format(-Math.round(i/60),`minute`):i<86400?t.format(-Math.round(i/3600),`hour`):i<86400*7?t.format(-Math.round(i/86400),`day`):n.format(new Date(e))},[n,t])}var Q=e=>{let t=e.split(/\s+|[._@-]/).filter(Boolean);return t.length===0?`?`:t.length===1?t[0].slice(0,2).toUpperCase():(t[0][0]+t[1][0]).toUpperCase()},$=25;function fe(){let{t:e,locale:t}=M(),n=P(),[r,i]=U.useState({}),a=k(r,$),o=U.useMemo(()=>(a.data?.pages??[]).flatMap(e=>e.events.slice(0,$)),[a.data]),s=ie(U.useMemo(()=>Array.from(new Set(o.map(e=>e.userId).filter(e=>!!e))),[o])),c=U.useMemo(()=>{let e={};for(let t of n)e[t.id]=t;return e},[n]),l=n.some(e=>e.id===B),u=de(t),d=U.useCallback(e=>new Intl.DateTimeFormat(t,{dateStyle:`medium`,timeStyle:`short`}).format(new Date(e)),[t]),f=U.useMemo(()=>Date.now(),[o]),p=U.useRef(null),{hasNextPage:m,isFetchingNextPage:b,fetchNextPage:x}=a;U.useEffect(()=>{let e=p.current;if(!e)return;let t=new IntersectionObserver(e=>{e[0]?.isIntersecting&&m&&!b&&x()},{rootMargin:`200px`});return t.observe(e),()=>t.disconnect()},[m,b,x]);let S=e=>{i(t=>({...t,...e}))};return(0,W.jsxs)(`div`,{className:`space-y-2 sm:space-y-4`,children:[(0,W.jsxs)(z,{children:[(0,W.jsx)(L,{children:(0,W.jsx)(I,{children:e(`audit:title`)})}),(0,W.jsxs)(R,{className:`grid gap-3 sm:grid-cols-2 md:grid-cols-4`,children:[(0,W.jsxs)(y,{value:r.resourceId??Y,onValueChange:e=>S({resourceId:e===Y?void 0:e}),children:[(0,W.jsx)(g,{"aria-label":e(`audit:resource`),children:(0,W.jsx)(h,{})}),(0,W.jsxs)(_,{children:[(0,W.jsx)(v,{value:Y,children:e(`audit:allResources`)}),Object.entries(X).map(([t,n])=>(0,W.jsx)(v,{value:t,children:e(n)},t)),n.map(e=>(0,W.jsxs)(v,{value:e.id,children:[e.name,e.name!==e.id&&(0,W.jsxs)(`span`,{className:`ml-1.5 text-xs text-muted-foreground`,children:[`(`,e.id,`)`]})]},e.id))]})]}),(0,W.jsxs)(y,{value:r.actions?.[0]??Y,onValueChange:e=>S({actions:e===Y?void 0:[e]}),children:[(0,W.jsx)(g,{"aria-label":e(`audit:action`),children:(0,W.jsx)(h,{})}),(0,W.jsxs)(_,{children:[(0,W.jsx)(v,{value:Y,children:e(`audit:allActions`)}),le.map(t=>{let n=Z[t];return(0,W.jsx)(v,{value:t,children:n?.titleKey?e(n.titleKey):t},t)})]})]}),(0,W.jsx)(H,{value:r.recordId??``,placeholder:e(`audit:recordId`),onChange:e=>S({recordId:e.target.value||void 0})}),(0,W.jsx)(H,{value:r.userId??``,placeholder:e(`audit:userId`),onChange:e=>S({userId:e.target.value||void 0})}),(0,W.jsx)(J,{from:r.from,to:r.to,onChange:(e,t)=>S({from:e||void 0,to:t||void 0}),className:`sm:col-span-2 md:col-span-4`,labels:{placeholder:e(`audit:dateRangePlaceholder`),apply:e(`common:apply`),clear:e(`common:clear`)}})]})]}),(0,W.jsx)(z,{children:(0,W.jsxs)(R,{children:[a.isLoading?(0,W.jsxs)(`div`,{className:`space-y-3`,children:[(0,W.jsx)(V,{className:`h-20 w-full`}),(0,W.jsx)(V,{className:`h-20 w-full`}),(0,W.jsx)(V,{className:`h-20 w-full`})]}):a.isError?(0,W.jsx)(`div`,{className:`rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive`,children:e(`audit:loadError`)}):o.length===0?(0,W.jsx)(`div`,{className:`rounded-md border border-dashed border-border p-6 text-center text-sm text-muted-foreground`,children:e(`audit:noEvents`)}):(0,W.jsx)(`ol`,{className:`space-y-3`,children:o.map((e,t)=>(0,W.jsx)(pe,{entry:e,resource:c[e.resourceId],user:e.userId?s.get(e.userId)??null:null,userResourceId:l?B:void 0,now:f,formatRelative:u,formatAbsolute:d},e.id??`${e.at}:${t}`))}),(0,W.jsx)(`div`,{ref:p,className:`h-1`,"aria-hidden":!0}),a.isFetchingNextPage&&(0,W.jsx)(`div`,{className:`flex justify-center py-4`,children:(0,W.jsx)(se,{className:`size-5 animate-spin text-muted-foreground`})})]})})]})}function pe({entry:e,resource:t,user:n,userResourceId:i,now:a,formatRelative:o,formatAbsolute:c}){let{t:u}=M(),[d,f]=U.useState(!1),p=Z[e.action]??ue,h=p.Icon,g=p.titleKey?u(p.titleKey):e.action,_=ae(n,e.userId??u(`history:unknownUser`)),v=u(`audit:by`,{user:`\0`}).split(`\0`),y=v[0]??``,b=v[1]??``,x=X[e.resourceId],S=x?u(x):t?.name??e.resourceId,C=e.action===`edit`&&!!e.recordId;return(0,W.jsx)(`li`,{className:`rounded-lg border border-border bg-card p-4 shadow-sm`,children:(0,W.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,W.jsx)(`span`,{className:r(`flex size-10 shrink-0 items-center justify-center rounded-full`,p.bgClass),children:(0,W.jsx)(h,{className:r(`size-5`,p.iconClass)})}),(0,W.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,W.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,W.jsxs)(`div`,{className:`min-w-0`,children:[(0,W.jsx)(`p`,{className:`truncate text-sm font-semibold`,children:g}),(0,W.jsxs)(`div`,{className:`mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground`,children:[(0,W.jsx)(`span`,{className:`font-medium`,children:S}),e.recordId&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`span`,{"aria-hidden":!0,children:`·`}),t?(0,W.jsxs)(N,{to:{name:`show`,resourceId:e.resourceId,recordId:e.recordId},className:`inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground hover:underline`,title:e.recordId,children:[(0,W.jsx)(`span`,{className:`truncate max-w-[12rem]`,children:e.recordTitle??`#${e.recordId}`}),(0,W.jsx)(l,{className:`size-3 shrink-0`})]}):e.recordTitle?(0,W.jsx)(`span`,{className:`truncate max-w-[12rem] rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground`,title:e.recordId,children:e.recordTitle}):null]}),!e.recordId&&e.recordIds?.length?(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`span`,{"aria-hidden":!0,children:`·`}),(0,W.jsxs)(`span`,{children:[e.recordIds.length,` `,u(`audit:records`)]})]}):null]}),(0,W.jsxs)(`p`,{className:`mt-1 flex items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,W.jsx)(`span`,{"aria-hidden":!0,className:`inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary`,children:Q(_)}),(0,W.jsxs)(`span`,{className:`truncate`,children:[y,i&&e.userId?(0,W.jsx)(N,{to:{name:`show`,resourceId:i,recordId:e.userId},className:`font-medium text-foreground hover:underline`,children:_}):_,b]})]})]}),(0,W.jsx)(`time`,{className:`shrink-0 text-xs text-muted-foreground`,dateTime:new Date(e.at).toISOString(),title:c(e.at),children:o(e.at,a)})]}),C&&(0,W.jsxs)(`div`,{className:`mt-3`,children:[(0,W.jsxs)(m,{variant:`ghost`,size:`sm`,className:`-ml-2 h-7 px-2 text-xs`,onClick:()=>f(e=>!e),"aria-expanded":d,children:[d?(0,W.jsx)(s,{className:`size-3.5`}):(0,W.jsx)(ce,{className:`size-3.5`}),u(d?`audit:hideChanges`:`audit:viewChanges`)]}),d&&(0,W.jsx)(`div`,{className:`mt-2`,children:(0,W.jsx)(me,{resourceId:e.resourceId,recordId:e.recordId,atMs:e.at})})]})]})]})})}function me({resourceId:e,recordId:t,atMs:n}){let{t:r}=M(),i=O(e),a=j(e,t,{limit:50}),o=A(e,t),s=U.useMemo(()=>{let e={};for(let t of i?.properties??[])e[t.path]=t.label;return e},[i]);if(a.isLoading)return(0,W.jsx)(V,{className:`h-16 w-full`});if(a.isError)return(0,W.jsx)(`div`,{className:`rounded-md border border-destructive/50 bg-destructive/10 p-3 text-xs text-destructive`,children:r(`audit:loadDiffError`)});let c=ge(a.data?.revisions??[],n);if(!c)return(0,W.jsx)(`div`,{className:`rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground`,children:r(`audit:noDiff`)});let l=re(c.snapshotBefore??{},c.snapshot).map(e=>({...e,label:s[e.path]})),u=o.data?.record?.title;return(0,W.jsxs)(`div`,{className:`space-y-2`,children:[u&&u!==t&&(0,W.jsx)(`p`,{className:`truncate text-xs text-muted-foreground`,title:u,children:u}),(0,W.jsx)(ne,{fields:l,labels:{added:r(`diff:added`),changed:r(`diff:changed`),removed:r(`diff:removed`),before:r(`diff:before`),after:r(`diff:after`),noChanges:r(`diff:noChanges`)}})]})}var he=6e4,ge=(e,t)=>{let n=null,r=1/0;for(let i of e){let e=new Date(i.createdAt).getTime();if(Number.isNaN(e))continue;let a=Math.abs(e-t);a<r&&a<=he&&(r=a,n=i)}return n};export{fe as AuditLogPage};
|
|
2
|
+
//# sourceMappingURL=audit-log-page-GHzf7mLS.js.map
|
|
Binary file
|
|
Binary file
|
package/dist/standalone/assets/{audit-log-page-2wuGiWg-.js.map → audit-log-page-GHzf7mLS.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-log-page-2wuGiWg-.js","names":[],"sources":["../../../../ui/src/components/date-range-input.tsx","../../../../react/src/pages/audit-log-page.tsx"],"sourcesContent":["// Popover-driven date-range picker.\n// A single trigger button shows the selected range (or placeholder);\n// clicking opens a popover with a two-month Calendar in range mode.\n// On narrow screens the calendar collapses to a single month.\n//\n// UX notes:\n// - The picker NEVER auto-closes on date selection — the user confirms\n// explicitly via the \"Apply\" button. This prevents the \"first click\n// closes the picker\" bug that occurs when a partial pending range\n// (from without to) is in state and the next click completes it.\n// - \"Clear\" inside the popover empties the range and closes.\n// - The X inline in the trigger clears directly without opening the picker.\n// - On open: only a COMPLETE committed range (both ends) is mirrored into\n// the calendar state so the user can edit from a known baseline.\n// A committed partial range (from only) resets to blank to avoid\n// accidentally completing it on the first click.\n// - Escape / click-outside discards in-progress selection and reverts to\n// the last committed range.\n//\n// i18n-unaware by design: all visible strings are passed via `labels`.\n\nimport * as React from 'react'\nimport { addMonths, format, isSameMonth, isValid, parse, parseISO, startOfMonth } from 'date-fns'\nimport { CalendarRange, X } from 'lucide-react'\nimport type { DateRange } from 'react-day-picker'\nimport { cn } from '../lib/utils.js'\nimport { Button } from './button.js'\nimport { Calendar } from './calendar.js'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover.js'\n\nconst DATE_FMT = 'yyyy-MM-dd'\nconst DISPLAY_FMT = 'MMM d, yyyy'\n\nfunction parseDate(value: string | null | undefined): Date | undefined {\n if (!value) return undefined\n const d = value.length <= 10 ? parse(value, DATE_FMT, new Date()) : parseISO(value)\n return isValid(d) ? d : undefined\n}\n\nexport interface DateRangeInputLabels {\n /** Shown in the trigger when nothing is selected. */\n placeholder?: string\n /** \"Apply\" button inside the popover footer. */\n apply?: string\n /** \"Clear\" button inside the popover footer + aria-label for the inline X. */\n clear?: string\n}\n\nexport interface DateRangeInputProps {\n from: string | null | undefined\n to: string | null | undefined\n onChange(from: string, to: string): void\n disabled?: boolean\n className?: string\n labels?: DateRangeInputLabels\n}\n\nexport function DateRangeInput({\n from,\n to,\n onChange,\n disabled,\n className,\n labels = {},\n}: DateRangeInputProps): React.ReactElement {\n const placeholder = labels.placeholder ?? 'Select date range'\n const applyLabel = labels.apply ?? 'Apply'\n const clearLabel = labels.clear ?? 'Clear'\n\n const [open, setOpen] = React.useState(false)\n\n // Calendar selection in progress. Committed to onChange only via Apply.\n const [pending, setPending] = React.useState<DateRange | undefined>(() => {\n const f = parseDate(from)\n const t = parseDate(to)\n // Only restore a complete range on init — partial ranges start fresh.\n return f && t ? { from: f, to: t } : undefined\n })\n\n // Keep pending in sync when props change externally (e.g. programmatic reset).\n React.useEffect(() => {\n const f = parseDate(from)\n const t = parseDate(to)\n setPending(f ?? t ? { from: f, to: t } : undefined)\n }, [from, to])\n\n // Number of calendar months — 1 on narrow, 2 on wide viewports.\n const [months, setMonths] = React.useState(() =>\n typeof window !== 'undefined' && window.innerWidth >= 640 ? 2 : 1,\n )\n React.useEffect(() => {\n const mq = window.matchMedia('(min-width: 640px)')\n const handle = (e: MediaQueryListEvent): void => setMonths(e.matches ? 2 : 1)\n setMonths(mq.matches ? 2 : 1)\n mq.addEventListener('change', handle)\n return () => mq.removeEventListener('change', handle)\n }, [])\n\n // Independent navigation for the two-panel layout. Each panel keeps its\n // own visible month; the dropdowns on the right are constrained to\n // months >= left (and the left to months <= right) so the panels can\n // never cross over. State is (re)initialised from the committed range\n // every time the popover opens (see `handleOpenChange`).\n const [leftMonth, setLeftMonth] = React.useState<Date>(() => {\n const f = parseDate(from)\n return startOfMonth(f ?? new Date())\n })\n const [rightMonth, setRightMonth] = React.useState<Date>(() => {\n const f = parseDate(from)\n const t = parseDate(to)\n const left = startOfMonth(f ?? new Date())\n return t && !isSameMonth(left, t) ? startOfMonth(t) : addMonths(left, 1)\n })\n\n const handleOpenChange = (next: boolean): void => {\n if (next) {\n // On open: only restore a COMPLETE committed range so the user sees\n // their previous selection as a starting point. A partial range\n // (from without to) is intentionally dropped — carrying it over\n // would cause the very next calendar click to \"complete\" the range\n // and trigger an immediate commit + close.\n const f = parseDate(from)\n const t = parseDate(to)\n setPending(f && t ? { from: f, to: t } : undefined)\n // Re-derive panel navigation from the committed range so reopening\n // always lands the user on the months they were last looking at —\n // independently for the left and right panels.\n const left = startOfMonth(f ?? new Date())\n const right = t && !isSameMonth(left, t)\n ? startOfMonth(t)\n : addMonths(left, 1)\n setLeftMonth(left)\n setRightMonth(right)\n } else {\n // Closed without Apply (Escape / outside click) — discard in-progress\n // selection and revert to the last committed values.\n const f = parseDate(from)\n const t = parseDate(to)\n setPending(f ?? t ? { from: f, to: t } : undefined)\n }\n setOpen(next)\n }\n\n // Navigation guards. The right panel can never cross above the left's\n // month, and the left can never cross above the right's. If a user\n // navigates the LEFT forward past the right (via the prev/next arrow\n // when the dropdown bounds don't disable it), push the right one month\n // ahead; symmetric for the right going below the left.\n const handleLeftMonthChange = (next: Date): void => {\n setLeftMonth(next)\n if (!isSameMonth(next, rightMonth) && next > rightMonth) {\n setRightMonth(addMonths(next, 1))\n }\n }\n const handleRightMonthChange = (next: Date): void => {\n setRightMonth(next)\n if (!isSameMonth(next, leftMonth) && next < leftMonth) {\n setLeftMonth(addMonths(next, -1))\n }\n }\n\n // Update the in-progress selection; never auto-commit.\n const handleSelect = (range: DateRange | undefined): void => {\n setPending(range)\n }\n\n // Commit whatever is pending (at minimum a start date) and close.\n const handleApply = (): void => {\n if (pending?.from) {\n onChange(\n format(pending.from, DATE_FMT),\n pending.to ? format(pending.to, DATE_FMT) : '',\n )\n }\n setOpen(false)\n }\n\n // Clear inside the popover: empty the committed range and close.\n const handleClearPopover = (): void => {\n setPending(undefined)\n onChange('', '')\n setOpen(false)\n }\n\n // Inline X on the trigger: clear committed range without opening the picker.\n const handleClearInline = (e: React.MouseEvent): void => {\n e.stopPropagation()\n setPending(undefined)\n onChange('', '')\n }\n\n const fromDate = parseDate(from)\n const toDate = parseDate(to)\n const hasValue = !!(from || to)\n\n const displayText = hasValue\n ? [\n fromDate ? format(fromDate, DISPLAY_FMT) : '…',\n toDate ? format(toDate, DISPLAY_FMT) : '…',\n ].join(' – ')\n : null\n\n return (\n <Popover open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n className={cn(\n 'h-9 w-full justify-start gap-2 px-3 font-normal',\n !hasValue && 'text-muted-foreground',\n className,\n )}\n >\n <CalendarRange className=\"size-4 shrink-0 opacity-60\" />\n <span className=\"flex-1 truncate text-left text-sm\">\n {displayText ?? placeholder}\n </span>\n {hasValue && (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={clearLabel}\n onClick={handleClearInline}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n handleClearInline(e as unknown as React.MouseEvent)\n }\n }}\n className=\"ml-1 rounded-sm p-0.5 opacity-50 hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n >\n <X className=\"size-3.5\" />\n </span>\n )}\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-0\" align=\"start\">\n {months === 2 ? (\n // Two-panel layout: each Calendar is a fully independent single-\n // month instance with its own controlled `month` state. The left\n // panel's dropdowns can't reach past the right panel's month\n // (and vice versa) thanks to the shared start/end-month bounds.\n // Range selection still spans both panels because they share\n // the same `selected` + `onSelect`.\n <div className=\"flex flex-col sm:flex-row\">\n <Calendar\n mode=\"range\"\n selected={pending}\n onSelect={handleSelect}\n numberOfMonths={1}\n month={leftMonth}\n onMonthChange={handleLeftMonthChange}\n endMonth={rightMonth}\n autoFocus\n />\n <Calendar\n mode=\"range\"\n selected={pending}\n onSelect={handleSelect}\n numberOfMonths={1}\n month={rightMonth}\n onMonthChange={handleRightMonthChange}\n startMonth={leftMonth}\n />\n </div>\n ) : (\n <Calendar\n mode=\"range\"\n selected={pending}\n onSelect={handleSelect}\n numberOfMonths={1}\n // react-day-picker doesn't auto-navigate to the selected range on\n // mount — it stays on today's month. The popover re-mounts the\n // Calendar each time it opens, so deriving `defaultMonth` from\n // the (already-restored) pending range puts the user back where\n // they left off without controlling navigation explicitly.\n defaultMonth={pending?.from ?? pending?.to ?? undefined}\n autoFocus\n />\n )}\n <div className=\"flex items-center justify-between border-t border-border px-3 py-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearPopover}>\n {clearLabel}\n </Button>\n <Button size=\"sm\" onClick={handleApply} disabled={!pending?.from}>\n {applyLabel}\n </Button>\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nDateRangeInput.displayName = 'DateRangeInput'\n","import * as React from 'react'\nimport { diffSnapshots } from '@modern-admin/core'\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n DateRangeInput,\n DiffView,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n cn,\n} from '@modern-admin/ui'\nimport { ChevronDown, ChevronUp, ExternalLink, FilePlus, FileText, Key, KeyRound, Loader2, LogIn, Pencil, Trash2 } from 'lucide-react'\nimport { useInfiniteAuditLog, useRecord, useRecordHistory, useResource, useResources } from '../hooks.js'\nimport { useI18n } from '../i18n.js'\nimport { Link } from '../router.js'\nimport { USERS_RESOURCE_ID, useUserDirectory, userLabelOf } from '../user-directory.js'\nimport type {\n AuditLogEntry,\n AuditLogQuery,\n HistoryDiffEntry,\n HistoryRevision,\n} from '../client.js'\nimport type { RecordJSON, ResourceJSON } from '../types.js'\n\nconst ALL = '__all__'\nconst ACTIONS = ['new', 'edit', 'delete', 'bulkDelete', 'login', 'apiKey.create', 'apiKey.update', 'apiKey.delete']\n/** Virtual resource IDs that don't map to ORM resources. */\nconst VIRTUAL_RESOURCE_LABELS: Record<string, string> = {\n __auth__: 'audit:virtualResource.auth',\n __api_keys__: 'audit:virtualResource.apiKeys',\n}\ninterface ActionStyle {\n Icon: React.ComponentType<{ className?: string }>\n iconClass: string\n bgClass: string\n titleKey: string\n}\n\nconst FALLBACK_STYLE: ActionStyle = {\n Icon: FileText,\n iconClass: 'text-muted-foreground',\n bgClass: 'bg-muted',\n titleKey: '',\n}\n\nconst ACTION_STYLES: Record<string, ActionStyle> = {\n new: {\n Icon: FilePlus,\n iconClass: 'text-blue-600 dark:text-blue-300',\n bgClass: 'bg-blue-100 dark:bg-blue-950/40',\n titleKey: 'audit:action.new',\n },\n edit: {\n Icon: Pencil,\n iconClass: 'text-emerald-600 dark:text-emerald-300',\n bgClass: 'bg-emerald-100 dark:bg-emerald-950/40',\n titleKey: 'audit:action.edit',\n },\n delete: {\n Icon: Trash2,\n iconClass: 'text-rose-600 dark:text-rose-300',\n bgClass: 'bg-rose-100 dark:bg-rose-950/40',\n titleKey: 'audit:action.delete',\n },\n bulkDelete: {\n Icon: Trash2,\n iconClass: 'text-rose-600 dark:text-rose-300',\n bgClass: 'bg-rose-100 dark:bg-rose-950/40',\n titleKey: 'audit:action.bulkDelete',\n },\n login: {\n Icon: LogIn,\n iconClass: 'text-violet-600 dark:text-violet-300',\n bgClass: 'bg-violet-100 dark:bg-violet-950/40',\n titleKey: 'audit:action.login',\n },\n 'apiKey.create': {\n Icon: Key,\n iconClass: 'text-amber-600 dark:text-amber-300',\n bgClass: 'bg-amber-100 dark:bg-amber-950/40',\n titleKey: 'audit:action.apiKeyCreate',\n },\n 'apiKey.update': {\n Icon: KeyRound,\n iconClass: 'text-amber-600 dark:text-amber-300',\n bgClass: 'bg-amber-100 dark:bg-amber-950/40',\n titleKey: 'audit:action.apiKeyUpdate',\n },\n 'apiKey.delete': {\n Icon: Trash2,\n iconClass: 'text-rose-600 dark:text-rose-300',\n bgClass: 'bg-rose-100 dark:bg-rose-950/40',\n titleKey: 'audit:action.apiKeyDelete',\n },\n}\n\n/** Format `entry.at` (unix-ms) as a relative phrase like \"2m ago\", falling\n * back to absolute date for entries older than a week. Uses\n * `Intl.RelativeTimeFormat` so output is locale-aware. */\nfunction useRelativeTimeFormatter(\n locale: string,\n): (atMs: number, nowMs: number) => string {\n const rtf = React.useMemo(\n () => new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }),\n [locale],\n )\n const dtf = React.useMemo(\n () => new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }),\n [locale],\n )\n return React.useCallback(\n (atMs, nowMs) => {\n const sec = Math.round((nowMs - atMs) / 1000)\n if (sec < 45) return rtf.format(-Math.max(sec, 0), 'second')\n if (sec < 3600) return rtf.format(-Math.round(sec / 60), 'minute')\n if (sec < 86400) return rtf.format(-Math.round(sec / 3600), 'hour')\n if (sec < 86400 * 7) return rtf.format(-Math.round(sec / 86400), 'day')\n return dtf.format(new Date(atMs))\n },\n [dtf, rtf],\n )\n}\n\nconst initialsOf = (label: string): string => {\n const parts = label.split(/\\s+|[._@-]/).filter(Boolean)\n if (parts.length === 0) return '?'\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase()\n return (parts[0]![0]! + parts[1]![0]!).toUpperCase()\n}\n\nconst PAGE_SIZE = 25\n\nexport function AuditLogPage(): React.ReactElement {\n const { t, locale } = useI18n()\n const resources = useResources()\n const [filters, setFilters] = React.useState<Omit<AuditLogQuery, 'before' | 'limit' | 'offset'>>({})\n\n const log = useInfiniteAuditLog(filters, PAGE_SIZE)\n\n // Flatten all pages into one list, trimming the sentinel \"+1\" entry from each page\n const events = React.useMemo(\n () =>\n (log.data?.pages ?? []).flatMap((page) => page.events.slice(0, PAGE_SIZE)),\n [log.data],\n )\n\n const userIds = React.useMemo(\n () => Array.from(new Set(events.map((e) => e.userId).filter((v): v is string => !!v))),\n [events],\n )\n const users = useUserDirectory(userIds)\n\n const resourceMap = React.useMemo(() => {\n const map: Record<string, ResourceJSON> = {}\n for (const r of resources) map[r.id] = r\n return map\n }, [resources])\n\n const userResourceExists = resources.some((r) => r.id === USERS_RESOURCE_ID)\n\n const formatRelative = useRelativeTimeFormatter(locale)\n const formatAbsolute = React.useCallback(\n (value: number) =>\n new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value)),\n [locale],\n )\n\n // `now` is a snapshot for relative-time labels — we want it to refresh\n // whenever a new page of events arrives, even though `events` is not read\n // inside the callback.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const now = React.useMemo(() => Date.now(), [events])\n\n // IntersectionObserver sentinel — triggers next page load when visible\n const sentinelRef = React.useRef<HTMLDivElement>(null)\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = log\n React.useEffect(() => {\n const el = sentinelRef.current\n if (!el) return\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage()\n }\n },\n { rootMargin: '200px' },\n )\n observer.observe(el)\n return () => observer.disconnect()\n }, [hasNextPage, isFetchingNextPage, fetchNextPage])\n\n const resetFilters = (patch: Partial<typeof filters>): void => {\n setFilters((prev) => ({ ...prev, ...patch }))\n }\n\n return (\n <div className=\"space-y-2 sm:space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle>{t('audit:title')}</CardTitle>\n </CardHeader>\n <CardContent className=\"grid gap-3 sm:grid-cols-2 md:grid-cols-4\">\n <Select\n value={filters.resourceId ?? ALL}\n onValueChange={(v) => resetFilters({ resourceId: v === ALL ? undefined : v })}\n >\n <SelectTrigger aria-label={t('audit:resource')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{t('audit:allResources')}</SelectItem>\n {Object.entries(VIRTUAL_RESOURCE_LABELS).map(([id, key]) => (\n <SelectItem key={id} value={id}>{t(key)}</SelectItem>\n ))}\n {resources.map((resource) => (\n <SelectItem key={resource.id} value={resource.id}>\n {resource.name}\n {resource.name !== resource.id && (\n <span className=\"ml-1.5 text-xs text-muted-foreground\">({resource.id})</span>\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select\n value={filters.actions?.[0] ?? ALL}\n onValueChange={(v) => resetFilters({ actions: v === ALL ? undefined : [v] })}\n >\n <SelectTrigger aria-label={t('audit:action')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{t('audit:allActions')}</SelectItem>\n {ACTIONS.map((action) => {\n const style = ACTION_STYLES[action]\n return (\n <SelectItem key={action} value={action}>\n {style?.titleKey ? t(style.titleKey) : action}\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n <Input\n value={filters.recordId ?? ''}\n placeholder={t('audit:recordId')}\n onChange={(e) => resetFilters({ recordId: e.target.value || undefined })}\n />\n <Input\n value={filters.userId ?? ''}\n placeholder={t('audit:userId')}\n onChange={(e) => resetFilters({ userId: e.target.value || undefined })}\n />\n <DateRangeInput\n from={filters.from}\n to={filters.to}\n onChange={(from, to) =>\n resetFilters({ from: from || undefined, to: to || undefined })\n }\n className=\"sm:col-span-2 md:col-span-4\"\n labels={{\n placeholder: t('audit:dateRangePlaceholder'),\n apply: t('common:apply'),\n clear: t('common:clear'),\n }}\n />\n </CardContent>\n </Card>\n <Card>\n <CardContent>\n {log.isLoading ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-20 w-full\" />\n </div>\n ) : log.isError ? (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive\">\n {t('audit:loadError')}\n </div>\n ) : events.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border p-6 text-center text-sm text-muted-foreground\">\n {t('audit:noEvents')}\n </div>\n ) : (\n <ol className=\"space-y-3\">\n {events.map((entry, i) => (\n <AuditEntryCard\n key={entry.id ?? `${entry.at}:${i}`}\n entry={entry}\n resource={resourceMap[entry.resourceId]}\n user={entry.userId ? users.get(entry.userId) ?? null : null}\n userResourceId={userResourceExists ? USERS_RESOURCE_ID : undefined}\n now={now}\n formatRelative={formatRelative}\n formatAbsolute={formatAbsolute}\n />\n ))}\n </ol>\n )}\n {/* Sentinel div — observed by IntersectionObserver to trigger next page */}\n <div ref={sentinelRef} className=\"h-1\" aria-hidden />\n {log.isFetchingNextPage && (\n <div className=\"flex justify-center py-4\">\n <Loader2 className=\"size-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n )\n}\n\ninterface AuditEntryCardProps {\n entry: AuditLogEntry\n resource: ResourceJSON | undefined\n user: RecordJSON | null | undefined\n userResourceId?: string\n now: number\n formatRelative: (atMs: number, nowMs: number) => string\n formatAbsolute: (value: number) => string\n}\n\nfunction AuditEntryCard({\n entry,\n resource,\n user,\n userResourceId,\n now,\n formatRelative,\n formatAbsolute,\n}: AuditEntryCardProps): React.ReactElement {\n const { t } = useI18n()\n const [expanded, setExpanded] = React.useState(false)\n\n const style = ACTION_STYLES[entry.action] ?? FALLBACK_STYLE\n const Icon = style.Icon\n const title = style.titleKey ? t(style.titleKey) : entry.action\n\n const userFallback = entry.userId ?? t('history:unknownUser')\n const userLabel = userLabelOf(user, userFallback)\n\n const byTpl = t('audit:by', { user: '\\u0000' })\n const byParts = byTpl.split('\\u0000')\n const byPrefix = byParts[0] ?? ''\n const bySuffix = byParts[1] ?? ''\n\n const virtualResourceKey = VIRTUAL_RESOURCE_LABELS[entry.resourceId]\n const resourceLabel = virtualResourceKey\n ? t(virtualResourceKey)\n : (resource?.name ?? entry.resourceId)\n\n // Diff drill-down only makes sense when we have a single recordId AND\n // history exists for that resource (we'll discover that lazily on first\n // expand). Bulk operations and `new`/`delete` actions skip the toggle.\n const canExpand = entry.action === 'edit' && !!entry.recordId\n\n return (\n <li className=\"rounded-lg border border-border bg-card p-4 shadow-sm\">\n <div className=\"flex items-start gap-3\">\n <span\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n style.bgClass,\n )}\n >\n <Icon className={cn('size-5', style.iconClass)} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold\">{title}</p>\n <div className=\"mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground\">\n <span className=\"font-medium\">{resourceLabel}</span>\n {entry.recordId && (\n <>\n <span aria-hidden>·</span>\n {resource ? (\n <Link\n to={{\n name: 'show',\n resourceId: entry.resourceId,\n recordId: entry.recordId,\n }}\n className=\"inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground hover:underline\"\n title={entry.recordId}\n >\n <span className=\"truncate max-w-[12rem]\">\n {entry.recordTitle ?? `#${entry.recordId}`}\n </span>\n <ExternalLink className=\"size-3 shrink-0\" />\n </Link>\n ) : entry.recordTitle ? (\n <span\n className=\"truncate max-w-[12rem] rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground\"\n title={entry.recordId}\n >\n {entry.recordTitle}\n </span>\n ) : null}\n </>\n )}\n {!entry.recordId && entry.recordIds?.length ? (\n <>\n <span aria-hidden>·</span>\n <span>\n {entry.recordIds.length} {t('audit:records')}\n </span>\n </>\n ) : null}\n </div>\n <p className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span\n aria-hidden\n className=\"inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary\"\n >\n {initialsOf(userLabel)}\n </span>\n <span className=\"truncate\">\n {byPrefix}\n {userResourceId && entry.userId ? (\n <Link\n to={{ name: 'show', resourceId: userResourceId, recordId: entry.userId }}\n className=\"font-medium text-foreground hover:underline\"\n >\n {userLabel}\n </Link>\n ) : (\n userLabel\n )}\n {bySuffix}\n </span>\n </p>\n </div>\n <time\n className=\"shrink-0 text-xs text-muted-foreground\"\n dateTime={new Date(entry.at).toISOString()}\n title={formatAbsolute(entry.at)}\n >\n {formatRelative(entry.at, now)}\n </time>\n </div>\n {canExpand && (\n <div className=\"mt-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ml-2 h-7 px-2 text-xs\"\n onClick={() => setExpanded((x) => !x)}\n aria-expanded={expanded}\n >\n {expanded ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n {expanded ? t('audit:hideChanges') : t('audit:viewChanges')}\n </Button>\n {expanded && (\n <div className=\"mt-2\">\n <AuditEntryChanges\n resourceId={entry.resourceId}\n recordId={entry.recordId!}\n atMs={entry.at}\n />\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n </li>\n )\n}\n\n/** Lazy-loaded diff for a single audit entry. Pulls the record's revision\n * history (server returns the freshest 50) and picks the revision whose\n * `createdAt` is closest to the entry's `at` timestamp. We require the\n * match to be within a 60s window to avoid showing an unrelated revision\n * when the matching one was pruned. */\nfunction AuditEntryChanges({\n resourceId,\n recordId,\n atMs,\n}: {\n resourceId: string\n recordId: string\n atMs: number\n}): React.ReactElement {\n const { t } = useI18n()\n const resource = useResource(resourceId)\n const history = useRecordHistory(resourceId, recordId, { limit: 50 })\n // Pull the live record so we can show its title (e.g. user's email) at\n // the top of the diff — helpful when the audit log lists many entries.\n const record = useRecord(resourceId, recordId)\n\n const labelByPath = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const p of resource?.properties ?? []) map[p.path] = p.label\n return map\n }, [resource])\n\n if (history.isLoading) {\n return <Skeleton className=\"h-16 w-full\" />\n }\n if (history.isError) {\n return (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-3 text-xs text-destructive\">\n {t('audit:loadDiffError')}\n </div>\n )\n }\n\n const revisions = history.data?.revisions ?? []\n const target = findNearestRevision(revisions, atMs)\n\n if (!target) {\n return (\n <div className=\"rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground\">\n {t('audit:noDiff')}\n </div>\n )\n }\n\n const fields: HistoryDiffEntry[] = diffSnapshots(\n target.snapshotBefore ?? {},\n target.snapshot,\n ).map((f) => ({ ...f, label: labelByPath[f.path] }))\n const recordTitle = record.data?.record?.title\n const showTitle = recordTitle && recordTitle !== recordId\n\n return (\n <div className=\"space-y-2\">\n {showTitle && (\n <p className=\"truncate text-xs text-muted-foreground\" title={recordTitle}>\n {recordTitle}\n </p>\n )}\n <DiffView\n fields={fields}\n labels={{\n added: t('diff:added'),\n changed: t('diff:changed'),\n removed: t('diff:removed'),\n before: t('diff:before'),\n after: t('diff:after'),\n noChanges: t('diff:noChanges'),\n }}\n />\n </div>\n )\n}\n\nconst TOLERANCE_MS = 60_000\n\nconst findNearestRevision = (\n revisions: ReadonlyArray<HistoryRevision>,\n atMs: number,\n): HistoryRevision | null => {\n let best: HistoryRevision | null = null\n let bestDiff = Infinity\n for (const r of revisions) {\n const t = new Date(r.createdAt).getTime()\n if (Number.isNaN(t)) continue\n const d = Math.abs(t - atMs)\n if (d < bestDiff && d <= TOLERANCE_MS) {\n bestDiff = d\n best = r\n }\n }\n return best\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audit-log-page-GHzf7mLS.js","names":[],"sources":["../../../../ui/src/components/date-range-input.tsx","../../../../react/src/pages/audit-log-page.tsx"],"sourcesContent":["// Popover-driven date-range picker.\n// A single trigger button shows the selected range (or placeholder);\n// clicking opens a popover with a two-month Calendar in range mode.\n// On narrow screens the calendar collapses to a single month.\n//\n// UX notes:\n// - The picker NEVER auto-closes on date selection — the user confirms\n// explicitly via the \"Apply\" button. This prevents the \"first click\n// closes the picker\" bug that occurs when a partial pending range\n// (from without to) is in state and the next click completes it.\n// - \"Clear\" inside the popover empties the range and closes.\n// - The X inline in the trigger clears directly without opening the picker.\n// - On open: only a COMPLETE committed range (both ends) is mirrored into\n// the calendar state so the user can edit from a known baseline.\n// A committed partial range (from only) resets to blank to avoid\n// accidentally completing it on the first click.\n// - Escape / click-outside discards in-progress selection and reverts to\n// the last committed range.\n//\n// i18n-unaware by design: all visible strings are passed via `labels`.\n\nimport * as React from 'react'\nimport { addMonths, format, isSameMonth, isValid, parse, parseISO, startOfMonth } from 'date-fns'\nimport { CalendarRange, X } from 'lucide-react'\nimport type { DateRange } from 'react-day-picker'\nimport { cn } from '../lib/utils.js'\nimport { Button } from './button.js'\nimport { Calendar } from './calendar.js'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover.js'\n\nconst DATE_FMT = 'yyyy-MM-dd'\nconst DISPLAY_FMT = 'MMM d, yyyy'\n\nfunction parseDate(value: string | null | undefined): Date | undefined {\n if (!value) return undefined\n const d = value.length <= 10 ? parse(value, DATE_FMT, new Date()) : parseISO(value)\n return isValid(d) ? d : undefined\n}\n\nexport interface DateRangeInputLabels {\n /** Shown in the trigger when nothing is selected. */\n placeholder?: string\n /** \"Apply\" button inside the popover footer. */\n apply?: string\n /** \"Clear\" button inside the popover footer + aria-label for the inline X. */\n clear?: string\n}\n\nexport interface DateRangeInputProps {\n from: string | null | undefined\n to: string | null | undefined\n onChange(from: string, to: string): void\n disabled?: boolean\n className?: string\n labels?: DateRangeInputLabels\n}\n\nexport function DateRangeInput({\n from,\n to,\n onChange,\n disabled,\n className,\n labels = {},\n}: DateRangeInputProps): React.ReactElement {\n const placeholder = labels.placeholder ?? 'Select date range'\n const applyLabel = labels.apply ?? 'Apply'\n const clearLabel = labels.clear ?? 'Clear'\n\n const [open, setOpen] = React.useState(false)\n\n // Calendar selection in progress. Committed to onChange only via Apply.\n const [pending, setPending] = React.useState<DateRange | undefined>(() => {\n const f = parseDate(from)\n const t = parseDate(to)\n // Only restore a complete range on init — partial ranges start fresh.\n return f && t ? { from: f, to: t } : undefined\n })\n\n // Keep pending in sync when props change externally (e.g. programmatic reset).\n React.useEffect(() => {\n const f = parseDate(from)\n const t = parseDate(to)\n setPending(f ?? t ? { from: f, to: t } : undefined)\n }, [from, to])\n\n // Number of calendar months — 1 on narrow, 2 on wide viewports.\n const [months, setMonths] = React.useState(() =>\n typeof window !== 'undefined' && window.innerWidth >= 640 ? 2 : 1,\n )\n React.useEffect(() => {\n const mq = window.matchMedia('(min-width: 640px)')\n const handle = (e: MediaQueryListEvent): void => setMonths(e.matches ? 2 : 1)\n setMonths(mq.matches ? 2 : 1)\n mq.addEventListener('change', handle)\n return () => mq.removeEventListener('change', handle)\n }, [])\n\n // Independent navigation for the two-panel layout. Each panel keeps its\n // own visible month; the dropdowns on the right are constrained to\n // months >= left (and the left to months <= right) so the panels can\n // never cross over. State is (re)initialised from the committed range\n // every time the popover opens (see `handleOpenChange`).\n const [leftMonth, setLeftMonth] = React.useState<Date>(() => {\n const f = parseDate(from)\n return startOfMonth(f ?? new Date())\n })\n const [rightMonth, setRightMonth] = React.useState<Date>(() => {\n const f = parseDate(from)\n const t = parseDate(to)\n const left = startOfMonth(f ?? new Date())\n return t && !isSameMonth(left, t) ? startOfMonth(t) : addMonths(left, 1)\n })\n\n const handleOpenChange = (next: boolean): void => {\n if (next) {\n // On open: only restore a COMPLETE committed range so the user sees\n // their previous selection as a starting point. A partial range\n // (from without to) is intentionally dropped — carrying it over\n // would cause the very next calendar click to \"complete\" the range\n // and trigger an immediate commit + close.\n const f = parseDate(from)\n const t = parseDate(to)\n setPending(f && t ? { from: f, to: t } : undefined)\n // Re-derive panel navigation from the committed range so reopening\n // always lands the user on the months they were last looking at —\n // independently for the left and right panels.\n const left = startOfMonth(f ?? new Date())\n const right = t && !isSameMonth(left, t)\n ? startOfMonth(t)\n : addMonths(left, 1)\n setLeftMonth(left)\n setRightMonth(right)\n } else {\n // Closed without Apply (Escape / outside click) — discard in-progress\n // selection and revert to the last committed values.\n const f = parseDate(from)\n const t = parseDate(to)\n setPending(f ?? t ? { from: f, to: t } : undefined)\n }\n setOpen(next)\n }\n\n // Navigation guards. The right panel can never cross above the left's\n // month, and the left can never cross above the right's. If a user\n // navigates the LEFT forward past the right (via the prev/next arrow\n // when the dropdown bounds don't disable it), push the right one month\n // ahead; symmetric for the right going below the left.\n const handleLeftMonthChange = (next: Date): void => {\n setLeftMonth(next)\n if (!isSameMonth(next, rightMonth) && next > rightMonth) {\n setRightMonth(addMonths(next, 1))\n }\n }\n const handleRightMonthChange = (next: Date): void => {\n setRightMonth(next)\n if (!isSameMonth(next, leftMonth) && next < leftMonth) {\n setLeftMonth(addMonths(next, -1))\n }\n }\n\n // Update the in-progress selection; never auto-commit.\n const handleSelect = (range: DateRange | undefined): void => {\n setPending(range)\n }\n\n // Commit whatever is pending (at minimum a start date) and close.\n const handleApply = (): void => {\n if (pending?.from) {\n onChange(\n format(pending.from, DATE_FMT),\n pending.to ? format(pending.to, DATE_FMT) : '',\n )\n }\n setOpen(false)\n }\n\n // Clear inside the popover: empty the committed range and close.\n const handleClearPopover = (): void => {\n setPending(undefined)\n onChange('', '')\n setOpen(false)\n }\n\n // Inline X on the trigger: clear committed range without opening the picker.\n const handleClearInline = (e: React.MouseEvent): void => {\n e.stopPropagation()\n setPending(undefined)\n onChange('', '')\n }\n\n const fromDate = parseDate(from)\n const toDate = parseDate(to)\n const hasValue = !!(from || to)\n\n const displayText = hasValue\n ? [\n fromDate ? format(fromDate, DISPLAY_FMT) : '…',\n toDate ? format(toDate, DISPLAY_FMT) : '…',\n ].join(' – ')\n : null\n\n return (\n <Popover open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n className={cn(\n 'h-9 w-full justify-start gap-2 px-3 font-normal',\n !hasValue && 'text-muted-foreground',\n className,\n )}\n >\n <CalendarRange className=\"size-4 shrink-0 opacity-60\" />\n <span className=\"flex-1 truncate text-left text-sm\">\n {displayText ?? placeholder}\n </span>\n {hasValue && (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={clearLabel}\n onClick={handleClearInline}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n handleClearInline(e as unknown as React.MouseEvent)\n }\n }}\n className=\"ml-1 rounded-sm p-0.5 opacity-50 hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n >\n <X className=\"size-3.5\" />\n </span>\n )}\n </Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-0\" align=\"start\">\n {months === 2 ? (\n // Two-panel layout: each Calendar is a fully independent single-\n // month instance with its own controlled `month` state. The left\n // panel's dropdowns can't reach past the right panel's month\n // (and vice versa) thanks to the shared start/end-month bounds.\n // Range selection still spans both panels because they share\n // the same `selected` + `onSelect`.\n <div className=\"flex flex-col sm:flex-row\">\n <Calendar\n mode=\"range\"\n selected={pending}\n onSelect={handleSelect}\n numberOfMonths={1}\n month={leftMonth}\n onMonthChange={handleLeftMonthChange}\n endMonth={rightMonth}\n autoFocus\n />\n <Calendar\n mode=\"range\"\n selected={pending}\n onSelect={handleSelect}\n numberOfMonths={1}\n month={rightMonth}\n onMonthChange={handleRightMonthChange}\n startMonth={leftMonth}\n />\n </div>\n ) : (\n <Calendar\n mode=\"range\"\n selected={pending}\n onSelect={handleSelect}\n numberOfMonths={1}\n // react-day-picker doesn't auto-navigate to the selected range on\n // mount — it stays on today's month. The popover re-mounts the\n // Calendar each time it opens, so deriving `defaultMonth` from\n // the (already-restored) pending range puts the user back where\n // they left off without controlling navigation explicitly.\n defaultMonth={pending?.from ?? pending?.to ?? undefined}\n autoFocus\n />\n )}\n <div className=\"flex items-center justify-between border-t border-border px-3 py-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearPopover}>\n {clearLabel}\n </Button>\n <Button size=\"sm\" onClick={handleApply} disabled={!pending?.from}>\n {applyLabel}\n </Button>\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nDateRangeInput.displayName = 'DateRangeInput'\n","import * as React from 'react'\nimport { diffSnapshots } from '@modern-admin/core'\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n DateRangeInput,\n DiffView,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n cn,\n} from '@modern-admin/ui'\nimport { ChevronDown, ChevronUp, ExternalLink, FilePlus, FileText, Key, KeyRound, Loader2, LogIn, Pencil, Trash2 } from 'lucide-react'\nimport { useInfiniteAuditLog, useRecord, useRecordHistory, useResource, useResources } from '../hooks.js'\nimport { useI18n } from '../i18n.js'\nimport { Link } from '../router.js'\nimport { USERS_RESOURCE_ID, useUserDirectory, userLabelOf } from '../user-directory.js'\nimport type {\n AuditLogEntry,\n AuditLogQuery,\n HistoryDiffEntry,\n HistoryRevision,\n} from '../client.js'\nimport type { RecordJSON, ResourceJSON } from '../types.js'\n\nconst ALL = '__all__'\nconst ACTIONS = ['new', 'edit', 'delete', 'bulkDelete', 'login', 'apiKey.create', 'apiKey.update', 'apiKey.delete']\n/** Virtual resource IDs that don't map to ORM resources. */\nconst VIRTUAL_RESOURCE_LABELS: Record<string, string> = {\n __auth__: 'audit:virtualResource.auth',\n __api_keys__: 'audit:virtualResource.apiKeys',\n}\ninterface ActionStyle {\n Icon: React.ComponentType<{ className?: string }>\n iconClass: string\n bgClass: string\n titleKey: string\n}\n\nconst FALLBACK_STYLE: ActionStyle = {\n Icon: FileText,\n iconClass: 'text-muted-foreground',\n bgClass: 'bg-muted',\n titleKey: '',\n}\n\nconst ACTION_STYLES: Record<string, ActionStyle> = {\n new: {\n Icon: FilePlus,\n iconClass: 'text-blue-600 dark:text-blue-300',\n bgClass: 'bg-blue-100 dark:bg-blue-950/40',\n titleKey: 'audit:action.new',\n },\n edit: {\n Icon: Pencil,\n iconClass: 'text-emerald-600 dark:text-emerald-300',\n bgClass: 'bg-emerald-100 dark:bg-emerald-950/40',\n titleKey: 'audit:action.edit',\n },\n delete: {\n Icon: Trash2,\n iconClass: 'text-rose-600 dark:text-rose-300',\n bgClass: 'bg-rose-100 dark:bg-rose-950/40',\n titleKey: 'audit:action.delete',\n },\n bulkDelete: {\n Icon: Trash2,\n iconClass: 'text-rose-600 dark:text-rose-300',\n bgClass: 'bg-rose-100 dark:bg-rose-950/40',\n titleKey: 'audit:action.bulkDelete',\n },\n login: {\n Icon: LogIn,\n iconClass: 'text-violet-600 dark:text-violet-300',\n bgClass: 'bg-violet-100 dark:bg-violet-950/40',\n titleKey: 'audit:action.login',\n },\n 'apiKey.create': {\n Icon: Key,\n iconClass: 'text-amber-600 dark:text-amber-300',\n bgClass: 'bg-amber-100 dark:bg-amber-950/40',\n titleKey: 'audit:action.apiKeyCreate',\n },\n 'apiKey.update': {\n Icon: KeyRound,\n iconClass: 'text-amber-600 dark:text-amber-300',\n bgClass: 'bg-amber-100 dark:bg-amber-950/40',\n titleKey: 'audit:action.apiKeyUpdate',\n },\n 'apiKey.delete': {\n Icon: Trash2,\n iconClass: 'text-rose-600 dark:text-rose-300',\n bgClass: 'bg-rose-100 dark:bg-rose-950/40',\n titleKey: 'audit:action.apiKeyDelete',\n },\n}\n\n/** Format `entry.at` (unix-ms) as a relative phrase like \"2m ago\", falling\n * back to absolute date for entries older than a week. Uses\n * `Intl.RelativeTimeFormat` so output is locale-aware. */\nfunction useRelativeTimeFormatter(\n locale: string,\n): (atMs: number, nowMs: number) => string {\n const rtf = React.useMemo(\n () => new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }),\n [locale],\n )\n const dtf = React.useMemo(\n () => new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }),\n [locale],\n )\n return React.useCallback(\n (atMs, nowMs) => {\n const sec = Math.round((nowMs - atMs) / 1000)\n if (sec < 45) return rtf.format(-Math.max(sec, 0), 'second')\n if (sec < 3600) return rtf.format(-Math.round(sec / 60), 'minute')\n if (sec < 86400) return rtf.format(-Math.round(sec / 3600), 'hour')\n if (sec < 86400 * 7) return rtf.format(-Math.round(sec / 86400), 'day')\n return dtf.format(new Date(atMs))\n },\n [dtf, rtf],\n )\n}\n\nconst initialsOf = (label: string): string => {\n const parts = label.split(/\\s+|[._@-]/).filter(Boolean)\n if (parts.length === 0) return '?'\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase()\n return (parts[0]![0]! + parts[1]![0]!).toUpperCase()\n}\n\nconst PAGE_SIZE = 25\n\nexport function AuditLogPage(): React.ReactElement {\n const { t, locale } = useI18n()\n const resources = useResources()\n const [filters, setFilters] = React.useState<Omit<AuditLogQuery, 'before' | 'limit' | 'offset'>>({})\n\n const log = useInfiniteAuditLog(filters, PAGE_SIZE)\n\n // Flatten all pages into one list, trimming the sentinel \"+1\" entry from each page\n const events = React.useMemo(\n () =>\n (log.data?.pages ?? []).flatMap((page) => page.events.slice(0, PAGE_SIZE)),\n [log.data],\n )\n\n const userIds = React.useMemo(\n () => Array.from(new Set(events.map((e) => e.userId).filter((v): v is string => !!v))),\n [events],\n )\n const users = useUserDirectory(userIds)\n\n const resourceMap = React.useMemo(() => {\n const map: Record<string, ResourceJSON> = {}\n for (const r of resources) map[r.id] = r\n return map\n }, [resources])\n\n const userResourceExists = resources.some((r) => r.id === USERS_RESOURCE_ID)\n\n const formatRelative = useRelativeTimeFormatter(locale)\n const formatAbsolute = React.useCallback(\n (value: number) =>\n new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value)),\n [locale],\n )\n\n // `now` is a snapshot for relative-time labels — we want it to refresh\n // whenever a new page of events arrives, even though `events` is not read\n // inside the callback.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const now = React.useMemo(() => Date.now(), [events])\n\n // IntersectionObserver sentinel — triggers next page load when visible\n const sentinelRef = React.useRef<HTMLDivElement>(null)\n const { hasNextPage, isFetchingNextPage, fetchNextPage } = log\n React.useEffect(() => {\n const el = sentinelRef.current\n if (!el) return\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage) {\n void fetchNextPage()\n }\n },\n { rootMargin: '200px' },\n )\n observer.observe(el)\n return () => observer.disconnect()\n }, [hasNextPage, isFetchingNextPage, fetchNextPage])\n\n const resetFilters = (patch: Partial<typeof filters>): void => {\n setFilters((prev) => ({ ...prev, ...patch }))\n }\n\n return (\n <div className=\"space-y-2 sm:space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle>{t('audit:title')}</CardTitle>\n </CardHeader>\n <CardContent className=\"grid gap-3 sm:grid-cols-2 md:grid-cols-4\">\n <Select\n value={filters.resourceId ?? ALL}\n onValueChange={(v) => resetFilters({ resourceId: v === ALL ? undefined : v })}\n >\n <SelectTrigger aria-label={t('audit:resource')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{t('audit:allResources')}</SelectItem>\n {Object.entries(VIRTUAL_RESOURCE_LABELS).map(([id, key]) => (\n <SelectItem key={id} value={id}>{t(key)}</SelectItem>\n ))}\n {resources.map((resource) => (\n <SelectItem key={resource.id} value={resource.id}>\n {resource.name}\n {resource.name !== resource.id && (\n <span className=\"ml-1.5 text-xs text-muted-foreground\">({resource.id})</span>\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select\n value={filters.actions?.[0] ?? ALL}\n onValueChange={(v) => resetFilters({ actions: v === ALL ? undefined : [v] })}\n >\n <SelectTrigger aria-label={t('audit:action')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL}>{t('audit:allActions')}</SelectItem>\n {ACTIONS.map((action) => {\n const style = ACTION_STYLES[action]\n return (\n <SelectItem key={action} value={action}>\n {style?.titleKey ? t(style.titleKey) : action}\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n <Input\n value={filters.recordId ?? ''}\n placeholder={t('audit:recordId')}\n onChange={(e) => resetFilters({ recordId: e.target.value || undefined })}\n />\n <Input\n value={filters.userId ?? ''}\n placeholder={t('audit:userId')}\n onChange={(e) => resetFilters({ userId: e.target.value || undefined })}\n />\n <DateRangeInput\n from={filters.from}\n to={filters.to}\n onChange={(from, to) =>\n resetFilters({ from: from || undefined, to: to || undefined })\n }\n className=\"sm:col-span-2 md:col-span-4\"\n labels={{\n placeholder: t('audit:dateRangePlaceholder'),\n apply: t('common:apply'),\n clear: t('common:clear'),\n }}\n />\n </CardContent>\n </Card>\n <Card>\n <CardContent>\n {log.isLoading ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-20 w-full\" />\n </div>\n ) : log.isError ? (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive\">\n {t('audit:loadError')}\n </div>\n ) : events.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border p-6 text-center text-sm text-muted-foreground\">\n {t('audit:noEvents')}\n </div>\n ) : (\n <ol className=\"space-y-3\">\n {events.map((entry, i) => (\n <AuditEntryCard\n key={entry.id ?? `${entry.at}:${i}`}\n entry={entry}\n resource={resourceMap[entry.resourceId]}\n user={entry.userId ? users.get(entry.userId) ?? null : null}\n userResourceId={userResourceExists ? USERS_RESOURCE_ID : undefined}\n now={now}\n formatRelative={formatRelative}\n formatAbsolute={formatAbsolute}\n />\n ))}\n </ol>\n )}\n {/* Sentinel div — observed by IntersectionObserver to trigger next page */}\n <div ref={sentinelRef} className=\"h-1\" aria-hidden />\n {log.isFetchingNextPage && (\n <div className=\"flex justify-center py-4\">\n <Loader2 className=\"size-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n )\n}\n\ninterface AuditEntryCardProps {\n entry: AuditLogEntry\n resource: ResourceJSON | undefined\n user: RecordJSON | null | undefined\n userResourceId?: string\n now: number\n formatRelative: (atMs: number, nowMs: number) => string\n formatAbsolute: (value: number) => string\n}\n\nfunction AuditEntryCard({\n entry,\n resource,\n user,\n userResourceId,\n now,\n formatRelative,\n formatAbsolute,\n}: AuditEntryCardProps): React.ReactElement {\n const { t } = useI18n()\n const [expanded, setExpanded] = React.useState(false)\n\n const style = ACTION_STYLES[entry.action] ?? FALLBACK_STYLE\n const Icon = style.Icon\n const title = style.titleKey ? t(style.titleKey) : entry.action\n\n const userFallback = entry.userId ?? t('history:unknownUser')\n const userLabel = userLabelOf(user, userFallback)\n\n const byTpl = t('audit:by', { user: '\\u0000' })\n const byParts = byTpl.split('\\u0000')\n const byPrefix = byParts[0] ?? ''\n const bySuffix = byParts[1] ?? ''\n\n const virtualResourceKey = VIRTUAL_RESOURCE_LABELS[entry.resourceId]\n const resourceLabel = virtualResourceKey\n ? t(virtualResourceKey)\n : (resource?.name ?? entry.resourceId)\n\n // Diff drill-down only makes sense when we have a single recordId AND\n // history exists for that resource (we'll discover that lazily on first\n // expand). Bulk operations and `new`/`delete` actions skip the toggle.\n const canExpand = entry.action === 'edit' && !!entry.recordId\n\n return (\n <li className=\"rounded-lg border border-border bg-card p-4 shadow-sm\">\n <div className=\"flex items-start gap-3\">\n <span\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n style.bgClass,\n )}\n >\n <Icon className={cn('size-5', style.iconClass)} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold\">{title}</p>\n <div className=\"mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground\">\n <span className=\"font-medium\">{resourceLabel}</span>\n {entry.recordId && (\n <>\n <span aria-hidden>·</span>\n {resource ? (\n <Link\n to={{\n name: 'show',\n resourceId: entry.resourceId,\n recordId: entry.recordId,\n }}\n className=\"inline-flex items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground hover:underline\"\n title={entry.recordId}\n >\n <span className=\"truncate max-w-[12rem]\">\n {entry.recordTitle ?? `#${entry.recordId}`}\n </span>\n <ExternalLink className=\"size-3 shrink-0\" />\n </Link>\n ) : entry.recordTitle ? (\n <span\n className=\"truncate max-w-[12rem] rounded bg-muted px-1.5 py-0.5 text-[11px] text-foreground\"\n title={entry.recordId}\n >\n {entry.recordTitle}\n </span>\n ) : null}\n </>\n )}\n {!entry.recordId && entry.recordIds?.length ? (\n <>\n <span aria-hidden>·</span>\n <span>\n {entry.recordIds.length} {t('audit:records')}\n </span>\n </>\n ) : null}\n </div>\n <p className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span\n aria-hidden\n className=\"inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary\"\n >\n {initialsOf(userLabel)}\n </span>\n <span className=\"truncate\">\n {byPrefix}\n {userResourceId && entry.userId ? (\n <Link\n to={{ name: 'show', resourceId: userResourceId, recordId: entry.userId }}\n className=\"font-medium text-foreground hover:underline\"\n >\n {userLabel}\n </Link>\n ) : (\n userLabel\n )}\n {bySuffix}\n </span>\n </p>\n </div>\n <time\n className=\"shrink-0 text-xs text-muted-foreground\"\n dateTime={new Date(entry.at).toISOString()}\n title={formatAbsolute(entry.at)}\n >\n {formatRelative(entry.at, now)}\n </time>\n </div>\n {canExpand && (\n <div className=\"mt-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ml-2 h-7 px-2 text-xs\"\n onClick={() => setExpanded((x) => !x)}\n aria-expanded={expanded}\n >\n {expanded ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n {expanded ? t('audit:hideChanges') : t('audit:viewChanges')}\n </Button>\n {expanded && (\n <div className=\"mt-2\">\n <AuditEntryChanges\n resourceId={entry.resourceId}\n recordId={entry.recordId!}\n atMs={entry.at}\n />\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n </li>\n )\n}\n\n/** Lazy-loaded diff for a single audit entry. Pulls the record's revision\n * history (server returns the freshest 50) and picks the revision whose\n * `createdAt` is closest to the entry's `at` timestamp. We require the\n * match to be within a 60s window to avoid showing an unrelated revision\n * when the matching one was pruned. */\nfunction AuditEntryChanges({\n resourceId,\n recordId,\n atMs,\n}: {\n resourceId: string\n recordId: string\n atMs: number\n}): React.ReactElement {\n const { t } = useI18n()\n const resource = useResource(resourceId)\n const history = useRecordHistory(resourceId, recordId, { limit: 50 })\n // Pull the live record so we can show its title (e.g. user's email) at\n // the top of the diff — helpful when the audit log lists many entries.\n const record = useRecord(resourceId, recordId)\n\n const labelByPath = React.useMemo(() => {\n const map: Record<string, string> = {}\n for (const p of resource?.properties ?? []) map[p.path] = p.label\n return map\n }, [resource])\n\n if (history.isLoading) {\n return <Skeleton className=\"h-16 w-full\" />\n }\n if (history.isError) {\n return (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-3 text-xs text-destructive\">\n {t('audit:loadDiffError')}\n </div>\n )\n }\n\n const revisions = history.data?.revisions ?? []\n const target = findNearestRevision(revisions, atMs)\n\n if (!target) {\n return (\n <div className=\"rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground\">\n {t('audit:noDiff')}\n </div>\n )\n }\n\n const fields: HistoryDiffEntry[] = diffSnapshots(\n target.snapshotBefore ?? {},\n target.snapshot,\n ).map((f) => ({ ...f, label: labelByPath[f.path] }))\n const recordTitle = record.data?.record?.title\n const showTitle = recordTitle && recordTitle !== recordId\n\n return (\n <div className=\"space-y-2\">\n {showTitle && (\n <p className=\"truncate text-xs text-muted-foreground\" title={recordTitle}>\n {recordTitle}\n </p>\n )}\n <DiffView\n fields={fields}\n labels={{\n added: t('diff:added'),\n changed: t('diff:changed'),\n removed: t('diff:removed'),\n before: t('diff:before'),\n after: t('diff:after'),\n noChanges: t('diff:noChanges'),\n }}\n />\n </div>\n )\n}\n\nconst TOLERANCE_MS = 60_000\n\nconst findNearestRevision = (\n revisions: ReadonlyArray<HistoryRevision>,\n atMs: number,\n): HistoryRevision | null => {\n let best: HistoryRevision | null = null\n let bestDiff = Infinity\n for (const r of revisions) {\n const t = new Date(r.createdAt).getTime()\n if (Number.isNaN(t)) continue\n const d = Math.abs(t - atMs)\n if (d < bestDiff && d <= TOLERANCE_MS) {\n bestDiff = d\n best = r\n }\n }\n return best\n}\n"],"mappings":"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"}
|