@burdenoff/microfe-assethandler 2026.825.1 → 2026.826.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"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,+BA0IjF"}
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;AA2ND,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,qBAAqB,+BAmP/E"}
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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"}
@@ -1,4 +1,47 @@
1
1
  import { default as React } from 'react';
2
2
 
3
+ declare const ESG_CATEGORIES: readonly ["environment", "social", "governance"];
4
+ declare const ESG_TRENDS: readonly ["improving", "stable", "declining"];
5
+ interface MetricFormValues {
6
+ metricName: string;
7
+ category: (typeof ESG_CATEGORIES)[number];
8
+ unit: string;
9
+ value: number;
10
+ target: number | null;
11
+ trend: (typeof ESG_TRENDS)[number];
12
+ periodStart: string;
13
+ periodEnd: string;
14
+ }
15
+ export declare function buildMetricMutationVariables(metricId: string | null, form: MetricFormValues): {
16
+ kind: "update";
17
+ variables: {
18
+ id: string;
19
+ input: {
20
+ metricName: string;
21
+ category: "environment" | "social" | "governance";
22
+ unit: string;
23
+ value: number;
24
+ target: number | null;
25
+ trend: "improving" | "stable" | "declining";
26
+ };
27
+ };
28
+ } | {
29
+ kind: "create";
30
+ variables: {
31
+ input: {
32
+ metricKey: string;
33
+ periodStart: string;
34
+ periodEnd: string;
35
+ metricName: string;
36
+ category: "environment" | "social" | "governance";
37
+ unit: string;
38
+ value: number;
39
+ target: number | null;
40
+ trend: "improving" | "stable" | "declining";
41
+ };
42
+ id?: undefined;
43
+ };
44
+ };
3
45
  export declare function EsgDashboardPage(): React.JSX.Element;
46
+ export {};
4
47
  //# sourceMappingURL=EsgDashboardPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EsgDashboardPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/EsgDashboardPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA0T/B,wBAAgB,gBAAgB,sBAuO/B"}
