@burdenoff/microfe-assethandler 2026.825.1 → 2026.826.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assethandler/components/ActivityTimeline.d.ts.map +1 -1
- package/dist/assethandler/components/ActivityTimeline.js +2 -0
- package/dist/assethandler/components/ActivityTimeline.js.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.d.ts.map +1 -1
- package/dist/assethandler/components/AttachmentsPanel.js.map +1 -1
- package/dist/types/assistant.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"names":[],"mappings":"AA2BA;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+ED,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,qBAAqB,+
|
|
1
|
+
{"version":3,"file":"ActivityTimeline.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"names":[],"mappings":"AA2BA;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+ED,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,qBAAqB,+BA4IjF"}
|
|
@@ -102,6 +102,8 @@ function k({ resourceId: i, limit: a = 20 }) {
|
|
|
102
102
|
let { workspaceId: o, currentUser: s } = e(), { data: c, loading: l } = g(w, {
|
|
103
103
|
variables: { filter: {
|
|
104
104
|
resourceId: i,
|
|
105
|
+
resourceType: "asset",
|
|
106
|
+
productId: "assethandler",
|
|
105
107
|
...o ? { workspaceId: o } : {},
|
|
106
108
|
limit: a
|
|
107
109
|
} },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityTimeline.js","names":[],"sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Activity,\n Plus,\n Pencil,\n Trash2,\n CheckCircle2,\n LogOut,\n LogIn,\n RefreshCw,\n Paperclip,\n MessageSquare,\n Wrench,\n ShieldAlert,\n} from 'lucide-react';\nimport { useAssetHandler } from '../providers/AssetHandlerProvider';\nimport { Sparkline } from './charts';\n\n/**\n * Recent activity for a single AssetHandler resource, sourced from the\n * federated wspace-activity-svc `getActivities` query (fed by be-sdk\n * `trackOperation` on the backend). Read-only, with a graceful empty state.\n *\n * The header carries an activity-volume sparkline bucketed from the real\n * timestamps — it only renders when there is enough history to be meaningful,\n * never fabricated.\n */\nexport interface ActivityTimelineProps {\n resourceId: string;\n /** Optional cap on rows shown. Defaults to 20. */\n limit?: number;\n}\n\ninterface ActivityLogNode {\n id: string;\n action: string;\n description?: string | null;\n timestamp: string;\n userId: string;\n resourceType?: string | null;\n}\n\ninterface GetActivitiesResult {\n getActivities: ActivityLogNode[];\n}\n\nconst GET_ACTIVITIES_QUERY = gql`\n query AssetHandlerResourceActivities($filter: ActivityLogFilter) {\n getActivities(filter: $filter) {\n id\n action\n description\n timestamp\n userId\n resourceType\n }\n }\n`;\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype Tone = 'success' | 'info' | 'warning' | 'error' | 'neutral';\n\nconst TONE_CLASS: Record<Tone, { icon: string; tile: string }> = {\n success: { icon: 'text-status-success-text', tile: 'bg-status-success-bg-subtle' },\n info: { icon: 'text-status-info-text', tile: 'bg-status-info-bg-subtle' },\n warning: { icon: 'text-status-warning-text', tile: 'bg-status-warning-bg-subtle' },\n error: { icon: 'text-status-error-text', tile: 'bg-status-error-bg-subtle' },\n neutral: { icon: 'text-text-secondary', tile: 'bg-bg-sunken' },\n};\n\n/** Map an activity action verb to a node icon + status tone. */\nfunction actionVisual(action: string): { Icon: typeof Activity; tone: Tone } {\n const a = action.toLowerCase();\n if (/(complete|resolve|close|done|approve|pass|success)/.test(a))\n return { Icon: CheckCircle2, tone: 'success' };\n if (/(delete|remove|dispose|retire|archive|reject|fail)/.test(a))\n return { Icon: Trash2, tone: 'error' };\n if (/(check[_.\\s]?out|assign|issue|lend)/.test(a)) return { Icon: LogOut, tone: 'warning' };\n if (/(check[_.\\s]?in|return|receive)/.test(a)) return { Icon: LogIn, tone: 'success' };\n if (/(maintenance|repair|service|calibrat)/.test(a)) return { Icon: Wrench, tone: 'warning' };\n if (/(incident|breach|alert|violation|risk|security)/.test(a))\n return { Icon: ShieldAlert, tone: 'error' };\n if (/(upload|attach|file)/.test(a)) return { Icon: Paperclip, tone: 'info' };\n if (/(comment|note|reply|discuss)/.test(a)) return { Icon: MessageSquare, tone: 'info' };\n if (/(create|add|new|register|provision)/.test(a)) return { Icon: Plus, tone: 'success' };\n if (/(update|edit|change|modify|revise|status)/.test(a)) return { Icon: Pencil, tone: 'info' };\n if (/(sync|refresh|move|transfer|relocat)/.test(a)) return { Icon: RefreshCw, tone: 'info' };\n return { Icon: Activity, tone: 'neutral' };\n}\n\nfunction titleize(action: string): string {\n const s = action.replace(/[._]/g, ' ').trim();\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nexport function ActivityTimeline({ resourceId, limit = 20 }: ActivityTimelineProps) {\n const { workspaceId, currentUser } = useAssetHandler();\n\n const { data, loading } = useQuery<GetActivitiesResult>(GET_ACTIVITIES_QUERY, {\n variables: {\n filter: {\n resourceId,\n ...(workspaceId ? { workspaceId } : {}),\n limit,\n },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n skip: !resourceId,\n });\n\n const activities = useMemo(() => data?.getActivities ?? [], [data]);\n\n // Bucket real timestamps into per-day volume for a header sparkline. Only\n // meaningful when there is enough history spanning multiple days — otherwise\n // we omit it rather than draw a flat/fabricated line.\n const volumeSeries = useMemo(() => {\n if (activities.length < 6) return null;\n const DAYS = 14;\n const now = new Date();\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const buckets = new Array<number>(DAYS).fill(0);\n let distinctDays = 0;\n const seen = new Set<number>();\n for (const item of activities) {\n const t = new Date(item.timestamp).getTime();\n if (Number.isNaN(t)) continue;\n const dayIndex = DAYS - 1 - Math.floor((startOfToday - t) / 86_400_000);\n if (dayIndex >= 0 && dayIndex < DAYS) {\n buckets[dayIndex] += 1;\n const dayKey = Math.floor(t / 86_400_000);\n if (!seen.has(dayKey)) {\n seen.add(dayKey);\n distinctDays += 1;\n }\n }\n }\n return distinctDays >= 3 ? buckets : null;\n }, [activities]);\n\n const actorLabel = (userId: string): string =>\n currentUser?.id && userId === currentUser.id\n ? currentUser.name ||\n [currentUser.firstName, currentUser.lastName].filter(Boolean).join(' ') ||\n currentUser.username ||\n currentUser.email?.split('@')[0] ||\n 'You'\n : userId === 'system' || userId === 'SYSTEM'\n ? 'System'\n : `Member ${userId.slice(0, 6)}`;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Activity className=\"h-4 w-4\" />\n Activity\n </CardTitle>\n {volumeSeries ? (\n <div className=\"flex items-center gap-2\" title=\"Activity over the last 14 days\">\n <span className=\"hidden text-xs text-text-secondary sm:inline\">Last 14 days</span>\n <Sparkline\n data={volumeSeries}\n type=\"area\"\n height={28}\n className=\"w-24\"\n ariaLabel=\"Activity volume over the last 14 days\"\n />\n </div>\n ) : null}\n </CardHeader>\n <CardContent>\n {loading && activities.length === 0 ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div\n key={i}\n className=\"h-12 rounded-md bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : activities.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"No activity yet\"\n description=\"Changes to this record — updates, status changes and assignments — will show up here.\"\n />\n ) : (\n <ol className=\"space-y-1\">\n {activities.map((item, index) => {\n const { Icon, tone } = actionVisual(item.action);\n const tc = TONE_CLASS[tone];\n const isLast = index === activities.length - 1;\n return (\n <li key={item.id} className=\"relative flex gap-3 pb-3\">\n {/* Connector line between nodes. */}\n {!isLast ? (\n <span\n className=\"absolute left-[0.9375rem] top-8 h-[calc(100%-1rem)] w-px bg-border-subtle\"\n aria-hidden=\"true\"\n />\n ) : null}\n <span\n className={`relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${tc.tile}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-4 w-4 ${tc.icon}`} />\n </span>\n <div className=\"min-w-0 flex-1 pt-0.5\">\n <p className=\"text-sm font-medium text-text-primary\">{titleize(item.action)}</p>\n {item.description ? (\n <p className=\"truncate text-sm text-text-secondary\" title={item.description}>\n {item.description}\n </p>\n ) : null}\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n <span className=\"font-medium text-text-secondary\">\n {actorLabel(item.userId)}\n </span>{' '}\n ·{' '}\n <span title={new Date(item.timestamp).toLocaleString()}>\n {relativeTime(item.timestamp)}\n </span>\n </p>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActivityTimeline.js","names":[],"sources":["../../../src/assethandler/components/ActivityTimeline.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Activity,\n Plus,\n Pencil,\n Trash2,\n CheckCircle2,\n LogOut,\n LogIn,\n RefreshCw,\n Paperclip,\n MessageSquare,\n Wrench,\n ShieldAlert,\n} from 'lucide-react';\nimport { useAssetHandler } from '../providers/AssetHandlerProvider';\nimport { Sparkline } from './charts';\n\n/**\n * Recent activity for a single AssetHandler resource, sourced from the\n * federated wspace-activity-svc `getActivities` query (fed by be-sdk\n * `trackOperation` on the backend). Read-only, with a graceful empty state.\n *\n * The header carries an activity-volume sparkline bucketed from the real\n * timestamps — it only renders when there is enough history to be meaningful,\n * never fabricated.\n */\nexport interface ActivityTimelineProps {\n resourceId: string;\n /** Optional cap on rows shown. Defaults to 20. */\n limit?: number;\n}\n\ninterface ActivityLogNode {\n id: string;\n action: string;\n description?: string | null;\n timestamp: string;\n userId: string;\n resourceType?: string | null;\n}\n\ninterface GetActivitiesResult {\n getActivities: ActivityLogNode[];\n}\n\nconst GET_ACTIVITIES_QUERY = gql`\n query AssetHandlerResourceActivities($filter: ActivityLogFilter) {\n getActivities(filter: $filter) {\n id\n action\n description\n timestamp\n userId\n resourceType\n }\n }\n`;\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype Tone = 'success' | 'info' | 'warning' | 'error' | 'neutral';\n\nconst TONE_CLASS: Record<Tone, { icon: string; tile: string }> = {\n success: { icon: 'text-status-success-text', tile: 'bg-status-success-bg-subtle' },\n info: { icon: 'text-status-info-text', tile: 'bg-status-info-bg-subtle' },\n warning: { icon: 'text-status-warning-text', tile: 'bg-status-warning-bg-subtle' },\n error: { icon: 'text-status-error-text', tile: 'bg-status-error-bg-subtle' },\n neutral: { icon: 'text-text-secondary', tile: 'bg-bg-sunken' },\n};\n\n/** Map an activity action verb to a node icon + status tone. */\nfunction actionVisual(action: string): { Icon: typeof Activity; tone: Tone } {\n const a = action.toLowerCase();\n if (/(complete|resolve|close|done|approve|pass|success)/.test(a))\n return { Icon: CheckCircle2, tone: 'success' };\n if (/(delete|remove|dispose|retire|archive|reject|fail)/.test(a))\n return { Icon: Trash2, tone: 'error' };\n if (/(check[_.\\s]?out|assign|issue|lend)/.test(a)) return { Icon: LogOut, tone: 'warning' };\n if (/(check[_.\\s]?in|return|receive)/.test(a)) return { Icon: LogIn, tone: 'success' };\n if (/(maintenance|repair|service|calibrat)/.test(a)) return { Icon: Wrench, tone: 'warning' };\n if (/(incident|breach|alert|violation|risk|security)/.test(a))\n return { Icon: ShieldAlert, tone: 'error' };\n if (/(upload|attach|file)/.test(a)) return { Icon: Paperclip, tone: 'info' };\n if (/(comment|note|reply|discuss)/.test(a)) return { Icon: MessageSquare, tone: 'info' };\n if (/(create|add|new|register|provision)/.test(a)) return { Icon: Plus, tone: 'success' };\n if (/(update|edit|change|modify|revise|status)/.test(a)) return { Icon: Pencil, tone: 'info' };\n if (/(sync|refresh|move|transfer|relocat)/.test(a)) return { Icon: RefreshCw, tone: 'info' };\n return { Icon: Activity, tone: 'neutral' };\n}\n\nfunction titleize(action: string): string {\n const s = action.replace(/[._]/g, ' ').trim();\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nexport function ActivityTimeline({ resourceId, limit = 20 }: ActivityTimelineProps) {\n const { workspaceId, currentUser } = useAssetHandler();\n\n const { data, loading } = useQuery<GetActivitiesResult>(GET_ACTIVITIES_QUERY, {\n variables: {\n filter: {\n resourceId,\n resourceType: 'asset',\n productId: 'assethandler',\n ...(workspaceId ? { workspaceId } : {}),\n limit,\n },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n skip: !resourceId,\n });\n\n const activities = useMemo(() => data?.getActivities ?? [], [data]);\n\n // Bucket real timestamps into per-day volume for a header sparkline. Only\n // meaningful when there is enough history spanning multiple days — otherwise\n // we omit it rather than draw a flat/fabricated line.\n const volumeSeries = useMemo(() => {\n if (activities.length < 6) return null;\n const DAYS = 14;\n const now = new Date();\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();\n const buckets = new Array<number>(DAYS).fill(0);\n let distinctDays = 0;\n const seen = new Set<number>();\n for (const item of activities) {\n const t = new Date(item.timestamp).getTime();\n if (Number.isNaN(t)) continue;\n const dayIndex = DAYS - 1 - Math.floor((startOfToday - t) / 86_400_000);\n if (dayIndex >= 0 && dayIndex < DAYS) {\n buckets[dayIndex] += 1;\n const dayKey = Math.floor(t / 86_400_000);\n if (!seen.has(dayKey)) {\n seen.add(dayKey);\n distinctDays += 1;\n }\n }\n }\n return distinctDays >= 3 ? buckets : null;\n }, [activities]);\n\n const actorLabel = (userId: string): string =>\n currentUser?.id && userId === currentUser.id\n ? currentUser.name ||\n [currentUser.firstName, currentUser.lastName].filter(Boolean).join(' ') ||\n currentUser.username ||\n currentUser.email?.split('@')[0] ||\n 'You'\n : userId === 'system' || userId === 'SYSTEM'\n ? 'System'\n : `Member ${userId.slice(0, 6)}`;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Activity className=\"h-4 w-4\" />\n Activity\n </CardTitle>\n {volumeSeries ? (\n <div className=\"flex items-center gap-2\" title=\"Activity over the last 14 days\">\n <span className=\"hidden text-xs text-text-secondary sm:inline\">Last 14 days</span>\n <Sparkline\n data={volumeSeries}\n type=\"area\"\n height={28}\n className=\"w-24\"\n ariaLabel=\"Activity volume over the last 14 days\"\n />\n </div>\n ) : null}\n </CardHeader>\n <CardContent>\n {loading && activities.length === 0 ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div\n key={i}\n className=\"h-12 rounded-md bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : activities.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"No activity yet\"\n description=\"Changes to this record — updates, status changes and assignments — will show up here.\"\n />\n ) : (\n <ol className=\"space-y-1\">\n {activities.map((item, index) => {\n const { Icon, tone } = actionVisual(item.action);\n const tc = TONE_CLASS[tone];\n const isLast = index === activities.length - 1;\n return (\n <li key={item.id} className=\"relative flex gap-3 pb-3\">\n {/* Connector line between nodes. */}\n {!isLast ? (\n <span\n className=\"absolute left-[0.9375rem] top-8 h-[calc(100%-1rem)] w-px bg-border-subtle\"\n aria-hidden=\"true\"\n />\n ) : null}\n <span\n className={`relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${tc.tile}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-4 w-4 ${tc.icon}`} />\n </span>\n <div className=\"min-w-0 flex-1 pt-0.5\">\n <p className=\"text-sm font-medium text-text-primary\">{titleize(item.action)}</p>\n {item.description ? (\n <p className=\"truncate text-sm text-text-secondary\" title={item.description}>\n {item.description}\n </p>\n ) : null}\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n <span className=\"font-medium text-text-secondary\">\n {actorLabel(item.userId)}\n </span>{' '}\n ·{' '}\n <span title={new Date(item.timestamp).toLocaleString()}>\n {relativeTime(item.timestamp)}\n </span>\n </p>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttachmentsPanel.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"names":[],"mappings":"AAiCA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"AttachmentsPanel.d.ts","sourceRoot":"","sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"names":[],"mappings":"AAiCA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAmND,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,qBAAqB,+BAmP/E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttachmentsPanel.js","names":[],"sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Paperclip,\n Upload,\n Link2,\n FileText,\n FileSpreadsheet,\n FileArchive,\n FileCode2,\n FileVideo,\n FileAudio,\n Image as ImageIcon,\n File as FileIcon,\n Loader2,\n Download,\n MoreHorizontal,\n} from 'lucide-react';\nimport { notify } from '../utils/toast';\n\n/**\n * Attach and reference files for an AssetHandler entity, backed by the\n * federated wspace-files-svc (same wspace gateway/Apollo client). Fully real (no fabricated data) —\n * lists workspace files via `getFiles`, uploads new files through the\n * `initiateUpload` → presigned PUT → `completeUpload` flow, and mints public\n * share links via `createFileShareLink`. All entries are live data.\n *\n * `entityType`/`entityId` scope the panel to the record it is mounted on and\n * are used for context labelling; uploaded files land in the workspace file\n * store so they can be shared and reused.\n */\nexport interface AttachmentsPanelProps {\n entityType: string;\n entityId: string;\n}\n\ninterface FileNode {\n id: string;\n name: string;\n extension: string;\n mimeType: string;\n size: number;\n downloadUrl?: string | null;\n createdAt: string;\n}\n\ninterface GetFilesResult {\n getFiles: {\n files: FileNode[];\n total: number;\n hasMore: boolean;\n };\n}\n\nconst GET_FILES_QUERY = gql`\n query AssetHandlerGetFiles($pagination: PaginationInput, $sort: FileSortInput) {\n getFiles(pagination: $pagination, sort: $sort) {\n total\n hasMore\n files {\n id\n name\n extension\n mimeType\n size\n downloadUrl\n createdAt\n }\n }\n }\n`;\n\nconst INITIATE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session {\n id\n }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n name\n }\n }\n }\n`;\n\nconst CREATE_SHARE_LINK_MUTATION = gql`\n mutation AssetHandlerCreateFileShareLink($input: CreateShareGrantLinkInput!) {\n createFileShareLink(input: $input) {\n id\n shareUrl\n }\n }\n`;\n\nfunction formatSize(bytes: number): string {\n if (!bytes) return '0 B';\n const units = ['B', 'KB', 'MB', 'GB'];\n const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);\n const value = bytes / Math.pow(1024, exponent);\n return `${value.toFixed(exponent === 0 ? 0 : 1)} ${units[exponent]}`;\n}\n\nfunction extensionOf(fileName: string): string {\n const dot = fileName.lastIndexOf('.');\n return dot >= 0 ? fileName.slice(dot + 1).toLowerCase() : 'bin';\n}\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype FileKind =\n | 'image'\n | 'pdf'\n | 'sheet'\n | 'doc'\n | 'archive'\n | 'code'\n | 'video'\n | 'audio'\n | 'other';\n\n/** Map a file's extension/mime to a recognisable kind for icon + tint. */\nfunction kindOf(file: FileNode): FileKind {\n const mime = (file.mimeType || '').toLowerCase();\n const ext = (file.extension || extensionOf(file.name)).toLowerCase();\n if (\n mime.startsWith('image/') ||\n ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext)\n )\n return 'image';\n if (mime.startsWith('video/') || ['mp4', 'mov', 'webm', 'avi', 'mkv'].includes(ext))\n return 'video';\n if (mime.startsWith('audio/') || ['mp3', 'wav', 'ogg', 'm4a', 'flac'].includes(ext))\n return 'audio';\n if (mime === 'application/pdf' || ext === 'pdf') return 'pdf';\n if (['xls', 'xlsx', 'csv', 'tsv', 'ods'].includes(ext)) return 'sheet';\n if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(ext)) return 'archive';\n if (\n [\n 'js',\n 'ts',\n 'tsx',\n 'jsx',\n 'json',\n 'xml',\n 'yml',\n 'yaml',\n 'html',\n 'css',\n 'sh',\n 'py',\n 'sql',\n ].includes(ext)\n )\n return 'code';\n if (['doc', 'docx', 'txt', 'md', 'rtf', 'odt', 'pages'].includes(ext)) return 'doc';\n return 'other';\n}\n\nconst KIND_STYLE: Record<\n FileKind,\n { Icon: typeof FileText; iconClass: string; tileClass: string }\n> = {\n image: {\n Icon: ImageIcon,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n pdf: {\n Icon: FileText,\n iconClass: 'text-status-error-text',\n tileClass: 'bg-status-error-bg-subtle',\n },\n sheet: {\n Icon: FileSpreadsheet,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n doc: {\n Icon: FileText,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n archive: {\n Icon: FileArchive,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n code: {\n Icon: FileCode2,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n video: {\n Icon: FileVideo,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n audio: {\n Icon: FileAudio,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n other: { Icon: FileIcon, iconClass: 'text-text-secondary', tileClass: 'bg-bg-sunken' },\n};\n\n/** File-type badge tile — an inline thumbnail for images, a tinted glyph otherwise. */\nfunction FileBadge({ file }: { file: FileNode }) {\n const kind = kindOf(file);\n const { Icon, iconClass, tileClass } = KIND_STYLE[kind];\n if (kind === 'image' && file.downloadUrl) {\n return (\n <img\n src={file.downloadUrl}\n alt=\"\"\n loading=\"lazy\"\n className=\"h-10 w-10 shrink-0 rounded-lg border border-border-subtle object-cover\"\n />\n );\n }\n return (\n <span\n className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${tileClass}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-5 w-5 ${iconClass}`} />\n </span>\n );\n}\n\nexport function AttachmentsPanel({ entityType, entityId }: AttachmentsPanelProps) {\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [uploading, setUploading] = useState(false);\n const [sharingId, setSharingId] = useState<string | null>(null);\n\n const { data, loading, refetch } = useQuery<GetFilesResult>(GET_FILES_QUERY, {\n variables: {\n pagination: { limit: 25, offset: 0 },\n sort: { field: 'CREATED_AT', order: 'DESC' },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n });\n\n const [initiateUpload] = useMutation<{\n initiateUpload: { uploadUrl?: string | null; session: { id: string } };\n }>(INITIATE_UPLOAD_MUTATION);\n const [completeUpload] = useMutation(COMPLETE_UPLOAD_MUTATION);\n const [createShareLink] = useMutation<{\n createFileShareLink: { id: string; shareUrl?: string | null };\n }>(CREATE_SHARE_LINK_MUTATION);\n\n const files = useMemo(() => data?.getFiles.files ?? [], [data]);\n\n const handleFileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) return;\n\n setUploading(true);\n try {\n const initiated = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n extension: extensionOf(file.name),\n fileSize: file.size,\n mimeType: file.type || 'application/octet-stream',\n visibility: 'WORKSPACE',\n },\n },\n });\n\n const uploadUrl = initiated.data?.initiateUpload.uploadUrl;\n const sessionId = initiated.data?.initiateUpload.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('Upload could not be initialised.');\n }\n\n const putResponse = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n });\n if (!putResponse.ok) {\n throw new Error(`Storage rejected the upload (HTTP ${putResponse.status}).`);\n }\n\n await completeUpload({\n variables: { input: { sessionId, fileSize: file.size } },\n });\n\n notify.success('File uploaded', `${file.name} is now available on this record.`);\n await refetch();\n } catch (error) {\n notify.error(\n 'Upload failed',\n error instanceof Error ? error.message : 'The file could not be uploaded.'\n );\n } finally {\n setUploading(false);\n }\n };\n\n const handleShare = async (fileId: string) => {\n setSharingId(fileId);\n try {\n const result = await createShareLink({\n variables: { input: { resourceId: fileId, role: 'VIEWER' } },\n });\n const url = result.data?.createFileShareLink.shareUrl;\n if (!url) {\n throw new Error('No share link was returned.');\n }\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(url);\n notify.success('Share link copied', 'A view-only link is on your clipboard.');\n } else {\n notify.info('Share link ready', url);\n }\n } catch (error) {\n notify.error(\n 'Could not create share link',\n error instanceof Error ? error.message : 'Please try again.'\n );\n } finally {\n setSharingId(null);\n }\n };\n\n const total = data?.getFiles.total ?? 0;\n const hasFiles = files.length > 0;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-2 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Paperclip className=\"h-4 w-4\" />\n Attachments\n {total ? (\n <span className=\"text-sm font-normal text-text-secondary\">\n {total} file{total === 1 ? '' : 's'}\n </span>\n ) : null}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <input\n ref={fileInputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(event) => void handleFileSelected(event)}\n aria-label={`Upload a file for ${entityType} ${entityId}`}\n />\n\n {loading && !hasFiles ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div\n key={i}\n className=\"h-14 rounded-lg bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : !hasFiles ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No files yet\"\n description=\"Upload photos, receipts, warranties or documents to keep them with this record.\"\n />\n ) : (\n <ul className=\"space-y-1.5\">\n {files.map((file) => {\n const isSharing = sharingId === file.id;\n return (\n <li\n key={file.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-2.5\"\n >\n <FileBadge file={file} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\" title={file.name}>\n {file.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatSize(Number(file.size))} · {relativeTime(file.createdAt)}\n </p>\n </div>\n {/* Desktop: inline actions. Mobile: kebab menu to avoid crowding. */}\n <div className=\"hidden items-center gap-1 sm:flex\">\n {file.downloadUrl ? (\n <a\n href={file.downloadUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Download ${file.name}`}\n title=\"Download\"\n >\n <Download className=\"h-4 w-4\" />\n </a>\n ) : null}\n <button\n type=\"button\"\n disabled={isSharing}\n onClick={() => void handleShare(file.id)}\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60\"\n aria-label={`Copy share link for ${file.name}`}\n title=\"Copy share link\"\n >\n {isSharing ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Link2 className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n <div className=\"sm:hidden\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Actions for ${file.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {file.downloadUrl ? (\n <DropdownMenuItem asChild>\n <a href={file.downloadUrl} target=\"_blank\" rel=\"noreferrer\">\n <Download className=\"mr-2 h-4 w-4\" />\n Download\n </a>\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuItem\n disabled={isSharing}\n onSelect={(e) => {\n e.preventDefault();\n void handleShare(file.id);\n }}\n >\n <Link2 className=\"mr-2 h-4 w-4\" />\n Copy share link\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n {/* Prominent full-width primary upload CTA (matches the reference concept). */}\n <Button\n className=\"w-full\"\n disabled={uploading}\n onClick={() => fileInputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <Upload className=\"mr-2 h-4 w-4\" />\n )}\n {uploading ? 'Uploading…' : 'Upload files'}\n </Button>\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AttachmentsPanel.js","names":[],"sources":["../../../src/assethandler/components/AttachmentsPanel.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n IllustratedEmptyState,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Paperclip,\n Upload,\n Link2,\n FileText,\n FileSpreadsheet,\n FileArchive,\n FileCode2,\n FileVideo,\n FileAudio,\n Image as ImageIcon,\n File as FileIcon,\n Loader2,\n Download,\n MoreHorizontal,\n} from 'lucide-react';\nimport { notify } from '../utils/toast';\n\n/**\n * Attach and reference files for an AssetHandler entity, backed by the\n * federated wspace-files-svc (same wspace gateway/Apollo client). Fully real (no fabricated data) —\n * lists workspace files via `getFiles`, uploads new files through the\n * `initiateUpload` → presigned PUT → `completeUpload` flow, and mints public\n * share links via `createFileShareLink`. All entries are live data.\n *\n * `entityType`/`entityId` scope the panel to the record it is mounted on and\n * are used for context labelling; uploaded files land in the workspace file\n * store so they can be shared and reused.\n */\nexport interface AttachmentsPanelProps {\n entityType: string;\n entityId: string;\n}\n\ninterface FileNode {\n id: string;\n name: string;\n extension: string;\n mimeType: string;\n size: number;\n downloadUrl?: string | null;\n createdAt: string;\n}\n\ninterface GetFilesResult {\n getFiles: {\n files: FileNode[];\n total: number;\n hasMore: boolean;\n };\n}\n\nconst GET_FILES_QUERY = gql`\n query AssetHandlerGetFiles($pagination: PaginationInput, $sort: FileSortInput) {\n getFiles(pagination: $pagination, sort: $sort) {\n total\n hasMore\n files {\n id\n name\n extension\n mimeType\n size\n downloadUrl\n createdAt\n }\n }\n }\n`;\n\nconst INITIATE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session {\n id\n }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD_MUTATION = gql`\n mutation AssetHandlerCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file {\n id\n name\n }\n }\n }\n`;\n\nconst CREATE_SHARE_LINK_MUTATION = gql`\n mutation AssetHandlerCreateFileShareLink($input: CreateShareGrantLinkInput!) {\n createFileShareLink(input: $input) {\n id\n shareUrl\n }\n }\n`;\n\nfunction formatSize(bytes: number): string {\n if (!bytes) return '0 B';\n const units = ['B', 'KB', 'MB', 'GB'];\n const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);\n const value = bytes / Math.pow(1024, exponent);\n return `${value.toFixed(exponent === 0 ? 0 : 1)} ${units[exponent]}`;\n}\n\nfunction extensionOf(fileName: string): string {\n const dot = fileName.lastIndexOf('.');\n return dot >= 0 ? fileName.slice(dot + 1).toLowerCase() : 'bin';\n}\n\n/** Compact relative time (\"2 hrs ago\"), with the full timestamp on hover. */\nfunction relativeTime(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return '';\n const diff = Date.now() - then;\n const sec = Math.round(diff / 1000);\n if (sec < 45) return 'just now';\n const min = Math.round(sec / 60);\n if (min < 60) return `${min} min ago`;\n const hr = Math.round(min / 60);\n if (hr < 24) return `${hr} hr${hr === 1 ? '' : 's'} ago`;\n const day = Math.round(hr / 24);\n if (day < 30) return `${day} day${day === 1 ? '' : 's'} ago`;\n return new Date(iso).toLocaleDateString();\n}\n\ntype FileKind =\n 'image' | 'pdf' | 'sheet' | 'doc' | 'archive' | 'code' | 'video' | 'audio' | 'other';\n\n/** Map a file's extension/mime to a recognisable kind for icon + tint. */\nfunction kindOf(file: FileNode): FileKind {\n const mime = (file.mimeType || '').toLowerCase();\n const ext = (file.extension || extensionOf(file.name)).toLowerCase();\n if (\n mime.startsWith('image/') ||\n ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext)\n )\n return 'image';\n if (mime.startsWith('video/') || ['mp4', 'mov', 'webm', 'avi', 'mkv'].includes(ext))\n return 'video';\n if (mime.startsWith('audio/') || ['mp3', 'wav', 'ogg', 'm4a', 'flac'].includes(ext))\n return 'audio';\n if (mime === 'application/pdf' || ext === 'pdf') return 'pdf';\n if (['xls', 'xlsx', 'csv', 'tsv', 'ods'].includes(ext)) return 'sheet';\n if (['zip', 'rar', '7z', 'tar', 'gz', 'bz2'].includes(ext)) return 'archive';\n if (\n [\n 'js',\n 'ts',\n 'tsx',\n 'jsx',\n 'json',\n 'xml',\n 'yml',\n 'yaml',\n 'html',\n 'css',\n 'sh',\n 'py',\n 'sql',\n ].includes(ext)\n )\n return 'code';\n if (['doc', 'docx', 'txt', 'md', 'rtf', 'odt', 'pages'].includes(ext)) return 'doc';\n return 'other';\n}\n\nconst KIND_STYLE: Record<\n FileKind,\n { Icon: typeof FileText; iconClass: string; tileClass: string }\n> = {\n image: {\n Icon: ImageIcon,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n pdf: {\n Icon: FileText,\n iconClass: 'text-status-error-text',\n tileClass: 'bg-status-error-bg-subtle',\n },\n sheet: {\n Icon: FileSpreadsheet,\n iconClass: 'text-status-success-text',\n tileClass: 'bg-status-success-bg-subtle',\n },\n doc: {\n Icon: FileText,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n archive: {\n Icon: FileArchive,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n code: {\n Icon: FileCode2,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n video: {\n Icon: FileVideo,\n iconClass: 'text-status-info-text',\n tileClass: 'bg-status-info-bg-subtle',\n },\n audio: {\n Icon: FileAudio,\n iconClass: 'text-status-warning-text',\n tileClass: 'bg-status-warning-bg-subtle',\n },\n other: { Icon: FileIcon, iconClass: 'text-text-secondary', tileClass: 'bg-bg-sunken' },\n};\n\n/** File-type badge tile — an inline thumbnail for images, a tinted glyph otherwise. */\nfunction FileBadge({ file }: { file: FileNode }) {\n const kind = kindOf(file);\n const { Icon, iconClass, tileClass } = KIND_STYLE[kind];\n if (kind === 'image' && file.downloadUrl) {\n return (\n <img\n src={file.downloadUrl}\n alt=\"\"\n loading=\"lazy\"\n className=\"h-10 w-10 shrink-0 rounded-lg border border-border-subtle object-cover\"\n />\n );\n }\n return (\n <span\n className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-lg ${tileClass}`}\n aria-hidden=\"true\"\n >\n <Icon className={`h-5 w-5 ${iconClass}`} />\n </span>\n );\n}\n\nexport function AttachmentsPanel({ entityType, entityId }: AttachmentsPanelProps) {\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [uploading, setUploading] = useState(false);\n const [sharingId, setSharingId] = useState<string | null>(null);\n\n const { data, loading, refetch } = useQuery<GetFilesResult>(GET_FILES_QUERY, {\n variables: {\n pagination: { limit: 25, offset: 0 },\n sort: { field: 'CREATED_AT', order: 'DESC' },\n },\n fetchPolicy: 'cache-and-network',\n errorPolicy: 'all',\n });\n\n const [initiateUpload] = useMutation<{\n initiateUpload: { uploadUrl?: string | null; session: { id: string } };\n }>(INITIATE_UPLOAD_MUTATION);\n const [completeUpload] = useMutation(COMPLETE_UPLOAD_MUTATION);\n const [createShareLink] = useMutation<{\n createFileShareLink: { id: string; shareUrl?: string | null };\n }>(CREATE_SHARE_LINK_MUTATION);\n\n const files = useMemo(() => data?.getFiles.files ?? [], [data]);\n\n const handleFileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Reset the input so selecting the same file again re-triggers change.\n event.target.value = '';\n if (!file) return;\n\n setUploading(true);\n try {\n const initiated = await initiateUpload({\n variables: {\n input: {\n fileName: file.name,\n extension: extensionOf(file.name),\n fileSize: file.size,\n mimeType: file.type || 'application/octet-stream',\n visibility: 'WORKSPACE',\n },\n },\n });\n\n const uploadUrl = initiated.data?.initiateUpload.uploadUrl;\n const sessionId = initiated.data?.initiateUpload.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('Upload could not be initialised.');\n }\n\n const putResponse = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n });\n if (!putResponse.ok) {\n throw new Error(`Storage rejected the upload (HTTP ${putResponse.status}).`);\n }\n\n await completeUpload({\n variables: { input: { sessionId, fileSize: file.size } },\n });\n\n notify.success('File uploaded', `${file.name} is now available on this record.`);\n await refetch();\n } catch (error) {\n notify.error(\n 'Upload failed',\n error instanceof Error ? error.message : 'The file could not be uploaded.'\n );\n } finally {\n setUploading(false);\n }\n };\n\n const handleShare = async (fileId: string) => {\n setSharingId(fileId);\n try {\n const result = await createShareLink({\n variables: { input: { resourceId: fileId, role: 'VIEWER' } },\n });\n const url = result.data?.createFileShareLink.shareUrl;\n if (!url) {\n throw new Error('No share link was returned.');\n }\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(url);\n notify.success('Share link copied', 'A view-only link is on your clipboard.');\n } else {\n notify.info('Share link ready', url);\n }\n } catch (error) {\n notify.error(\n 'Could not create share link',\n error instanceof Error ? error.message : 'Please try again.'\n );\n } finally {\n setSharingId(null);\n }\n };\n\n const total = data?.getFiles.total ?? 0;\n const hasFiles = files.length > 0;\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-2 space-y-0\">\n <CardTitle className=\"flex items-center gap-2 text-base\">\n <Paperclip className=\"h-4 w-4\" />\n Attachments\n {total ? (\n <span className=\"text-sm font-normal text-text-secondary\">\n {total} file{total === 1 ? '' : 's'}\n </span>\n ) : null}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <input\n ref={fileInputRef}\n type=\"file\"\n className=\"hidden\"\n onChange={(event) => void handleFileSelected(event)}\n aria-label={`Upload a file for ${entityType} ${entityId}`}\n />\n\n {loading && !hasFiles ? (\n <div className=\"space-y-2\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div\n key={i}\n className=\"h-14 rounded-lg bg-bg-sunken motion-safe:animate-vc-shimmer\"\n />\n ))}\n </div>\n ) : !hasFiles ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No files yet\"\n description=\"Upload photos, receipts, warranties or documents to keep them with this record.\"\n />\n ) : (\n <ul className=\"space-y-1.5\">\n {files.map((file) => {\n const isSharing = sharingId === file.id;\n return (\n <li\n key={file.id}\n className=\"flex items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-2.5\"\n >\n <FileBadge file={file} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\" title={file.name}>\n {file.name}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {formatSize(Number(file.size))} · {relativeTime(file.createdAt)}\n </p>\n </div>\n {/* Desktop: inline actions. Mobile: kebab menu to avoid crowding. */}\n <div className=\"hidden items-center gap-1 sm:flex\">\n {file.downloadUrl ? (\n <a\n href={file.downloadUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Download ${file.name}`}\n title=\"Download\"\n >\n <Download className=\"h-4 w-4\" />\n </a>\n ) : null}\n <button\n type=\"button\"\n disabled={isSharing}\n onClick={() => void handleShare(file.id)}\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60\"\n aria-label={`Copy share link for ${file.name}`}\n title=\"Copy share link\"\n >\n {isSharing ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Link2 className=\"h-4 w-4\" />\n )}\n </button>\n </div>\n <div className=\"sm:hidden\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={`Actions for ${file.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {file.downloadUrl ? (\n <DropdownMenuItem asChild>\n <a href={file.downloadUrl} target=\"_blank\" rel=\"noreferrer\">\n <Download className=\"mr-2 h-4 w-4\" />\n Download\n </a>\n </DropdownMenuItem>\n ) : null}\n <DropdownMenuItem\n disabled={isSharing}\n onSelect={(e) => {\n e.preventDefault();\n void handleShare(file.id);\n }}\n >\n <Link2 className=\"mr-2 h-4 w-4\" />\n Copy share link\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n\n {/* Prominent full-width primary upload CTA (matches the reference concept). */}\n <Button\n className=\"w-full\"\n disabled={uploading}\n onClick={() => fileInputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <Upload className=\"mr-2 h-4 w-4\" />\n )}\n {uploading ? 'Uploading…' : 'Upload files'}\n </Button>\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../src/types/assistant.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAElG,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAEhG,MAAM,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../src/types/assistant.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAElG,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,CAAC;AAEhG,MAAM,MAAM,2BAA2B,GACrC,MAAM,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AAE9E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAChC,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,QAAQ,CAAC,EAAE,wBAAwB,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,sBAAsB,CAAC;CAChC;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,2BAA2B,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,aAAa,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,eAAe,EAAE,mBAAmB,EAAkC,CAAC"}
|