Add export functionality with OData, Excel, and CSV options; introduce new icons and components for export buttons.

This commit is contained in:
Tom Butcher 2026-03-02 00:53:43 +00:00
parent 49a8bb3a6d
commit 7ea5eaf1f5
45 changed files with 354 additions and 1 deletions

12
assets/icons/csvicon.svg Normal file
View File

@ -0,0 +1,12 @@
<?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 12 12" 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(0.156042,0,0,0.156042,1.80453,0.721021)">
<rect x="0" y="0" width="53.774" height="67.661" style="fill-opacity:0;"/>
<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)">
<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;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,14 @@
<?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(0.124,0,0,0.124,1.867256,1)">
<path d="M69.43,159.719C69.43,125.199 97.41,97.219 131.922,97.219L486.012,97.219L486.012,458.328C486.012,481.34 467.359,500 444.352,500L152.738,500C106.73,500 69.43,462.691 69.43,416.672L69.43,159.719Z" style="fill:rgb(38,120,43);fill-rule:nonzero;"/>
<path d="M69.43,229.172C69.43,194.648 97.41,166.672 131.922,166.672L319.379,166.672C296.371,166.672 277.719,185.328 277.719,208.34L277.719,291.672C277.719,314.68 259.07,333.34 236.059,333.34L152.75,333.34C106.738,333.34 69.441,370.648 69.441,416.672L69.441,229.172L69.43,229.172Z" style="fill:rgb(74,165,70);fill-rule:nonzero;"/>
<path d="M69.43,83.328C69.43,37.309 106.73,0 152.738,0L319.371,0L319.371,166.672L152.738,166.672C106.73,166.672 69.43,203.98 69.43,250L69.43,83.328Z" style="fill:rgb(115,218,95);fill-rule:nonzero;"/>
<path d="M319.371,0L444.34,0C467.348,0 486,18.652 486,41.66L486,125.012C486,148.02 467.348,166.672 444.34,166.672L319.371,166.672C296.363,166.672 277.711,148.02 277.711,125.012L277.711,41.66C277.711,18.652 296.363,0 319.371,0Z" style="fill:rgb(143,233,111);fill-rule:nonzero;"/>
<g transform="matrix(1.250027,0,0,1.250027,0,-114.594378)">
<path d="M45.129,236.109L177.039,236.109C201.965,236.109 222.172,256.316 222.172,281.238L222.172,413.199C222.172,438.125 201.965,458.328 177.039,458.328L45.129,458.328C20.207,458.328 0,438.125 0,413.199L0,281.238C0,256.316 20.207,236.109 45.129,236.109Z" style="fill:rgb(11,78,46);fill-rule:nonzero;"/>
<path d="M169.48,410.711L135.23,410.711L113.73,370.238C112.961,368.82 112.371,367.699 111.961,366.871C111.609,365.988 111.219,364.98 110.809,363.859L110.461,363.859C109.93,365.281 109.43,366.43 108.961,367.309C108.488,368.199 107.93,369.289 107.281,370.59L84.98,410.699L52.68,410.699L91.441,347.121L55.34,283.719L89.141,283.719L108.25,319.852C109.02,321.328 109.672,322.629 110.199,323.75C110.789,324.809 111.379,326.078 111.969,327.559L112.32,327.559C113.141,325.852 113.789,324.488 114.27,323.488C114.801,322.488 115.512,321.16 116.391,319.512L136.211,283.738L168.422,283.738L131.789,346.172L169.488,410.719L169.48,410.711Z" style="fill:rgb(254,254,254);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,9 @@
<?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(0.760492,0,0,0.760492,9.844746,-1.023725)">
<path d="M19.578,35.125L14.438,35.125C10.359,35.125 8.062,37.422 8.062,41.5L8.062,64.734C8.062,68.828 10.359,71.125 14.438,71.125L43.828,71.125C47.906,71.125 50.203,68.828 50.203,64.734L50.203,41.5C50.203,37.422 47.906,35.125 43.828,35.125L38.672,35.125L38.672,27.062L44.266,27.062C53.234,27.062 58.266,32.062 58.266,41.047L58.266,65.188C58.266,74.156 53.234,79.188 44.266,79.188L14,79.188C5.016,79.188 0,74.156 0,65.188L0,41.047C0,32.062 5.016,27.062 14,27.062L19.578,27.062L19.578,35.125Z" style="fill-rule:nonzero;"/>
<path d="M32.423,14.141L32.75,18.578L32.75,49.219C32.75,51.125 31.141,52.766 29.125,52.766C27.109,52.766 25.516,51.125 25.516,49.219L25.516,18.578L25.851,14.122L29.125,9.438L32.423,14.141Z" style="fill-rule:nonzero;"/>
<path d="M18.172,21.328C19.078,21.328 19.938,20.953 20.547,20.297L24.375,16.234L29.125,9.438L33.891,16.234L37.703,20.297C38.312,20.953 39.156,21.328 40.062,21.328C41.703,21.328 43.188,20.094 43.188,18.328C43.188,17.406 42.859,16.734 42.234,16.109L31.984,6.281C31.047,5.359 30.109,5.031 29.125,5.031C28.156,5.031 27.234,5.359 26.266,6.281L16.031,16.109C15.422,16.734 15.062,17.406 15.062,18.328C15.062,20.094 16.531,21.328 18.172,21.328Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,7 @@
<?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(0.069972,0,0,0.069972,-2.986098,-11.802594)">
<path d="M242,226C206,232 174.833,248.333 148.5,275C122.167,301.667 106,333 100,369L100,884C106,919.333 122,950 148,976C174,1002 204.333,1018.333 239,1025L243,1026L758,1026L761,1025C796.333,1018.333 826.833,1001.833 852.5,975.5C878.167,949.167 894,918.333 900,883L900,368C894,332.667 877.667,301.667 851,275C824.333,248.333 793,232 757,226L242,226ZM802,325L802,401C731.333,401.667 636.667,401.667 518,401L519,326L802,325ZM481,326C481.667,342.667 482,367.667 482,401L200,401L200,326L481,326ZM518,438L802,438L802,514L518,514L518,438ZM482,438L482,514L200,513L200,439L482,438ZM554,550C590.667,550 645.667,550.333 719,551L801,551L801,627L518,628L518,550L554,550ZM482,550L482,628L200,628L200,551L482,551L482,550ZM518,665L801,665C801.667,677 801.667,695.333 801,720L801,740L519,740L518,665ZM311,700C327.667,700 343.833,703.5 359.5,710.5C375.167,717.5 388.333,727.333 399,740C413.667,756.667 422,776.5 424,799.5C426,822.5 422,844.5 412,865.5C402,886.5 387,902 367,912C348.333,922.667 327.5,927.167 304.5,925.5C281.5,923.833 260.833,916.333 242.5,903C224.167,889.667 212,872.333 206,851C198,831.667 196.333,811.333 201,790C205.667,768.667 215.333,750.167 230,734.5C244.667,718.833 262.333,708.667 283,704C292.333,701.333 301.667,700 311,700Z" style="fill:rgb(255,152,0);fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Invoices = () => {
const [newInvoiceOpen, setNewInvoiceOpen] = useState(false)
@ -56,6 +57,7 @@ const Invoices = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='invoice' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Payments = () => {
const [newPaymentOpen, setNewPaymentOpen] = useState(false)
@ -56,6 +57,7 @@ const Payments = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='payment' />
</Space>
<Space>
<Button

View File

@ -13,6 +13,7 @@ import ListIcon from '../../Icons/ListIcon'
import GridIcon from '../../Icons/GridIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const FilamentStocks = () => {
const tableRef = useRef()
@ -61,6 +62,7 @@ const FilamentStocks = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='filamentStock' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const OrderItems = () => {
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
@ -56,6 +57,7 @@ const OrderItems = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='orderItem' />
</Space>
<Space>
<Button

View File

@ -13,6 +13,7 @@ import ListIcon from '../../Icons/ListIcon'
import GridIcon from '../../Icons/GridIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const PartStocks = () => {
const tableRef = useRef()
@ -61,6 +62,7 @@ const PartStocks = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='partStock' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const PurchaseOrders = () => {
const [newPurchaseOrderOpen, setNewPurchaseOrderOpen] = useState(false)
@ -56,6 +57,7 @@ const PurchaseOrders = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='purchaseOrder' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Shipments = () => {
const [newShipmentOpen, setNewShipmentOpen] = useState(false)
@ -56,6 +57,7 @@ const Shipments = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='shipment' />
</Space>
<Space>
<Button

View File

@ -13,6 +13,7 @@ import ListIcon from '../../Icons/ListIcon'
import GridIcon from '../../Icons/GridIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const StockAudits = () => {
const tableRef = useRef()
@ -61,6 +62,7 @@ const StockAudits = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='stockAudit' />
</Space>
<Space>
<Button

View File

@ -6,6 +6,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import ReloadIcon from '../../Icons/ReloadIcon'
const StockEvents = () => {
@ -44,6 +45,7 @@ const StockEvents = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='stockEvent' />
</Space>
<Space>
<Button

View File

@ -5,6 +5,7 @@ import ReloadIcon from '../../Icons/ReloadIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import ObjectTable from '../common/ObjectTable'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const AuditLogs = () => {
const tableRef = useRef()
@ -41,6 +42,7 @@ const AuditLogs = () => {
visibleState={columnVisibility}
updateVisibleState={updateColumnVisibility}
/>
<ExportListButton objectType='auditLog' />
</Space>
</Flex>

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const CourierServices = () => {
const [newCourierServiceOpen, setNewCourierServiceOpen] = useState(false)
@ -56,6 +57,7 @@ const CourierServices = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='courierService' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Couriers = () => {
const [newCourierOpen, setNewCourierOpen] = useState(false)
@ -55,6 +56,7 @@ const Couriers = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='courier' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const DocumentJobs = () => {
const [newDocumentJobOpen, setNewDocumentJobOpen] = useState(false)
@ -56,6 +57,7 @@ const DocumentJobs = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='documentJob' />
</Space>
<Space>
<Button

View File

@ -8,6 +8,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import NewDocumentPrinter from './DocumentPrinters/NewDocumentPrinter'
const DocumentPrinters = () => {
@ -55,6 +56,7 @@ const DocumentPrinters = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='documentPrinter' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const DocumentSizes = () => {
const [newDocumentSizeOpen, setNewDocumentSizeOpen] = useState(false)
@ -54,6 +55,7 @@ const DocumentSizes = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='documentSize' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const DocumentTemplates = () => {
const [newDocumentTemplateOpen, setNewDocumentTemplateOpen] = useState(false)
@ -56,6 +57,7 @@ const DocumentTemplates = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='documentTemplate' />
</Space>
<Space>
<Button

View File

@ -13,6 +13,7 @@ import ReloadIcon from '../../Icons/ReloadIcon'
import ListIcon from '../../Icons/ListIcon'
import GridIcon from '../../Icons/GridIcon'
import useViewMode from '../hooks/useViewMode'
import ExportListButton from '../common/ExportListButton'
const Filaments = () => {
const [newFilamentOpen, setNewFilamentOpen] = useState(false)
@ -61,6 +62,7 @@ const Filaments = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='filament' />
</Space>
<Space>
<Button

View File

@ -7,6 +7,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Files = () => {
const tableRef = useRef()
@ -44,6 +45,7 @@ const Files = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='file' />
</Space>
<Space>
<Button

View File

@ -7,6 +7,7 @@ import NewHost from './Hosts/NewHost'
import useColumnVisibility from '../hooks/useColumnVisibility'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
@ -60,6 +61,7 @@ const Hosts = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='host' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const NoteTypes = () => {
const [newNoteTypeOpen, setNewNoteTypeOpen] = useState(false)
@ -56,6 +57,7 @@ const NoteTypes = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='noteType' />
</Space>
<Space>
<Button

View File

@ -16,6 +16,7 @@ import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Parts = (filter) => {
const [newPartOpen, setNewPartOpen] = useState(false)
@ -60,6 +61,7 @@ const Parts = (filter) => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='part' />
</Space>
<Space>
<Button

View File

@ -28,6 +28,7 @@ import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ExportListButton from '../common/ExportListButton'
const Products = () => {
const navigate = useNavigate()
@ -325,6 +326,7 @@ const Products = () => {
<Dropdown menu={actionItems}>
<Button>Actions</Button>
</Dropdown>
<ExportListButton objectType='product' />
<Popover
content={getViewDropdownItems()}
placement='bottomLeft'

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const TaxRates = () => {
const [newTaxRateOpen, setNewTaxRateOpen] = useState(false)
@ -55,6 +56,7 @@ const TaxRates = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='taxRate' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const TaxRecords = () => {
const [newTaxRecordOpen, setNewTaxRecordOpen] = useState(false)
@ -56,6 +57,7 @@ const TaxRecords = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='taxRecord' />
</Space>
<Space>
<Button

View File

@ -7,6 +7,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Users = () => {
const tableRef = useRef()
@ -43,6 +44,7 @@ const Users = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='user' />
</Space>
<Space>
<Button

View File

@ -0,0 +1,73 @@
import PropTypes from 'prop-types'
import { useContext, useState } from 'react'
import { Result, Typography, Flex, Button } from 'antd'
import { ApiServerContext } from '../../context/ApiServerContext'
import CopyButton from '../../common/CopyButton'
import LockIcon from '../../../Icons/LockIcon'
import ReloadIcon from '../../../Icons/ReloadIcon'
const { Text } = Typography
const SetAppPassword = ({ id }) => {
const { sendObjectFunction } = useContext(ApiServerContext)
const [appPassword, setAppPassword] = useState(null)
const [loading, setLoading] = useState(false)
const [passwordGenerated, setPasswordGenerated] = useState(false)
const handleSet = async () => {
setLoading(true)
setAppPassword(null)
try {
const result = await sendObjectFunction(id, 'user', 'setAppPassword', {})
if (result?.appPassword) {
setAppPassword(result.appPassword)
setPasswordGenerated(true)
}
} finally {
setLoading(false)
}
}
return (
<Flex vertical align='center'>
<Result
title={
passwordGenerated ? 'App Password Generated' : 'Roll New Password'
}
disabled={passwordGenerated}
subTitle={
appPassword ? (
<Text>Copy this password now. It will not be shown again.</Text>
) : (
<Text>Generate a new app password for API access.</Text>
)
}
icon={<LockIcon />}
>
<Flex justify='center' style={{ minWidth: '395px' }}>
<Flex justify='center'>
<Flex gap='small' align='center' justify='center'>
<CopyButton size='default' text={appPassword} />
<Text code style={{ fontSize: '18px' }}>
{appPassword || '••••••••••••••••••••••••••••••••'}
</Text>
<Button
type='texts'
loading={loading}
onClick={handleSet}
icon={<ReloadIcon />}
/>
</Flex>
</Flex>
</Flex>
</Result>
</Flex>
)
}
SetAppPassword.propTypes = {
id: PropTypes.string.isRequired
}
export default SetAppPassword

View File

@ -1,6 +1,6 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { Space, Flex, Card } from 'antd'
import { Space, Flex, Card, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import useCollapseState from '../../hooks/useCollapseState'
import NotesPanel from '../../common/NotesPanel'
@ -20,12 +20,14 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import SetAppPassword from './SetAppPassword.jsx'
const UserInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const userId = new URLSearchParams(location.search).get('userId')
const [setAppPasswordOpen, setSetAppPasswordOpen] = useState(false)
const [collapseState, updateCollapseState] = useCollapseState('UserInfo', {
info: true,
notes: true,
@ -45,6 +47,10 @@ const UserInfo = () => {
objectFormRef?.current?.fetchObject?.()
return true
},
setAppPassword: () => {
setSetAppPasswordOpen(true)
return false
},
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@ -185,6 +191,19 @@ const UserInfo = () => {
</Flex>
</ScrollBox>
</Flex>
<Modal
open={setAppPasswordOpen}
destroyOnClose
width={650}
onCancel={() => {
actionHandlerRef.current?.clearAction?.()
setSetAppPasswordOpen(false)
}}
footer={null}
>
<SetAppPassword id={userId} />
</Modal>
</>
)
}

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Vendors = () => {
const [newVendorOpen, setNewVendorOpen] = useState(false)
@ -55,6 +56,7 @@ const Vendors = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
<ExportListButton objectType='vendor' />
</Space>
<Space>
<Button

View File

@ -12,6 +12,7 @@ import GridIcon from '../../Icons/GridIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const GCodeFiles = () => {
const [newGCodeFileOpen, setNewGCodeFileOpen] = useState(false)
@ -58,6 +59,7 @@ const GCodeFiles = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='gcodeFile' />
</Space>
<Space>
<Button

View File

@ -11,6 +11,7 @@ import ListIcon from '../../Icons/ListIcon.jsx'
import GridIcon from '../../Icons/GridIcon.jsx'
import useViewMode from '../hooks/useViewMode.jsx'
import ColumnViewButton from '../common/ColumnViewButton.jsx'
import ExportListButton from '../common/ExportListButton.jsx'
const Jobs = () => {
const [newJobOpen, setNewJobOpen] = useState(false)
@ -60,6 +61,7 @@ const Jobs = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='job' />
</Space>
<Space>

View File

@ -7,6 +7,7 @@ import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import ObjectTable from '../common/ObjectTable'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
@ -60,6 +61,7 @@ const Printers = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='printer' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import ListIcon from '../../Icons/ListIcon.jsx'
import GridIcon from '../../Icons/GridIcon.jsx'
import useViewMode from '../hooks/useViewMode.jsx'
import ColumnViewButton from '../common/ColumnViewButton.jsx'
import ExportListButton from '../common/ExportListButton.jsx'
const SubJobs = () => {
const tableRef = useRef()
@ -45,6 +46,7 @@ const SubJobs = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='subJob' />
</Space>
<Space>

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const Clients = () => {
const [newClientOpen, setNewClientOpen] = useState(false)
@ -55,6 +56,7 @@ const Clients = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='client' />
</Space>
<Space>
<Button

View File

@ -9,6 +9,7 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const SalesOrders = () => {
const [newSalesOrderOpen, setNewSalesOrderOpen] = useState(false)
@ -56,6 +57,7 @@ const SalesOrders = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='salesOrder' />
</Space>
<Space>
<Button

View File

@ -0,0 +1,78 @@
import PropTypes from 'prop-types'
import { useState } 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'
const ExportListButton = ({
objectType,
disabled = false,
size = 'middle',
...buttonProps
}) => {
const [odataModalOpen, setOdataModalOpen] = useState(false)
const menuItems = [
{
key: 'excel',
label: 'Excel',
icon: <ExcelIcon />,
disabled: true,
onClick: () => {
// TODO: implement Excel export
}
},
{
key: 'odata',
label: 'OData',
icon: <ODataIcon />,
onClick: () => setOdataModalOpen(true)
},
{
key: 'csv',
label: 'CSV',
icon: <CsvIcon />,
disabled: true,
onClick: () => {
// TODO: implement CSV export
}
}
]
return (
<>
<Dropdown
menu={{ items: menuItems }}
trigger={['hover']}
disabled={disabled}
>
<Button
icon={<ExportIcon />}
disabled={disabled}
size={size}
{...buttonProps}
/>
</Dropdown>
<Modal
open={odataModalOpen}
destroyOnClose
width={650}
onCancel={() => setOdataModalOpen(false)}
footer={null}
>
<ODataURL objectType={objectType} />
</Modal>
</>
)
}
ExportListButton.propTypes = {
objectType: PropTypes.string.isRequired,
disabled: PropTypes.bool,
size: PropTypes.oneOf(['large', 'middle', 'small'])
}
export default ExportListButton

View File

@ -0,0 +1,45 @@
import PropTypes from 'prop-types'
import { Result, Typography, Flex } from 'antd'
import CopyButton from './CopyButton'
import ODataIcon from '../../Icons/ODataIcon'
import config from '../../../config'
const { Text } = Typography
const ODataURL = ({ objectType }) => {
const baseUrl = config.backendUrl?.replace(/\/$/, '') || ''
const odataUrl = `${baseUrl}/odata/${objectType}`
return (
<Flex vertical align='center'>
<Result
title='OData URL'
subTitle={
<Text>
Use this URL to connect Power BI, Excel, or other OData clients. An
app password is required and can be configured in your user
settings.
</Text>
}
icon={<ODataIcon />}
>
<Flex justify='center' style={{ minWidth: '395px' }}>
<Flex justify='center'>
<Flex gap='small' align='center' justify='center'>
<CopyButton size='default' text={odataUrl} />
<Text code style={{ fontSize: '14px', wordBreak: 'break-all' }}>
{odataUrl}
</Text>
</Flex>
</Flex>
</Flex>
</Result>
</Flex>
)
}
ODataURL.propTypes = {
objectType: PropTypes.string.isRequired
}
export default ODataURL

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import PersonIcon from '../../components/Icons/PersonIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import LockIcon from '../../components/Icons/LockIcon'
export const User = {
name: 'user',
@ -22,6 +23,13 @@ export const User = {
icon: ReloadIcon,
url: (_id) =>
`/dashboard/management/users/info?userId=${_id}&action=reload`
},
{
name: 'setAppPassword',
label: 'Set App Password',
icon: LockIcon,
url: (_id) =>
`/dashboard/management/users/info?userId=${_id}&action=setAppPassword`
}
],
columns: ['name', '_reference', 'username', 'email', 'role', 'createdAt'],