1
+ {"version":3,"file":"EsgDashboardPage.d.ts","sourceRoot":"","sources":["../../../src/assethandler/pages/EsgDashboardPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAoG/B,QAAA,MAAM,cAAc,kDAAmD,CAAC;AACxE,QAAA,MAAM,UAAU,+CAAgD,CAAC;AA6BjE,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsB3F;AAwOD,wBAAgB,gBAAgB,sBAyT/B"}
@@ -9,13 +9,13 @@ import { ChartCard as l } from "../components/ChartCard.js";
9
9
  import { SectionHeader as u } from "../components/SectionHeader.js";
10
10
  import "../components/index.js";
11
11
  import { useMemo as d, useState as f } from "react";
12
- import { Leaf as p, Minus as m, Plus as h, TrendingDown as g, TrendingUp as _ } from "lucide-react";
13
- import { gql as v } from "@apollo/client";
14
- import { useMutation as y, useQuery as b } from "@apollo/client/react";
15
- import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
16
- import { Button as w, GlassCard as T, IllustratedEmptyState as E, Input as D, Label as O, PagePurpose as k, ResponsiveDialog as A, ResponsiveDialogContent as j, ResponsiveDialogDescription as M, ResponsiveDialogFooter as N, ResponsiveDialogHeader as P, ResponsiveDialogTitle as F, Select as I, SelectContent as L, SelectItem as R, SelectTrigger as z, SelectValue as B } from "@burdenoff/fe-libs/ui";
12
+ import { Archive as p, Leaf as m, Minus as h, Pencil as g, Plus as _, TrendingDown as v, TrendingUp as y } from "lucide-react";
13
+ import { gql as b } from "@apollo/client";
14
+ import { useMutation as x, useQuery as S } from "@apollo/client/react";
15
+ import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
16
+ import { Button as E, GlassCard as D, IllustratedEmptyState as O, Input as k, Label as A, PagePurpose as j, ResponsiveDialog as M, ResponsiveDialogContent as N, ResponsiveDialogDescription as P, ResponsiveDialogFooter as F, ResponsiveDialogHeader as I, ResponsiveDialogTitle as L, Select as R, SelectContent as z, SelectItem as B, SelectTrigger as V, SelectValue as H } from "@burdenoff/fe-libs/ui";
17
17
  //#region src/assethandler/pages/EsgDashboardPage.tsx
18
- var V = v`
18
+ var U = b`
19
19
  query AssetHandlerEsgMetrics {
20
20
  esgMetrics {
21
21
  id
@@ -30,162 +30,202 @@ var V = v`
30
30
  periodEnd
31
31
  }
32
32
  }
33
- `, H = v`
34
- mutation AssetHandlerUpsertEsgMetric($input: UpsertEsgMetricInput!) {
35
- upsertEsgMetric(input: $input) {
33
+ `, W = b`
34
+ mutation AssetHandlerCreateEsgMetric($input: CreateEsgMetricInput!) {
35
+ createEsgMetric(input: $input) {
36
36
  id
37
37
  metricName
38
38
  }
39
39
  }
40
- `, U = [
40
+ `, G = b`
41
+ mutation AssetHandlerUpdateEsgMetric($id: ID!, $input: UpdateEsgMetricInput!) {
42
+ updateEsgMetric(id: $id, input: $input) {
43
+ id
44
+ metricName
45
+ }
46
+ }
47
+ `, K = b`
48
+ mutation AssetHandlerArchiveEsgMetric($id: ID!) {
49
+ archiveEsgMetric(id: $id)
50
+ }
51
+ `, q = [
41
52
  "environment",
42
53
  "social",
43
54
  "governance"
44
- ], W = [
55
+ ], J = [
45
56
  "improving",
46
57
  "stable",
47
58
  "declining"
48
- ], G = {
59
+ ], Y = {
49
60
  improving: {
50
61
  label: "Improving",
51
- icon: _,
62
+ icon: y,
52
63
  tone: "positive",
53
64
  dir: "up"
54
65
  },
55
66
  declining: {
56
67
  label: "Declining",
57
- icon: g,
68
+ icon: v,
58
69
  tone: "negative",
59
70
  dir: "down"
60
71
  },
61
72
  stable: {
62
73
  label: "Stable",
63
- icon: m,
74
+ icon: h,
64
75
  tone: "neutral",
65
76
  dir: "neutral"
66
77
  }
67
- }, K = {
78
+ }, X = {
68
79
  positive: "bg-status-success-bg-subtle text-status-success-text",
69
80
  negative: "bg-status-error-bg-subtle text-status-error-text",
70
81
  neutral: "bg-bg-sunken text-text-secondary"
71
- }, q = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
72
- function J() {
82
+ }, Z = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
83
+ function ee(e, t) {
84
+ let n = {
85
+ metricName: t.metricName.trim(),
86
+ category: t.category,
87
+ unit: t.unit.trim(),
88
+ value: t.value,
89
+ target: t.target,
90
+ trend: t.trend
91
+ };
92
+ return e ? {
93
+ kind: "update",
94
+ variables: {
95
+ id: e,
96
+ input: n
97
+ }
98
+ } : {
99
+ kind: "create",
100
+ variables: { input: {
101
+ ...n,
102
+ metricKey: Z(t.metricName),
103
+ periodStart: new Date(t.periodStart).toISOString(),
104
+ periodEnd: new Date(t.periodEnd).toISOString()
105
+ } }
106
+ };
107
+ }
108
+ function Q() {
73
109
  let e = /* @__PURE__ */ new Date();
74
110
  return new Date(e.getFullYear(), e.getMonth(), 1).toISOString().slice(0, 10);
75
111
  }
76
- function Y() {
112
+ function te() {
77
113
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
78
114
  }
79
- function X({ trend: e }) {
80
- let t = G[e ?? "stable"] ?? G.stable, r = t.icon;
81
- return /* @__PURE__ */ C("span", {
82
- className: n("inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium", K[t.tone]),
83
- children: [/* @__PURE__ */ S(r, { className: "h-3 w-3" }), t.label]
115
+ function ne({ trend: e }) {
116
+ let t = Y[e ?? "stable"] ?? Y.stable, r = t.icon;
117
+ return /* @__PURE__ */ T("span", {
118
+ className: n("inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium", X[t.tone]),
119
+ children: [/* @__PURE__ */ w(r, { className: "h-3 w-3" }), t.label]
84
120
  });
85
121
  }
86
- function Z({ open: e, onOpenChange: t, onSaved: n }) {
87
- let [i, a] = f(""), [o, s] = f("environment"), [c, l] = f(""), [u, d] = f(""), [p, m] = f(""), [h, g] = f("stable"), [_, v] = f(J()), [b, x] = f(Y()), [T, { loading: E }] = y(H, {
122
+ function re({ open: e, metric: t, onOpenChange: n, onSaved: i }) {
123
+ let [a, o] = f(t?.metricName ?? ""), [s, c] = f(t?.category ?? "environment"), [l, u] = f(t?.unit ?? ""), [d, p] = f(t ? String(t.value) : ""), [m, h] = f(t?.target == null ? "" : String(t.target)), [g, _] = f(t?.trend ?? "stable"), [v, y] = f(t?.periodStart.slice(0, 10) ?? Q()), [b, S] = f(t?.periodEnd.slice(0, 10) ?? te()), C = { onError: (e) => r.error("Could not save metric", e.message) }, [D, { loading: O }] = x(W, {
124
+ ...C,
88
125
  onCompleted: (e) => {
89
- r.success("Metric saved", `${e.upsertEsgMetric.metricName} was recorded.`), a(""), l(""), d(""), m(""), t(!1), n();
90
- },
91
- onError: (e) => r.error("Could not save metric", e.message)
92
- });
93
- return /* @__PURE__ */ S(A, {
126
+ r.success("Metric saved", `${e.createEsgMetric.metricName} was recorded.`), n(!1), i();
127
+ }
128
+ }), [j, { loading: U }] = x(G, {
129
+ ...C,
130
+ onCompleted: (e) => {
131
+ r.success("Metric updated", `${e.updateEsgMetric.metricName} was updated.`), n(!1), i();
132
+ }
133
+ }), K = O || U;
134
+ return /* @__PURE__ */ w(M, {
94
135
  open: e,
95
- onOpenChange: t,
96
- children: /* @__PURE__ */ C(j, {
136
+ onOpenChange: n,
137
+ children: /* @__PURE__ */ T(N, {
97
138
  className: "max-h-[85vh] overflow-y-auto sm:max-w-2xl",
98
- children: [/* @__PURE__ */ C(P, { children: [/* @__PURE__ */ S(F, { children: "Save Metric" }), /* @__PURE__ */ S(M, { children: "Record an ESG metric to track progress toward your sustainability targets." })] }), /* @__PURE__ */ C("form", {
139
+ children: [/* @__PURE__ */ T(I, { children: [/* @__PURE__ */ w(L, { children: t ? "Edit Metric" : "Save Metric" }), /* @__PURE__ */ w(P, { children: t ? "Update this metric’s values. Its source identity and reporting period stay unchanged." : "Record an ESG metric to track progress toward your sustainability targets." })] }), /* @__PURE__ */ T("form", {
99
140
  className: "space-y-3",
100
141
  onSubmit: (e) => {
101
142
  e.preventDefault();
102
- let t = Number(u);
103
- if (!i.trim() || !c.trim()) {
143
+ let n = Number(d);
144
+ if (!a.trim() || !l.trim()) {
104
145
  r.warning("Missing details", "Metric name and unit are required.");
105
146
  return;
106
147
  }
107
- if (!Number.isFinite(t) || u.trim() === "") {
148
+ if (!Number.isFinite(n) || d.trim() === "") {
108
149
  r.warning("Invalid value", "Enter a numeric metric value.");
109
150
  return;
110
151
  }
111
- if (!_ || !b) {
152
+ if (!v || !b) {
112
153
  r.warning("Missing period", "Period start and end are required.");
113
154
  return;
114
155
  }
115
- let n = p.trim() === "" ? null : Number(p);
116
- T({ variables: { input: {
117
- metricKey: q(i),
118
- metricName: i.trim(),
119
- category: o,
120
- unit: c.trim(),
121
- value: t,
122
- target: n != null && Number.isFinite(n) ? n : null,
123
- trend: h,
124
- periodStart: new Date(_).toISOString(),
125
- periodEnd: new Date(b).toISOString()
126
- } } });
156
+ let i = m.trim() === "" ? null : Number(m), o = ee(t?.id ?? null, {
157
+ metricName: a,
158
+ category: s,
159
+ unit: l,
160
+ value: n,
161
+ target: i != null && Number.isFinite(i) ? i : null,
162
+ trend: g,
163
+ periodStart: v,
164
+ periodEnd: b
165
+ });
166
+ o.kind === "update" ? j(o) : D(o);
127
167
  },
128
168
  children: [
129
- /* @__PURE__ */ C("div", {
169
+ /* @__PURE__ */ T("div", {
130
170
  className: "grid gap-3 md:grid-cols-3",
131
171
  children: [
132
- /* @__PURE__ */ C("div", {
172
+ /* @__PURE__ */ T("div", {
133
173
  className: "space-y-2",
134
- children: [/* @__PURE__ */ S(O, { children: "Metric Name *" }), /* @__PURE__ */ S(D, {
174
+ children: [/* @__PURE__ */ w(A, { children: "Metric Name *" }), /* @__PURE__ */ w(k, {
135
175
  placeholder: "e.g., Scope 2 carbon emissions",
136
- value: i,
137
- onChange: (e) => a(e.target.value)
176
+ value: a,
177
+ onChange: (e) => o(e.target.value)
138
178
  })]
139
179
  }),
140
- /* @__PURE__ */ C("div", {
180
+ /* @__PURE__ */ T("div", {
141
181
  className: "space-y-2",
142
- children: [/* @__PURE__ */ S(O, { children: "Category" }), /* @__PURE__ */ C(I, {
143
- value: o,
144
- onValueChange: (e) => s(e),
145
- children: [/* @__PURE__ */ S(z, { children: /* @__PURE__ */ S(B, {}) }), /* @__PURE__ */ S(L, { children: U.map((e) => /* @__PURE__ */ S(R, {
182
+ children: [/* @__PURE__ */ w(A, { children: "Category" }), /* @__PURE__ */ T(R, {
183
+ value: s,
184
+ onValueChange: (e) => c(e),
185
+ children: [/* @__PURE__ */ w(V, { children: /* @__PURE__ */ w(H, {}) }), /* @__PURE__ */ w(z, { children: q.map((e) => /* @__PURE__ */ w(B, {
146
186
  value: e,
147
187
  className: "capitalize",
148
188
  children: e
149
189
  }, e)) })]
150
190
  })]
151
191
  }),
152
- /* @__PURE__ */ C("div", {
192
+ /* @__PURE__ */ T("div", {
153
193
  className: "space-y-2",
154
- children: [/* @__PURE__ */ S(O, { children: "Unit *" }), /* @__PURE__ */ S(D, {
194
+ children: [/* @__PURE__ */ w(A, { children: "Unit *" }), /* @__PURE__ */ w(k, {
155
195
  placeholder: "e.g., tCO2e",
156
- value: c,
157
- onChange: (e) => l(e.target.value)
196
+ value: l,
197
+ onChange: (e) => u(e.target.value)
158
198
  })]
159
199
  })
160
200
  ]
161
201
  }),
162
- /* @__PURE__ */ C("div", {
202
+ /* @__PURE__ */ T("div", {
163
203
  className: "grid gap-3 md:grid-cols-3",
164
204
  children: [
165
- /* @__PURE__ */ C("div", {
205
+ /* @__PURE__ */ T("div", {
166
206
  className: "space-y-2",
167
- children: [/* @__PURE__ */ S(O, { children: "Value *" }), /* @__PURE__ */ S(D, {
207
+ children: [/* @__PURE__ */ w(A, { children: "Value *" }), /* @__PURE__ */ w(k, {
168
208
  type: "number",
169
209
  step: "any",
170
- value: u,
171
- onChange: (e) => d(e.target.value)
210
+ value: d,
211
+ onChange: (e) => p(e.target.value)
172
212
  })]
173
213
  }),
174
- /* @__PURE__ */ C("div", {
214
+ /* @__PURE__ */ T("div", {
175
215
  className: "space-y-2",
176
- children: [/* @__PURE__ */ S(O, { children: "Target" }), /* @__PURE__ */ S(D, {
216
+ children: [/* @__PURE__ */ w(A, { children: "Target" }), /* @__PURE__ */ w(k, {
177
217
  type: "number",
178
218
  step: "any",
179
- value: p,
180
- onChange: (e) => m(e.target.value)
219
+ value: m,
220
+ onChange: (e) => h(e.target.value)
181
221
  })]
182
222
  }),
183
- /* @__PURE__ */ C("div", {
223
+ /* @__PURE__ */ T("div", {
184
224
  className: "space-y-2",
185
- children: [/* @__PURE__ */ S(O, { children: "Trend" }), /* @__PURE__ */ C(I, {
186
- value: h,
187
- onValueChange: (e) => g(e),
188
- children: [/* @__PURE__ */ S(z, { children: /* @__PURE__ */ S(B, {}) }), /* @__PURE__ */ S(L, { children: W.map((e) => /* @__PURE__ */ S(R, {
225
+ children: [/* @__PURE__ */ w(A, { children: "Trend" }), /* @__PURE__ */ T(R, {
226
+ value: g,
227
+ onValueChange: (e) => _(e),
228
+ children: [/* @__PURE__ */ w(V, { children: /* @__PURE__ */ w(H, {}) }), /* @__PURE__ */ w(z, { children: J.map((e) => /* @__PURE__ */ w(B, {
189
229
  value: e,
190
230
  className: "capitalize",
191
231
  children: e
@@ -194,111 +234,146 @@ function Z({ open: e, onOpenChange: t, onSaved: n }) {
194
234
  })
195
235
  ]
196
236
  }),
197
- /* @__PURE__ */ C("div", {
237
+ /* @__PURE__ */ T("div", {
198
238
  className: "grid gap-3 md:grid-cols-2",
199
- children: [/* @__PURE__ */ C("div", {
239
+ children: [/* @__PURE__ */ T("div", {
200
240
  className: "space-y-2",
201
- children: [/* @__PURE__ */ S(O, { children: "Period Start *" }), /* @__PURE__ */ S(D, {
241
+ children: [/* @__PURE__ */ w(A, { children: "Period Start *" }), /* @__PURE__ */ w(k, {
202
242
  type: "date",
203
- value: _,
204
- onChange: (e) => v(e.target.value)
243
+ value: v,
244
+ onChange: (e) => y(e.target.value),
245
+ disabled: t != null
205
246
  })]
206
- }), /* @__PURE__ */ C("div", {
247
+ }), /* @__PURE__ */ T("div", {
207
248
  className: "space-y-2",
208
- children: [/* @__PURE__ */ S(O, { children: "Period End *" }), /* @__PURE__ */ S(D, {
249
+ children: [/* @__PURE__ */ w(A, { children: "Period End *" }), /* @__PURE__ */ w(k, {
209
250
  type: "date",
210
251
  value: b,
211
- onChange: (e) => x(e.target.value)
252
+ onChange: (e) => S(e.target.value),
253
+ disabled: t != null
212
254
  })]
213
255
  })]
214
256
  }),
215
- /* @__PURE__ */ C(N, { children: [/* @__PURE__ */ S(w, {
257
+ /* @__PURE__ */ T(F, { children: [/* @__PURE__ */ w(E, {
216
258
  type: "button",
217
259
  variant: "outline",
218
- onClick: () => t(!1),
219
- disabled: E,
260
+ onClick: () => n(!1),
261
+ disabled: K,
220
262
  children: "Cancel"
221
- }), /* @__PURE__ */ S(w, {
263
+ }), /* @__PURE__ */ w(E, {
222
264
  type: "submit",
223
- disabled: E,
224
- children: E ? "Saving…" : "Save Metric"
265
+ disabled: K,
266
+ children: K ? "Saving…" : t ? "Update Metric" : "Save Metric"
225
267
  })] })
226
268
  ]
227
269
  })]
228
270
  })
229
271
  });
230
272
  }
231
- function Q() {
232
- let [r, m] = f(!1), [g, _] = f("ALL"), { data: v, loading: y, refetch: D } = b(V, { fetchPolicy: "cache-and-network" }), O = d(() => v?.esgMetrics ?? [], [v?.esgMetrics]), A = d(() => O.slice(0, 4), [O]), j = d(() => g === "ALL" ? O : O.filter((e) => e.category === g), [O, g]), M = d(() => {
233
- let e = O.reduce((e, t) => (e[t.category] = (e[t.category] ?? 0) + 1, e), {});
273
+ function $() {
274
+ let [h, v] = f(!1), [y, b] = f(null), [k, A] = f(null), [R, z] = f("ALL"), { data: B, loading: V, refetch: H } = S(U, { fetchPolicy: "cache-and-network" }), W = d(() => B?.esgMetrics ?? [], [B?.esgMetrics]), [G, { loading: J }] = x(K, {
275
+ onCompleted: () => {
276
+ r.success("Metric archived", "The metric was removed from the active ESG dashboard."), A(null), H();
277
+ },
278
+ onError: (e) => r.error("Could not archive metric", e.message)
279
+ }), X = d(() => W.slice(0, 4), [W]), Z = d(() => R === "ALL" ? W : W.filter((e) => e.category === R), [W, R]), ee = d(() => {
280
+ let e = W.reduce((e, t) => (e[t.category] = (e[t.category] ?? 0) + 1, e), {});
234
281
  return Object.entries(e).map(([e, n], r) => ({
235
282
  name: e.charAt(0).toUpperCase() + e.slice(1),
236
283
  value: n,
237
284
  color: t(r)
238
285
  }));
239
- }, [O]), N = d(() => O.filter((e) => e.target != null).slice(0, 8).map((e) => ({
286
+ }, [W]), Q = d(() => W.filter((e) => e.target != null).slice(0, 8).map((e) => ({
240
287
  name: e.metricName,
241
288
  value: e.value,
242
289
  target: e.target
243
- })), [O]), P = d(() => {
244
- let e = { ALL: O.length };
245
- for (let t of O) e[t.category] = (e[t.category] ?? 0) + 1;
290
+ })), [W]), te = d(() => {
291
+ let e = { ALL: W.length };
292
+ for (let t of W) e[t.category] = (e[t.category] ?? 0) + 1;
246
293
  return e;
247
- }, [O]), F = s(j, {
294
+ }, [W]), $ = s(Z, {
248
295
  pageSize: 8,
249
- resetKey: g
296
+ resetKey: R
250
297
  });
251
- return /* @__PURE__ */ C("div", {
298
+ return /* @__PURE__ */ T("div", {
252
299
  className: "space-y-5 p-4 sm:p-6",
253
300
  children: [
254
- /* @__PURE__ */ S(u, {
301
+ /* @__PURE__ */ w(u, {
255
302
  hero: !0,
256
- icon: p,
303
+ icon: m,
257
304
  title: "ESG Dashboard",
258
305
  description: "Measure the environmental and governance impact of your estate against targets.",
259
- actions: /* @__PURE__ */ C(w, {
306
+ actions: /* @__PURE__ */ T(E, {
260
307
  className: "w-full sm:w-auto",
261
- onClick: () => m(!0),
262
- children: [/* @__PURE__ */ S(h, { className: "mr-2 h-4 w-4" }), "Save Metric"]
308
+ onClick: () => {
309
+ b(null), v(!0);
310
+ },
311
+ children: [/* @__PURE__ */ w(_, { className: "mr-2 h-4 w-4" }), "Save Metric"]
263
312
  })
264
313
  }),
265
- /* @__PURE__ */ S(k, { children: "Track energy use, carbon emissions and responsible disposal against targets — and produce the evidence ESG reporting and audits require." }),
266
- /* @__PURE__ */ S(Z, {
267
- open: r,
268
- onOpenChange: m,
269
- onSaved: () => void D()
314
+ /* @__PURE__ */ w(j, { children: "Track energy use, carbon emissions and responsible disposal against targets — and produce the evidence ESG reporting and audits require." }),
315
+ h ? /* @__PURE__ */ w(re, {
316
+ open: !0,
317
+ metric: y,
318
+ onOpenChange: (e) => {
319
+ v(e), e || b(null);
320
+ },
321
+ onSaved: () => void H()
322
+ }) : null,
323
+ /* @__PURE__ */ w(M, {
324
+ open: k != null,
325
+ onOpenChange: (e) => {
326
+ e || A(null);
327
+ },
328
+ children: /* @__PURE__ */ T(N, { children: [/* @__PURE__ */ T(I, { children: [/* @__PURE__ */ w(L, { children: "Archive metric" }), /* @__PURE__ */ T(P, { children: [
329
+ "Archive ",
330
+ k?.metricName ?? "this metric",
331
+ "? It will leave the active ESG dashboard. Archiving does not refund monthly usage."
332
+ ] })] }), /* @__PURE__ */ T(F, { children: [/* @__PURE__ */ w(E, {
333
+ variant: "outline",
334
+ onClick: () => A(null),
335
+ disabled: J,
336
+ children: "Cancel"
337
+ }), /* @__PURE__ */ w(E, {
338
+ variant: "destructive",
339
+ disabled: J || k == null,
340
+ onClick: () => {
341
+ k && G({ variables: { id: k.id } });
342
+ },
343
+ children: J ? "Archiving…" : "Archive Metric"
344
+ })] })] })
270
345
  }),
271
- y && O.length === 0 ? /* @__PURE__ */ S("div", {
346
+ V && W.length === 0 ? /* @__PURE__ */ w("div", {
272
347
  className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
273
- children: Array.from({ length: 4 }).map((e, t) => /* @__PURE__ */ S("div", { className: "h-28 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer" }, t))
348
+ children: Array.from({ length: 4 }).map((e, t) => /* @__PURE__ */ w("div", { className: "h-28 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer" }, t))
274
349
  }) : null,
275
- !y && O.length === 0 ? /* @__PURE__ */ S(E, {
350
+ !V && W.length === 0 ? /* @__PURE__ */ w(O, {
276
351
  illustration: "empty-data",
277
352
  title: "No ESG metrics recorded yet",
278
353
  description: "Save a metric to start tracking sustainability progress against your targets."
279
354
  }) : null,
280
- A.length > 0 ? /* @__PURE__ */ S("div", {
355
+ X.length > 0 ? /* @__PURE__ */ w("div", {
281
356
  className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
282
- children: A.map((t) => {
283
- let n = G[t.trend ?? "stable"] ?? G.stable;
284
- return /* @__PURE__ */ S(c, {
357
+ children: X.map((t) => {
358
+ let n = Y[t.trend ?? "stable"] ?? Y.stable;
359
+ return /* @__PURE__ */ w(c, {
285
360
  label: `${t.metricName} (${t.unit})`,
286
361
  value: t.value,
287
362
  format: e,
288
363
  delta: n.label,
289
364
  deltaDirection: n.dir,
290
365
  deltaTone: n.tone,
291
- icon: p
366
+ icon: m
292
367
  }, t.id);
293
368
  })
294
369
  }) : null,
295
- O.length > 0 ? /* @__PURE__ */ C("div", {
370
+ W.length > 0 ? /* @__PURE__ */ T("div", {
296
371
  className: "grid gap-4 lg:grid-cols-2",
297
- children: [/* @__PURE__ */ S(l, {
372
+ children: [/* @__PURE__ */ w(l, {
298
373
  title: "Value vs. target",
299
374
  subtitle: "Metrics measured against their targets",
300
- children: N.length > 0 ? /* @__PURE__ */ S(i, {
301
- data: N,
375
+ children: Q.length > 0 ? /* @__PURE__ */ w(i, {
376
+ data: Q,
302
377
  xKey: "name",
303
378
  series: [{
304
379
  key: "value",
@@ -311,86 +386,109 @@ function Q() {
311
386
  height: 240,
312
387
  categoryWidth: 150,
313
388
  ariaLabel: "Metric value versus target"
314
- }) : /* @__PURE__ */ S("div", {
389
+ }) : /* @__PURE__ */ w("div", {
315
390
  className: "flex h-[240px] items-center justify-center text-sm text-text-secondary",
316
391
  children: "Set a target on a metric to compare value against target."
317
392
  })
318
- }), /* @__PURE__ */ S(l, {
393
+ }), /* @__PURE__ */ w(l, {
319
394
  title: "By category",
320
395
  subtitle: "Metrics across E / S / G",
321
- children: /* @__PURE__ */ S(a, {
322
- data: M,
396
+ children: /* @__PURE__ */ w(a, {
397
+ data: ee,
323
398
  height: 240,
324
399
  centerLabel: "Metrics",
325
- centerValue: O.length,
400
+ centerValue: W.length,
326
401
  ariaLabel: "Metrics by category"
327
402
  })
328
403
  })]
329
404
  }) : null,
330
- O.length > 0 ? /* @__PURE__ */ C(x, { children: [
331
- /* @__PURE__ */ S("div", {
405
+ W.length > 0 ? /* @__PURE__ */ T(C, { children: [
406
+ /* @__PURE__ */ w("div", {
332
407
  className: "flex flex-wrap items-center gap-2",
333
- children: ["ALL", ...U].map((e) => /* @__PURE__ */ C("button", {
408
+ children: ["ALL", ...q].map((e) => /* @__PURE__ */ T("button", {
334
409
  type: "button",
335
- onClick: () => _(e),
336
- className: n("rounded-full px-3.5 py-1.5 text-sm font-medium capitalize transition-colors", g === e ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
337
- children: [e === "ALL" ? "All" : e, /* @__PURE__ */ S("span", {
410
+ onClick: () => z(e),
411
+ className: n("rounded-full px-3.5 py-1.5 text-sm font-medium capitalize transition-colors", R === e ? "bg-action-primary-bg text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
412
+ children: [e === "ALL" ? "All" : e, /* @__PURE__ */ w("span", {
338
413
  className: "ml-1.5 text-xs opacity-70 tabular-nums",
339
- children: P[e] ?? 0
414
+ children: te[e] ?? 0
340
415
  })]
341
416
  }, e))
342
417
  }),
343
- /* @__PURE__ */ S("div", {
418
+ /* @__PURE__ */ w("div", {
344
419
  className: "grid gap-4 sm:grid-cols-2",
345
- children: F.pageItems.map((t) => {
420
+ children: $.pageItems.map((t) => {
346
421
  let n = t.target && t.target > 0 ? Math.min(100, Math.round(t.value / t.target * 100)) : null;
347
- return /* @__PURE__ */ S(T, {
422
+ return /* @__PURE__ */ w(D, {
348
423
  treatment: "glass",
349
424
  className: "border-border-seam",
350
- children: /* @__PURE__ */ C("div", {
425
+ children: /* @__PURE__ */ T("div", {
351
426
  className: "space-y-3 p-4",
352
427
  children: [
353
- /* @__PURE__ */ C("div", {
428
+ /* @__PURE__ */ T("div", {
354
429
  className: "flex items-start justify-between gap-2",
355
- children: [/* @__PURE__ */ C("div", {
430
+ children: [/* @__PURE__ */ T("div", {
356
431
  className: "min-w-0",
357
- children: [/* @__PURE__ */ S("p", {
432
+ children: [/* @__PURE__ */ w("p", {
358
433
  className: "text-xs uppercase tracking-wide text-text-secondary",
359
434
  children: t.category
360
- }), /* @__PURE__ */ S("h2", {
435
+ }), /* @__PURE__ */ w("h2", {
361
436
  className: "mt-0.5 text-base font-semibold text-text-primary",
362
437
  children: t.metricName
363
438
  })]
364
- }), /* @__PURE__ */ S(X, { trend: t.trend })]
439
+ }), /* @__PURE__ */ T("div", {
440
+ className: "flex items-center gap-1",
441
+ children: [
442
+ /* @__PURE__ */ w(ne, { trend: t.trend }),
443
+ /* @__PURE__ */ w(E, {
444
+ type: "button",
445
+ variant: "ghost",
446
+ size: "icon",
447
+ "aria-label": `Edit ${t.metricName}`,
448
+ onClick: () => {
449
+ b(t), v(!0);
450
+ },
451
+ children: /* @__PURE__ */ w(g, { className: "h-4 w-4" })
452
+ }),
453
+ /* @__PURE__ */ w(E, {
454
+ type: "button",
455
+ variant: "ghost",
456
+ size: "icon",
457
+ "aria-label": `Archive ${t.metricName}`,
458
+ onClick: () => A(t),
459
+ children: /* @__PURE__ */ w(p, { className: "h-4 w-4" })
460
+ })
461
+ ]
462
+ })]
365
463
  }),
366
- /* @__PURE__ */ C("div", {
464
+ /* @__PURE__ */ T("div", {
367
465
  className: "flex items-baseline gap-2",
368
466
  children: [
369
- /* @__PURE__ */ S("span", {
467
+ /* @__PURE__ */ w("span", {
370
468
  className: "text-2xl font-bold tabular-nums text-text-primary",
371
469
  children: e(t.value)
372
470
  }),
373
- /* @__PURE__ */ S("span", {
471
+ /* @__PURE__ */ w("span", {
374
472
  className: "text-sm text-text-secondary",
375
473
  children: t.unit
376
474
  }),
377
- t.target == null ? null : /* @__PURE__ */ C("span", {
475
+ t.target == null ? null : /* @__PURE__ */ T("span", {
378
476
  className: "text-xs text-text-muted",
379
477
  children: ["target ", e(t.target)]
380
478
  })
381
479
  ]
382
480
  }),
383
- n == null ? null : /* @__PURE__ */ C("div", {
481
+ n == null ? null : /* @__PURE__ */ T("div", {
384
482
  className: "space-y-1",
385
- children: [/* @__PURE__ */ C("div", {
483
+ children: [/* @__PURE__ */ T("div", {
386
484
  className: "flex items-center justify-between text-xs text-text-secondary",
387
- children: [/* @__PURE__ */ S("span", { children: "Progress to target" }), /* @__PURE__ */ C("span", {
485
+ children: [/* @__PURE__ */ w("span", { children: "Progress to target" }), /* @__PURE__ */ T("span", {
388
486
  className: "font-medium tabular-nums",
389
487
  children: [n, "%"]
390
488
  })]
391
- }), /* @__PURE__ */ S("div", {
489
+ }), /* @__PURE__ */ w("div", {
392
490
  className: "h-2 overflow-hidden rounded-full bg-bg-sunken",
393
- children: /* @__PURE__ */ S("div", {
491
+ children: /* @__PURE__ */ w("div", {
394
492
  className: "h-full rounded-full bg-action-primary-bg",
395
493
  style: { width: `${n}%` }
396
494
  })
@@ -401,16 +499,16 @@ function Q() {
401
499
  }, t.id);
402
500
  })
403
501
  }),
404
- /* @__PURE__ */ S(o, {
405
- page: F.page,
406
- pageCount: F.pageCount,
407
- rangeStart: F.rangeStart,
408
- rangeEnd: F.rangeEnd,
409
- totalItems: F.totalItems,
410
- canPrev: F.canPrev,
411
- canNext: F.canNext,
412
- onPrev: F.prevPage,
413
- onNext: F.nextPage,
502
+ /* @__PURE__ */ w(o, {
503
+ page: $.page,
504
+ pageCount: $.pageCount,
505
+ rangeStart: $.rangeStart,
506
+ rangeEnd: $.rangeEnd,
507
+ totalItems: $.totalItems,
508
+ canPrev: $.canPrev,
509
+ canNext: $.canNext,
510
+ onPrev: $.prevPage,
511
+ onNext: $.nextPage,
414
512
  itemLabel: "metrics"
415
513
  })
416
514
  ] }) : null
@@ -418,6 +516,6 @@ function Q() {
418
516
  });
419
517
  }
420
518
  //#endregion
421
- export { Q as EsgDashboardPage };
519
+ export { $ as EsgDashboardPage };
422
520
 
423
521
  //# sourceMappingURL=EsgDashboardPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EsgDashboardPage.js","names":[],"sources":["../../../src/assethandler/pages/EsgDashboardPage.tsx"],"sourcesContent":["import { Leaf, Plus, TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport type React from 'react';\nimport {\n Button,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Pagination,\n usePagination,\n SectionHeader,\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n seriesColor,\n formatNumber,\n type DonutSlice,\n} from '../components';\nimport { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\n\nconst ESG_METRICS_QUERY = gql`\n query AssetHandlerEsgMetrics {\n esgMetrics {\n id\n metricKey\n metricName\n category\n unit\n value\n target\n trend\n periodStart\n periodEnd\n }\n }\n`;\n\nconst UPSERT_ESG_METRIC_MUTATION = gql`\n mutation AssetHandlerUpsertEsgMetric($input: UpsertEsgMetricInput!) {\n upsertEsgMetric(input: $input) {\n id\n metricName\n }\n }\n`;\n\ninterface EcoMetric {\n id: string;\n metricKey: string;\n metricName: string;\n category: string;\n unit: string;\n value: number;\n target: number | null;\n trend: string | null;\n periodStart: string;\n periodEnd: string;\n}\n\ninterface UpsertEsgMetricResult {\n upsertEsgMetric: { id: string; metricName: string };\n}\n\nconst ESG_CATEGORIES = ['environment', 'social', 'governance'] as const;\nconst ESG_TRENDS = ['improving', 'stable', 'declining'] as const;\n\nconst TREND_META: Record<\n string,\n {\n label: string;\n icon: typeof TrendingUp;\n tone: 'positive' | 'negative' | 'neutral';\n dir: 'up' | 'down' | 'neutral';\n }\n> = {\n improving: { label: 'Improving', icon: TrendingUp, tone: 'positive', dir: 'up' },\n declining: { label: 'Declining', icon: TrendingDown, tone: 'negative', dir: 'down' },\n stable: { label: 'Stable', icon: Minus, tone: 'neutral', dir: 'neutral' },\n};\n\nconst TREND_PILL: Record<'positive' | 'negative' | 'neutral', string> = {\n positive: 'bg-status-success-bg-subtle text-status-success-text',\n negative: 'bg-status-error-bg-subtle text-status-error-text',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\nfunction firstOfMonthISO(): string {\n const now = new Date();\n return new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);\n}\n\nfunction todayISO(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction TrendBadge({ trend }: { trend: string | null }) {\n const meta = TREND_META[trend ?? 'stable'] ?? TREND_META.stable;\n const Icon = meta.icon;\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',\n TREND_PILL[meta.tone]\n )}\n >\n <Icon className=\"h-3 w-3\" />\n {meta.label}\n </span>\n );\n}\n\nfunction RecordMetricDialog({\n open,\n onOpenChange,\n onSaved,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSaved: () => void;\n}) {\n const [metricName, setMetricName] = useState('');\n const [category, setCategory] = useState<(typeof ESG_CATEGORIES)[number]>('environment');\n const [unit, setUnit] = useState('');\n const [value, setValue] = useState('');\n const [target, setTarget] = useState('');\n const [trend, setTrend] = useState<(typeof ESG_TRENDS)[number]>('stable');\n const [periodStart, setPeriodStart] = useState(firstOfMonthISO());\n const [periodEnd, setPeriodEnd] = useState(todayISO());\n\n const [upsertEsgMetric, { loading: saving }] = useMutation<UpsertEsgMetricResult>(\n UPSERT_ESG_METRIC_MUTATION,\n {\n onCompleted: (result) => {\n notify.success('Metric saved', `${result.upsertEsgMetric.metricName} was recorded.`);\n setMetricName('');\n setUnit('');\n setValue('');\n setTarget('');\n onOpenChange(false);\n onSaved();\n },\n onError: (error) => notify.error('Could not save metric', error.message),\n }\n );\n\n const handleSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n const parsedValue = Number(value);\n if (!metricName.trim() || !unit.trim()) {\n notify.warning('Missing details', 'Metric name and unit are required.');\n return;\n }\n if (!Number.isFinite(parsedValue) || value.trim() === '') {\n notify.warning('Invalid value', 'Enter a numeric metric value.');\n return;\n }\n if (!periodStart || !periodEnd) {\n notify.warning('Missing period', 'Period start and end are required.');\n return;\n }\n const parsedTarget = target.trim() === '' ? null : Number(target);\n void upsertEsgMetric({\n variables: {\n input: {\n metricKey: slugify(metricName),\n metricName: metricName.trim(),\n category,\n unit: unit.trim(),\n value: parsedValue,\n target: parsedTarget != null && Number.isFinite(parsedTarget) ? parsedTarget : null,\n trend,\n periodStart: new Date(periodStart).toISOString(),\n periodEnd: new Date(periodEnd).toISOString(),\n },\n },\n });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-2xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Save Metric</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record an ESG metric to track progress toward your sustainability targets.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form className=\"space-y-3\" onSubmit={handleSubmit}>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"space-y-2\">\n <Label>Metric Name *</Label>\n <Input\n placeholder=\"e.g., Scope 2 carbon emissions\"\n value={metricName}\n onChange={(e) => setMetricName(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Category</Label>\n <Select value={category} onValueChange={(v) => setCategory(v as typeof category)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ESG_CATEGORIES.map((c) => (\n <SelectItem key={c} value={c} className=\"capitalize\">\n {c}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-2\">\n <Label>Unit *</Label>\n <Input\n placeholder=\"e.g., tCO2e\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n />\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"space-y-2\">\n <Label>Value *</Label>\n <Input\n type=\"number\"\n step=\"any\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Target</Label>\n <Input\n type=\"number\"\n step=\"any\"\n value={target}\n onChange={(e) => setTarget(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Trend</Label>\n <Select value={trend} onValueChange={(v) => setTrend(v as typeof trend)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ESG_TRENDS.map((t) => (\n <SelectItem key={t} value={t} className=\"capitalize\">\n {t}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Period Start *</Label>\n <Input\n type=\"date\"\n value={periodStart}\n onChange={(e) => setPeriodStart(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Period End *</Label>\n <Input type=\"date\" value={periodEnd} onChange={(e) => setPeriodEnd(e.target.value)} />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={saving}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving}>\n {saving ? 'Saving…' : 'Save Metric'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function EsgDashboardPage() {\n const [dialogOpen, setDialogOpen] = useState(false);\n const [category, setCategory] = useState<string>('ALL');\n\n const { data, loading, refetch } = useQuery<{ esgMetrics: EcoMetric[] }>(ESG_METRICS_QUERY, {\n fetchPolicy: 'cache-and-network',\n });\n\n const metrics = useMemo(() => data?.esgMetrics ?? [], [data?.esgMetrics]);\n\n const headline = useMemo(() => metrics.slice(0, 4), [metrics]);\n\n const filtered = useMemo(\n () => (category === 'ALL' ? metrics : metrics.filter((m) => m.category === category)),\n [metrics, category]\n );\n\n const categoryMix = useMemo<DonutSlice[]>(() => {\n const counts = metrics.reduce<Record<string, number>>((acc, m) => {\n acc[m.category] = (acc[m.category] ?? 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts).map(([cat, value], i) => ({\n name: cat.charAt(0).toUpperCase() + cat.slice(1),\n value,\n color: seriesColor(i),\n }));\n }, [metrics]);\n\n const valueVsTarget = useMemo(\n () =>\n metrics\n .filter((m) => m.target != null)\n .slice(0, 8)\n .map((m) => ({ name: m.metricName, value: m.value, target: m.target as number })),\n [metrics]\n );\n\n const categoryCounts = useMemo(() => {\n const counts: Record<string, number> = { ALL: metrics.length };\n for (const m of metrics) counts[m.category] = (counts[m.category] ?? 0) + 1;\n return counts;\n }, [metrics]);\n\n const pagination = usePagination(filtered, { pageSize: 8, resetKey: category });\n\n return (\n <div className=\"space-y-5 p-4 sm:p-6\">\n <SectionHeader\n hero\n icon={Leaf}\n title=\"ESG Dashboard\"\n description=\"Measure the environmental and governance impact of your estate against targets.\"\n actions={\n <Button className=\"w-full sm:w-auto\" onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n Save Metric\n </Button>\n }\n />\n <PagePurpose>\n Track energy use, carbon emissions and responsible disposal against targets — and produce\n the evidence ESG reporting and audits require.\n </PagePurpose>\n\n <RecordMetricDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n onSaved={() => void refetch()}\n />\n\n {loading && metrics.length === 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-28 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer\" />\n ))}\n </div>\n ) : null}\n\n {!loading && metrics.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No ESG metrics recorded yet\"\n description=\"Save a metric to start tracking sustainability progress against your targets.\"\n />\n ) : null}\n\n {headline.length > 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {headline.map((m) => {\n const meta = TREND_META[m.trend ?? 'stable'] ?? TREND_META.stable;\n return (\n <StatTile\n key={m.id}\n label={`${m.metricName} (${m.unit})`}\n value={m.value}\n format={formatNumber}\n delta={meta.label}\n deltaDirection={meta.dir}\n deltaTone={meta.tone}\n icon={Leaf}\n />\n );\n })}\n </div>\n ) : null}\n\n {metrics.length > 0 ? (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <ChartCard title=\"Value vs. target\" subtitle=\"Metrics measured against their targets\">\n {valueVsTarget.length > 0 ? (\n <BarsChart\n data={valueVsTarget}\n xKey=\"name\"\n series={[\n { key: 'value', label: 'Value' },\n { key: 'target', label: 'Target' },\n ]}\n orientation=\"horizontal\"\n height={240}\n categoryWidth={150}\n ariaLabel=\"Metric value versus target\"\n />\n ) : (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-secondary\">\n Set a target on a metric to compare value against target.\n </div>\n )}\n </ChartCard>\n <ChartCard title=\"By category\" subtitle=\"Metrics across E / S / G\">\n <DonutChart\n data={categoryMix}\n height={240}\n centerLabel=\"Metrics\"\n centerValue={metrics.length}\n ariaLabel=\"Metrics by category\"\n />\n </ChartCard>\n </div>\n ) : null}\n\n {metrics.length > 0 ? (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n {(['ALL', ...ESG_CATEGORIES] as const).map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setCategory(c)}\n className={cn(\n 'rounded-full px-3.5 py-1.5 text-sm font-medium capitalize transition-colors',\n category === c\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {c === 'ALL' ? 'All' : c}\n <span className=\"ml-1.5 text-xs opacity-70 tabular-nums\">\n {categoryCounts[c] ?? 0}\n </span>\n </button>\n ))}\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {pagination.pageItems.map((metric) => {\n const pct =\n metric.target && metric.target > 0\n ? Math.min(100, Math.round((metric.value / metric.target) * 100))\n : null;\n return (\n <GlassCard key={metric.id} treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"space-y-3 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">\n {metric.category}\n </p>\n <h2 className=\"mt-0.5 text-base font-semibold text-text-primary\">\n {metric.metricName}\n </h2>\n </div>\n <TrendBadge trend={metric.trend} />\n </div>\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatNumber(metric.value)}\n </span>\n <span className=\"text-sm text-text-secondary\">{metric.unit}</span>\n {metric.target != null ? (\n <span className=\"text-xs text-text-muted\">\n target {formatNumber(metric.target)}\n </span>\n ) : null}\n </div>\n {pct != null ? (\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Progress to target</span>\n <span className=\"font-medium tabular-nums\">{pct}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n ) : null}\n </div>\n </GlassCard>\n );\n })}\n </div>\n\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"metrics\"\n />\n </>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EsgDashboardPage.js","names":[],"sources":["../../../src/assethandler/pages/EsgDashboardPage.tsx"],"sourcesContent":["import { Archive, Leaf, Minus, Pencil, Plus, TrendingDown, TrendingUp } from 'lucide-react';\nimport type React from 'react';\nimport {\n Button,\n IllustratedEmptyState,\n GlassCard,\n PagePurpose,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Pagination,\n usePagination,\n SectionHeader,\n StatTile,\n ChartCard,\n DonutChart,\n BarsChart,\n seriesColor,\n formatNumber,\n type DonutSlice,\n} from '../components';\nimport { cn } from '../utils/cn';\nimport { notify } from '../utils/toast';\n\nconst ESG_METRICS_QUERY = gql`\n query AssetHandlerEsgMetrics {\n esgMetrics {\n id\n metricKey\n metricName\n category\n unit\n value\n target\n trend\n periodStart\n periodEnd\n }\n }\n`;\n\nconst CREATE_ESG_METRIC_MUTATION = gql`\n mutation AssetHandlerCreateEsgMetric($input: CreateEsgMetricInput!) {\n createEsgMetric(input: $input) {\n id\n metricName\n }\n }\n`;\n\nconst UPDATE_ESG_METRIC_MUTATION = gql`\n mutation AssetHandlerUpdateEsgMetric($id: ID!, $input: UpdateEsgMetricInput!) {\n updateEsgMetric(id: $id, input: $input) {\n id\n metricName\n }\n }\n`;\n\nconst ARCHIVE_ESG_METRIC_MUTATION = gql`\n mutation AssetHandlerArchiveEsgMetric($id: ID!) {\n archiveEsgMetric(id: $id)\n }\n`;\n\ninterface EcoMetric {\n id: string;\n metricKey: string;\n metricName: string;\n category: string;\n unit: string;\n value: number;\n target: number | null;\n trend: string | null;\n periodStart: string;\n periodEnd: string;\n}\n\ninterface CreateEsgMetricResult {\n createEsgMetric: { id: string; metricName: string };\n}\n\ninterface UpdateEsgMetricResult {\n updateEsgMetric: { id: string; metricName: string };\n}\n\nconst ESG_CATEGORIES = ['environment', 'social', 'governance'] as const;\nconst ESG_TRENDS = ['improving', 'stable', 'declining'] as const;\n\nconst TREND_META: Record<\n string,\n {\n label: string;\n icon: typeof TrendingUp;\n tone: 'positive' | 'negative' | 'neutral';\n dir: 'up' | 'down' | 'neutral';\n }\n> = {\n improving: { label: 'Improving', icon: TrendingUp, tone: 'positive', dir: 'up' },\n declining: { label: 'Declining', icon: TrendingDown, tone: 'negative', dir: 'down' },\n stable: { label: 'Stable', icon: Minus, tone: 'neutral', dir: 'neutral' },\n};\n\nconst TREND_PILL: Record<'positive' | 'negative' | 'neutral', string> = {\n positive: 'bg-status-success-bg-subtle text-status-success-text',\n negative: 'bg-status-error-bg-subtle text-status-error-text',\n neutral: 'bg-bg-sunken text-text-secondary',\n};\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n\ninterface MetricFormValues {\n metricName: string;\n category: (typeof ESG_CATEGORIES)[number];\n unit: string;\n value: number;\n target: number | null;\n trend: (typeof ESG_TRENDS)[number];\n periodStart: string;\n periodEnd: string;\n}\n\nexport function buildMetricMutationVariables(metricId: string | null, form: MetricFormValues) {\n const mutableInput = {\n metricName: form.metricName.trim(),\n category: form.category,\n unit: form.unit.trim(),\n value: form.value,\n target: form.target,\n trend: form.trend,\n };\n if (metricId)\n return { kind: 'update' as const, variables: { id: metricId, input: mutableInput } };\n return {\n kind: 'create' as const,\n variables: {\n input: {\n ...mutableInput,\n metricKey: slugify(form.metricName),\n periodStart: new Date(form.periodStart).toISOString(),\n periodEnd: new Date(form.periodEnd).toISOString(),\n },\n },\n };\n}\n\nfunction firstOfMonthISO(): string {\n const now = new Date();\n return new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);\n}\n\nfunction todayISO(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction TrendBadge({ trend }: { trend: string | null }) {\n const meta = TREND_META[trend ?? 'stable'] ?? TREND_META.stable;\n const Icon = meta.icon;\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',\n TREND_PILL[meta.tone]\n )}\n >\n <Icon className=\"h-3 w-3\" />\n {meta.label}\n </span>\n );\n}\n\nfunction RecordMetricDialog({\n open,\n metric,\n onOpenChange,\n onSaved,\n}: {\n open: boolean;\n metric: EcoMetric | null;\n onOpenChange: (open: boolean) => void;\n onSaved: () => void;\n}) {\n const [metricName, setMetricName] = useState(metric?.metricName ?? '');\n const [category, setCategory] = useState<(typeof ESG_CATEGORIES)[number]>(\n (metric?.category as (typeof ESG_CATEGORIES)[number]) ?? 'environment'\n );\n const [unit, setUnit] = useState(metric?.unit ?? '');\n const [value, setValue] = useState(metric ? String(metric.value) : '');\n const [target, setTarget] = useState(metric?.target == null ? '' : String(metric.target));\n const [trend, setTrend] = useState<(typeof ESG_TRENDS)[number]>(\n (metric?.trend as (typeof ESG_TRENDS)[number]) ?? 'stable'\n );\n const [periodStart, setPeriodStart] = useState(\n metric?.periodStart.slice(0, 10) ?? firstOfMonthISO()\n );\n const [periodEnd, setPeriodEnd] = useState(metric?.periodEnd.slice(0, 10) ?? todayISO());\n\n const mutationOptions = {\n onError: (error: Error) => notify.error('Could not save metric', error.message),\n };\n const [createEsgMetric, { loading: creating }] = useMutation<CreateEsgMetricResult>(\n CREATE_ESG_METRIC_MUTATION,\n {\n ...mutationOptions,\n onCompleted: (result) => {\n notify.success('Metric saved', `${result.createEsgMetric.metricName} was recorded.`);\n onOpenChange(false);\n onSaved();\n },\n }\n );\n const [updateEsgMetric, { loading: updating }] = useMutation<UpdateEsgMetricResult>(\n UPDATE_ESG_METRIC_MUTATION,\n {\n ...mutationOptions,\n onCompleted: (result) => {\n notify.success('Metric updated', `${result.updateEsgMetric.metricName} was updated.`);\n onOpenChange(false);\n onSaved();\n },\n }\n );\n const saving = creating || updating;\n\n const handleSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n const parsedValue = Number(value);\n if (!metricName.trim() || !unit.trim()) {\n notify.warning('Missing details', 'Metric name and unit are required.');\n return;\n }\n if (!Number.isFinite(parsedValue) || value.trim() === '') {\n notify.warning('Invalid value', 'Enter a numeric metric value.');\n return;\n }\n if (!periodStart || !periodEnd) {\n notify.warning('Missing period', 'Period start and end are required.');\n return;\n }\n const parsedTarget = target.trim() === '' ? null : Number(target);\n const mutation = buildMetricMutationVariables(metric?.id ?? null, {\n metricName,\n category,\n unit,\n value: parsedValue,\n target: parsedTarget != null && Number.isFinite(parsedTarget) ? parsedTarget : null,\n trend,\n periodStart,\n periodEnd,\n });\n if (mutation.kind === 'update') {\n void updateEsgMetric(mutation);\n } else {\n void createEsgMetric(mutation);\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-2xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{metric ? 'Edit Metric' : 'Save Metric'}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {metric\n ? 'Update this metric’s values. Its source identity and reporting period stay unchanged.'\n : 'Record an ESG metric to track progress toward your sustainability targets.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form className=\"space-y-3\" onSubmit={handleSubmit}>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"space-y-2\">\n <Label>Metric Name *</Label>\n <Input\n placeholder=\"e.g., Scope 2 carbon emissions\"\n value={metricName}\n onChange={(e) => setMetricName(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Category</Label>\n <Select value={category} onValueChange={(v) => setCategory(v as typeof category)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ESG_CATEGORIES.map((c) => (\n <SelectItem key={c} value={c} className=\"capitalize\">\n {c}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-2\">\n <Label>Unit *</Label>\n <Input\n placeholder=\"e.g., tCO2e\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n />\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"space-y-2\">\n <Label>Value *</Label>\n <Input\n type=\"number\"\n step=\"any\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Target</Label>\n <Input\n type=\"number\"\n step=\"any\"\n value={target}\n onChange={(e) => setTarget(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Trend</Label>\n <Select value={trend} onValueChange={(v) => setTrend(v as typeof trend)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ESG_TRENDS.map((t) => (\n <SelectItem key={t} value={t} className=\"capitalize\">\n {t}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label>Period Start *</Label>\n <Input\n type=\"date\"\n value={periodStart}\n onChange={(e) => setPeriodStart(e.target.value)}\n disabled={metric != null}\n />\n </div>\n <div className=\"space-y-2\">\n <Label>Period End *</Label>\n <Input\n type=\"date\"\n value={periodEnd}\n onChange={(e) => setPeriodEnd(e.target.value)}\n disabled={metric != null}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={saving}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving}>\n {saving ? 'Saving…' : metric ? 'Update Metric' : 'Save Metric'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function EsgDashboardPage() {\n const [dialogOpen, setDialogOpen] = useState(false);\n const [editingMetric, setEditingMetric] = useState<EcoMetric | null>(null);\n const [archivingMetric, setArchivingMetric] = useState<EcoMetric | null>(null);\n const [category, setCategory] = useState<string>('ALL');\n\n const { data, loading, refetch } = useQuery<{ esgMetrics: EcoMetric[] }>(ESG_METRICS_QUERY, {\n fetchPolicy: 'cache-and-network',\n });\n\n const metrics = useMemo(() => data?.esgMetrics ?? [], [data?.esgMetrics]);\n\n const [archiveEsgMetric, { loading: archiving }] = useMutation<{ archiveEsgMetric: boolean }>(\n ARCHIVE_ESG_METRIC_MUTATION,\n {\n onCompleted: () => {\n notify.success('Metric archived', 'The metric was removed from the active ESG dashboard.');\n setArchivingMetric(null);\n void refetch();\n },\n onError: (error) => notify.error('Could not archive metric', error.message),\n }\n );\n\n const headline = useMemo(() => metrics.slice(0, 4), [metrics]);\n\n const filtered = useMemo(\n () => (category === 'ALL' ? metrics : metrics.filter((m) => m.category === category)),\n [metrics, category]\n );\n\n const categoryMix = useMemo<DonutSlice[]>(() => {\n const counts = metrics.reduce<Record<string, number>>((acc, m) => {\n acc[m.category] = (acc[m.category] ?? 0) + 1;\n return acc;\n }, {});\n return Object.entries(counts).map(([cat, value], i) => ({\n name: cat.charAt(0).toUpperCase() + cat.slice(1),\n value,\n color: seriesColor(i),\n }));\n }, [metrics]);\n\n const valueVsTarget = useMemo(\n () =>\n metrics\n .filter((m) => m.target != null)\n .slice(0, 8)\n .map((m) => ({ name: m.metricName, value: m.value, target: m.target as number })),\n [metrics]\n );\n\n const categoryCounts = useMemo(() => {\n const counts: Record<string, number> = { ALL: metrics.length };\n for (const m of metrics) counts[m.category] = (counts[m.category] ?? 0) + 1;\n return counts;\n }, [metrics]);\n\n const pagination = usePagination(filtered, { pageSize: 8, resetKey: category });\n\n return (\n <div className=\"space-y-5 p-4 sm:p-6\">\n <SectionHeader\n hero\n icon={Leaf}\n title=\"ESG Dashboard\"\n description=\"Measure the environmental and governance impact of your estate against targets.\"\n actions={\n <Button\n className=\"w-full sm:w-auto\"\n onClick={() => {\n setEditingMetric(null);\n setDialogOpen(true);\n }}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n Save Metric\n </Button>\n }\n />\n <PagePurpose>\n Track energy use, carbon emissions and responsible disposal against targets — and produce\n the evidence ESG reporting and audits require.\n </PagePurpose>\n\n {dialogOpen ? (\n <RecordMetricDialog\n open\n metric={editingMetric}\n onOpenChange={(nextOpen) => {\n setDialogOpen(nextOpen);\n if (!nextOpen) setEditingMetric(null);\n }}\n onSaved={() => void refetch()}\n />\n ) : null}\n\n <ResponsiveDialog\n open={archivingMetric != null}\n onOpenChange={(nextOpen) => {\n if (!nextOpen) setArchivingMetric(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Archive metric</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Archive {archivingMetric?.metricName ?? 'this metric'}? It will leave the active ESG\n dashboard. Archiving does not refund monthly usage.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setArchivingMetric(null)} disabled={archiving}>\n Cancel\n </Button>\n <Button\n variant=\"destructive\"\n disabled={archiving || archivingMetric == null}\n onClick={() => {\n if (archivingMetric) {\n void archiveEsgMetric({ variables: { id: archivingMetric.id } });\n }\n }}\n >\n {archiving ? 'Archiving…' : 'Archive Metric'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n {loading && metrics.length === 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"h-28 rounded-xl bg-bg-sunken motion-safe:animate-vc-shimmer\" />\n ))}\n </div>\n ) : null}\n\n {!loading && metrics.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No ESG metrics recorded yet\"\n description=\"Save a metric to start tracking sustainability progress against your targets.\"\n />\n ) : null}\n\n {headline.length > 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {headline.map((m) => {\n const meta = TREND_META[m.trend ?? 'stable'] ?? TREND_META.stable;\n return (\n <StatTile\n key={m.id}\n label={`${m.metricName} (${m.unit})`}\n value={m.value}\n format={formatNumber}\n delta={meta.label}\n deltaDirection={meta.dir}\n deltaTone={meta.tone}\n icon={Leaf}\n />\n );\n })}\n </div>\n ) : null}\n\n {metrics.length > 0 ? (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <ChartCard title=\"Value vs. target\" subtitle=\"Metrics measured against their targets\">\n {valueVsTarget.length > 0 ? (\n <BarsChart\n data={valueVsTarget}\n xKey=\"name\"\n series={[\n { key: 'value', label: 'Value' },\n { key: 'target', label: 'Target' },\n ]}\n orientation=\"horizontal\"\n height={240}\n categoryWidth={150}\n ariaLabel=\"Metric value versus target\"\n />\n ) : (\n <div className=\"flex h-[240px] items-center justify-center text-sm text-text-secondary\">\n Set a target on a metric to compare value against target.\n </div>\n )}\n </ChartCard>\n <ChartCard title=\"By category\" subtitle=\"Metrics across E / S / G\">\n <DonutChart\n data={categoryMix}\n height={240}\n centerLabel=\"Metrics\"\n centerValue={metrics.length}\n ariaLabel=\"Metrics by category\"\n />\n </ChartCard>\n </div>\n ) : null}\n\n {metrics.length > 0 ? (\n <>\n <div className=\"flex flex-wrap items-center gap-2\">\n {(['ALL', ...ESG_CATEGORIES] as const).map((c) => (\n <button\n key={c}\n type=\"button\"\n onClick={() => setCategory(c)}\n className={cn(\n 'rounded-full px-3.5 py-1.5 text-sm font-medium capitalize transition-colors',\n category === c\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {c === 'ALL' ? 'All' : c}\n <span className=\"ml-1.5 text-xs opacity-70 tabular-nums\">\n {categoryCounts[c] ?? 0}\n </span>\n </button>\n ))}\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {pagination.pageItems.map((metric) => {\n const pct =\n metric.target && metric.target > 0\n ? Math.min(100, Math.round((metric.value / metric.target) * 100))\n : null;\n return (\n <GlassCard key={metric.id} treatment=\"glass\" className=\"border-border-seam\">\n <div className=\"space-y-3 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">\n {metric.category}\n </p>\n <h2 className=\"mt-0.5 text-base font-semibold text-text-primary\">\n {metric.metricName}\n </h2>\n </div>\n <div className=\"flex items-center gap-1\">\n <TrendBadge trend={metric.trend} />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={`Edit ${metric.metricName}`}\n onClick={() => {\n setEditingMetric(metric);\n setDialogOpen(true);\n }}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={`Archive ${metric.metricName}`}\n onClick={() => setArchivingMetric(metric)}\n >\n <Archive className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatNumber(metric.value)}\n </span>\n <span className=\"text-sm text-text-secondary\">{metric.unit}</span>\n {metric.target != null ? (\n <span className=\"text-xs text-text-muted\">\n target {formatNumber(metric.target)}\n </span>\n ) : null}\n </div>\n {pct != null ? (\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Progress to target</span>\n <span className=\"font-medium tabular-nums\">{pct}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n ) : null}\n </div>\n </GlassCard>\n );\n })}\n </div>\n\n <Pagination\n page={pagination.page}\n pageCount={pagination.pageCount}\n rangeStart={pagination.rangeStart}\n rangeEnd={pagination.rangeEnd}\n totalItems={pagination.totalItems}\n canPrev={pagination.canPrev}\n canNext={pagination.canNext}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"metrics\"\n />\n </>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,IAAM,IAAoB,CAAG;;;;;;;;;;;;;;;GAiBvB,IAA6B,CAAG;;;;;;;GAShC,IAA6B,CAAG;;;;;;;GAShC,IAA8B,CAAG;;;;GA2BjC,IAAiB;CAAC;CAAe;CAAU;CAAa,EACxD,IAAa;CAAC;CAAa;CAAU;CAAY,EAEjD,IAQF;CACF,WAAW;EAAE,OAAO;EAAa,MAAM;EAAY,MAAM;EAAY,KAAK;EAAM;CAChF,WAAW;EAAE,OAAO;EAAa,MAAM;EAAc,MAAM;EAAY,KAAK;EAAQ;CACpF,QAAQ;EAAE,OAAO;EAAU,MAAM;EAAO,MAAM;EAAW,KAAK;EAAW;CAC1E,EAEK,IAAkE;CACtE,UAAU;CACV,UAAU;CACV,SAAS;CACV,EAEK,KAAW,MACf,EACG,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG;AAa5B,SAAgB,GAA6B,GAAyB,GAAwB;CAC5F,IAAM,IAAe;EACnB,YAAY,EAAK,WAAW,MAAM;EAClC,UAAU,EAAK;EACf,MAAM,EAAK,KAAK,MAAM;EACtB,OAAO,EAAK;EACZ,QAAQ,EAAK;EACb,OAAO,EAAK;EACb;AAGD,QAFI,IACK;EAAE,MAAM;EAAmB,WAAW;GAAE,IAAI;GAAU,OAAO;GAAc;EAAE,GAC/E;EACL,MAAM;EACN,WAAW,EACT,OAAO;GACL,GAAG;GACH,WAAW,EAAQ,EAAK,WAAW;GACnC,aAAa,IAAI,KAAK,EAAK,YAAY,CAAC,aAAa;GACrD,WAAW,IAAI,KAAK,EAAK,UAAU,CAAC,aAAa;GAClD,EACF;EACF;;AAGH,SAAS,IAA0B;CACjC,IAAM,oBAAM,IAAI,MAAM;AACtB,QAAO,IAAI,KAAK,EAAI,aAAa,EAAE,EAAI,UAAU,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;AAGlF,SAAS,KAAmB;AAC1B,yBAAO,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;AAG9C,SAAS,GAAW,EAAE,YAAmC;CACvD,IAAM,IAAO,EAAW,KAAS,aAAa,EAAW,QACnD,IAAO,EAAK;AAClB,QACE,kBAAC,QAAD;EACE,WAAW,EACT,+EACA,EAAW,EAAK,MACjB;YAJH,CAME,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAK,MACD;;;AAIX,SAAS,GAAmB,EAC1B,SACA,WACA,iBACA,cAMC;CACD,IAAM,CAAC,GAAY,KAAiB,EAAS,GAAQ,cAAc,GAAG,EAChE,CAAC,GAAU,KAAe,EAC7B,GAAQ,YAAgD,cAC1D,EACK,CAAC,GAAM,KAAW,EAAS,GAAQ,QAAQ,GAAG,EAC9C,CAAC,GAAO,KAAY,EAAS,IAAS,OAAO,EAAO,MAAM,GAAG,GAAG,EAChE,CAAC,GAAQ,KAAa,EAAS,GAAQ,UAAU,OAAO,KAAK,OAAO,EAAO,OAAO,CAAC,EACnF,CAAC,GAAO,KAAY,EACvB,GAAQ,SAAyC,SACnD,EACK,CAAC,GAAa,KAAkB,EACpC,GAAQ,YAAY,MAAM,GAAG,GAAG,IAAI,GAAiB,CACtD,EACK,CAAC,GAAW,KAAgB,EAAS,GAAQ,UAAU,MAAM,GAAG,GAAG,IAAI,IAAU,CAAC,EAElF,IAAkB,EACtB,UAAU,MAAiB,EAAO,MAAM,yBAAyB,EAAM,QAAQ,EAChF,EACK,CAAC,GAAiB,EAAE,SAAS,OAAc,EAC/C,GACA;EACE,GAAG;EACH,cAAc,MAAW;AAGvB,GAFA,EAAO,QAAQ,gBAAgB,GAAG,EAAO,gBAAgB,WAAW,gBAAgB,EACpF,EAAa,GAAM,EACnB,GAAS;;EAEZ,CACF,EACK,CAAC,GAAiB,EAAE,SAAS,OAAc,EAC/C,GACA;EACE,GAAG;EACH,cAAc,MAAW;AAGvB,GAFA,EAAO,QAAQ,kBAAkB,GAAG,EAAO,gBAAgB,WAAW,eAAe,EACrF,EAAa,GAAM,EACnB,GAAS;;EAEZ,CACF,EACK,IAAS,KAAY;AAmC3B,QACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAwB,IAAS,gBAAgB,eAAsC,CAAA,EACvF,kBAAC,GAAD,EAAA,UACG,IACG,0FACA,8EACwB,CAAA,CACP,EAAA,CAAA,EACzB,kBAAC,QAAD;IAAM,WAAU;IAAY,WA5CZ,MAA2B;AAC/C,OAAM,gBAAgB;KACtB,IAAM,IAAc,OAAO,EAAM;AACjC,SAAI,CAAC,EAAW,MAAM,IAAI,CAAC,EAAK,MAAM,EAAE;AACtC,QAAO,QAAQ,mBAAmB,qCAAqC;AACvE;;AAEF,SAAI,CAAC,OAAO,SAAS,EAAY,IAAI,EAAM,MAAM,KAAK,IAAI;AACxD,QAAO,QAAQ,iBAAiB,gCAAgC;AAChE;;AAEF,SAAI,CAAC,KAAe,CAAC,GAAW;AAC9B,QAAO,QAAQ,kBAAkB,qCAAqC;AACtE;;KAEF,IAAM,IAAe,EAAO,MAAM,KAAK,KAAK,OAAO,OAAO,EAAO,EAC3D,IAAW,GAA6B,GAAQ,MAAM,MAAM;MAChE;MACA;MACA;MACA,OAAO;MACP,QAAQ,KAAgB,QAAQ,OAAO,SAAS,EAAa,GAAG,IAAe;MAC/E;MACA;MACA;MACD,CAAC;AACF,KAAI,EAAS,SAAS,WACf,EAAgB,EAAS,GAEzB,EAAgB,EAAS;;cAe5B;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,iBAAqB,CAAA,EAC5B,kBAAC,GAAD;SACE,aAAY;SACZ,OAAO;SACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;SAC9C,CAAA,CACE;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,YAAgB,CAAA,EACvB,kBAAC,GAAD;SAAQ,OAAO;SAAU,gBAAgB,MAAM,EAAY,EAAqB;mBAAhF,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAe,CAAA,EACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAe,KAAK,MACnB,kBAAC,GAAD;UAAoB,OAAO;UAAG,WAAU;oBACrC;UACU,EAFI,EAEJ,CACb,EACY,CAAA,CACT;WACL;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,EACrB,kBAAC,GAAD;SACE,aAAY;SACZ,OAAO;SACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;SACxC,CAAA,CACE;;OACF;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,WAAe,CAAA,EACtB,kBAAC,GAAD;SACE,MAAK;SACL,MAAK;SACE;SACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;SACzC,CAAA,CACE;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,UAAc,CAAA,EACrB,kBAAC,GAAD;SACE,MAAK;SACL,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;SAC1C,CAAA,CACE;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,SAAa,CAAA,EACpB,kBAAC,GAAD;SAAQ,OAAO;SAAO,gBAAgB,MAAM,EAAS,EAAkB;mBAAvE,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAe,CAAA,EACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAW,KAAK,MACf,kBAAC,GAAD;UAAoB,OAAO;UAAG,WAAU;oBACrC;UACU,EAFI,EAEJ,CACb,EACY,CAAA,CACT;WACL;;OACF;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,kBAAsB,CAAA,EAC7B,kBAAC,GAAD;QACE,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,UAAU,KAAU;QACpB,CAAA,CACE;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAA,UAAO,gBAAoB,CAAA,EAC3B,kBAAC,GAAD;QACE,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;QAC7C,UAAU,KAAU;QACpB,CAAA,CACE;SACF;;KACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,eAAe,EAAa,GAAM;MAClC,UAAU;gBACX;MAEQ,CAAA,EACT,kBAAC,GAAD;MAAQ,MAAK;MAAS,UAAU;gBAC7B,IAAS,YAAY,IAAS,kBAAkB;MAC1C,CAAA,CACc,EAAA,CAAA;KACpB;MACiB;;EACT,CAAA;;AAIvB,SAAgB,IAAmB;CACjC,IAAM,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAe,KAAoB,EAA2B,KAAK,EACpE,CAAC,GAAiB,KAAsB,EAA2B,KAAK,EACxE,CAAC,GAAU,KAAe,EAAiB,MAAM,EAEjD,EAAE,SAAM,YAAS,eAAY,EAAsC,GAAmB,EAC1F,aAAa,qBACd,CAAC,EAEI,IAAU,QAAc,GAAM,cAAc,EAAE,EAAE,CAAC,GAAM,WAAW,CAAC,EAEnE,CAAC,GAAkB,EAAE,SAAS,OAAe,EACjD,GACA;EACE,mBAAmB;AAGZ,GAFL,EAAO,QAAQ,mBAAmB,wDAAwD,EAC1F,EAAmB,KAAK,EACnB,GAAS;;EAEhB,UAAU,MAAU,EAAO,MAAM,4BAA4B,EAAM,QAAQ;EAC5E,CACF,EAEK,IAAW,QAAc,EAAQ,MAAM,GAAG,EAAE,EAAE,CAAC,EAAQ,CAAC,EAExD,IAAW,QACR,MAAa,QAAQ,IAAU,EAAQ,QAAQ,MAAM,EAAE,aAAa,EAAS,EACpF,CAAC,GAAS,EAAS,CACpB,EAEK,KAAc,QAA4B;EAC9C,IAAM,IAAS,EAAQ,QAAgC,GAAK,OAC1D,EAAI,EAAE,aAAa,EAAI,EAAE,aAAa,KAAK,GACpC,IACN,EAAE,CAAC;AACN,SAAO,OAAO,QAAQ,EAAO,CAAC,KAAK,CAAC,GAAK,IAAQ,OAAO;GACtD,MAAM,EAAI,OAAO,EAAE,CAAC,aAAa,GAAG,EAAI,MAAM,EAAE;GAChD;GACA,OAAO,EAAY,EAAE;GACtB,EAAE;IACF,CAAC,EAAQ,CAAC,EAEP,IAAgB,QAElB,EACG,QAAQ,MAAM,EAAE,UAAU,KAAK,CAC/B,MAAM,GAAG,EAAE,CACX,KAAK,OAAO;EAAE,MAAM,EAAE;EAAY,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAkB,EAAE,EACrF,CAAC,EAAQ,CACV,EAEK,KAAiB,QAAc;EACnC,IAAM,IAAiC,EAAE,KAAK,EAAQ,QAAQ;AAC9D,OAAK,IAAM,KAAK,EAAS,GAAO,EAAE,aAAa,EAAO,EAAE,aAAa,KAAK;AAC1E,SAAO;IACN,CAAC,EAAQ,CAAC,EAEP,IAAa,EAAc,GAAU;EAAE,UAAU;EAAG,UAAU;EAAU,CAAC;AAE/E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,MAAA;IACA,MAAM;IACN,OAAM;IACN,aAAY;IACZ,SACE,kBAAC,GAAD;KACE,WAAU;KACV,eAAe;AAEb,MADA,EAAiB,KAAK,EACtB,EAAc,GAAK;;eAJvB,CAOE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,cAE1B;;IAEX,CAAA;GACF,kBAAC,GAAD,EAAA,UAAa,4IAGC,CAAA;GAEb,IACC,kBAAC,IAAD;IACE,MAAA;IACA,QAAQ;IACR,eAAe,MAAa;AAE1B,KADA,EAAc,EAAS,EAClB,KAAU,EAAiB,KAAK;;IAEvC,eAAe,KAAK,GAAS;IAC7B,CAAA,GACA;GAEJ,kBAAC,GAAD;IACE,MAAM,KAAmB;IACzB,eAAe,MAAa;AAC1B,KAAK,KAAU,EAAmB,KAAK;;cAGzC,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,kBAAsC,CAAA,EAC7D,kBAAC,GAAD,EAAA,UAAA;KAA6B;KAClB,GAAiB,cAAc;KAAc;KAE1B,EAAA,CAAA,CACP,EAAA,CAAA,EACzB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAmB,KAAK;KAAE,UAAU;eAAW;KAE/E,CAAA,EACT,kBAAC,GAAD;KACE,SAAQ;KACR,UAAU,KAAa,KAAmB;KAC1C,eAAe;AACb,MAAI,KACG,EAAiB,EAAE,WAAW,EAAE,IAAI,EAAgB,IAAI,EAAE,CAAC;;eAInE,IAAY,eAAe;KACrB,CAAA,CACc,EAAA,CAAA,CACD,EAAA,CAAA;IACT,CAAA;GAElB,KAAW,EAAQ,WAAW,IAC7B,kBAAC,OAAD;IAAK,WAAU;cACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,OAAD,EAAa,WAAU,+DAAgE,EAA7E,EAA6E,CACvF;IACE,CAAA,GACJ;GAEH,CAAC,KAAW,EAAQ,WAAW,IAC9B,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,CAAA,GACA;GAEH,EAAS,SAAS,IACjB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MAAM;KACnB,IAAM,IAAO,EAAW,EAAE,SAAS,aAAa,EAAW;AAC3D,YACE,kBAAC,GAAD;MAEE,OAAO,GAAG,EAAE,WAAW,IAAI,EAAE,KAAK;MAClC,OAAO,EAAE;MACT,QAAQ;MACR,OAAO,EAAK;MACZ,gBAAgB,EAAK;MACrB,WAAW,EAAK;MAChB,MAAM;MACN,EARK,EAAE,GAQP;MAEJ;IACE,CAAA,GACJ;GAEH,EAAQ,SAAS,IAChB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,OAAM;KAAmB,UAAS;eAC1C,EAAc,SAAS,IACtB,kBAAC,GAAD;MACE,MAAM;MACN,MAAK;MACL,QAAQ,CACN;OAAE,KAAK;OAAS,OAAO;OAAS,EAChC;OAAE,KAAK;OAAU,OAAO;OAAU,CACnC;MACD,aAAY;MACZ,QAAQ;MACR,eAAe;MACf,WAAU;MACV,CAAA,GAEF,kBAAC,OAAD;MAAK,WAAU;gBAAyE;MAElF,CAAA;KAEE,CAAA,EACZ,kBAAC,GAAD;KAAW,OAAM;KAAc,UAAS;eACtC,kBAAC,GAAD;MACE,MAAM;MACN,QAAQ;MACR,aAAY;MACZ,aAAa,EAAQ;MACrB,WAAU;MACV,CAAA;KACQ,CAAA,CACR;QACJ;GAEH,EAAQ,SAAS,IAChB,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;eACX,CAAC,OAAO,GAAG,EAAe,CAAW,KAAK,MAC1C,kBAAC,UAAD;MAEE,MAAK;MACL,eAAe,EAAY,EAAE;MAC7B,WAAW,EACT,+EACA,MAAa,IACT,kDACA,2DACL;gBATH,CAWG,MAAM,QAAQ,QAAQ,GACvB,kBAAC,QAAD;OAAM,WAAU;iBACb,GAAe,MAAM;OACjB,CAAA,CACA;QAdF,EAcE,CACT;KACE,CAAA;IAEN,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAW,UAAU,KAAK,MAAW;MACpC,IAAM,IACJ,EAAO,UAAU,EAAO,SAAS,IAC7B,KAAK,IAAI,KAAK,KAAK,MAAO,EAAO,QAAQ,EAAO,SAAU,IAAI,CAAC,GAC/D;AACN,aACE,kBAAC,GAAD;OAA2B,WAAU;OAAQ,WAAU;iBACrD,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAO;YACN,CAAA,EACJ,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAO;YACL,CAAA,CACD;cACN,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,IAAD,EAAY,OAAO,EAAO,OAAS,CAAA;YACnC,kBAAC,GAAD;aACE,MAAK;aACL,SAAQ;aACR,MAAK;aACL,cAAY,QAAQ,EAAO;aAC3B,eAAe;AAEb,cADA,EAAiB,EAAO,EACxB,EAAc,GAAK;;uBAGrB,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;aACvB,CAAA;YACT,kBAAC,GAAD;aACE,MAAK;aACL,SAAQ;aACR,MAAK;aACL,cAAY,WAAW,EAAO;aAC9B,eAAe,EAAmB,EAAO;uBAEzC,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;aACxB,CAAA;YACL;aACF;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAa,EAAO,MAAM;YACtB,CAAA;WACP,kBAAC,QAAD;YAAM,WAAU;sBAA+B,EAAO;YAAY,CAAA;WACjE,EAAO,UAAU,OAId,OAHF,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAA0C,WAChC,EAAa,EAAO,OAAO,CAC9B;;WAEL;;SACL,KAAO,OAaJ,OAZF,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD,EAAA,UAAM,sBAAyB,CAAA,EAC/B,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAA4C,GAAI,IAAQ;cACpD;cACN,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,OAAD;YACE,WAAU;YACV,OAAO,EAAE,OAAO,GAAG,EAAI,IAAI;YAC3B,CAAA;WACE,CAAA,CACF;;SAEJ;;OACI,EA9DI,EAAO,GA8DX;OAEd;KACE,CAAA;IAEN,kBAAC,GAAD;KACE,MAAM,EAAW;KACjB,WAAW,EAAW;KACtB,YAAY,EAAW;KACvB,UAAU,EAAW;KACrB,YAAY,EAAW;KACvB,SAAS,EAAW;KACpB,SAAS,EAAW;KACpB,QAAQ,EAAW;KACnB,QAAQ,EAAW;KACnB,WAAU;KACV,CAAA;IACD,EAAA,CAAA,GACD;GACA"}
@@ -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,GACnC,MAAM,GACN,cAAc,GACd,YAAY,GACZ,WAAW,GACX,UAAU,GACV,OAAO,CAAC;AAEZ,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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-assethandler",
3
- "version": "2026.825.1",
3
+ "version": "2026.826.2",
4
4
  "description": "Burdenoff AssetHandler Microfrontend",
5
5
  "type": "module",
6
6
  "files": [