diff --git a/assets/icons/csvicon.svg b/assets/icons/csvicon.svg
new file mode 100644
index 0000000..269627f
--- /dev/null
+++ b/assets/icons/csvicon.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/assets/icons/excelicon.svg b/assets/icons/excelicon.svg
new file mode 100644
index 0000000..3151c4d
--- /dev/null
+++ b/assets/icons/excelicon.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/assets/icons/exporticon.svg b/assets/icons/exporticon.svg
new file mode 100644
index 0000000..c3d4ca0
--- /dev/null
+++ b/assets/icons/exporticon.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/assets/icons/odataicon.svg b/assets/icons/odataicon.svg
new file mode 100644
index 0000000..75cceba
--- /dev/null
+++ b/assets/icons/odataicon.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/components/Dashboard/Finance/Invoices.jsx b/src/components/Dashboard/Finance/Invoices.jsx
index 5f59c6e..43dba5a 100644
--- a/src/components/Dashboard/Finance/Invoices.jsx
+++ b/src/components/Dashboard/Finance/Invoices.jsx
@@ -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}
/>
+
diff --git a/src/components/Dashboard/Management/CourierServices.jsx b/src/components/Dashboard/Management/CourierServices.jsx
index f00114e..6da21bf 100644
--- a/src/components/Dashboard/Management/CourierServices.jsx
+++ b/src/components/Dashboard/Management/CourierServices.jsx
@@ -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}
/>
+
{
const [newTaxRecordOpen, setNewTaxRecordOpen] = useState(false)
@@ -56,6 +57,7 @@ const TaxRecords = () => {
collapseState={columnVisibility}
updateCollapseState={setColumnVisibility}
/>
+
{
const tableRef = useRef()
@@ -43,6 +44,7 @@ const Users = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
+
{
+ 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 (
+
+ Copy this password now. It will not be shown again.
+ ) : (
+ Generate a new app password for API access.
+ )
+ }
+ icon={}
+ >
+
+
+
+
+
+
+ {appPassword || '••••••••••••••••••••••••••••••••'}
+
+ }
+ />
+
+
+
+
+
+ )
+}
+
+SetAppPassword.propTypes = {
+ id: PropTypes.string.isRequired
+}
+
+export default SetAppPassword
diff --git a/src/components/Dashboard/Management/Users/UserInfo.jsx b/src/components/Dashboard/Management/Users/UserInfo.jsx
index 51fb2d1..196b096 100644
--- a/src/components/Dashboard/Management/Users/UserInfo.jsx
+++ b/src/components/Dashboard/Management/Users/UserInfo.jsx
@@ -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 = () => {
+
+ {
+ actionHandlerRef.current?.clearAction?.()
+ setSetAppPasswordOpen(false)
+ }}
+ footer={null}
+ >
+
+
>
)
}
diff --git a/src/components/Dashboard/Management/Vendors.jsx b/src/components/Dashboard/Management/Vendors.jsx
index add3b7c..200495b 100644
--- a/src/components/Dashboard/Management/Vendors.jsx
+++ b/src/components/Dashboard/Management/Vendors.jsx
@@ -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}
/>
+
{
const [newGCodeFileOpen, setNewGCodeFileOpen] = useState(false)
@@ -58,6 +59,7 @@ const GCodeFiles = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
+
{
const [newJobOpen, setNewJobOpen] = useState(false)
@@ -60,6 +61,7 @@ const Jobs = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
+
diff --git a/src/components/Dashboard/Production/Printers.jsx b/src/components/Dashboard/Production/Printers.jsx
index e1ed5f6..70ad5fc 100644
--- a/src/components/Dashboard/Production/Printers.jsx
+++ b/src/components/Dashboard/Production/Printers.jsx
@@ -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}
/>
+
{
const tableRef = useRef()
@@ -45,6 +46,7 @@ const SubJobs = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
+
diff --git a/src/components/Dashboard/Sales/Clients.jsx b/src/components/Dashboard/Sales/Clients.jsx
index 49c799f..49a421b 100644
--- a/src/components/Dashboard/Sales/Clients.jsx
+++ b/src/components/Dashboard/Sales/Clients.jsx
@@ -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}
/>
+
{
const [newSalesOrderOpen, setNewSalesOrderOpen] = useState(false)
@@ -56,6 +57,7 @@ const SalesOrders = () => {
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
+
{
+ const [odataModalOpen, setOdataModalOpen] = useState(false)
+
+ const menuItems = [
+ {
+ key: 'excel',
+ label: 'Excel',
+ icon: ,
+ disabled: true,
+ onClick: () => {
+ // TODO: implement Excel export
+ }
+ },
+ {
+ key: 'odata',
+ label: 'OData',
+ icon: ,
+ onClick: () => setOdataModalOpen(true)
+ },
+ {
+ key: 'csv',
+ label: 'CSV',
+ icon: ,
+ disabled: true,
+ onClick: () => {
+ // TODO: implement CSV export
+ }
+ }
+ ]
+
+ return (
+ <>
+
+ }
+ disabled={disabled}
+ size={size}
+ {...buttonProps}
+ />
+
+ setOdataModalOpen(false)}
+ footer={null}
+ >
+
+
+ >
+ )
+}
+
+ExportListButton.propTypes = {
+ objectType: PropTypes.string.isRequired,
+ disabled: PropTypes.bool,
+ size: PropTypes.oneOf(['large', 'middle', 'small'])
+}
+
+export default ExportListButton
diff --git a/src/components/Dashboard/common/ODataURL.jsx b/src/components/Dashboard/common/ODataURL.jsx
new file mode 100644
index 0000000..4a00dae
--- /dev/null
+++ b/src/components/Dashboard/common/ODataURL.jsx
@@ -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 (
+
+
+ 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.
+
+ }
+ icon={}
+ >
+
+
+
+
+
+ {odataUrl}
+
+
+
+
+
+
+ )
+}
+
+ODataURL.propTypes = {
+ objectType: PropTypes.string.isRequired
+}
+
+export default ODataURL
diff --git a/src/components/Icons/CsvIcon.jsx b/src/components/Icons/CsvIcon.jsx
new file mode 100644
index 0000000..7bd784a
--- /dev/null
+++ b/src/components/Icons/CsvIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/csvicon.svg?react'
+
+const CsvIcon = (props) =>
+
+export default CsvIcon
diff --git a/src/components/Icons/ExcelIcon.jsx b/src/components/Icons/ExcelIcon.jsx
new file mode 100644
index 0000000..8e905a1
--- /dev/null
+++ b/src/components/Icons/ExcelIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/excelicon.svg?react'
+
+const ExcelIcon = (props) =>
+
+export default ExcelIcon
diff --git a/src/components/Icons/ExportIcon.jsx b/src/components/Icons/ExportIcon.jsx
new file mode 100644
index 0000000..e2057da
--- /dev/null
+++ b/src/components/Icons/ExportIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/exporticon.svg?react'
+
+const ExportIcon = (props) =>
+
+export default ExportIcon
diff --git a/src/components/Icons/ODataIcon.jsx b/src/components/Icons/ODataIcon.jsx
new file mode 100644
index 0000000..b088fea
--- /dev/null
+++ b/src/components/Icons/ODataIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/odataicon.svg?react'
+
+const ODataIcon = (props) =>
+
+export default ODataIcon
diff --git a/src/database/models/User.js b/src/database/models/User.js
index 1e73861..7f9f41b 100644
--- a/src/database/models/User.js
+++ b/src/database/models/User.js
@@ -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'],