Improved UI for export options.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

This commit is contained in:
Tom Butcher 2026-03-03 01:20:22 +00:00
parent 4a03b7bfd4
commit 1558e93b63
6 changed files with 74 additions and 15 deletions

View File

@ -6,7 +6,7 @@
<path d="M10.149,67.641L43.438,67.641C50.137,67.641 53.586,64.135 53.586,57.41L53.586,29.058C53.586,24.717 53.028,22.725 50.308,19.954L33.894,3.284C31.282,0.616 29.111,0 25.212,0L10.149,0C3.481,0 0,3.532 0,10.257L0,57.41C0,64.161 3.455,67.641 10.149,67.641ZM10.637,61.519C7.621,61.519 6.122,59.941 6.122,57.029L6.122,10.637C6.122,7.752 7.621,6.122 10.663,6.122L23.984,6.122L23.984,23.261C23.984,27.733 26.167,29.895 30.619,29.895L47.464,29.895L47.464,57.029C47.464,59.941 45.965,61.519 42.929,61.519L10.637,61.519ZM31.198,24.496C29.903,24.496 29.384,23.945 29.384,22.656L29.384,6.973L46.613,24.496L31.198,24.496Z" style="fill-rule:nonzero;"/> <path d="M10.149,67.641L43.438,67.641C50.137,67.641 53.586,64.135 53.586,57.41L53.586,29.058C53.586,24.717 53.028,22.725 50.308,19.954L33.894,3.284C31.282,0.616 29.111,0 25.212,0L10.149,0C3.481,0 0,3.532 0,10.257L0,57.41C0,64.161 3.455,67.641 10.149,67.641ZM10.637,61.519C7.621,61.519 6.122,59.941 6.122,57.029L6.122,10.637C6.122,7.752 7.621,6.122 10.663,6.122L23.984,6.122L23.984,23.261C23.984,27.733 26.167,29.895 30.619,29.895L47.464,29.895L47.464,57.029C47.464,59.941 45.965,61.519 42.929,61.519L10.637,61.519ZM31.198,24.496C29.903,24.496 29.384,23.945 29.384,22.656L29.384,6.973L46.613,24.496L31.198,24.496Z" style="fill-rule:nonzero;"/>
<g transform="matrix(12.808657,0,0,12.808657,-40.118025,-49.663642)"> <g transform="matrix(12.808657,0,0,12.808657,-40.118025,-49.663642)">
<path d="M4.662,8.034C4.545,8.034 4.448,8.015 4.37,7.978C4.293,7.94 4.236,7.89 4.2,7.825C4.163,7.761 4.145,7.69 4.145,7.612C4.145,7.528 4.166,7.454 4.208,7.389C4.25,7.325 4.316,7.274 4.405,7.236C4.495,7.198 4.607,7.178 4.743,7.178L5.084,7.178C5.084,7.115 5.076,7.063 5.06,7.022C5.045,6.981 5.019,6.95 4.984,6.93C4.948,6.909 4.9,6.899 4.838,6.899C4.773,6.899 4.718,6.912 4.673,6.938C4.629,6.964 4.601,7.005 4.59,7.061L4.187,7.061C4.197,6.961 4.23,6.873 4.286,6.798C4.343,6.724 4.419,6.665 4.514,6.622C4.609,6.58 4.718,6.558 4.841,6.558C4.975,6.558 5.092,6.58 5.19,6.624C5.289,6.668 5.366,6.731 5.421,6.815C5.476,6.899 5.503,7.003 5.503,7.128L5.503,8L5.154,8L5.104,7.796C5.083,7.831 5.059,7.864 5.031,7.892C5.003,7.921 4.971,7.946 4.933,7.968C4.896,7.989 4.855,8.006 4.81,8.017C4.766,8.028 4.716,8.034 4.662,8.034ZM4.766,7.715C4.81,7.715 4.849,7.708 4.883,7.693C4.917,7.678 4.945,7.657 4.97,7.631C4.994,7.605 5.014,7.575 5.03,7.54C5.046,7.506 5.057,7.468 5.065,7.427L5.065,7.424L4.794,7.424C4.747,7.424 4.708,7.43 4.678,7.443C4.647,7.455 4.624,7.472 4.609,7.494C4.594,7.517 4.587,7.543 4.587,7.572C4.587,7.604 4.595,7.631 4.611,7.652C4.626,7.674 4.648,7.689 4.675,7.7C4.702,7.71 4.732,7.715 4.766,7.715Z" style="fill-rule:nonzero;"/> <path d="M4.662,8.034C4.545,8.034 4.448,8.015 4.37,7.978C4.293,7.94 4.236,7.89 4.2,7.825C4.163,7.761 4.145,7.69 4.145,7.612C4.145,7.528 4.166,7.454 4.208,7.389C4.25,7.325 4.316,7.274 4.405,7.236C4.495,7.198 4.607,7.178 4.743,7.178L5.084,7.178C5.084,7.115 5.076,7.063 5.06,7.022C5.045,6.981 5.019,6.95 4.984,6.93C4.948,6.909 4.9,6.899 4.838,6.899C4.773,6.899 4.718,6.912 4.673,6.938C4.629,6.964 4.601,7.005 4.59,7.061L4.187,7.061C4.197,6.961 4.23,6.873 4.286,6.798C4.343,6.724 4.419,6.665 4.514,6.622C4.609,6.58 4.718,6.558 4.841,6.558C4.975,6.558 5.092,6.58 5.19,6.624C5.289,6.668 5.366,6.731 5.421,6.815C5.476,6.899 5.503,7.003 5.503,7.128L5.503,8L5.154,8L5.104,7.796C5.083,7.831 5.059,7.864 5.031,7.892C5.003,7.921 4.971,7.946 4.933,7.968C4.896,7.989 4.855,8.006 4.81,8.017C4.766,8.028 4.716,8.034 4.662,8.034ZM4.766,7.715C4.81,7.715 4.849,7.708 4.883,7.693C4.917,7.678 4.945,7.657 4.97,7.631C4.994,7.605 5.014,7.575 5.03,7.54C5.046,7.506 5.057,7.468 5.065,7.427L5.065,7.424L4.794,7.424C4.747,7.424 4.708,7.43 4.678,7.443C4.647,7.455 4.624,7.472 4.609,7.494C4.594,7.517 4.587,7.543 4.587,7.572C4.587,7.604 4.595,7.631 4.611,7.652C4.626,7.674 4.648,7.689 4.675,7.7C4.702,7.71 4.732,7.715 4.766,7.715Z" style="fill-rule:nonzero;"/>
<path d="M5.746,8.344L5.906,7.609L6.302,7.609L6.017,8.344L5.746,8.344Z" style="fill-rule:nonzero;"/> <path d="M5.835,7.521L6.43,7.521L6.077,8.432L5.637,8.432L5.835,7.521Z"/>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,1.056758,1.088008)">
<rect x="0" y="0" width="59.625" height="59.562" style="fill-opacity:0;"/>
</g>
<g transform="matrix(0.918716,0,0,0.918716,5,4.991502)">
<path d="M17.169,58.796L41.597,58.796C47.087,58.796 51.398,57.182 54.261,54.3C57.207,51.396 58.778,47.075 58.778,41.616L58.778,17.19C58.778,11.722 57.207,7.41 54.261,4.496C51.377,1.602 47.087,0 41.597,0L17.169,0C11.7,0 7.358,1.624 4.496,4.496C1.571,7.41 0,11.722 0,17.19L0,41.616C0,47.075 1.55,51.396 4.496,54.3C7.379,57.204 11.7,58.796 17.169,58.796ZM17.487,51.837C13.94,51.837 11.276,50.962 9.566,49.252C7.813,47.53 6.959,44.898 6.959,41.288L6.959,17.508C6.959,13.909 7.813,11.276 9.566,9.545C11.245,7.865 13.94,6.959 17.487,6.959L41.27,6.959C44.848,6.959 47.49,7.844 49.212,9.545C50.965,11.276 51.819,13.909 51.819,17.508L51.819,41.288C51.819,44.898 50.965,47.53 49.212,49.252C47.511,50.941 44.848,51.837 41.27,51.837L17.487,51.837Z" style="fill-rule:nonzero;"/>
<path d="M34.245,20.733L28.455,25.929L17.907,36.476C17.311,37.063 16.929,37.896 16.929,38.71C16.929,40.544 18.242,41.748 19.997,41.748C20.916,41.748 21.667,41.417 22.323,40.771L32.821,30.304L37.996,24.505C40.497,21.719 37.205,18.138 34.245,20.733ZM36.354,30.119L36.354,34.789C36.354,36.636 37.45,37.853 39.143,37.853C40.836,37.853 41.911,36.582 41.911,34.767L41.911,20.374C41.911,18.014 40.563,16.868 38.364,16.868L23.898,16.868C22.062,16.868 20.845,17.943 20.845,19.636C20.845,21.329 22.064,22.404 23.91,22.404L28.872,22.404L37.417,21.188L36.354,30.119Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -4,6 +4,8 @@ import { Button, Dropdown, Modal } from 'antd'
import ExcelIcon from '../../Icons/ExcelIcon' import ExcelIcon from '../../Icons/ExcelIcon'
import ODataIcon from '../../Icons/ODataIcon' import ODataIcon from '../../Icons/ODataIcon'
import CsvIcon from '../../Icons/CsvIcon' import CsvIcon from '../../Icons/CsvIcon'
import DownloadIcon from '../../Icons/DownloadIcon'
import OpenAppIcon from '../../Icons/OpenAppIcon'
import ExportIcon from '../../Icons/ExportIcon' import ExportIcon from '../../Icons/ExportIcon'
import ODataURL from './ODataURL' import ODataURL from './ODataURL'
import { ApiServerContext } from '../context/ApiServerContext' import { ApiServerContext } from '../context/ApiServerContext'
@ -16,7 +18,8 @@ const ExportListButton = ({
}) => { }) => {
const [odataModalOpen, setOdataModalOpen] = useState(false) const [odataModalOpen, setOdataModalOpen] = useState(false)
const [excelLoading, setExcelLoading] = useState(false) const [excelLoading, setExcelLoading] = useState(false)
const { exportToExcel } = useContext(ApiServerContext) const [csvLoading, setCsvLoading] = useState(false)
const { exportToExcel, exportToCsv } = useContext(ApiServerContext)
const handleExcelExport = async (mode) => { const handleExcelExport = async (mode) => {
setExcelLoading(true) setExcelLoading(true)
@ -27,40 +30,48 @@ const ExportListButton = ({
} }
} }
const exportLoading = excelLoading || csvLoading
const menuItems = [ const menuItems = [
{ {
key: 'excel', key: 'excel',
label: excelLoading ? 'Exporting...' : 'Excel', label: excelLoading ? 'Exporting...' : 'Microsoft Excel',
icon: <ExcelIcon />, icon: <ExcelIcon />,
disabled: excelLoading, disabled: exportLoading,
children: [ children: [
{ {
key: 'excel-download', key: 'excel-download',
label: 'Download', label: 'Download File',
disabled: excelLoading, icon: <DownloadIcon />,
disabled: exportLoading,
onClick: () => handleExcelExport('download') onClick: () => handleExcelExport('download')
}, },
{ {
key: 'excel-open', key: 'excel-open',
label: 'Open in Excel', label: 'Open in Excel',
disabled: excelLoading, icon: <OpenAppIcon />,
disabled: exportLoading,
onClick: () => handleExcelExport('open') onClick: () => handleExcelExport('open')
} }
] ]
}, },
{ {
key: 'odata', key: 'odata',
label: 'OData', label: 'OData Connection',
icon: <ODataIcon />, icon: <ODataIcon />,
onClick: () => setOdataModalOpen(true) onClick: () => setOdataModalOpen(true)
}, },
{ {
key: 'csv', key: 'csv',
label: 'CSV', label: csvLoading ? 'Exporting...' : 'CSV File',
icon: <CsvIcon />, icon: <CsvIcon />,
disabled: true, disabled: exportLoading,
onClick: () => { onClick: async () => {
// TODO: implement CSV export setCsvLoading(true)
try {
await exportToCsv(objectType)
} finally {
setCsvLoading(false)
}
} }
} }
] ]
@ -70,12 +81,13 @@ const ExportListButton = ({
<Dropdown <Dropdown
menu={{ items: menuItems }} menu={{ items: menuItems }}
trigger={['hover']} trigger={['hover']}
disabled={disabled} disabled={disabled || exportLoading}
> >
<Button <Button
icon={<ExportIcon />} icon={<ExportIcon />}
disabled={disabled} disabled={disabled || exportLoading}
size={size} size={size}
loading={exportLoading}
{...buttonProps} {...buttonProps}
/> />
</Dropdown> </Dropdown>

View File

@ -13,7 +13,7 @@ const ODataURL = ({ objectType }) => {
return ( return (
<Flex vertical align='center'> <Flex vertical align='center'>
<Result <Result
title='OData URL' title='OData Connection'
subTitle={ subTitle={
<Text> <Text>
Use this URL to connect Power BI, Excel, or other OData clients. An Use this URL to connect Power BI, Excel, or other OData clients. An

View File

@ -915,6 +915,35 @@ const ApiServerProvider = ({ children }) => {
} }
} }
// Export list data to CSV and download
const exportToCsv = async (objectType) => {
try {
const response = await axios.get(
`${config.backendUrl}/csv/${objectType}`,
{
responseType: 'blob',
headers: {
Accept: 'text/csv',
Authorization: `Bearer ${token}`
}
}
)
const blob = new Blob([response.data], { type: 'text/csv' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `${objectType}-export-${new Date().toISOString().slice(0, 10)}.csv`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
message.success('CSV file downloaded')
} catch (err) {
console.error(err)
showError(err, () => exportToCsv(objectType))
}
}
// Export list data to Excel and download or open in Excel // Export list data to Excel and download or open in Excel
const exportToExcel = async (objectType, mode = 'download') => { const exportToExcel = async (objectType, mode = 'download') => {
try { try {
@ -1515,6 +1544,7 @@ const ApiServerProvider = ({ children }) => {
showError, showError,
fetchFileContent, fetchFileContent,
exportToExcel, exportToExcel,
exportToCsv,
fetchTemplatePreview, fetchTemplatePreview,
fetchTemplatePDF, fetchTemplatePDF,
fetchNotes, fetchNotes,

View File

@ -0,0 +1,6 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/openappicon.svg?react'
const OpenAppIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default OpenAppIcon