From e4f0424995a1bb97139bd63e1f6417908f0dfee8 Mon Sep 17 00:00:00 2001 From: snowrugar-beep Date: Sat, 30 May 2026 19:57:53 +0000 Subject: [PATCH] feat: implement CSV export for asset list Closes #781 --- frontend/app/(dashboard)/assets/page.tsx | 12 ++-- .../features/assets/ExportAssetsButton.tsx | 65 +++++++++++++++++++ frontend/package-lock.json | 2 +- frontend/package.json | 2 +- 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 frontend/opsce/features/assets/ExportAssetsButton.tsx diff --git a/frontend/app/(dashboard)/assets/page.tsx b/frontend/app/(dashboard)/assets/page.tsx index 3209bdda..9f396496 100644 --- a/frontend/app/(dashboard)/assets/page.tsx +++ b/frontend/app/(dashboard)/assets/page.tsx @@ -9,6 +9,7 @@ import { ConditionBadge } from "@/components/assets/condition-badge"; import { CreateAssetModal } from "@/components/assets/create-asset-modal"; import { useAssets } from "@/lib/query/hooks/useAssets"; import { AssetStatus, AssetCondition } from "@/lib/query/types/asset"; +import { ExportAssetsButton } from "@/opsce/features/assets/ExportAssetsButton"; const STATUS_OPTIONS = ["All", ...Object.values(AssetStatus)]; @@ -42,10 +43,13 @@ export default function AssetsPage() { : "No assets yet"}

- +
+ + +
{/* Filters */} diff --git a/frontend/opsce/features/assets/ExportAssetsButton.tsx b/frontend/opsce/features/assets/ExportAssetsButton.tsx new file mode 100644 index 00000000..6ab49de6 --- /dev/null +++ b/frontend/opsce/features/assets/ExportAssetsButton.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { Download } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { toast } from '@/components/ui/toast'; +import { api } from '@/lib/api'; + +interface ExportAssetsButtonProps { + filters?: Record; +} + +export function ExportAssetsButton({ filters = {} }: ExportAssetsButtonProps) { + const [loading, setLoading] = useState(false); + + const handleExport = useCallback(async () => { + setLoading(true); + try { + // Build query params from active filters + const params = new URLSearchParams(); + params.set('format', 'csv'); + Object.entries(filters).forEach(([key, value]) => { + if (value) params.set(key, value); + }); + + const response = await api.get(`/assets/export?${params.toString()}`, { + responseType: 'blob', + timeout: 30000, // 30 second timeout for large exports + }); + + const blob = new Blob([response.data], { type: 'text/csv' }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `assets_export_${new Date().toISOString().split('T')[0]}.csv`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + toast.success('Assets exported as CSV successfully'); + } catch (err: unknown) { + console.error('Export failed:', err); + const errorMessage = + (err as { response?: { status?: number } })?.response?.status === 403 + ? 'You do not have permission to export assets.' + : 'Failed to export assets. Please try again.'; + toast.error(errorMessage); + } finally { + setLoading(false); + } + }, [filters]); + + return ( + + ); +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2a376a59..b54b81d7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -51,7 +51,7 @@ "postcss": "^8.5.6", "tailwindcss": "^4.1.18", "ts-jest": "^29.4.4", - "typescript": "^5" + "typescript": "^5.9.3" } }, "node_modules/@alloc/quick-lru": { diff --git a/frontend/package.json b/frontend/package.json index 71a9245b..22427d1a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -53,6 +53,6 @@ "postcss": "^8.5.6", "tailwindcss": "^4.1.18", "ts-jest": "^29.4.4", - "typescript": "^5" + "typescript": "^5.9.3" } }