@burdenoff/microfe-assethandler 2026.820.4 → 2026.826.1
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/dist/assethandler/components/ActivityTimeline.d.ts.map +1 -1
- package/dist/assethandler/components/ActivityTimeline.js +2 -0
- package/dist/assethandler/components/ActivityTimeline.js.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.d.ts.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.js.map +1 -1
- package/dist/assethandler/providers/AssetHandlerProvider.d.ts +1 -0
- package/dist/assethandler/providers/AssetHandlerProvider.d.ts.map +1 -1
- package/dist/assethandler/providers/AssetHandlerProvider.js +37 -35
- package/dist/assethandler/providers/AssetHandlerProvider.js.map +1 -1
- package/dist/types/assistant.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"names":[],"mappings":"AA2BA;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+ED,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,qBAAqB,+
|
|
1
|
+
{"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"names":[],"mappings":"AA2BA;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+ED,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,qBAAqB,+BA4IjF"}
|
|
@@ -102,6 +102,8 @@ function k({ resourceId: i, limit: a = 20 }) {
|
|
|
102
102
|
let { workspaceId: o, currentUser: s } = e(), { data: c, loading: l } = g(w, {
|
|
103
103
|
variables: { filter: {
|
|
104
104
|
resourceId: i,
|
|
105
|
+
resourceType: "asset",
|
|
106
|
+
productId: "assethandler",
|
|
105
107
|
...o ? { workspaceId: o } : {},
|
|
106
108
|
limit: a
|
|
107
109
|
} },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityTimeline.js","names":[],"sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Activity,\n Plus,\n Pencil,\n Trash2,\n CheckCircle2,\n LogOut,\n LogIn,\n RefreshCw,\n Paperclip,\n MessageSquare,\n Wrench,\n ShieldAlert,\n} from 'lucide-react';\nimport { useAssetHandler } from '../providers/AssetHandlerProvider';\nimport { Sparkline } from './charts';\n\n/**\n * Recent activity for a single AssetHandler resource, sourced from the\n * federated wspace-activity-svc `getActivities` query (fed by be-sdk\n * `trackOperation` on the backend). Read-only, with a graceful empty state.\n *\n * The header carries an activity-volume sparkline bucketed from the real\n * timestamps — it only renders when there is enough history to be meaningful,\n * never fabricated.\n */\nexport interface ActivityTimelineProps {\n resourceId: string;\n /** Optional cap on rows shown. Defaults to 20. */\n limit?: number;\n}\n\ninterface ActivityLogNode {\n id: string;\n action: string;\n description?: string | null;\n timestamp: string;\n userId: string;\n resourceType?: string | null;\n}\n\ninterface GetActivitiesResult {\n getActivities: ActivityLogNode[];\n}\n\nconst GET_ACTIVITIES_QUERY = gql`\n query AssetHandlerResourceActivities($filter: ActivityLogFilter) {\n getActivities(filter: $filter) {\n id\n action\n description\n timestamp\n userId\n resourceType\n }\n }\n`;\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype Tone = 'success' | 'info' | 'warning' | 'error' | 'neutral';\n\nconst TONE_CLASS: Record<Tone, { icon: string; tile: string }> = {\n success: { icon: 'text-status-success-text', tile: 'bg-status-success-bg-subtle' },\n info: { icon: 'text-status-info-text', tile: 'bg-status-info-bg-subtle' },\n warning: { icon: 'text-status-warning-text', tile: 'bg-status-warning-bg-subtle' },\n error: { icon: 'text-status-error-text', tile: 'bg-status-error-bg-subtle' },\n neutral: { icon: 'text-text-secondary', tile: 'bg-bg-sunken' },\n};\n\n/** Map an activity action verb to a node icon + status tone. */\nfunction actionVisual(action: string): { Icon: typeof Activity; tone: Tone } {\n const a = action.toLowerCase();\n if (/(complete|resolve|close|done|approve|pass|success)/.test(a))\n return { Icon: CheckCircle2, tone: 'success' };\n if (/(delete|remove|dispose|retire|archive|reject|fail)/.test(a))\n return { Icon: Trash2, tone: 'error' };\n if (/(check[_.\\s]?out|assign|issue|lend)/.test(a)) return { Icon: LogOut, tone: 'warning' };\n if (/(check[_.\\s]?in|return|receive)/.test(a)) return { Icon: LogIn, tone: 'success' };\n if (/(maintenance|repair|service|calibrat)/.test(a)) return { Icon: Wrench, tone: 'warning' };\n if (/(incident|breach|alert|violation|risk|security)/.test(a))\n return { Icon: ShieldAlert, tone: 'error' };\n if (/(upload|attach|file)/.test(a)) return { Icon: Paperclip, tone: 'info' };\n if (/(comment|note|reply|discuss)/.test(a)) return { Icon: MessageSquare, tone: 'info' };\n if (/(create|add|new|register|provision)/.test(a)) return { Icon: Plus, tone: 'success' };\n if (/(update|edit|change|modify|revise|status)/.test(a)) return { Icon: Pencil, tone: 'info' };\n if (/(sync|refresh|move|transfer|relocat)/.test(a)) return { Icon: RefreshCw, tone: 'info' };\n return { Icon: Activity, tone: 'neutral' };\n}\n\nfunction titleize(action: string): string {\n const s = action.replace(/[._]/g, ' ').trim();\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nexport function ActivityTimeline({ resourceId, limit = 20 }: ActivityTimelineProps) {\n const { workspaceId, currentUser } = useAssetHandler();\n\n const { data, loading } = useQuery<GetActivitiesResult>(GET_ACTIVITIES_QUERY, {\n variables: {\n filter: {\n resourceId,\n ...(workspaceId ? { workspaceId } : {}),\n limit,\n },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n skip: !resourceId,\n });\n\n const activities = useMemo(() => data?.getActivities ?? [], [data]);\n\n // Bucket real timestamps into per-day volume for a header sparkline. Only\n // meaningful when there is enough history spanning multiple days — otherwise\n // we omit it rather than draw a flat/fabricated line.\n const volumeSeries = useMemo(() => {\n if (activities.length < 6) return null;\n const DAYS = 14;\n const now = new Date();\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const buckets = new Array<number>(DAYS).fill(0);\n let distinctDays = 0;\n const seen = new Set<number>();\n for (const item of activities) {\n const t = new Date(item.timestamp).getTime();\n if (Number.isNaN(t)) continue;\n const dayIndex = DAYS - 1 - Math.floor((startOfToday - t) / 86_400_000);\n if (dayIndex >= 0 && dayIndex < DAYS) {\n buckets[dayIndex] += 1;\n const dayKey = Math.floor(t / 86_400_000);\n if (!seen.has(dayKey)) {\n seen.add(dayKey);\n distinctDays += 1;\n }\n }\n }\n return distinctDays >= 3 ? buckets : null;\n }, [activities]);\n\n const actorLabel = (userId: string): string =>\n currentUser?.id && userId === currentUser.id\n ? currentUser.name ||\n [currentUser.firstName, currentUser.lastName].filter(Boolean).join(' ') ||\n currentUser.username ||\n currentUser.email?.split('@')[0] ||\n 'You'\n : userId === 'system' || userId === 'SYSTEM'\n ? 'System'\n : `Member ${userId.slice(0, 6)}`;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Activity className=\"h-4 w-4\" />\n Activity\n </CardTitle>\n {volumeSeries ? (\n <div className=\"flex items-center gap-2\" title=\"Activity over the last 14 days\">\n <span className=\"hidden text-xs text-text-secondary sm:inline\">Last 14 days</span>\n <Sparkline\n data={volumeSeries}\n type=\"area\"\n height={28}\n className=\"w-24\"\n ariaLabel=\"Activity volume over the last 14 days\"\n />\n </div>\n ) : null}\n </CardHeader>\n <CardContent>\n {loading && activities.length === 0 ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div\n key={i}\n className=\"h-12 rounded-md bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : activities.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"No activity yet\"\n description=\"Changes to this record — updates, status changes and assignments — will show up here.\"\n />\n ) : (\n <ol className=\"space-y-1\">\n {activities.map((item, index) => {\n const { Icon, tone } = actionVisual(item.action);\n const tc = TONE_CLASS[tone];\n const isLast = index === activities.length - 1;\n return (\n <li key={item.id} className=\"relative flex gap-3 pb-3\">\n {/* Connector line between nodes. */}\n {!isLast ? (\n <span\n className=\"absolute left-[0.9375rem] top-8 h-[calc(100%-1rem)] w-px bg-border-subtle\"\n aria-hidden=\"true\"\n />\n ) : null}\n <span\n className={`relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${tc.tile}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-4 w-4 ${tc.icon}`} />\n </span>\n <div className=\"min-w-0 flex-1 pt-0.5\">\n <p className=\"text-sm font-medium text-text-primary\">{titleize(item.action)}</p>\n {item.description ? (\n <p className=\"truncate text-sm text-text-secondary\" title={item.description}>\n {item.description}\n </p>\n ) : null}\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n <span className=\"font-medium text-text-secondary\">\n {actorLabel(item.userId)}\n </span>{' '}\n ·{' '}\n <span title={new Date(item.timestamp).toLocaleString()}>\n {relativeTime(item.timestamp)}\n </span>\n </p>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActivityTimeline.js","names":[],"sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Activity,\n Plus,\n Pencil,\n Trash2,\n CheckCircle2,\n LogOut,\n LogIn,\n RefreshCw,\n Paperclip,\n MessageSquare,\n Wrench,\n ShieldAlert,\n} from 'lucide-react';\nimport { useAssetHandler } from '../providers/AssetHandlerProvider';\nimport { Sparkline } from './charts';\n\n/**\n * Recent activity for a single AssetHandler resource, sourced from the\n * federated wspace-activity-svc `getActivities` query (fed by be-sdk\n * `trackOperation` on the backend). Read-only, with a graceful empty state.\n *\n * The header carries an activity-volume sparkline bucketed from the real\n * timestamps — it only renders when there is enough history to be meaningful,\n * never fabricated.\n */\nexport interface ActivityTimelineProps {\n resourceId: string;\n /** Optional cap on rows shown. Defaults to 20. */\n limit?: number;\n}\n\ninterface ActivityLogNode {\n id: string;\n action: string;\n description?: string | null;\n timestamp: string;\n userId: string;\n resourceType?: string | null;\n}\n\ninterface GetActivitiesResult {\n getActivities: ActivityLogNode[];\n}\n\nconst GET_ACTIVITIES_QUERY = gql`\n query AssetHandlerResourceActivities($filter: ActivityLogFilter) {\n getActivities(filter: $filter) {\n id\n action\n description\n timestamp\n userId\n resourceType\n }\n }\n`;\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype Tone = 'success' | 'info' | 'warning' | 'error' | 'neutral';\n\nconst TONE_CLASS: Record<Tone, { icon: string; tile: string }> = {\n success: { icon: 'text-status-success-text', tile: 'bg-status-success-bg-subtle' },\n info: { icon: 'text-status-info-text', tile: 'bg-status-info-bg-subtle' },\n warning: { icon: 'text-status-warning-text', tile: 'bg-status-warning-bg-subtle' },\n error: { icon: 'text-status-error-text', tile: 'bg-status-error-bg-subtle' },\n neutral: { icon: 'text-text-secondary', tile: 'bg-bg-sunken' },\n};\n\n/** Map an activity action verb to a node icon + status tone. */\nfunction actionVisual(action: string): { Icon: typeof Activity; tone: Tone } {\n const a = action.toLowerCase();\n if (/(complete|resolve|close|done|approve|pass|success)/.test(a))\n return { Icon: CheckCircle2, tone: 'success' };\n if (/(delete|remove|dispose|retire|archive|reject|fail)/.test(a))\n return { Icon: Trash2, tone: 'error' };\n if (/(check[_.\\s]?out|assign|issue|lend)/.test(a)) return { Icon: LogOut, tone: 'warning' };\n if (/(check[_.\\s]?in|return|receive)/.test(a)) return { Icon: LogIn, tone: 'success' };\n if (/(maintenance|repair|service|calibrat)/.test(a)) return { Icon: Wrench, tone: 'warning' };\n if (/(incident|breach|alert|violation|risk|security)/.test(a))\n return { Icon: ShieldAlert, tone: 'error' };\n if (/(upload|attach|file)/.test(a)) return { Icon: Paperclip, tone: 'info' };\n if (/(comment|note|reply|discuss)/.test(a)) return { Icon: MessageSquare, tone: 'info' };\n if (/(create|add|new|register|provision)/.test(a)) return { Icon: Plus, tone: 'success' };\n if (/(update|edit|change|modify|revise|status)/.test(a)) return { Icon: Pencil, tone: 'info' };\n if (/(sync|refresh|move|transfer|relocat)/.test(a)) return { Icon: RefreshCw, tone: 'info' };\n return { Icon: Activity, tone: 'neutral' };\n}\n\nfunction titleize(action: string): string {\n const s = action.replace(/[._]/g, ' ').trim();\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nexport function ActivityTimeline({ resourceId, limit = 20 }: ActivityTimelineProps) {\n const { workspaceId, currentUser } = useAssetHandler();\n\n const { data, loading } = useQuery<GetActivitiesResult>(GET_ACTIVITIES_QUERY, {\n variables: {\n filter: {\n resourceId,\n resourceType: 'asset',\n productId: 'assethandler',\n ...(workspaceId ? { workspaceId } : {}),\n limit,\n },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n skip: !resourceId,\n });\n\n const activities = useMemo(() => data?.getActivities ?? [], [data]);\n\n // Bucket real timestamps into per-day volume for a header sparkline. Only\n // meaningful when there is enough history spanning multiple days — otherwise\n // we omit it rather than draw a flat/fabricated line.\n const volumeSeries = useMemo(() => {\n if (activities.length < 6) return null;\n const DAYS = 14;\n const now = new Date();\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const buckets = new Array<number>(DAYS).fill(0);\n let distinctDays = 0;\n const seen = new Set<number>();\n for (const item of activities) {\n const t = new Date(item.timestamp).getTime();\n if (Number.isNaN(t)) continue;\n const dayIndex = DAYS - 1 - Math.floor((startOfToday - t) / 86_400_000);\n if (dayIndex >= 0 && dayIndex < DAYS) {\n buckets[dayIndex] += 1;\n const dayKey = Math.floor(t / 86_400_000);\n if (!seen.has(dayKey)) {\n seen.add(dayKey);\n distinctDays += 1;\n }\n }\n }\n return distinctDays >= 3 ? buckets : null;\n }, [activities]);\n\n const actorLabel = (userId: string): string =>\n currentUser?.id && userId === currentUser.id\n ? currentUser.name ||\n [currentUser.firstName, currentUser.lastName].filter(Boolean).join(' ') ||\n currentUser.username ||\n currentUser.email?.split('@')[0] ||\n 'You'\n : userId === 'system' || userId === 'SYSTEM'\n ? 'System'\n : `Member ${userId.slice(0, 6)}`;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Activity className=\"h-4 w-4\" />\n Activity\n </CardTitle>\n {volumeSeries ? (\n <div className=\"flex items-center gap-2\" title=\"Activity over the last 14 days\">\n <span className=\"hidden text-xs text-text-secondary sm:inline\">Last 14 days</span>\n <Sparkline\n data={volumeSeries}\n type=\"area\"\n height={28}\n className=\"w-24\"\n ariaLabel=\"Activity volume over the last 14 days\"\n />\n </div>\n ) : null}\n </CardHeader>\n <CardContent>\n {loading && activities.length === 0 ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div\n key={i}\n className=\"h-12 rounded-md bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : activities.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"No activity yet\"\n description=\"Changes to this record — updates, status changes and assignments — will show up here.\"\n />\n ) : (\n <ol className=\"space-y-1\">\n {activities.map((item, index) => {\n const { Icon, tone } = actionVisual(item.action);\n const tc = TONE_CLASS[tone];\n const isLast = index === activities.length - 1;\n return (\n <li key={item.id} className=\"relative flex gap-3 pb-3\">\n {/* Connector line between nodes. */}\n {!isLast ? (\n <span\n className=\"absolute left-[0.9375rem] top-8 h-[calc(100%-1rem)] w-px bg-border-subtle\"\n aria-hidden=\"true\"\n />\n ) : null}\n <span\n className={`relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${tc.tile}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-4 w-4 ${tc.icon}`} />\n </span>\n <div className=\"min-w-0 flex-1 pt-0.5\">\n <p className=\"text-sm font-medium text-text-primary\">{titleize(item.action)}</p>\n {item.description ? (\n <p className=\"truncate text-sm text-text-secondary\" title={item.description}>\n {item.description}\n </p>\n ) : null}\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n <span className=\"font-medium text-text-secondary\">\n {actorLabel(item.userId)}\n </span>{' '}\n ·{' '}\n <span title={new Date(item.timestamp).toLocaleString()}>\n {relativeTime(item.timestamp)}\n </span>\n </p>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttachmentsPanel.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"names":[],"mappings":"AAiCA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"AttachmentsPanel.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"names":[],"mappings":"AAiCA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAmND,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,qBAAqB,+BAmP/E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttachmentsPanel.js","names":[],"sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Paperclip,\n Upload,\n Link2,\n FileText,\n FileSpreadsheet,\n FileArchive,\n FileCode2,\n FileVideo,\n FileAudio,\n Image as ImageIcon,\n File as FileIcon,\n Loader2,\n Download,\n MoreHorizontal,\n} from 'lucide-react';\nimport { notify } from '../utils/toast';\n\n/**\n * Attach and reference files for an AssetHandler entity, backed by the\n * federated wspace-files-svc (same wspace gateway/Apollo client). Fully real (no fabricated data) —\n * lists workspace files via `getFiles`, uploads new files through the\n * `initiateUpload` → presigned PUT → `completeUpload` flow, and mints public\n * share links via `createFileShareLink`. All entries are live data.\n *\n * `entityType`/`entityId` scope the panel to the record it is mounted on and\n * are used for context labelling; uploaded files land in the workspace file\n * store so they can be shared and reused.\n */\nexport interface AttachmentsPanelProps {\n entityType: string;\n entityId: string;\n}\n\ninterface FileNode {\n id: string;\n name: string;\n extension: string;\n mimeType: string;\n size: number;\n downloadUrl?: string | null;\n createdAt: string;\n}\n\ninterface GetFilesResult {\n getFiles: {\n files: FileNode[];\n total: number;\n hasMore: boolean;\n };\n}\n\nconst GET_FILES_QUERY = gql`\n query AssetHandlerGetFiles($pagination: PaginationInput, $sort: FileSortInput) {\n getFiles(pagination: $pagination, sort: $sort) {\n total\n hasMore\n files {\n id\n name\n extension\n mimeType\n size\n downloadUrl\n createdAt\n }\n }\n }\n`;\n\nconst INITIATE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session {\n id\n }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n name\n }\n }\n }\n`;\n\nconst CREATE_SHARE_LINK_MUTATION = gql`\n mutation AssetHandlerCreateFileShareLink($input: CreateShareGrantLinkInput!) {\n createFileShareLink(input: $input) {\n id\n shareUrl\n }\n }\n`;\n\nfunction formatSize(bytes: number): string {\n if (!bytes) return '0 B';\n const units = ['B', 'KB', 'MB', 'GB'];\n const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);\n const value = bytes / Math.pow(1024, exponent);\n return `${value.toFixed(exponent === 0 ? 0 : 1)} ${units[exponent]}`;\n}\n\nfunction extensionOf(fileName: string): string {\n const dot = fileName.lastIndexOf('.');\n return dot >= 0 ? fileName.slice(dot + 1).toLowerCase() : 'bin';\n}\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype FileKind =\n | 'image'\n | 'pdf'\n | 'sheet'\n | 'doc'\n | 'archive'\n | 'code'\n | 'video'\n | 'audio'\n | 'other';\n\n/** Map a file's extension/mime to a recognisable kind for icon + tint. */\nfunction kindOf(file: FileNode): FileKind {\n const mime = (file.mimeType || '').toLowerCase();\n const ext = (file.extension || extensionOf(file.name)).toLowerCase();\n if (\n mime.startsWith('image/') ||\n ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext)\n )\n return 'image';\n if (mime.startsWith('video/') || ['mp4', 'mov', 'webm', 'avi', 'mkv'].includes(ext))\n return 'video';\n if (mime.startsWith('audio/') || ['mp3', 'wav', 'ogg', 'm4a', 'flac'].includes(ext))\n return 'audio';\n if (mime === 'application/pdf' || ext === 'pdf') return 'pdf';\n if (['xls', 'xlsx', 'csv', 'tsv', 'ods'].includes(ext)) return 'sheet';\n if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(ext)) return 'archive';\n if (\n [\n 'js',\n 'ts',\n 'tsx',\n 'jsx',\n 'json',\n 'xml',\n 'yml',\n 'yaml',\n 'html',\n 'css',\n 'sh',\n 'py',\n 'sql',\n ].includes(ext)\n )\n return 'code';\n if (['doc', 'docx', 'txt', 'md', 'rtf', 'odt', 'pages'].includes(ext)) return 'doc';\n return 'other';\n}\n\nconst KIND_STYLE: Record<\n FileKind,\n { Icon: typeof FileText; iconClass: string; tileClass: string }\n> = {\n image: {\n Icon: ImageIcon,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n pdf: {\n Icon: FileText,\n iconClass: 'text-status-error-text',\n tileClass: 'bg-status-error-bg-subtle',\n },\n sheet: {\n Icon: FileSpreadsheet,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n doc: {\n Icon: FileText,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n archive: {\n Icon: FileArchive,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n code: {\n Icon: FileCode2,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n video: {\n Icon: FileVideo,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n audio: {\n Icon: FileAudio,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n other: { Icon: FileIcon, iconClass: 'text-text-secondary', tileClass: 'bg-bg-sunken' },\n};\n\n/** File-type badge tile — an inline thumbnail for images, a tinted glyph otherwise. */\nfunction FileBadge({ file }: { file: FileNode }) {\n const kind = kindOf(file);\n const { Icon, iconClass, tileClass } = KIND_STYLE[kind];\n if (kind === 'image' && file.downloadUrl) {\n return (\n <img\n src={file.downloadUrl}\n alt=\"\"\n loading=\"lazy\"\n className=\"h-10 w-10 shrink-0 rounded-lg border border-border-subtle object-cover\"\n />\n );\n }\n return (\n <span\n className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${tileClass}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-5 w-5 ${iconClass}`} />\n </span>\n );\n}\n\nexport function AttachmentsPanel({ entityType, entityId }: AttachmentsPanelProps) {\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [uploading, setUploading] = useState(false);\n const [sharingId, setSharingId] = useState<string | null>(null);\n\n const { data, loading, refetch } = useQuery<GetFilesResult>(GET_FILES_QUERY, {\n variables: {\n pagination: { limit: 25, offset: 0 },\n sort: { field: 'CREATED_AT', order: 'DESC' },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n });\n\n const [initiateUpload] = useMutation<{\n initiateUpload: { uploadUrl?: string | null; session: { id: string } };\n }>(INITIATE_UPLOAD_MUTATION);\n const [completeUpload] = useMutation(COMPLETE_UPLOAD_MUTATION);\n const [createShareLink] = useMutation<{\n createFileShareLink: { id: string; shareUrl?: string | null };\n }>(CREATE_SHARE_LINK_MUTATION);\n\n const files = useMemo(() => data?.getFiles.files ?? [], [data]);\n\n const handleFileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) return;\n\n setUploading(true);\n try {\n const initiated = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n extension: extensionOf(file.name),\n fileSize: file.size,\n mimeType: file.type || 'application/octet-stream',\n visibility: 'WORKSPACE',\n },\n },\n });\n\n const uploadUrl = initiated.data?.initiateUpload.uploadUrl;\n const sessionId = initiated.data?.initiateUpload.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('Upload could not be initialised.');\n }\n\n const putResponse = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n });\n if (!putResponse.ok) {\n throw new Error(`Storage rejected the upload (HTTP ${putResponse.status}).`);\n }\n\n await completeUpload({\n variables: { input: { sessionId, fileSize: file.size } },\n });\n\n notify.success('File uploaded', `${file.name} is now available on this record.`);\n await refetch();\n } catch (error) {\n notify.error(\n 'Upload failed',\n error instanceof Error ? error.message : 'The file could not be uploaded.'\n );\n } finally {\n setUploading(false);\n }\n };\n\n const handleShare = async (fileId: string) => {\n setSharingId(fileId);\n try {\n const result = await createShareLink({\n variables: { input: { resourceId: fileId, role: 'VIEWER' } },\n });\n const url = result.data?.createFileShareLink.shareUrl;\n if (!url) {\n throw new Error('No share link was returned.');\n }\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(url);\n notify.success('Share link copied', 'A view-only link is on your clipboard.');\n } else {\n notify.info('Share link ready', url);\n }\n } catch (error) {\n notify.error(\n 'Could not create share link',\n error instanceof Error ? error.message : 'Please try again.'\n );\n } finally {\n setSharingId(null);\n }\n };\n\n const total = data?.getFiles.total ?? 0;\n const hasFiles = files.length > 0;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-2 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Paperclip className=\"h-4 w-4\" />\n Attachments\n {total ? (\n <span className=\"text-sm font-normal text-text-secondary\">\n {total} file{total === 1 ? '' : 's'}\n </span>\n ) : null}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <input\n ref={fileInputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(event) => void handleFileSelected(event)}\n aria-label={`Upload a file for ${entityType} ${entityId}`}\n />\n\n {loading && !hasFiles ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div\n key={i}\n className=\"h-14 rounded-lg bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : !hasFiles ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No files yet\"\n description=\"Upload photos, receipts, warranties or documents to keep them with this record.\"\n />\n ) : (\n <ul className=\"space-y-1.5\">\n {files.map((file) => {\n const isSharing = sharingId === file.id;\n return (\n <li\n key={file.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-2.5\"\n >\n <FileBadge file={file} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\" title={file.name}>\n {file.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatSize(Number(file.size))} · {relativeTime(file.createdAt)}\n </p>\n </div>\n {/* Desktop: inline actions. Mobile: kebab menu to avoid crowding. */}\n <div className=\"hidden items-center gap-1 sm:flex\">\n {file.downloadUrl ? (\n <a\n href={file.downloadUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Download ${file.name}`}\n title=\"Download\"\n >\n <Download className=\"h-4 w-4\" />\n </a>\n ) : null}\n <button\n type=\"button\"\n disabled={isSharing}\n onClick={() => void handleShare(file.id)}\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60\"\n aria-label={`Copy share link for ${file.name}`}\n title=\"Copy share link\"\n >\n {isSharing ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Link2 className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n <div className=\"sm:hidden\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Actions for ${file.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {file.downloadUrl ? (\n <DropdownMenuItem asChild>\n <a href={file.downloadUrl} target=\"_blank\" rel=\"noreferrer\">\n <Download className=\"mr-2 h-4 w-4\" />\n Download\n </a>\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuItem\n disabled={isSharing}\n onSelect={(e) => {\n e.preventDefault();\n void handleShare(file.id);\n }}\n >\n <Link2 className=\"mr-2 h-4 w-4\" />\n Copy share link\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n {/* Prominent full-width primary upload CTA (matches the reference concept). */}\n <Button\n className=\"w-full\"\n disabled={uploading}\n onClick={() => fileInputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <Upload className=\"mr-2 h-4 w-4\" />\n )}\n {uploading ? 'Uploading…' : 'Upload files'}\n </Button>\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AttachmentsPanel.js","names":[],"sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Paperclip,\n Upload,\n Link2,\n FileText,\n FileSpreadsheet,\n FileArchive,\n FileCode2,\n FileVideo,\n FileAudio,\n Image as ImageIcon,\n File as FileIcon,\n Loader2,\n Download,\n MoreHorizontal,\n} from 'lucide-react';\nimport { notify } from '../utils/toast';\n\n/**\n * Attach and reference files for an AssetHandler entity, backed by the\n * federated wspace-files-svc (same wspace gateway/Apollo client). Fully real (no fabricated data) —\n * lists workspace files via `getFiles`, uploads new files through the\n * `initiateUpload` → presigned PUT → `completeUpload` flow, and mints public\n * share links via `createFileShareLink`. All entries are live data.\n *\n * `entityType`/`entityId` scope the panel to the record it is mounted on and\n * are used for context labelling; uploaded files land in the workspace file\n * store so they can be shared and reused.\n */\nexport interface AttachmentsPanelProps {\n entityType: string;\n entityId: string;\n}\n\ninterface FileNode {\n id: string;\n name: string;\n extension: string;\n mimeType: string;\n size: number;\n downloadUrl?: string | null;\n createdAt: string;\n}\n\ninterface GetFilesResult {\n getFiles: {\n files: FileNode[];\n total: number;\n hasMore: boolean;\n };\n}\n\nconst GET_FILES_QUERY = gql`\n query AssetHandlerGetFiles($pagination: PaginationInput, $sort: FileSortInput) {\n getFiles(pagination: $pagination, sort: $sort) {\n total\n hasMore\n files {\n id\n name\n extension\n mimeType\n size\n downloadUrl\n createdAt\n }\n }\n }\n`;\n\nconst INITIATE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session {\n id\n }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n name\n }\n }\n }\n`;\n\nconst CREATE_SHARE_LINK_MUTATION = gql`\n mutation AssetHandlerCreateFileShareLink($input: CreateShareGrantLinkInput!) {\n createFileShareLink(input: $input) {\n id\n shareUrl\n }\n }\n`;\n\nfunction formatSize(bytes: number): string {\n if (!bytes) return '0 B';\n const units = ['B', 'KB', 'MB', 'GB'];\n const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);\n const value = bytes / Math.pow(1024, exponent);\n return `${value.toFixed(exponent === 0 ? 0 : 1)} ${units[exponent]}`;\n}\n\nfunction extensionOf(fileName: string): string {\n const dot = fileName.lastIndexOf('.');\n return dot >= 0 ? fileName.slice(dot + 1).toLowerCase() : 'bin';\n}\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype FileKind =\n 'image' | 'pdf' | 'sheet' | 'doc' | 'archive' | 'code' | 'video' | 'audio' | 'other';\n\n/** Map a file's extension/mime to a recognisable kind for icon + tint. */\nfunction kindOf(file: FileNode): FileKind {\n const mime = (file.mimeType || '').toLowerCase();\n const ext = (file.extension || extensionOf(file.name)).toLowerCase();\n if (\n mime.startsWith('image/') ||\n ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext)\n )\n return 'image';\n if (mime.startsWith('video/') || ['mp4', 'mov', 'webm', 'avi', 'mkv'].includes(ext))\n return 'video';\n if (mime.startsWith('audio/') || ['mp3', 'wav', 'ogg', 'm4a', 'flac'].includes(ext))\n return 'audio';\n if (mime === 'application/pdf' || ext === 'pdf') return 'pdf';\n if (['xls', 'xlsx', 'csv', 'tsv', 'ods'].includes(ext)) return 'sheet';\n if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(ext)) return 'archive';\n if (\n [\n 'js',\n 'ts',\n 'tsx',\n 'jsx',\n 'json',\n 'xml',\n 'yml',\n 'yaml',\n 'html',\n 'css',\n 'sh',\n 'py',\n 'sql',\n ].includes(ext)\n )\n return 'code';\n if (['doc', 'docx', 'txt', 'md', 'rtf', 'odt', 'pages'].includes(ext)) return 'doc';\n return 'other';\n}\n\nconst KIND_STYLE: Record<\n FileKind,\n { Icon: typeof FileText; iconClass: string; tileClass: string }\n> = {\n image: {\n Icon: ImageIcon,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n pdf: {\n Icon: FileText,\n iconClass: 'text-status-error-text',\n tileClass: 'bg-status-error-bg-subtle',\n },\n sheet: {\n Icon: FileSpreadsheet,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n doc: {\n Icon: FileText,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n archive: {\n Icon: FileArchive,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n code: {\n Icon: FileCode2,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n video: {\n Icon: FileVideo,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n audio: {\n Icon: FileAudio,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n other: { Icon: FileIcon, iconClass: 'text-text-secondary', tileClass: 'bg-bg-sunken' },\n};\n\n/** File-type badge tile — an inline thumbnail for images, a tinted glyph otherwise. */\nfunction FileBadge({ file }: { file: FileNode }) {\n const kind = kindOf(file);\n const { Icon, iconClass, tileClass } = KIND_STYLE[kind];\n if (kind === 'image' && file.downloadUrl) {\n return (\n <img\n src={file.downloadUrl}\n alt=\"\"\n loading=\"lazy\"\n className=\"h-10 w-10 shrink-0 rounded-lg border border-border-subtle object-cover\"\n />\n );\n }\n return (\n <span\n className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${tileClass}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-5 w-5 ${iconClass}`} />\n </span>\n );\n}\n\nexport function AttachmentsPanel({ entityType, entityId }: AttachmentsPanelProps) {\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [uploading, setUploading] = useState(false);\n const [sharingId, setSharingId] = useState<string | null>(null);\n\n const { data, loading, refetch } = useQuery<GetFilesResult>(GET_FILES_QUERY, {\n variables: {\n pagination: { limit: 25, offset: 0 },\n sort: { field: 'CREATED_AT', order: 'DESC' },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n });\n\n const [initiateUpload] = useMutation<{\n initiateUpload: { uploadUrl?: string | null; session: { id: string } };\n }>(INITIATE_UPLOAD_MUTATION);\n const [completeUpload] = useMutation(COMPLETE_UPLOAD_MUTATION);\n const [createShareLink] = useMutation<{\n createFileShareLink: { id: string; shareUrl?: string | null };\n }>(CREATE_SHARE_LINK_MUTATION);\n\n const files = useMemo(() => data?.getFiles.files ?? [], [data]);\n\n const handleFileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) return;\n\n setUploading(true);\n try {\n const initiated = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n extension: extensionOf(file.name),\n fileSize: file.size,\n mimeType: file.type || 'application/octet-stream',\n visibility: 'WORKSPACE',\n },\n },\n });\n\n const uploadUrl = initiated.data?.initiateUpload.uploadUrl;\n const sessionId = initiated.data?.initiateUpload.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('Upload could not be initialised.');\n }\n\n const putResponse = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n });\n if (!putResponse.ok) {\n throw new Error(`Storage rejected the upload (HTTP ${putResponse.status}).`);\n }\n\n await completeUpload({\n variables: { input: { sessionId, fileSize: file.size } },\n });\n\n notify.success('File uploaded', `${file.name} is now available on this record.`);\n await refetch();\n } catch (error) {\n notify.error(\n 'Upload failed',\n error instanceof Error ? error.message : 'The file could not be uploaded.'\n );\n } finally {\n setUploading(false);\n }\n };\n\n const handleShare = async (fileId: string) => {\n setSharingId(fileId);\n try {\n const result = await createShareLink({\n variables: { input: { resourceId: fileId, role: 'VIEWER' } },\n });\n const url = result.data?.createFileShareLink.shareUrl;\n if (!url) {\n throw new Error('No share link was returned.');\n }\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(url);\n notify.success('Share link copied', 'A view-only link is on your clipboard.');\n } else {\n notify.info('Share link ready', url);\n }\n } catch (error) {\n notify.error(\n 'Could not create share link',\n error instanceof Error ? error.message : 'Please try again.'\n );\n } finally {\n setSharingId(null);\n }\n };\n\n const total = data?.getFiles.total ?? 0;\n const hasFiles = files.length > 0;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-2 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Paperclip className=\"h-4 w-4\" />\n Attachments\n {total ? (\n <span className=\"text-sm font-normal text-text-secondary\">\n {total} file{total === 1 ? '' : 's'}\n </span>\n ) : null}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <input\n ref={fileInputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(event) => void handleFileSelected(event)}\n aria-label={`Upload a file for ${entityType} ${entityId}`}\n />\n\n {loading && !hasFiles ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div\n key={i}\n className=\"h-14 rounded-lg bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : !hasFiles ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No files yet\"\n description=\"Upload photos, receipts, warranties or documents to keep them with this record.\"\n />\n ) : (\n <ul className=\"space-y-1.5\">\n {files.map((file) => {\n const isSharing = sharingId === file.id;\n return (\n <li\n key={file.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-2.5\"\n >\n <FileBadge file={file} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\" title={file.name}>\n {file.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatSize(Number(file.size))} · {relativeTime(file.createdAt)}\n </p>\n </div>\n {/* Desktop: inline actions. Mobile: kebab menu to avoid crowding. */}\n <div className=\"hidden items-center gap-1 sm:flex\">\n {file.downloadUrl ? (\n <a\n href={file.downloadUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Download ${file.name}`}\n title=\"Download\"\n >\n <Download className=\"h-4 w-4\" />\n </a>\n ) : null}\n <button\n type=\"button\"\n disabled={isSharing}\n onClick={() => void handleShare(file.id)}\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60\"\n aria-label={`Copy share link for ${file.name}`}\n title=\"Copy share link\"\n >\n {isSharing ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Link2 className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n <div className=\"sm:hidden\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Actions for ${file.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {file.downloadUrl ? (\n <DropdownMenuItem asChild>\n <a href={file.downloadUrl} target=\"_blank\" rel=\"noreferrer\">\n <Download className=\"mr-2 h-4 w-4\" />\n Download\n </a>\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuItem\n disabled={isSharing}\n onSelect={(e) => {\n e.preventDefault();\n void handleShare(file.id);\n }}\n >\n <Link2 className=\"mr-2 h-4 w-4\" />\n Copy share link\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n {/* Prominent full-width primary upload CTA (matches the reference concept). */}\n <Button\n className=\"w-full\"\n disabled={uploading}\n onClick={() => fileInputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <Upload className=\"mr-2 h-4 w-4\" />\n )}\n {uploading ? 'Uploading…' : 'Upload files'}\n </Button>\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { AssetHandlerRootProps } from '../types';
|
|
|
3
3
|
import { ValueFormatter } from '../components/charts/chartTheme';
|
|
4
4
|
|
|
5
5
|
export { formatAssetCategory } from '../utils/categoryPresentation';
|
|
6
|
+
export declare function AssetHandlerBootstrapHydrator(): null;
|
|
6
7
|
export declare const useAssetHandler: () => AssetHandlerRootProps;
|
|
7
8
|
export declare const DEFAULT_TERMINOLOGY: {
|
|
8
9
|
readonly asset: "Asset";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetHandlerProvider.d.ts","sourceRoot":"","sources":["../../../src/assethandler/providers/AssetHandlerProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAKnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAItD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"AssetHandlerProvider.d.ts","sourceRoot":"","sources":["../../../src/assethandler/providers/AssetHandlerProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAKnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAItD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAiRpE,wBAAgB,6BAA6B,SA+D5C;AAED,eAAO,MAAM,eAAe,6BAI3B,CAAC;AAEF,eAAO,MAAM,mBAAmB;;;CAAgD,CAAC;AAEjF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAAO;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAMrE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,QAAO;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,KAAK,MAAM,CAAC;IACrD,kEAAkE;IAClE,OAAO,EAAE,cAAc,CAAC;CAazB,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,QAAO,GAAG,CAAC,MAAM,CAGjD,CAAC;AAEF,eAAO,MAAM,4BAA4B,QAAO;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;CAIpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,CAwC7E,CAAC"}
|
|
@@ -4,11 +4,11 @@ import { formatCurrencyCompact as r } from "../components/charts/chartTheme.js";
|
|
|
4
4
|
import "../utils/categoryPresentation.js";
|
|
5
5
|
import { createContext as i, useContext as a, useEffect as o, useMemo as s } from "react";
|
|
6
6
|
import { MemoryRouter as c } from "react-router";
|
|
7
|
-
import { ApolloClient as l, HttpLink as u, InMemoryCache as d,
|
|
8
|
-
import { ApolloProvider as
|
|
9
|
-
import { jsx as
|
|
7
|
+
import { ApolloClient as l, HttpLink as u, InMemoryCache as d, NetworkStatus as f, gql as p } from "@apollo/client";
|
|
8
|
+
import { ApolloProvider as m, useApolloClient as h, useQuery as g } from "@apollo/client/react";
|
|
9
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
10
10
|
//#region src/assethandler/providers/AssetHandlerProvider.tsx
|
|
11
|
-
var
|
|
11
|
+
var y = i(null), b = p`
|
|
12
12
|
query AssetHandlerBootstrap {
|
|
13
13
|
assets {
|
|
14
14
|
id
|
|
@@ -54,7 +54,7 @@ var v = i(null), y = f`
|
|
|
54
54
|
# PortfolioPortfolioOverviewPage, which now fetches it itself.
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
`,
|
|
57
|
+
`, x = p`
|
|
58
58
|
query AssetHandlerBootstrapOverview {
|
|
59
59
|
dashboardOverview {
|
|
60
60
|
totalAssets
|
|
@@ -63,7 +63,7 @@ var v = i(null), y = f`
|
|
|
63
63
|
totalTransactions
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
`,
|
|
66
|
+
`, S = p`
|
|
67
67
|
query AssetHandlerBootstrapOps {
|
|
68
68
|
incidents {
|
|
69
69
|
id
|
|
@@ -93,7 +93,7 @@ var v = i(null), y = f`
|
|
|
93
93
|
completedAt
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
`,
|
|
96
|
+
`, C = p`
|
|
97
97
|
query AssetHandlerBootstrapMaintenance {
|
|
98
98
|
maintenanceRecords {
|
|
99
99
|
id
|
|
@@ -124,7 +124,7 @@ var v = i(null), y = f`
|
|
|
124
124
|
occurredAt
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
`,
|
|
127
|
+
`, w = p`
|
|
128
128
|
query AssetHandlerBootstrapItil {
|
|
129
129
|
changeRequests {
|
|
130
130
|
id
|
|
@@ -158,7 +158,7 @@ var v = i(null), y = f`
|
|
|
158
158
|
updatedAt
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
-
`,
|
|
161
|
+
`, T = p`
|
|
162
162
|
query AssetHandlerBootstrapFinance {
|
|
163
163
|
budgets {
|
|
164
164
|
id
|
|
@@ -185,7 +185,7 @@ var v = i(null), y = f`
|
|
|
185
185
|
paidAt
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
|
-
`,
|
|
188
|
+
`, E = p`
|
|
189
189
|
query AssetHandlerBootstrapSecurity {
|
|
190
190
|
securityEvents: getVaultSecurityEvents {
|
|
191
191
|
items {
|
|
@@ -232,24 +232,25 @@ var v = i(null), y = f`
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
`;
|
|
235
|
-
function
|
|
236
|
-
let t = e((e) => e.hydrateFromBootstrap), n = e((e) => e.setBootstrapRefetch), r =
|
|
235
|
+
function D() {
|
|
236
|
+
let t = e((e) => e.hydrateFromBootstrap), n = e((e) => e.setBootstrapRefetch), r = h(), i = g(b, {
|
|
237
237
|
fetchPolicy: "cache-and-network",
|
|
238
|
-
errorPolicy: "all"
|
|
238
|
+
errorPolicy: "all",
|
|
239
|
+
notifyOnNetworkStatusChange: !0
|
|
239
240
|
});
|
|
240
241
|
o(() => {
|
|
241
242
|
i.data && t(i.data);
|
|
242
243
|
}, [i.data, t]), o(() => {
|
|
243
|
-
if (!i.data) return;
|
|
244
|
+
if (!i.data || i.networkStatus !== f.ready) return;
|
|
244
245
|
let e = !1;
|
|
245
246
|
return (async () => {
|
|
246
247
|
for (let n of [
|
|
247
|
-
b,
|
|
248
248
|
x,
|
|
249
249
|
S,
|
|
250
250
|
C,
|
|
251
251
|
w,
|
|
252
|
-
T
|
|
252
|
+
T,
|
|
253
|
+
E
|
|
253
254
|
]) {
|
|
254
255
|
if (e) return;
|
|
255
256
|
try {
|
|
@@ -266,6 +267,7 @@ function E() {
|
|
|
266
267
|
};
|
|
267
268
|
}, [
|
|
268
269
|
i.data,
|
|
270
|
+
i.networkStatus,
|
|
269
271
|
r,
|
|
270
272
|
t
|
|
271
273
|
]);
|
|
@@ -274,37 +276,37 @@ function E() {
|
|
|
274
276
|
a();
|
|
275
277
|
}), () => n(null)), [a, n]), null;
|
|
276
278
|
}
|
|
277
|
-
var
|
|
278
|
-
let e = a(
|
|
279
|
+
var O = () => {
|
|
280
|
+
let e = a(y);
|
|
279
281
|
if (!e) throw Error("useAssetHandler must be used within AssetHandlerProvider");
|
|
280
282
|
return e;
|
|
281
|
-
},
|
|
283
|
+
}, k = {
|
|
282
284
|
asset: "Asset",
|
|
283
285
|
assets: "Assets"
|
|
284
|
-
},
|
|
285
|
-
let { terminology: e } =
|
|
286
|
+
}, A = () => {
|
|
287
|
+
let { terminology: e } = O();
|
|
286
288
|
return {
|
|
287
|
-
asset: e?.asset ||
|
|
288
|
-
assets: e?.assets ||
|
|
289
|
+
asset: e?.asset || k.asset,
|
|
290
|
+
assets: e?.assets || k.assets
|
|
289
291
|
};
|
|
290
|
-
},
|
|
291
|
-
let { currency: e } =
|
|
292
|
+
}, j = () => {
|
|
293
|
+
let { currency: e } = O(), i = e || "USD";
|
|
292
294
|
return s(() => ({
|
|
293
295
|
currency: i,
|
|
294
296
|
symbol: t(i),
|
|
295
297
|
format: (e) => n(e, i),
|
|
296
298
|
compact: r(i)
|
|
297
299
|
}), [i]);
|
|
298
|
-
}, j = () => {
|
|
299
|
-
let { hiddenCategoryIds: e } = D();
|
|
300
|
-
return s(() => new Set(e ?? []), [e]);
|
|
301
300
|
}, M = () => {
|
|
302
|
-
let {
|
|
301
|
+
let { hiddenCategoryIds: e } = O();
|
|
302
|
+
return s(() => new Set(e ?? []), [e]);
|
|
303
|
+
}, N = () => {
|
|
304
|
+
let { categoryLabels: e, categoryOptions: t } = O();
|
|
303
305
|
return {
|
|
304
306
|
labels: e,
|
|
305
307
|
options: t
|
|
306
308
|
};
|
|
307
|
-
},
|
|
309
|
+
}, P = ({ children: e, ...t }) => {
|
|
308
310
|
let n = s(() => new l({
|
|
309
311
|
link: new u({
|
|
310
312
|
uri: t.apiGatewayUrl ? `${t.apiGatewayUrl}/workspaces/graphql` : "/workspaces/graphql",
|
|
@@ -318,16 +320,16 @@ var D = () => {
|
|
|
318
320
|
t.apiGatewayUrl,
|
|
319
321
|
t.authToken,
|
|
320
322
|
t.workspaceToken
|
|
321
|
-
]), r = !!t.basePath, i = /* @__PURE__ */
|
|
323
|
+
]), r = !!t.basePath, i = /* @__PURE__ */ v(y.Provider, {
|
|
322
324
|
value: t,
|
|
323
|
-
children: [/* @__PURE__ */
|
|
325
|
+
children: [/* @__PURE__ */ _(D, {}), e]
|
|
324
326
|
});
|
|
325
|
-
return r ? i : /* @__PURE__ */
|
|
327
|
+
return r ? i : /* @__PURE__ */ _(m, {
|
|
326
328
|
client: n,
|
|
327
|
-
children: /* @__PURE__ */
|
|
329
|
+
children: /* @__PURE__ */ _(c, { children: i })
|
|
328
330
|
});
|
|
329
331
|
};
|
|
330
332
|
//#endregion
|
|
331
|
-
export {
|
|
333
|
+
export { P as AssetHandlerProvider, N as useAssetCategoryPresentation, O as useAssetHandler, j as useAssetMoney, A as useAssetTerminology, M as useHiddenCategoryIds };
|
|
332
334
|
|
|
333
335
|
//# sourceMappingURL=AssetHandlerProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetHandlerProvider.js","names":[],"sources":["../../../src/assethandler/providers/AssetHandlerProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useEffect } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { ApolloClient, HttpLink, InMemoryCache, gql } from '@apollo/client';\nimport { ApolloProvider, useApolloClient, useQuery } from '@apollo/client/react';\nimport type { AssetHandlerRootProps } from '../types';\nimport type { AssetHandlerBootstrapPayload } from '../store/assethandlerStore';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { DEFAULT_CURRENCY, currencySymbol, formatMoney } from '../utils/money';\nimport type { ValueFormatter } from '../components/charts/chartTheme';\nimport { formatCurrencyCompact } from '../components/charts/chartTheme';\nexport { formatAssetCategory } from '../utils/categoryPresentation';\n\ntype AssetHandlerContextValue = AssetHandlerRootProps;\n\nconst AssetHandlerContext = createContext<AssetHandlerContextValue | null>(null);\n\n/**\n * The module's data is fetched as several small queries rather than one query\n * with all thirteen root fields.\n *\n * BOFF-6381: `wspace-assethandler-svc` sets `enableRlsGucTagging: true`, so\n * be-sdk routes every Prisma call through `runOperationWithRlsGuc`, which wraps\n * it in its own interactive transaction and pins a pooled connection for the\n * duration. GraphQL resolves root fields in parallel, so the old single query\n * asked for thirteen simultaneously-pinned transactions from a service that\n * measured a hard ceiling of about three. It failed 100% of the time in\n * production — `data: null`, so `hydrateFromBootstrap` never ran and every\n * AssetHandler page rendered empty, including the machinery register.\n *\n * Measured against prod (success rate by concurrent operations):\n * 1 -> 100% 2 -> 100% 3 -> 100% 4 -> 90% 6 -> 57% 8 -> 45%\n * A sibling service without the extension was 100% at all of those, so the\n * ceiling is this service's, not the database's.\n *\n * The batches below are therefore sized to stay at or under that ceiling, and\n * the hydrator awaits them one at a time — issuing them in parallel would\n * recreate exactly the concurrency the split exists to avoid.\n * `dashboardOverview` is alone because it internally fans out to four parallel\n * counts in `AssetHandlerService.getDashboardOverview`.\n *\n * The first batch keeps the name `AssetHandlerBootstrap` so the ~36 existing\n * `refetchQueries: ['AssetHandlerBootstrap']` call sites keep working. When it\n * refetches, its new `data` identity re-runs the sequential loop, so those\n * sites still refresh the whole module, as before.\n */\n\nconst Q_ASSETS = gql`\n query AssetHandlerBootstrap {\n assets {\n id\n assetNumber\n tagId\n name\n description\n category\n status\n condition\n serialNumber\n locationName\n assignedTo\n tags\n createdAt\n updatedAt\n purchaseValue\n purchaseDate\n usefulLifeMonths\n salvageValue\n depreciationMethod\n meterUnit\n department\n warrantyExpiry\n supplier\n disposalDate\n disposalMethod\n disposalNotes\n currentValue\n accumulatedDepreciation\n # totalCostOfOwnership is deliberately NOT selected here. Unlike\n # currentValue and accumulatedDepreciation, which are computed in-process\n # from fields already on the row, Asset.totalCostOfOwnership calls\n # AssetHandlerService.computeAssetTco, which issues TWO aggregate queries\n # per asset (maintenanceRecord + workOrder) under Promise.all. Selecting\n # it turns this one-root query into 1 + 2N database operations — 21 for\n # ten assets — against a service that caps out around three (BOFF-6395),\n # so it errored the batch and left the whole module empty again even\n # after the bootstrap was split. It scales with the number of assets, so\n # it can never be made to fit here.\n #\n # The only consumer that read it from the store is\n # PortfolioPortfolioOverviewPage, which now fetches it itself.\n }\n }\n`;\n\nconst Q_OVERVIEW = gql`\n query AssetHandlerBootstrapOverview {\n dashboardOverview {\n totalAssets\n openIncidents\n openWorkOrders\n totalTransactions\n }\n }\n`;\n\nconst Q_OPS = gql`\n query AssetHandlerBootstrapOps {\n incidents {\n id\n title\n description\n category\n priority\n status\n assignedTo\n reportedBy\n reportedAt\n resolvedAt\n resolution\n slaBreached\n }\n workOrders {\n id\n title\n description\n type\n status\n priority\n assetId\n assignedTo\n createdAt\n updatedAt\n completedAt\n }\n }\n`;\n\nconst Q_MAINTENANCE = gql`\n query AssetHandlerBootstrapMaintenance {\n maintenanceRecords {\n id\n assetId\n title\n description\n type\n status\n priority\n scheduledAt\n completedAt\n estimatedCost\n actualCost\n checklist\n createdAt\n updatedAt\n }\n transactions {\n id\n type\n status\n amount\n currency\n category\n description\n vendor\n department\n occurredAt\n }\n }\n`;\n\nconst Q_ITIL = gql`\n query AssetHandlerBootstrapItil {\n changeRequests {\n id\n title\n description\n type\n status\n risk\n requestedBy\n assignedTo\n affectedAssets\n scheduledStart\n scheduledEnd\n approvals\n createdAt\n updatedAt\n }\n problems {\n id\n title\n description\n status\n priority\n category\n assignedTo\n relatedIncidents\n rootCause\n workaround\n resolution\n createdAt\n updatedAt\n }\n }\n`;\n\nconst Q_FINANCE = gql`\n query AssetHandlerBootstrapFinance {\n budgets {\n id\n name\n category\n allocated\n spent\n remaining\n period\n department\n status\n }\n invoices {\n id\n number\n vendor\n amount\n currency\n issueDate\n dueDate\n status\n items\n relatedPO\n paidAt\n }\n }\n`;\n\nconst Q_SECURITY = gql`\n query AssetHandlerBootstrapSecurity {\n securityEvents: getVaultSecurityEvents {\n items {\n id\n eventType\n severity\n title: message\n description\n source: resourceType\n userId\n ipAddress\n occurredAt: timestamp\n resolved: reviewed\n }\n }\n complianceFrameworks: securityComplianceFrameworks(filter: { sourceProduct: \"assethandler\" }) {\n items {\n id\n name\n version\n status\n score\n totalControls\n passedControls\n failedControls\n lastAssessment\n nextAssessment\n lastAssessedAt\n }\n }\n securityPolicies: securityGovernancePolicies(filter: { sourceProduct: \"assethandler\" }) {\n items {\n id\n name\n description\n category\n status\n version\n effectiveDate\n reviewDate\n owner\n applicableTo\n }\n }\n }\n`;\n\nfunction AssetHandlerBootstrapHydrator() {\n const hydrateFromBootstrap = useAssetHandlerStore((s) => s.hydrateFromBootstrap);\n const setBootstrapRefetch = useAssetHandlerStore((s) => s.setBootstrapRefetch);\n\n const client = useApolloClient();\n\n // Batch 1 stays a `useQuery` named `AssetHandlerBootstrap` so Apollo keeps it\n // as an active, named, cached query — that is what makes the existing\n // `refetchQueries: ['AssetHandlerBootstrap']` call sites work.\n const assets = useQuery<AssetHandlerBootstrapPayload>(Q_ASSETS, {\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n });\n\n useEffect(() => {\n if (assets.data) hydrateFromBootstrap(assets.data);\n }, [assets.data, hydrateFromBootstrap]);\n\n // The remaining batches run strictly one after another, awaited in a loop\n // rather than as parallel hooks — running them concurrently would recreate\n // exactly the load the split exists to avoid. Hydration is additive, so a\n // batch that fails leaves its slice at the previous value instead of\n // blanking the whole module the way the single combined query did.\n useEffect(() => {\n if (!assets.data) return;\n let cancelled = false;\n void (async () => {\n for (const query of [Q_OVERVIEW, Q_OPS, Q_MAINTENANCE, Q_ITIL, Q_FINANCE, Q_SECURITY]) {\n if (cancelled) return;\n try {\n const result = await client.query<AssetHandlerBootstrapPayload>({\n query,\n fetchPolicy: 'network-only',\n errorPolicy: 'all',\n });\n if (!cancelled && result.data) hydrateFromBootstrap(result.data);\n } catch {\n // One failed section must not stop the others, and must not clear\n // what is already on screen.\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [assets.data, client, hydrateFromBootstrap]);\n\n // Refetching batch 1 is enough to refresh the module: a new `assets.data`\n // identity re-runs the sequential loop above, so the existing\n // `refetchQueries: ['AssetHandlerBootstrap']` sites still refresh everything.\n const refetchAssets = assets.refetch;\n useEffect(() => {\n setBootstrapRefetch(() => {\n void refetchAssets();\n });\n return () => setBootstrapRefetch(null);\n }, [refetchAssets, setBootstrapRefetch]);\n\n return null;\n}\n\nexport const useAssetHandler = () => {\n const context = useContext(AssetHandlerContext);\n if (!context) throw new Error('useAssetHandler must be used within AssetHandlerProvider');\n return context;\n};\n\nexport const DEFAULT_TERMINOLOGY = { asset: 'Asset', assets: 'Assets' } as const;\n\n/**\n * Resolved singular/plural noun for the module, applying consumer overrides on\n * top of the module defaults (\"Asset\"/\"Assets\").\n */\nexport const useAssetTerminology = (): { asset: string; assets: string } => {\n const { terminology } = useAssetHandler();\n return {\n asset: terminology?.asset || DEFAULT_TERMINOLOGY.asset,\n assets: terminology?.assets || DEFAULT_TERMINOLOGY.assets,\n };\n};\n\n/**\n * Resolved money presentation for the module: the consumer's `currency` prop\n * with the module default (\"USD\") applied, plus the three formatters every\n * surface needs. Amounts are never converted — see {@link formatMoney}.\n */\nexport const useAssetMoney = (): {\n currency: string;\n symbol: string;\n /** Full amount; renders an em dash for null/undefined/non-finite. */\n format: (value: number | null | undefined) => string;\n /** Compact amount for axis ticks and dense KPIs, e.g. `₹8.5L`. */\n compact: ValueFormatter;\n} => {\n const { currency } = useAssetHandler();\n const resolved = currency || DEFAULT_CURRENCY;\n return useMemo(\n () => ({\n currency: resolved,\n symbol: currencySymbol(resolved),\n format: (value: number | null | undefined) => formatMoney(value, resolved),\n compact: formatCurrencyCompact(resolved),\n }),\n [resolved]\n );\n};\n\n/** Set of category ids the consumer asked to hide (empty by default). */\nexport const useHiddenCategoryIds = (): Set<string> => {\n const { hiddenCategoryIds } = useAssetHandler();\n return useMemo(() => new Set(hiddenCategoryIds ?? []), [hiddenCategoryIds]);\n};\n\nexport const useAssetCategoryPresentation = (): {\n labels?: Record<string, string>;\n options?: AssetHandlerRootProps['categoryOptions'];\n} => {\n const { categoryLabels, categoryOptions } = useAssetHandler();\n return { labels: categoryLabels, options: categoryOptions };\n};\n\nexport const AssetHandlerProvider: FC<PropsWithChildren<AssetHandlerRootProps>> = ({\n children,\n ...props\n}) => {\n const apolloClient = useMemo(\n () =>\n new ApolloClient({\n link: new HttpLink({\n uri: props.apiGatewayUrl\n ? `${props.apiGatewayUrl}/workspaces/graphql`\n : '/workspaces/graphql',\n headers: props.authToken\n ? {\n Authorization: `Bearer ${props.authToken}`,\n ...(props.workspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${props.workspaceToken}` }\n : {}),\n }\n : undefined,\n }),\n cache: new InMemoryCache(),\n }),\n [props.apiGatewayUrl, props.authToken, props.workspaceToken]\n );\n\n const isIntegrated = !!props.basePath;\n const content = (\n <AssetHandlerContext.Provider value={props}>\n <AssetHandlerBootstrapHydrator />\n {children}\n </AssetHandlerContext.Provider>\n );\n\n return isIntegrated ? (\n content\n ) : (\n <ApolloProvider client={apolloClient}>\n <MemoryRouter>{content}</MemoryRouter>\n </ApolloProvider>\n );\n};\n"],"mappings":";;;;;;;;;;AAeA,IAAM,IAAsB,EAA+C,KAAK,EAgC1E,IAAW,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDd,IAAa,CAAG;;;;;;;;;GAWhB,IAAQ,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCX,IAAgB,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCnB,IAAS,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCZ,IAAY,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6Bf,IAAa,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDtB,SAAS,IAAgC;CACvC,IAAM,IAAuB,GAAsB,MAAM,EAAE,qBAAqB,EAC1E,IAAsB,GAAsB,MAAM,EAAE,oBAAoB,EAExE,IAAS,GAAiB,EAK1B,IAAS,EAAuC,GAAU;EAC9D,aAAa;EACb,aAAa;EACd,CAAC;AAWF,CATA,QAAgB;AACd,EAAI,EAAO,QAAM,EAAqB,EAAO,KAAK;IACjD,CAAC,EAAO,MAAM,EAAqB,CAAC,EAOvC,QAAgB;AACd,MAAI,CAAC,EAAO,KAAM;EAClB,IAAI,IAAY;AAiBhB,UAhBM,YAAY;AAChB,QAAK,IAAM,KAAS;IAAC;IAAY;IAAO;IAAe;IAAQ;IAAW;IAAW,EAAE;AACrF,QAAI,EAAW;AACf,QAAI;KACF,IAAM,IAAS,MAAM,EAAO,MAAoC;MAC9D;MACA,aAAa;MACb,aAAa;MACd,CAAC;AACF,KAAI,CAAC,KAAa,EAAO,QAAM,EAAqB,EAAO,KAAK;YAC1D;;MAKR,QACS;AACX,OAAY;;IAEb;EAAC,EAAO;EAAM;EAAQ;EAAqB,CAAC;CAK/C,IAAM,IAAgB,EAAO;AAQ7B,QAPA,SACE,QAA0B;AACnB,KAAe;GACpB,QACW,EAAoB,KAAK,GACrC,CAAC,GAAe,EAAoB,CAAC,EAEjC;;AAGT,IAAa,UAAwB;CACnC,IAAM,IAAU,EAAW,EAAoB;AAC/C,KAAI,CAAC,EAAS,OAAU,MAAM,2DAA2D;AACzF,QAAO;GAGI,IAAsB;CAAE,OAAO;CAAS,QAAQ;CAAU,EAM1D,UAA+D;CAC1E,IAAM,EAAE,mBAAgB,GAAiB;AACzC,QAAO;EACL,OAAO,GAAa,SAAS,EAAoB;EACjD,QAAQ,GAAa,UAAU,EAAoB;EACpD;GAQU,UAOR;CACH,IAAM,EAAE,gBAAa,GAAiB,EAChC,IAAW,KAAA;AACjB,QAAO,SACE;EACL,UAAU;EACV,QAAQ,EAAe,EAAS;EAChC,SAAS,MAAqC,EAAY,GAAO,EAAS;EAC1E,SAAS,EAAsB,EAAS;EACzC,GACD,CAAC,EAAS,CACX;GAIU,UAA0C;CACrD,IAAM,EAAE,yBAAsB,GAAiB;AAC/C,QAAO,QAAc,IAAI,IAAI,KAAqB,EAAE,CAAC,EAAE,CAAC,EAAkB,CAAC;GAGhE,UAGR;CACH,IAAM,EAAE,mBAAgB,uBAAoB,GAAiB;AAC7D,QAAO;EAAE,QAAQ;EAAgB,SAAS;EAAiB;GAGhD,KAAsE,EACjF,aACA,GAAG,QACC;CACJ,IAAM,IAAe,QAEjB,IAAI,EAAa;EACf,MAAM,IAAI,EAAS;GACjB,KAAK,EAAM,gBACP,GAAG,EAAM,cAAc,uBACvB;GACJ,SAAS,EAAM,YACX;IACE,eAAe,UAAU,EAAM;IAC/B,GAAI,EAAM,iBACN,EAAE,6BAA6B,UAAU,EAAM,kBAAkB,GACjE,EAAE;IACP,GACD,KAAA;GACL,CAAC;EACF,OAAO,IAAI,GAAe;EAC3B,CAAC,EACJ;EAAC,EAAM;EAAe,EAAM;EAAW,EAAM;EAAe,CAC7D,EAEK,IAAe,CAAC,CAAC,EAAM,UACvB,IACJ,kBAAC,EAAoB,UAArB;EAA8B,OAAO;YAArC,CACE,kBAAC,GAAD,EAAiC,CAAA,EAChC,EAC4B;;AAGjC,QAAO,IACL,IAEA,kBAAC,GAAD;EAAgB,QAAQ;YACtB,kBAAC,GAAD,EAAA,UAAe,GAAuB,CAAA;EACvB,CAAA"}
|
|
1
|
+
{"version":3,"file":"AssetHandlerProvider.js","names":[],"sources":["../../../src/assethandler/providers/AssetHandlerProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useEffect } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { ApolloClient, HttpLink, InMemoryCache, NetworkStatus, gql } from '@apollo/client';\nimport { ApolloProvider, useApolloClient, useQuery } from '@apollo/client/react';\nimport type { AssetHandlerRootProps } from '../types';\nimport type { AssetHandlerBootstrapPayload } from '../store/assethandlerStore';\nimport { useAssetHandlerStore } from '../store/assethandlerStore';\nimport { DEFAULT_CURRENCY, currencySymbol, formatMoney } from '../utils/money';\nimport type { ValueFormatter } from '../components/charts/chartTheme';\nimport { formatCurrencyCompact } from '../components/charts/chartTheme';\nexport { formatAssetCategory } from '../utils/categoryPresentation';\n\ntype AssetHandlerContextValue = AssetHandlerRootProps;\n\nconst AssetHandlerContext = createContext<AssetHandlerContextValue | null>(null);\n\n/**\n * The module's data is fetched as several small queries rather than one query\n * with all thirteen root fields.\n *\n * BOFF-6381: `wspace-assethandler-svc` sets `enableRlsGucTagging: true`, so\n * be-sdk routes every Prisma call through `runOperationWithRlsGuc`, which wraps\n * it in its own interactive transaction and pins a pooled connection for the\n * duration. GraphQL resolves root fields in parallel, so the old single query\n * asked for thirteen simultaneously-pinned transactions from a service that\n * measured a hard ceiling of about three. It failed 100% of the time in\n * production — `data: null`, so `hydrateFromBootstrap` never ran and every\n * AssetHandler page rendered empty, including the machinery register.\n *\n * Measured against prod (success rate by concurrent operations):\n * 1 -> 100% 2 -> 100% 3 -> 100% 4 -> 90% 6 -> 57% 8 -> 45%\n * A sibling service without the extension was 100% at all of those, so the\n * ceiling is this service's, not the database's.\n *\n * The batches below are therefore sized to stay at or under that ceiling, and\n * the hydrator awaits them one at a time — issuing them in parallel would\n * recreate exactly the concurrency the split exists to avoid.\n * `dashboardOverview` is alone because it internally fans out to four parallel\n * counts in `AssetHandlerService.getDashboardOverview`.\n *\n * The first batch keeps the name `AssetHandlerBootstrap` so the ~36 existing\n * `refetchQueries: ['AssetHandlerBootstrap']` call sites keep working. When it\n * refetches, its new `data` identity re-runs the sequential loop, so those\n * sites still refresh the whole module, as before.\n */\n\nconst Q_ASSETS = gql`\n query AssetHandlerBootstrap {\n assets {\n id\n assetNumber\n tagId\n name\n description\n category\n status\n condition\n serialNumber\n locationName\n assignedTo\n tags\n createdAt\n updatedAt\n purchaseValue\n purchaseDate\n usefulLifeMonths\n salvageValue\n depreciationMethod\n meterUnit\n department\n warrantyExpiry\n supplier\n disposalDate\n disposalMethod\n disposalNotes\n currentValue\n accumulatedDepreciation\n # totalCostOfOwnership is deliberately NOT selected here. Unlike\n # currentValue and accumulatedDepreciation, which are computed in-process\n # from fields already on the row, Asset.totalCostOfOwnership calls\n # AssetHandlerService.computeAssetTco, which issues TWO aggregate queries\n # per asset (maintenanceRecord + workOrder) under Promise.all. Selecting\n # it turns this one-root query into 1 + 2N database operations — 21 for\n # ten assets — against a service that caps out around three (BOFF-6395),\n # so it errored the batch and left the whole module empty again even\n # after the bootstrap was split. It scales with the number of assets, so\n # it can never be made to fit here.\n #\n # The only consumer that read it from the store is\n # PortfolioPortfolioOverviewPage, which now fetches it itself.\n }\n }\n`;\n\nconst Q_OVERVIEW = gql`\n query AssetHandlerBootstrapOverview {\n dashboardOverview {\n totalAssets\n openIncidents\n openWorkOrders\n totalTransactions\n }\n }\n`;\n\nconst Q_OPS = gql`\n query AssetHandlerBootstrapOps {\n incidents {\n id\n title\n description\n category\n priority\n status\n assignedTo\n reportedBy\n reportedAt\n resolvedAt\n resolution\n slaBreached\n }\n workOrders {\n id\n title\n description\n type\n status\n priority\n assetId\n assignedTo\n createdAt\n updatedAt\n completedAt\n }\n }\n`;\n\nconst Q_MAINTENANCE = gql`\n query AssetHandlerBootstrapMaintenance {\n maintenanceRecords {\n id\n assetId\n title\n description\n type\n status\n priority\n scheduledAt\n completedAt\n estimatedCost\n actualCost\n checklist\n createdAt\n updatedAt\n }\n transactions {\n id\n type\n status\n amount\n currency\n category\n description\n vendor\n department\n occurredAt\n }\n }\n`;\n\nconst Q_ITIL = gql`\n query AssetHandlerBootstrapItil {\n changeRequests {\n id\n title\n description\n type\n status\n risk\n requestedBy\n assignedTo\n affectedAssets\n scheduledStart\n scheduledEnd\n approvals\n createdAt\n updatedAt\n }\n problems {\n id\n title\n description\n status\n priority\n category\n assignedTo\n relatedIncidents\n rootCause\n workaround\n resolution\n createdAt\n updatedAt\n }\n }\n`;\n\nconst Q_FINANCE = gql`\n query AssetHandlerBootstrapFinance {\n budgets {\n id\n name\n category\n allocated\n spent\n remaining\n period\n department\n status\n }\n invoices {\n id\n number\n vendor\n amount\n currency\n issueDate\n dueDate\n status\n items\n relatedPO\n paidAt\n }\n }\n`;\n\nconst Q_SECURITY = gql`\n query AssetHandlerBootstrapSecurity {\n securityEvents: getVaultSecurityEvents {\n items {\n id\n eventType\n severity\n title: message\n description\n source: resourceType\n userId\n ipAddress\n occurredAt: timestamp\n resolved: reviewed\n }\n }\n complianceFrameworks: securityComplianceFrameworks(filter: { sourceProduct: \"assethandler\" }) {\n items {\n id\n name\n version\n status\n score\n totalControls\n passedControls\n failedControls\n lastAssessment\n nextAssessment\n lastAssessedAt\n }\n }\n securityPolicies: securityGovernancePolicies(filter: { sourceProduct: \"assethandler\" }) {\n items {\n id\n name\n description\n category\n status\n version\n effectiveDate\n reviewDate\n owner\n applicableTo\n }\n }\n }\n`;\n\nexport function AssetHandlerBootstrapHydrator() {\n const hydrateFromBootstrap = useAssetHandlerStore((s) => s.hydrateFromBootstrap);\n const setBootstrapRefetch = useAssetHandlerStore((s) => s.setBootstrapRefetch);\n\n const client = useApolloClient();\n\n // Batch 1 stays a `useQuery` named `AssetHandlerBootstrap` so Apollo keeps it\n // as an active, named, cached query — that is what makes the existing\n // `refetchQueries: ['AssetHandlerBootstrap']` call sites work.\n const assets = useQuery<AssetHandlerBootstrapPayload>(Q_ASSETS, {\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n // A mutation can change only a supplemental slice (maintenance, ITIL,\n // finance, security) while returning an identical assets payload. The\n // network-status transition is therefore the reliable refetch signal.\n notifyOnNetworkStatusChange: true,\n });\n\n useEffect(() => {\n if (assets.data) hydrateFromBootstrap(assets.data);\n }, [assets.data, hydrateFromBootstrap]);\n\n // The remaining batches run strictly one after another, awaited in a loop\n // rather than as parallel hooks — running them concurrently would recreate\n // exactly the load the split exists to avoid. Hydration is additive, so a\n // batch that fails leaves its slice at the previous value instead of\n // blanking the whole module the way the single combined query did.\n useEffect(() => {\n if (!assets.data || assets.networkStatus !== NetworkStatus.ready) return;\n let cancelled = false;\n void (async () => {\n for (const query of [Q_OVERVIEW, Q_OPS, Q_MAINTENANCE, Q_ITIL, Q_FINANCE, Q_SECURITY]) {\n if (cancelled) return;\n try {\n const result = await client.query<AssetHandlerBootstrapPayload>({\n query,\n fetchPolicy: 'network-only',\n errorPolicy: 'all',\n });\n if (!cancelled && result.data) hydrateFromBootstrap(result.data);\n } catch {\n // One failed section must not stop the others, and must not clear\n // what is already on screen.\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [assets.data, assets.networkStatus, client, hydrateFromBootstrap]);\n\n // Refetching batch 1 is enough to refresh the module: a new `assets.data`\n // identity re-runs the sequential loop above, so the existing\n // `refetchQueries: ['AssetHandlerBootstrap']` sites still refresh everything.\n const refetchAssets = assets.refetch;\n useEffect(() => {\n setBootstrapRefetch(() => {\n void refetchAssets();\n });\n return () => setBootstrapRefetch(null);\n }, [refetchAssets, setBootstrapRefetch]);\n\n return null;\n}\n\nexport const useAssetHandler = () => {\n const context = useContext(AssetHandlerContext);\n if (!context) throw new Error('useAssetHandler must be used within AssetHandlerProvider');\n return context;\n};\n\nexport const DEFAULT_TERMINOLOGY = { asset: 'Asset', assets: 'Assets' } as const;\n\n/**\n * Resolved singular/plural noun for the module, applying consumer overrides on\n * top of the module defaults (\"Asset\"/\"Assets\").\n */\nexport const useAssetTerminology = (): { asset: string; assets: string } => {\n const { terminology } = useAssetHandler();\n return {\n asset: terminology?.asset || DEFAULT_TERMINOLOGY.asset,\n assets: terminology?.assets || DEFAULT_TERMINOLOGY.assets,\n };\n};\n\n/**\n * Resolved money presentation for the module: the consumer's `currency` prop\n * with the module default (\"USD\") applied, plus the three formatters every\n * surface needs. Amounts are never converted — see {@link formatMoney}.\n */\nexport const useAssetMoney = (): {\n currency: string;\n symbol: string;\n /** Full amount; renders an em dash for null/undefined/non-finite. */\n format: (value: number | null | undefined) => string;\n /** Compact amount for axis ticks and dense KPIs, e.g. `₹8.5L`. */\n compact: ValueFormatter;\n} => {\n const { currency } = useAssetHandler();\n const resolved = currency || DEFAULT_CURRENCY;\n return useMemo(\n () => ({\n currency: resolved,\n symbol: currencySymbol(resolved),\n format: (value: number | null | undefined) => formatMoney(value, resolved),\n compact: formatCurrencyCompact(resolved),\n }),\n [resolved]\n );\n};\n\n/** Set of category ids the consumer asked to hide (empty by default). */\nexport const useHiddenCategoryIds = (): Set<string> => {\n const { hiddenCategoryIds } = useAssetHandler();\n return useMemo(() => new Set(hiddenCategoryIds ?? []), [hiddenCategoryIds]);\n};\n\nexport const useAssetCategoryPresentation = (): {\n labels?: Record<string, string>;\n options?: AssetHandlerRootProps['categoryOptions'];\n} => {\n const { categoryLabels, categoryOptions } = useAssetHandler();\n return { labels: categoryLabels, options: categoryOptions };\n};\n\nexport const AssetHandlerProvider: FC<PropsWithChildren<AssetHandlerRootProps>> = ({\n children,\n ...props\n}) => {\n const apolloClient = useMemo(\n () =>\n new ApolloClient({\n link: new HttpLink({\n uri: props.apiGatewayUrl\n ? `${props.apiGatewayUrl}/workspaces/graphql`\n : '/workspaces/graphql',\n headers: props.authToken\n ? {\n Authorization: `Bearer ${props.authToken}`,\n ...(props.workspaceToken\n ? { 'X-Workspace-Authorization': `Bearer ${props.workspaceToken}` }\n : {}),\n }\n : undefined,\n }),\n cache: new InMemoryCache(),\n }),\n [props.apiGatewayUrl, props.authToken, props.workspaceToken]\n );\n\n const isIntegrated = !!props.basePath;\n const content = (\n <AssetHandlerContext.Provider value={props}>\n <AssetHandlerBootstrapHydrator />\n {children}\n </AssetHandlerContext.Provider>\n );\n\n return isIntegrated ? (\n content\n ) : (\n <ApolloProvider client={apolloClient}>\n <MemoryRouter>{content}</MemoryRouter>\n </ApolloProvider>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../src/types/assistant.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAElG,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAEhG,MAAM,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../src/types/assistant.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAElG,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAEhG,MAAM,MAAM,2BAA2B,GACrC,MAAM,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AAE9E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAChC,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,QAAQ,CAAC,EAAE,wBAAwB,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,sBAAsB,CAAC;CAChC;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,2BAA2B,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,aAAa,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,eAAe,EAAE,mBAAmB,EAAkC,CAAC"}
|