diff --git a/.standalone.pid b/.standalone.pid index de831c97..aa7bda71 100644 --- a/.standalone.pid +++ b/.standalone.pid @@ -1,3 +1,3 @@ -831167 -831168 -831182 +831457 +831458 +831472 diff --git a/backend/routers/admin/exports.py b/backend/routers/admin/exports.py index 7a9040c6..6139188e 100644 --- a/backend/routers/admin/exports.py +++ b/backend/routers/admin/exports.py @@ -5,7 +5,7 @@ Supports CSV and Excel formats. from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query -from fastapi.responses import FileResponse +from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session from backend.database import get_db @@ -70,20 +70,16 @@ async def export_inventory_snapshot( # Return file response if format_type == "csv": - # For CSV, use FileResponse with bytes - import io - return FileResponse( + return StreamingResponse( io.BytesIO(content.encode("utf-8")), media_type=media_type, - filename=filename, + headers={"Content-Disposition": f"attachment; filename={filename}"} ) else: - # For Excel, content is already bytes - import io - return FileResponse( + return StreamingResponse( io.BytesIO(content), media_type=media_type, - filename=filename, + headers={"Content-Disposition": f"attachment; filename={filename}"} ) @@ -126,22 +122,20 @@ async def export_audit_trail( # Return file response if format_type == "csv": - import io - return FileResponse( + return StreamingResponse( io.BytesIO(content.encode("utf-8")), media_type=media_type, - filename=filename, + headers={"Content-Disposition": f"attachment; filename={filename}"} ) else: - import io - return FileResponse( + return StreamingResponse( io.BytesIO(content), media_type=media_type, - filename=filename, + headers={"Content-Disposition": f"attachment; filename={filename}"} ) -@router.get("/db/export") +@router.get("/reports/export") async def export_db( format: str = Query("csv", description="Export format: csv or xlsx"), type: str = Query("inventory", description="Export type: inventory, audit, or combined"), @@ -226,14 +220,14 @@ async def export_db( # Return file response if format_type == "csv": - return FileResponse( + return StreamingResponse( io.BytesIO(content.encode("utf-8")), media_type=media_type, - filename=filename, + headers={"Content-Disposition": f"attachment; filename={filename}"} ) else: - return FileResponse( + return StreamingResponse( io.BytesIO(content), media_type=media_type, - filename=filename, + headers={"Content-Disposition": f"attachment; filename={filename}"} ) diff --git a/backups/ainventory_2026-04-23_14-51-52.tar.gz b/backups/ainventory_2026-04-23_14-51-52.tar.gz new file mode 100644 index 00000000..d386182e Binary files /dev/null and b/backups/ainventory_2026-04-23_14-51-52.tar.gz differ diff --git a/frontend/components/admin/ExportPanel.tsx b/frontend/components/admin/ExportPanel.tsx index 25a4892b..279e293c 100644 --- a/frontend/components/admin/ExportPanel.tsx +++ b/frontend/components/admin/ExportPanel.tsx @@ -1,136 +1,131 @@ "use client"; import { useState } from "react"; -import { FileDown, Loader2 } from "lucide-react"; +import { FileDown, Loader2, FileSpreadsheet, FileText } from "lucide-react"; import { useExport } from "@/hooks/useExport"; -import { Toast } from "@/components/Toast"; +import { toast } from "react-hot-toast"; export function ExportPanel() { const { exportSnapshot, exportAuditTrail, isLoading, error } = useExport(); - const [successMessage, setSuccessMessage] = useState(null); const handleExportSnapshot = async (format: "csv" | "xlsx") => { try { - setSuccessMessage(null); await exportSnapshot(format); const formatName = format === "csv" ? "CSV" : "Excel"; - setSuccessMessage(`Inventory snapshot exported as ${formatName}`); - setTimeout(() => setSuccessMessage(null), 4000); + toast.success(`Inventory snapshot exported as ${formatName}`); } catch (err) { - // Error handled by useExport hook + // Error handled by hook } }; const handleExportAuditTrail = async (format: "csv" | "xlsx") => { try { - setSuccessMessage(null); await exportAuditTrail(format); const formatName = format === "csv" ? "CSV" : "Excel"; - setSuccessMessage(`Audit trail exported as ${formatName}`); - setTimeout(() => setSuccessMessage(null), 4000); + toast.success(`Audit trail exported as ${formatName}`); } catch (err) { - // Error handled by useExport hook + // Error handled by hook } }; return ( -
-
-
-
- -
+
+
+
+ +
+
+

Export & Reports

+

+ Download inventory snapshots and audit trails +

+
+
+ +
+ {/* Inventory Snapshot Section */} +
-

Export & Reports

-

- Download inventory snapshots and audit trails in CSV or Excel formats +

Inventory Snapshot

+

+ Export current inventory state with all item details

-
- - {/* Inventory Snapshot Section */} -
-

Inventory Snapshot

-

- Export current inventory state with all item details -

-
+ +
{/* Audit Trail Section */} -
-

Audit Trail

-

- Export complete audit log of all system actions and changes -

-
+
+
+

Audit Trail

+

+ Complete log of system actions and changes +

+
+ +
- {/* Status Messages */} {error && ( - {}} - /> - )} - {successMessage && ( - {}} /> +
+ Error: {error} +
)}
); diff --git a/frontend/hooks/useExport.ts b/frontend/hooks/useExport.ts index 55d1a967..e7c9f523 100644 --- a/frontend/hooks/useExport.ts +++ b/frontend/hooks/useExport.ts @@ -50,7 +50,7 @@ export function useExport(): UseExportReturn { try { const response = await axiosInstance.get( - `/admin/db/export?format=${format}&type=inventory`, + `/admin/reports/export?format=${format}&type=inventory`, { responseType: "blob" } @@ -83,7 +83,7 @@ export function useExport(): UseExportReturn { try { const response = await axiosInstance.get( - `/admin/db/export?format=${format}&type=audit`, + `/admin/reports/export?format=${format}&type=audit`, { responseType: "blob" }