diff --git a/src/components/Dashboard/common/ExportListButton.jsx b/src/components/Dashboard/common/ExportListButton.jsx index c8357ea..f29d60c 100644 --- a/src/components/Dashboard/common/ExportListButton.jsx +++ b/src/components/Dashboard/common/ExportListButton.jsx @@ -1,11 +1,12 @@ import PropTypes from 'prop-types' -import { useState } from 'react' +import { useState, useContext } from 'react' import { Button, Dropdown, Modal } from 'antd' import ExcelIcon from '../../Icons/ExcelIcon' import ODataIcon from '../../Icons/ODataIcon' import CsvIcon from '../../Icons/CsvIcon' import ExportIcon from '../../Icons/ExportIcon' import ODataURL from './ODataURL' +import { ApiServerContext } from '../context/ApiServerContext' const ExportListButton = ({ objectType, @@ -14,16 +15,38 @@ const ExportListButton = ({ ...buttonProps }) => { const [odataModalOpen, setOdataModalOpen] = useState(false) + const [excelLoading, setExcelLoading] = useState(false) + const { exportToExcel } = useContext(ApiServerContext) + + const handleExcelExport = async (mode) => { + setExcelLoading(true) + try { + await exportToExcel(objectType, mode) + } finally { + setExcelLoading(false) + } + } const menuItems = [ { key: 'excel', - label: 'Excel', + label: excelLoading ? 'Exporting...' : 'Excel', icon: , - disabled: true, - onClick: () => { - // TODO: implement Excel export - } + disabled: excelLoading, + children: [ + { + key: 'excel-download', + label: 'Download', + disabled: excelLoading, + onClick: () => handleExcelExport('download') + }, + { + key: 'excel-open', + label: 'Open in Excel', + disabled: excelLoading, + onClick: () => handleExcelExport('open') + } + ] }, { key: 'odata', diff --git a/src/components/Dashboard/context/ApiServerContext.jsx b/src/components/Dashboard/context/ApiServerContext.jsx index b3f89e3..d4d6dd8 100644 --- a/src/components/Dashboard/context/ApiServerContext.jsx +++ b/src/components/Dashboard/context/ApiServerContext.jsx @@ -120,8 +120,7 @@ const ApiServerProvider = ({ children }) => { newSocket.on('notification', (data) => { let notification try { - notification = - typeof data === 'string' ? JSON.parse(data) : data + notification = typeof data === 'string' ? JSON.parse(data) : data } catch (err) { logger.error('Failed to parse notification:', err) return @@ -916,6 +915,60 @@ const ApiServerProvider = ({ children }) => { } } + // Export list data to Excel and download or open in Excel + const exportToExcel = async (objectType, mode = 'download') => { + try { + if (mode === 'open') { + // Open in Excel: get a temporary https URL (Excel fetches it when opening) + const response = await axios.post( + `${config.backendUrl}/excel/${objectType}/open`, + {}, + { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}` + } + } + ) + const url = response.data?.url + if (!url || typeof url !== 'string') { + throw new Error('No URL received from server') + } + const excelUri = `ms-excel:ofe|u|${url}` + window.location.href = excelUri + message.success('Opening in Excel') + } else if (mode === 'download') { + const response = await axios.get( + `${config.backendUrl}/excel/${objectType}`, + { + responseType: 'blob', + headers: { + Accept: + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + Authorization: `Bearer ${token}` + } + } + ) + const blob = new Blob([response.data], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = `${objectType}-export-${new Date().toISOString().slice(0, 10)}.xlsx` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + message.success('Excel file downloaded') + } else { + throw new Error('Invalid mode') + } + } catch (err) { + console.error(err) + showError(err, () => exportToExcel(objectType)) + } + } + // Download GCode file content const fetchFileContent = async (file, download = false) => { try { @@ -1002,7 +1055,10 @@ const ApiServerProvider = ({ children }) => { } } ) - spotlightCache.set(query, { data: response.data, timestamp: Date.now() }) + spotlightCache.set(query, { + data: response.data, + timestamp: Date.now() + }) return response.data } catch (err) { console.error(err) @@ -1458,6 +1514,7 @@ const ApiServerProvider = ({ children }) => { fetchLoading, showError, fetchFileContent, + exportToExcel, fetchTemplatePreview, fetchTemplatePDF, fetchNotes,