diff --git a/src/components/Dashboard/Management/Filaments.jsx b/src/components/Dashboard/Management/Filaments.jsx
index d2e23fd..f9d3419 100644
--- a/src/components/Dashboard/Management/Filaments.jsx
+++ b/src/components/Dashboard/Management/Filaments.jsx
@@ -1,210 +1,33 @@
// src/filaments.js
-import React, { useState, useContext, useCallback, useEffect } from 'react'
-import { useNavigate } from 'react-router-dom'
-import axios from 'axios'
-import {
- Table,
- Badge,
- Button,
- Flex,
- Space,
- Modal,
- message,
- Dropdown,
- Typography,
- Checkbox,
- Popover,
- Input,
- Spin
-} from 'antd'
-import { createStyles } from 'antd-style'
-import { LoadingOutlined } from '@ant-design/icons'
+import React, { useContext, useRef, useState } from 'react'
+import { Button, Flex, Space, Modal, message, Dropdown } from 'antd'
import { AuthContext } from '../context/AuthContext'
import NewFilament from './Filaments/NewFilament'
-import IdDisplay from '../common/IdDisplay'
-import FilamentIcon from '../../Icons/FilamentIcon'
-import InfoCircleIcon from '../../Icons/InfoCircleIcon'
+
+import useColumnVisibility from '../hooks/useColumnVisibility'
+import ColumnViewButton from '../common/ColumnViewButton'
+import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
-import XMarkIcon from '../../Icons/XMarkIcon'
-import CheckIcon from '../../Icons/CheckIcon'
-import useColumnVisibility from '../hooks/useColumnVisibility'
-import TimeDisplay from '../common/TimeDisplay'
-
-import config from '../../../config'
-
-const { Text } = Typography
-
-const useStyle = createStyles(({ css, token }) => {
- const { antCls } = token
- return {
- customTable: css`
- ${antCls}-table {
- ${antCls}-table-container {
- ${antCls}-table-body,
- ${antCls}-table-content {
- scrollbar-width: thin;
- scrollbar-color: #eaeaea transparent;
- scrollbar-gutter: stable;
- }
- }
- }
- `
- }
-})
+import ListIcon from '../../Icons/ListIcon'
+import GridIcon from '../../Icons/GridIcon'
+import useViewMode from '../hooks/useViewMode'
const Filaments = () => {
const [messageApi, contextHolder] = message.useMessage()
- const navigate = useNavigate()
- const { styles } = useStyle()
-
- const [filamentsData, setFilamentsData] = useState([])
const [newFilamentOpen, setNewFilamentOpen] = useState(false)
- const [loading, setLoading] = useState(true)
+ const tableRef = useRef()
+
+ // View mode state (cards/list), persisted in sessionStorage via custom hook
+ const [viewMode, setViewMode] = useViewMode('filament')
+
+ const [columnVisibility, setColumnVisibility] =
+ useColumnVisibility('filament')
const { authenticated } = useContext(AuthContext)
- const [page, setPage] = useState(1)
- const [hasMore, setHasMore] = useState(true)
- const [lazyLoading, setLazyLoading] = useState(false)
- const [filters, setFilters] = useState({})
- const [sorter, setSorter] = useState({})
-
- const fetchFilamentsData = useCallback(
- async (pageNum = 1, append = false) => {
- try {
- const response = await axios.get(`${config.backendUrl}/filaments`, {
- params: {
- page: pageNum,
- limit: 25,
- ...filters,
- sort: sorter.field,
- order: sorter.order
- },
- headers: {
- Accept: 'application/json'
- },
- withCredentials: true
- })
-
- const newData = response.data
- setHasMore(newData.length === 25)
-
- if (append) {
- setFilamentsData((prev) => [...prev, ...newData])
- } else {
- setFilamentsData(newData)
- }
-
- setLoading(false)
- setLazyLoading(false)
- } catch (err) {
- messageApi.info(err)
- setLoading(false)
- setLazyLoading(false)
- }
- },
- [messageApi, filters, sorter]
- )
-
- const handleScroll = useCallback(
- (e) => {
- const { target } = e
- const scrollHeight = target.scrollHeight
- const scrollTop = target.scrollTop
- const clientHeight = target.clientHeight
-
- if (
- scrollHeight - scrollTop - clientHeight < 100 &&
- !lazyLoading &&
- hasMore
- ) {
- setLazyLoading(true)
- const nextPage = page + 1
- setPage(nextPage)
- fetchFilamentsData(nextPage, true)
- }
- },
- [page, lazyLoading, hasMore, fetchFilamentsData]
- )
-
- const getFilterDropdown = ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName
- }) => {
- return (
-
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- style={{ width: 200, display: 'block' }}
- />
-
-
- )
- }
-
- const getViewDropdownItems = () => {
- const columnItems = columns
- .filter((col) => col.key && col.title !== '')
- .map((col) => (
- {
- updateColumnVisibility(col.key, e.target.checked)
- }}
- >
- {col.title}
-
- ))
-
- return (
-
-
- {columnItems}
-
-
- )
- }
-
- const handleTableChange = (pagination, filters, sorter) => {
- const newFilters = {}
- Object.entries(filters).forEach(([key, value]) => {
- if (value && value.length > 0) {
- newFilters[key] = value[0]
- }
- })
- setPage(1)
- setFilters(newFilters)
- setSorter({
- field: sorter.field,
- order: sorter.order
- })
- }
-
const actionItems = {
items: [
{
@@ -221,291 +44,65 @@ const Filaments = () => {
],
onClick: ({ key }) => {
if (key === 'reloadList') {
- fetchFilamentsData()
+ tableRef.current?.reload()
} else if (key === 'newFilament') {
setNewFilamentOpen(true)
}
}
}
- const getFilamentActionItems = (id) => {
- return {
- items: [
- {
- label: 'Info',
- key: 'info',
- icon:
- }
- ],
- onClick: ({ key }) => {
- if (key === 'info') {
- navigate(`/dashboard/management/filaments/info?filamentId=${id}`)
- }
- }
- }
- }
-
- // Column definitions
- const columns = [
- {
- title: '',
- dataIndex: '',
- key: 'icon',
- width: 40,
- fixed: 'left',
- render: () =>
- },
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- width: 200,
- fixed: 'left',
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'name'
- }),
- onFilter: (value, record) =>
- record.name.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'ID',
- dataIndex: '_id',
- key: 'id',
- width: 180,
- render: (text) => (
-
- ),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'ID'
- }),
- onFilter: (value, record) =>
- record._id.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Vendor',
- dataIndex: 'vendor',
- key: 'vendor',
- width: 200,
- render: (vendor) => {
- return vendor.name
- },
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'vendor'
- }),
- onFilter: (value, record) =>
- record.vendor.name.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Material',
- dataIndex: 'type',
- width: 150,
- key: 'material',
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'material'
- }),
- onFilter: (value, record) =>
- record.type.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Cost',
- dataIndex: 'cost',
- width: 120,
- key: 'cost',
- render: (cost) => {
- return {'£' + cost + ' per kg'}
- },
- sorter: true
- },
- {
- title: 'Colour',
- dataIndex: 'color',
- key: 'color',
- width: 120,
- render: (color) => {
- return
- },
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'color'
- }),
- onFilter: (value, record) =>
- record.color.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Created At',
- dataIndex: 'createdAt',
- key: 'createdAt',
- width: 180,
- render: (createdAt) => {
- if (createdAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Updated At',
- dataIndex: 'updatedAt',
- key: 'updatedAt',
- width: 180,
- render: (updatedAt) => {
- if (updatedAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Actions',
- key: 'actions',
- fixed: 'right',
- width: 150,
- render: (text, record) => {
- return (
-
- }
- onClick={() =>
- navigate(
- `/dashboard/management/filaments/info?filamentId=${record._id}`
- )
- }
- />
-
- Actions
-
-
- )
- }
- }
- ]
-
- const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
- 'Filaments',
- columns
- )
-
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
-
- useEffect(() => {
- if (authenticated) {
- fetchFilamentsData()
- }
- }, [authenticated, fetchFilamentsData])
-
return (
<>
{contextHolder}
-
+
Actions
-
- View
-
+
+
+
+ : }
+ onClick={() =>
+ setViewMode(viewMode === 'cards' ? 'list' : 'cards')
+ }
+ />
- {lazyLoading && } />}
- }}
- onScroll={handleScroll}
- onChange={handleTableChange}
- showSorterTooltip={false}
+
+
-
- {
- setNewFilamentOpen(false)
- }}
- destroyOnHidden={true}
- >
- {
+
+ {
setNewFilamentOpen(false)
- messageApi.success('New filament created successfully.')
- fetchFilamentsData()
}}
- reset={newFilamentOpen}
- />
-
+ >
+ {
+ setNewFilamentOpen(false)
+ messageApi.success('New filament added successfully.')
+ tableRef.current?.reload()
+ }}
+ reset={newFilamentOpen}
+ />
+
+
>
)
}
diff --git a/src/components/Dashboard/Management/NoteTypes.jsx b/src/components/Dashboard/Management/NoteTypes.jsx
index eee9ab3..d0d17ba 100644
--- a/src/components/Dashboard/Management/NoteTypes.jsx
+++ b/src/components/Dashboard/Management/NoteTypes.jsx
@@ -1,259 +1,25 @@
import React, { useState, useContext, useRef } from 'react'
-import { useNavigate } from 'react-router-dom'
-import {
- Button,
- Flex,
- Space,
- Modal,
- Dropdown,
- message,
- Checkbox,
- Popover,
- Input,
- Badge,
- Typography
-} from 'antd'
+import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import { AuthContext } from '../context/AuthContext'
-import IdDisplay from '../common/IdDisplay'
import NewNoteType from './NoteTypes/NewNoteType'
-import TimeDisplay from '../common/TimeDisplay'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
-import XMarkIcon from '../../Icons/XMarkIcon'
-import CheckIcon from '../../Icons/CheckIcon'
-import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
-
-import config from '../../../config'
-import NoteTypeIcon from '../../Icons/NoteTypeIcon'
-import BoolDisplay from '../common/BoolDisplay'
-
-const { Text } = Typography
+import ColumnViewButton from '../common/ColumnViewButton'
const NoteTypes = () => {
const [messageApi, contextHolder] = message.useMessage()
- const navigate = useNavigate()
const [newNoteTypeOpen, setNewNoteTypeOpen] = useState(false)
const tableRef = useRef()
const { authenticated } = useContext(AuthContext)
- const [viewMode, setViewMode] = useViewMode('NoteTypes')
+ const [viewMode, setViewMode] = useViewMode('noteType')
- const getFilterDropdown = ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName
- }) => {
- return (
-
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- style={{ width: 200, display: 'block' }}
- />
- {
- clearFilters()
- confirm()
- }}
- icon={}
- />
- confirm()}
- icon={}
- />
-
-
- )
- }
-
- const getViewDropdownItems = () => {
- const columnItems = columns
- .filter((col) => col.key && col.title !== '')
- .map((col) => (
- {
- updateColumnVisibility(col.key, e.target.checked)
- }}
- >
- {col.title}
-
- ))
-
- return (
-
-
- {columnItems}
-
-
- )
- }
-
- const getNoteTypeActionItems = (id) => {
- return {
- items: [
- {
- label: 'Info',
- key: 'info',
- icon:
- }
- ],
- onClick: ({ key }) => {
- if (key === 'info') {
- navigate(`/dashboard/management/notetypes/info?noteTypeId=${id}`)
- }
- }
- }
- }
-
- const columns = [
- {
- title: ,
- key: 'icon',
- width: 40,
- fixed: 'left',
- render: () =>
- },
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- width: 200,
- fixed: 'left',
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'name'
- }),
- onFilter: (value, record) =>
- record.name.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'ID',
- dataIndex: '_id',
- key: 'id',
- width: 180,
- render: (text) => (
-
- ),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'ID'
- }),
- onFilter: (value, record) =>
- record._id.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Color',
- dataIndex: 'color',
- key: 'color',
- width: 120,
- render: (color) =>
- color ? : n/a
- },
- {
- title: 'Active',
- dataIndex: 'active',
- key: 'active',
- width: 100,
- render: (active) => ,
- sorter: true
- },
- {
- title: 'Created At',
- dataIndex: 'createdAt',
- key: 'createdAt',
- width: 180,
- render: (createdAt) => {
- if (createdAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Updated At',
- dataIndex: 'updatedAt',
- key: 'updatedAt',
- width: 180,
- render: (updatedAt) => {
- if (updatedAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Actions',
- key: 'actions',
- fixed: 'right',
- width: 150,
- render: (record) => {
- return (
-
- }
- onClick={() =>
- navigate(
- `/dashboard/management/notetypes/info?noteTypeId=${record._id}`
- )
- }
- />
-
- Actions
-
-
- )
- }
- }
- ]
-
- const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
- 'NoteTypes',
- columns
- )
-
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
+ const [columnVisibility, setColumnVisibility] =
+ useColumnVisibility('noteType')
const actionItems = {
items: [
@@ -287,13 +53,12 @@ const NoteTypes = () => {
Actions
-
- View
-
+
{
diff --git a/src/components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx b/src/components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx
index fa85598..211e85e 100644
--- a/src/components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx
+++ b/src/components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx
@@ -13,6 +13,10 @@ import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import EditObjectForm from '../../common/EditObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../Filaments/LockIndicator'
+import {
+ getModelProperties,
+ getPropertyValue
+} from '../../../../database/ObjectModels.js'
const NoteTypeInfo = () => {
const location = useLocation()
@@ -108,50 +112,11 @@ const NoteTypeInfo = () => {
loading={loading}
indicator={}
isEditing={isEditing}
- type='notetype'
- items={[
- {
- name: 'id',
- label: 'ID',
- value: objectData?._id,
- type: 'id',
- objectType: 'notetype',
- showCopy: true
- },
- {
- name: 'createdAt',
- label: 'Created At',
- value: objectData?.createdAt,
- type: 'dateTime',
- readOnly: true
- },
- {
- name: 'name',
- label: 'Name',
- value: objectData?.name,
- required: true,
- type: 'text'
- },
- {
- name: 'updatedAt',
- label: 'Updated At',
- value: objectData?.updatedAt,
- type: 'dateTime',
- readOnly: true
- },
- {
- name: 'color',
- label: 'Color',
- value: objectData?.color,
- type: 'color'
- },
- {
- name: 'active',
- label: 'Active',
- value: objectData?.active,
- type: 'bool'
- }
- ]}
+ type='noteType'
+ items={getModelProperties('noteType').map((prop) => ({
+ ...prop,
+ value: getPropertyValue(objectData, prop.name)
+ }))}
/>
diff --git a/src/components/Dashboard/Management/Parts.jsx b/src/components/Dashboard/Management/Parts.jsx
index 650f15c..d690b1a 100644
--- a/src/components/Dashboard/Management/Parts.jsx
+++ b/src/components/Dashboard/Management/Parts.jsx
@@ -1,237 +1,34 @@
// src/gcodefiles.js
import React, { useState, useContext, useRef } from 'react'
-import { useNavigate } from 'react-router-dom'
-import {
- Button,
- Flex,
- Space,
- Modal,
- Dropdown,
- Typography,
- Checkbox,
- Popover,
- Input,
- message
-} from 'antd'
-import { DownloadOutlined } from '@ant-design/icons'
+
+import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import { AuthContext } from '../context/AuthContext'
-import IdDisplay from '../common/IdDisplay'
+
import ObjectTable from '../common/ObjectTable'
import NewProduct from './Products/NewProduct'
-import PartIcon from '../../Icons/PartIcon'
-import InfoCircleIcon from '../../Icons/InfoCircleIcon'
+
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
-import XMarkIcon from '../../Icons/XMarkIcon'
-import CheckIcon from '../../Icons/CheckIcon'
+
import useColumnVisibility from '../hooks/useColumnVisibility'
-import TimeDisplay from '../common/TimeDisplay'
+
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
-import config from '../../../config'
-
-const { Text } = Typography
+import ColumnViewButton from '../common/ColumnViewButton'
const Parts = () => {
const [messageApi, contextHolder] = message.useMessage()
- const navigate = useNavigate()
+
const [newProductOpen, setNewProductOpen] = useState(false)
const tableRef = useRef()
const { authenticated } = useContext(AuthContext)
- const [viewMode, setViewMode] = useViewMode('Parts')
+ const [viewMode, setViewMode] = useViewMode('part')
- // Column definitions
- const columns = [
- {
- title: ,
- key: 'icon',
- width: 40,
- fixed: 'left',
- render: () =>
- },
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- width: 200,
- fixed: 'left',
- render: (text) => {text},
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'name'
- }),
- onFilter: (value, record) =>
- record.name.toLowerCase().includes(value.toLowerCase())
- },
- {
- title: 'ID',
- dataIndex: '_id',
- key: 'id',
- width: 180,
- render: (text) =>
- },
- {
- title: 'Product Name',
- key: 'productName',
- width: 200,
- render: (record) => {record?.product?.name},
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'product name'
- }),
- onFilter: (value, record) =>
- record.product.name.toLowerCase().includes(value.toLowerCase())
- },
- {
- title: 'Product ID',
- key: 'productId',
- width: 180,
- render: (record) => (
-
- )
- },
- {
- title: 'Created At',
- dataIndex: 'createdAt',
- key: 'createdAt',
- width: 180,
- render: (createdAt) => {
- if (createdAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true
- },
- {
- title: 'Updated At',
- dataIndex: 'updatedAt',
- key: 'updatedAt',
- width: 180,
- render: (updatedAt) => {
- if (updatedAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true
- },
- {
- title: 'Actions',
- key: 'actions',
- fixed: 'right',
- width: 150,
- render: (record) => {
- return (
-
- }
- onClick={() =>
- navigate(
- `/dashboard/management/parts/info?partId=${record._id}`
- )
- }
- />
-
- Actions
-
-
- )
- }
- }
- ]
-
- const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
- 'Parts',
- columns
- )
-
- const getPartActionItems = (id) => {
- return {
- items: [
- {
- label: 'Info',
- key: 'info',
- icon:
- },
- {
- label: 'Download',
- key: 'download',
- icon:
- }
- ],
- onClick: ({ key }) => {
- if (key === 'info') {
- navigate(`/dashboard/management/parts/info?partId=${id}`)
- }
- }
- }
- }
-
- const getFilterDropdown = ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName
- }) => {
- return (
-
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- style={{ width: 200, display: 'block' }}
- />
- {
- clearFilters()
- confirm()
- }}
- icon={}
- />
- confirm()}
- icon={}
- />
-
-
- )
- }
+ const [columnVisibility, setColumnVisibility] = useColumnVisibility('part')
const actionItems = {
items: [
@@ -256,34 +53,6 @@ const Parts = () => {
}
}
- const getViewDropdownItems = () => {
- const columnItems = columns
- .filter((col) => col.key && col.title !== '')
- .map((col) => (
- {
- updateColumnVisibility(col.key, e.target.checked)
- }}
- >
- {col.title}
-
- ))
-
- return (
-
-
- {columnItems}
-
-
- )
- }
-
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
-
return (
<>
@@ -293,13 +62,12 @@ const Parts = () => {
Actions
-
- View
-
+
{
diff --git a/src/components/Dashboard/Management/Products.jsx b/src/components/Dashboard/Management/Products.jsx
index 56efbed..3b39648 100644
--- a/src/components/Dashboard/Management/Products.jsx
+++ b/src/components/Dashboard/Management/Products.jsx
@@ -32,8 +32,6 @@ import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
-import config from '../../../config'
-
const Products = () => {
const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate()
@@ -323,10 +321,6 @@ const Products = () => {
)
}
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
-
return (
<>
@@ -355,8 +349,7 @@ const Products = () => {
diff --git a/src/components/Dashboard/Management/Users.jsx b/src/components/Dashboard/Management/Users.jsx
index 1a42c56..4c92a28 100644
--- a/src/components/Dashboard/Management/Users.jsx
+++ b/src/components/Dashboard/Management/Users.jsx
@@ -1,334 +1,20 @@
import React, { useContext, useRef } from 'react'
-import { useNavigate } from 'react-router-dom'
-import {
- Button,
- Flex,
- Space,
- Dropdown,
- Typography,
- Checkbox,
- Popover,
- Input
-} from 'antd'
-import { ExportOutlined } from '@ant-design/icons'
+import { Button, Flex, Space, Dropdown } from 'antd'
import { AuthContext } from '../context/AuthContext'
-import IdDisplay from '../common/IdDisplay'
-import TimeDisplay from '../common/TimeDisplay'
import ObjectTable from '../common/ObjectTable'
-import PersonIcon from '../../Icons/PersonIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
-import XMarkIcon from '../../Icons/XMarkIcon'
-import CheckIcon from '../../Icons/CheckIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
-import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
-
-import config from '../../../config'
-
-const { Link, Text } = Typography
+import ColumnViewButton from '../common/ColumnViewButton'
const Users = () => {
- const navigate = useNavigate()
const tableRef = useRef()
const { authenticated } = useContext(AuthContext)
- const [viewMode, setViewMode] = useViewMode('Users')
+ const [viewMode, setViewMode] = useViewMode('user')
- const getFilterDropdown = ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName
- }) => {
- return (
-
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- style={{ width: 200, display: 'block' }}
- />
- {
- clearFilters()
- confirm()
- }}
- icon={}
- />
- confirm()}
- icon={}
- />
-
-
- )
- }
-
- const getViewDropdownItems = () => {
- const columnItems = columns
- .filter((col) => col.key && col.title !== '')
- .map((col) => (
- {
- updateColumnVisibility(col.key, e.target.checked)
- }}
- >
- {col.title}
-
- ))
-
- return (
-
-
- {columnItems}
-
-
- )
- }
-
- const getUserActionItems = (id) => {
- return {
- items: [
- {
- label: 'Info',
- key: 'info',
- icon:
- }
- ],
- onClick: ({ key }) => {
- if (key === 'info') {
- navigate(`/dashboard/management/users/info?userId=${id}`)
- }
- }
- }
- }
-
- const columns = [
- {
- title: ,
- key: 'icon',
- width: 40,
- fixed: 'left',
- render: () =>
- },
-
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- width: 200,
- render: (text) => (text ? text : 'n/a'),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'name'
- }),
- onFilter: (value, record) =>
- record.name?.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Username',
- dataIndex: 'username',
- key: 'username',
- width: 150,
- fixed: 'left',
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'username'
- }),
- onFilter: (value, record) =>
- record.username.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'First Name',
- dataIndex: 'firstName',
- key: 'firstName',
- width: 150,
- render: (text) => (text ? text : 'n/a'),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'first name'
- }),
- onFilter: (value, record) =>
- record.firstName?.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Last Name',
- dataIndex: 'lastName',
- key: 'lastName',
- width: 150,
- render: (text) => (text ? text : 'n/a'),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'last name'
- }),
- onFilter: (value, record) =>
- record.lastName?.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Email',
- dataIndex: 'email',
- key: 'email',
- width: 250,
- render: (email) =>
- email ? (
-
- {email}
-
- ) : (
- n/a
- ),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'email'
- }),
- onFilter: (value, record) =>
- record.email?.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'ID',
- dataIndex: '_id',
- key: 'id',
- width: 180,
- render: (text) => ,
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'ID'
- }),
- onFilter: (value, record) =>
- record._id.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Created At',
- dataIndex: 'createdAt',
- key: 'createdAt',
- width: 180,
- render: (createdAt) => {
- if (createdAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Updated At',
- dataIndex: 'updatedAt',
- key: 'updatedAt',
- width: 180,
- render: (updatedAt) => {
- if (updatedAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Actions',
- key: 'actions',
- fixed: 'right',
- width: 150,
- render: (record) => {
- return (
-
- }
- onClick={() =>
- navigate(
- `/dashboard/management/users/info?userId=${record._id}`
- )
- }
- />
-
- Actions
-
-
- )
- }
- }
- ]
-
- const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
- 'Users',
- columns
- )
-
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
+ const [columnVisibility, setColumnVisibility] = useColumnVisibility('user')
const actionItems = {
items: [
@@ -352,13 +38,12 @@ const Users = () => {
Actions
-
- View
-
+
{
diff --git a/src/components/Dashboard/Management/Users/UserInfo.jsx b/src/components/Dashboard/Management/Users/UserInfo.jsx
index 5eedc68..667598c 100644
--- a/src/components/Dashboard/Management/Users/UserInfo.jsx
+++ b/src/components/Dashboard/Management/Users/UserInfo.jsx
@@ -15,6 +15,10 @@ import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import EditObjectForm from '../../common/EditObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../Filaments/LockIndicator'
+import {
+ getModelProperties,
+ getPropertyValue
+} from '../../../../database/ObjectModels.js'
const UserInfo = () => {
const location = useLocation()
@@ -110,63 +114,10 @@ const UserInfo = () => {
indicator={}
isEditing={isEditing}
type='user'
- items={[
- {
- name: '_id',
- label: 'ID',
- value: objectData?._id,
- type: 'id',
- objectType: 'user',
- showCopy: true
- },
- {
- name: 'createdAt',
- label: 'Created At',
- value: objectData?.createdAt,
- type: 'dateTime',
- readOnly: true
- },
- {
- name: 'name',
- label: 'Name',
- value: objectData?.name,
- required: true,
- type: 'text'
- },
- {
- name: 'updatedAt',
- label: 'Updated At',
- value: objectData?.updatedAt,
- type: 'dateTime',
- readOnly: true
- },
-
- {
- name: 'firstName',
- label: 'First Name',
- value: objectData?.firstName,
- type: 'text'
- },
- {
- name: 'username',
- label: 'Username',
- value: objectData?.username,
- required: true,
- type: 'text'
- },
- {
- name: 'lastName',
- label: 'Last Name',
- value: objectData?.lastName,
- type: 'text'
- },
- {
- name: 'email',
- label: 'Email',
- value: objectData?.email,
- type: 'email'
- }
- ]}
+ items={getModelProperties('user').map((prop) => ({
+ ...prop,
+ value: getPropertyValue(objectData, prop.name)
+ }))}
/>
diff --git a/src/components/Dashboard/Management/Vendors.jsx b/src/components/Dashboard/Management/Vendors.jsx
index cb9db33..17e0f8f 100644
--- a/src/components/Dashboard/Management/Vendors.jsx
+++ b/src/components/Dashboard/Management/Vendors.jsx
@@ -1,318 +1,24 @@
import React, { useState, useContext, useRef } from 'react'
-import { useNavigate } from 'react-router-dom'
-import {
- Button,
- Flex,
- Space,
- Modal,
- Dropdown,
- message,
- Typography,
- Checkbox,
- Popover,
- Input
-} from 'antd'
-import { ExportOutlined } from '@ant-design/icons'
+import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import { AuthContext } from '../context/AuthContext'
-import IdDisplay from '../common/IdDisplay'
import NewVendor from './Vendors/NewVendor'
-import CountryDisplay from '../common/CountryDisplay'
-import TimeDisplay from '../common/TimeDisplay'
import ObjectTable from '../common/ObjectTable'
-import VendorIcon from '../../Icons/VendorIcon'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
-import XMarkIcon from '../../Icons/XMarkIcon'
-import CheckIcon from '../../Icons/CheckIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
-import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
-
-import config from '../../../config'
-
-const { Link } = Typography
+import ColumnViewButton from '../common/ColumnViewButton'
const Vendors = () => {
const [messageApi, contextHolder] = message.useMessage()
- const navigate = useNavigate()
const [newVendorOpen, setNewVendorOpen] = useState(false)
const tableRef = useRef()
const { authenticated } = useContext(AuthContext)
- const [viewMode, setViewMode] = useViewMode('Vendors')
+ const [viewMode, setViewMode] = useViewMode('vendor')
- const getFilterDropdown = ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName
- }) => {
- return (
-
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- style={{ width: 200, display: 'block' }}
- />
- {
- clearFilters()
- confirm()
- }}
- icon={}
- />
- confirm()}
- icon={}
- />
-
-
- )
- }
-
- const getViewDropdownItems = () => {
- const columnItems = columns
- .filter((col) => col.key && col.title !== '')
- .map((col) => (
- {
- updateColumnVisibility(col.key, e.target.checked)
- }}
- >
- {col.title}
-
- ))
-
- return (
-
-
- {columnItems}
-
-
- )
- }
-
- const getVendorActionItems = (id) => {
- return {
- items: [
- {
- label: 'Info',
- key: 'info',
- icon:
- }
- ],
- onClick: ({ key }) => {
- if (key === 'info') {
- navigate(`/dashboard/management/vendors/info?vendorId=${id}`)
- }
- }
- }
- }
-
- const columns = [
- {
- title: ,
- key: 'icon',
- width: 40,
- fixed: 'left',
- render: () =>
- },
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- width: 200,
- fixed: 'left',
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'name'
- }),
- onFilter: (value, record) =>
- record.name.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'ID',
- dataIndex: '_id',
- key: 'id',
- width: 180,
- render: (text) => ,
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'ID'
- }),
- onFilter: (value, record) =>
- record._id.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Website',
- dataIndex: 'website',
- key: 'website',
- width: 200,
- render: (text) =>
- text ? (
-
- {new URL(text).hostname + ' '}
-
-
- ) : (
- 'n/a'
- ),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'website'
- }),
- onFilter: (value, record) =>
- record.website?.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Country',
- dataIndex: 'country',
- key: 'country',
- width: 200,
- render: (text) => (text ? : 'n/a'),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'country'
- }),
- onFilter: (value, record) =>
- record.country?.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Contact',
- dataIndex: 'contact',
- key: 'contact',
- width: 200,
- render: (text) => (text ? text : 'n/a'),
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'contact'
- }),
- onFilter: (value, record) =>
- record.contact?.toLowerCase().includes(value.toLowerCase()),
- sorter: true
- },
- {
- title: 'Created At',
- dataIndex: 'createdAt',
- key: 'createdAt',
- width: 180,
- render: (createdAt) => {
- if (createdAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Updated At',
- dataIndex: 'updatedAt',
- key: 'updatedAt',
- width: 180,
- render: (updatedAt) => {
- if (updatedAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true,
- defaultSortOrder: 'descend'
- },
- {
- title: 'Actions',
- key: 'actions',
- fixed: 'right',
- width: 150,
- render: (record) => {
- return (
-
- }
- onClick={() =>
- navigate(
- `/dashboard/management/vendors/info?vendorId=${record._id}`
- )
- }
- />
-
- Actions
-
-
- )
- }
- }
- ]
-
- const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
- 'Vendors',
- columns
- )
-
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
+ const [columnVisibility, setColumnVisibility] = useColumnVisibility('vendor')
const actionItems = {
items: [
@@ -346,13 +52,12 @@ const Vendors = () => {
Actions
-
- View
-
+
{
diff --git a/src/components/Dashboard/Production/GCodeFiles.jsx b/src/components/Dashboard/Production/GCodeFiles.jsx
index fed5181..354a2db 100644
--- a/src/components/Dashboard/Production/GCodeFiles.jsx
+++ b/src/components/Dashboard/Production/GCodeFiles.jsx
@@ -1,300 +1,30 @@
// src/gcodefiles.js
import React, { useState, useContext, useRef } from 'react'
-import { useNavigate } from 'react-router-dom'
-import axios from 'axios'
-import {
- Badge,
- Button,
- Flex,
- Space,
- Modal,
- Dropdown,
- Typography,
- message,
- Checkbox,
- Divider,
- Popover,
- Input
-} from 'antd'
-import { DownloadOutlined } from '@ant-design/icons'
-
+import { Button, Flex, Space, Modal, Dropdown, message } from 'antd'
import { AuthContext } from '../context/AuthContext'
import NewGCodeFile from './GCodeFiles/NewGCodeFile'
-import IdDisplay from '../common/IdDisplay'
-import GCodeFileIcon from '../../Icons/GCodeFileIcon'
-import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import useColumnVisibility from '../hooks/useColumnVisibility'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
-import XMarkIcon from '../../Icons/XMarkIcon'
-import CheckIcon from '../../Icons/CheckIcon'
-import TimeDisplay from '../common/TimeDisplay'
import ObjectTable from '../common/ObjectTable'
import ListIcon from '../../Icons/ListIcon'
import GridIcon from '../../Icons/GridIcon'
import useViewMode from '../hooks/useViewMode'
-import config from '../../../config'
-
-const { Text } = Typography
+import ColumnViewButton from '../common/ColumnViewButton'
const GCodeFiles = () => {
const [messageApi, contextHolder] = message.useMessage()
- const navigate = useNavigate()
const [newGCodeFileOpen, setNewGCodeFileOpen] = useState(false)
- const [showDeleted, setShowDeleted] = useState(false)
const tableRef = useRef()
- const [viewMode, setViewMode] = useViewMode('GCodeFiles')
+ const [viewMode, setViewMode] = useViewMode('gcodeFile')
- const getFilterDropdown = ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName
- }) => {
- return (
-
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- style={{ width: 200, display: 'block' }}
- />
- {
- clearFilters()
- confirm()
- }}
- icon={}
- />
- confirm()}
- icon={}
- />
-
-
- )
- }
-
- // Column definitions
- const columns = [
- {
- title: ,
- key: 'icon',
- width: 40,
- fixed: 'left',
- render: () =>
- },
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- width: 200,
- fixed: 'left',
- render: (text) => {text},
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'name'
- }),
- onFilter: (value, record) =>
- record.name.toLowerCase().includes(value.toLowerCase())
- },
- {
- title: 'ID',
- dataIndex: '_id',
- key: 'id',
- width: 180,
- render: (text) => (
-
- )
- },
- {
- title: 'Filament',
- key: 'filament',
- width: 200,
- render: (record) => {
- return (
-
- )
- },
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'filament'
- }),
- onFilter: (value, record) =>
- record.filament.name.toLowerCase().includes(value.toLowerCase())
- },
- {
- title: 'Cost',
- dataIndex: 'cost',
- key: 'cost',
- width: 120,
- render: (cost) => {
- return '£' + cost?.toFixed(2)
- },
- sorter: true
- },
- {
- title: 'Print Time',
- key: 'estimatedPrintingTimeNormalMode',
- width: 140,
- render: (record) => {
- return `${record?.gcodeFileInfo?.estimatedPrintingTimeNormalMode}`
- },
- sorter: true
- },
- {
- title: 'Created At',
- dataIndex: 'createdAt',
- key: 'createdAt',
- width: 180,
- render: (createdAt) => {
- if (createdAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true
- },
- {
- title: 'Updated At',
- dataIndex: 'updatedAt',
- key: 'updatedAt',
- width: 180,
- render: (createdAt) => {
- if (createdAt) {
- return
- } else {
- return 'n/a'
- }
- },
- sorter: true
- },
- {
- title: 'Actions',
- key: 'actions',
- fixed: 'right',
- width: 150,
- render: (record) => {
- return (
-
- }
- onClick={() =>
- navigate(
- `/dashboard/production/gcodefiles/info?gcodeFileId=${record._id}`
- )
- }
- />
-
- Actions
-
-
- )
- }
- }
- ]
-
- const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
- 'GCodeFiles',
- columns
- )
+ const [columnVisibility, setColumnVisibility] =
+ useColumnVisibility('gcodeFile')
const { authenticated } = useContext(AuthContext)
- const getGCodeFileActionItems = (id) => {
- return {
- items: [
- {
- label: 'Info',
- key: 'info',
- icon:
- },
- {
- label: 'Download',
- key: 'download',
- icon:
- }
- ],
- onClick: ({ key }) => {
- if (key === 'info') {
- navigate(`/dashboard/production/gcodefiles/info?gcodeFileId=${id}`)
- } else if (key === 'download') {
- handleDownloadGCode(
- id,
- tableRef.current?.getData().find((file) => file._id === id)?.name +
- '.gcode'
- )
- }
- }
- }
- }
-
- const handleDownloadGCode = async (id, fileName) => {
- if (!authenticated) {
- return
- }
- try {
- const response = await axios.get(
- `${config.backendUrl}/gcodefiles/${id}/content`,
- {
- headers: {
- Accept: 'application/json'
- },
- withCredentials: true
- }
- )
-
- const fileURL = window.URL.createObjectURL(new Blob([response.data]))
- const fileLink = document.createElement('a')
- fileLink.href = fileURL
- fileLink.setAttribute('download', fileName)
- document.body.appendChild(fileLink)
- fileLink.click()
- fileLink.parentNode.removeChild(fileLink)
- } catch (error) {
- if (error.response) {
- messageApi.error(
- 'Error updating printer details:',
- error.response.status
- )
- } else {
- messageApi.error(
- 'An unexpected error occurred. Please try again later.'
- )
- }
- }
- }
-
const actionItems = {
items: [
{
@@ -318,43 +48,6 @@ const GCodeFiles = () => {
}
}
- const getViewDropdownItems = () => {
- const columnItems = columns
- .filter((col) => col.key && col.title !== '')
- .map((col) => (
- {
- updateColumnVisibility(col.key, e.target.checked)
- }}
- >
- {col.title}
-
- ))
-
- return (
-
-
- {columnItems}
-
-
-
- setShowDeleted(e.target.checked)}
- >
- Show Deleted
-
-
-
- )
- }
-
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
-
return (
<>
@@ -364,13 +57,12 @@ const GCodeFiles = () => {
Actions
-
- View
-
+
{
{
const location = useLocation()
const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId')
+
+ const { handleDownloadContent } = useContext(ApiServerContext)
const [collapseState, updateCollapseState] = useCollapseState(
'GCodeFileInfo',
{
@@ -36,154 +40,183 @@ const GCodeFileInfo = () => {
}
)
+ // Define actions that can be triggered via URL
+ const actions = {
+ download: () => {
+ if (gcodeFileId) {
+ handleDownloadContent(
+ gcodeFileId,
+ 'gcodeFile',
+ `gcodeFile-${gcodeFileId}.gcode`
+ )
+ }
+ }
+ }
+
return (
-
- {({
- loading,
- isEditing,
- startEditing,
- cancelEditing,
- handleUpdate,
- formValid,
- objectData,
- editLoading,
- lock,
- fetchObject
- }) => (
-
-
-
-
-
+ <>
+
+
+ {({
+ loading,
+ isEditing,
+ startEditing,
+ cancelEditing,
+ handleUpdate,
+ formValid,
+ objectData,
+ editLoading,
+ lock,
+ fetchObject
+ }) => (
+
+
+
+
+
+ },
+ {
+ label: 'Download GCode File',
+ key: 'download',
+ icon:
+ }
+ ],
+ onClick: ({ key }) => {
+ if (key === 'reload') {
+ fetchObject()
+ } else if (key === 'download' && gcodeFileId) {
+ handleDownloadContent(
+ gcodeFileId,
+ 'gcodefile',
+ `gcodefile-${gcodeFileId}.gcode`
+ )
+ }
}
- ],
- onClick: ({ key }) => {
- if (key === 'reload') {
- fetchObject()
- }
- }
- }}
- >
- Actions
-
-
+ Actions
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- }
- active={collapseState.info}
- onToggle={(expanded) => updateCollapseState('info', expanded)}
- key='info'
- >
- }
- isEditing={isEditing}
- items={getModelProperties('gcodeFile').map((prop) => ({
- ...prop,
- value: getPropertyValue(objectData, prop.name)
- }))}
- objectData={objectData}
- type='gcodefile'
- />
-
-
- }
- active={collapseState.preview}
- onToggle={(expanded) =>
- updateCollapseState('preview', expanded)
- }
- key='preview'
- >
-
- {objectData?.gcodeFileInfo?.thumbnail ? (
-
- ) : (
- n/a
- )}
-
-
-
- }
- active={collapseState.notes}
- onToggle={(expanded) => updateCollapseState('notes', expanded)}
- key='notes'
- >
-
-
-
-
-
- }
- active={collapseState.auditLogs}
- onToggle={(expanded) =>
- updateCollapseState('auditLogs', expanded)
- }
- key='auditLogs'
- >
-
-
-
-
- )}
-
+
+
+
+ }
+ active={collapseState.info}
+ onToggle={(expanded) => updateCollapseState('info', expanded)}
+ key='info'
+ >
+ }
+ isEditing={isEditing}
+ items={getModelProperties('gcodeFile').map((prop) => ({
+ ...prop,
+ value: getPropertyValue(objectData, prop.name)
+ }))}
+ objectData={objectData}
+ type='gcodefile'
+ />
+
+
+ }
+ active={collapseState.preview}
+ onToggle={(expanded) =>
+ updateCollapseState('preview', expanded)
+ }
+ key='preview'
+ >
+
+ {objectData?.gcodeFileInfo?.thumbnail ? (
+
+ ) : (
+ n/a
+ )}
+
+
+
+ }
+ active={collapseState.notes}
+ onToggle={(expanded) =>
+ updateCollapseState('notes', expanded)
+ }
+ key='notes'
+ >
+
+
+
+
+
+ }
+ active={collapseState.auditLogs}
+ onToggle={(expanded) =>
+ updateCollapseState('auditLogs', expanded)
+ }
+ key='auditLogs'
+ >
+
+
+
+
+
+ )}
+
+ >
)
}
diff --git a/src/components/Dashboard/Production/Jobs.jsx b/src/components/Dashboard/Production/Jobs.jsx
index 18c3506..2ee0074 100644
--- a/src/components/Dashboard/Production/Jobs.jsx
+++ b/src/components/Dashboard/Production/Jobs.jsx
@@ -41,8 +41,6 @@ import ListIcon from '../../Icons/ListIcon.jsx'
import GridIcon from '../../Icons/GridIcon.jsx'
import useViewMode from '../hooks/useViewMode.js'
-import config from '../../../config.js'
-
const { Text } = Typography
const Jobs = () => {
@@ -362,10 +360,6 @@ const Jobs = () => {
)
}
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
-
return (
<>
{notificationContextHolder}
@@ -396,8 +390,7 @@ const Jobs = () => {
diff --git a/src/components/Dashboard/Production/Jobs/JobInfo.jsx b/src/components/Dashboard/Production/Jobs/JobInfo.jsx
index f70bcbd..276929a 100644
--- a/src/components/Dashboard/Production/Jobs/JobInfo.jsx
+++ b/src/components/Dashboard/Production/Jobs/JobInfo.jsx
@@ -12,6 +12,7 @@ import EditObjectForm from '../../common/EditObjectForm'
import EditButtons from '../../common/EditButtons'
import LockIndicator from '../../Management/Filaments/LockIndicator'
import SubJobsTree from '../../common/SubJobsTree'
+import ActionHandler from '../../common/ActionHandler'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon'
import JobIcon from '../../../Icons/JobIcon'
import AuditLogIcon from '../../../Icons/AuditLogIcon'
@@ -32,143 +33,153 @@ const JobInfo = () => {
auditLogs: true
})
+ // Define actions that can be triggered via URL
+ const actions = {
+ // Add job-specific actions here as needed
+ }
+
return (
-
- {({
- loading,
- isEditing,
- startEditing,
- cancelEditing,
- handleUpdate,
- formValid,
- objectData,
- editLoading,
- lock,
- fetchObject
- }) => (
-
-
-
-
-
+ <>
+
+
+ {({
+ loading,
+ isEditing,
+ startEditing,
+ cancelEditing,
+ handleUpdate,
+ formValid,
+ objectData,
+ editLoading,
+ lock,
+ fetchObject
+ }) => (
+
+
+
+
+
+ }
+ ],
+ onClick: ({ key }) => {
+ if (key === 'reload') {
+ fetchObject()
+ }
}
- ],
- onClick: ({ key }) => {
- if (key === 'reload') {
- fetchObject()
- }
- }
- }}
- >
- Actions
-
-
+ Actions
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- }
- active={collapseState.info}
- onToggle={(expanded) => updateCollapseState('info', expanded)}
- key='info'
- >
- }
- isEditing={isEditing}
- type='job'
- items={getModelProperties('job').map((prop) => ({
- ...prop,
- value: getPropertyValue(objectData, prop.name)
- }))}
- />
-
-
- }
- active={collapseState.subJobs}
- onToggle={(expanded) =>
- updateCollapseState('subJobs', expanded)
- }
- key='subJobs'
- >
-
-
-
- }
- active={collapseState.notes}
- onToggle={(expanded) => updateCollapseState('notes', expanded)}
- key='notes'
- >
-
-
-
-
-
- }
- active={collapseState.auditLogs}
- onToggle={(expanded) =>
- updateCollapseState('auditLogs', expanded)
- }
- key='auditLogs'
- >
-
-
-
-
- )}
-
+
+
+
+ }
+ active={collapseState.info}
+ onToggle={(expanded) => updateCollapseState('info', expanded)}
+ key='info'
+ >
+ }
+ isEditing={isEditing}
+ type='job'
+ items={getModelProperties('job').map((prop) => ({
+ ...prop,
+ value: getPropertyValue(objectData, prop.name)
+ }))}
+ />
+
+
+ }
+ active={collapseState.subJobs}
+ onToggle={(expanded) =>
+ updateCollapseState('subJobs', expanded)
+ }
+ key='subJobs'
+ >
+
+
+
+ }
+ active={collapseState.notes}
+ onToggle={(expanded) =>
+ updateCollapseState('notes', expanded)
+ }
+ key='notes'
+ >
+
+
+
+
+
+ }
+ active={collapseState.auditLogs}
+ onToggle={(expanded) =>
+ updateCollapseState('auditLogs', expanded)
+ }
+ key='auditLogs'
+ >
+
+
+
+
+
+ )}
+
+ >
)
}
diff --git a/src/components/Dashboard/Production/Printers.jsx b/src/components/Dashboard/Production/Printers.jsx
index f88c1d7..518de6f 100644
--- a/src/components/Dashboard/Production/Printers.jsx
+++ b/src/components/Dashboard/Production/Printers.jsx
@@ -1,263 +1,31 @@
// src/Printers.js
import React, { useState, useContext, useRef } from 'react'
-import { useNavigate } from 'react-router-dom'
-import {
- Button,
- message,
- Dropdown,
- Space,
- Flex,
- Input,
- Tag,
- Modal,
- Popover,
- Checkbox
-} from 'antd'
+import { Button, message, Dropdown, Space, Flex, Modal } from 'antd'
import { AuthContext } from '../context/AuthContext'
-import PrinterState from '../common/PrinterState'
import NewPrinter from './Printers/NewPrinter'
-import IdDisplay from '../common/IdDisplay'
-import PrinterIcon from '../../Icons/PrinterIcon'
-import InfoCircleIcon from '../../Icons/InfoCircleIcon'
-import ControlIcon from '../../Icons/ControlIcon'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
-import XMarkIcon from '../../Icons/XMarkIcon'
-import CheckIcon from '../../Icons/CheckIcon'
import ObjectTable from '../common/ObjectTable'
+import ColumnViewButton from '../common/ColumnViewButton'
-import config from '../../../config'
import GridIcon from '../../Icons/GridIcon'
import ListIcon from '../../Icons/ListIcon'
import useViewMode from '../hooks/useViewMode'
+import useColumnVisibility from '../hooks/useColumnVisibility'
const Printers = () => {
const [messageApi, contextHolder] = message.useMessage()
const { authenticated } = useContext(AuthContext)
const [newPrinterOpen, setNewPrinterOpen] = useState(false)
- const navigate = useNavigate()
const tableRef = useRef()
// View mode state (cards/list), persisted in sessionStorage via custom hook
const [viewMode, setViewMode] = useViewMode('Printers')
- // Column definitions
- const columns = [
- {
- title: ,
- key: 'icon',
- width: 40,
- fixed: 'left',
- render: () =>
- },
- {
- title: 'Name',
- dataIndex: 'name',
- key: 'name',
- width: 200,
- fixed: 'left',
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'name'
- }),
- onFilter: (value, record) =>
- record.name.toLowerCase().includes(value.toLowerCase())
- },
- {
- title: 'ID',
- dataIndex: '_id',
- key: 'id',
- width: 180,
- render: (text) =>
- },
- {
- title: 'State',
- dataIndex: 'state',
- key: 'state',
- width: 240,
- render: (state) => {
- return (
-
- )
- }
- },
- {
- title: 'Tags',
- dataIndex: 'tags',
- key: 'tags',
- width: 170,
- render: (tags) => {
- if (!tags || !Array.isArray(tags)) return 'n/a'
- if (tags.length == 0) return 'n/a'
- return (
-
- {tags.map((tag, index) => (
-
- {tag}
-
- ))}
-
- )
- },
- filterDropdown: ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters
- }) =>
- getFilterDropdown({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName: 'tags'
- }),
- onFilter: (value, record) =>
- record.tags &&
- record.tags.some((tag) =>
- tag.toLowerCase().includes(value.toLowerCase())
- )
- },
- {
- title: 'Actions',
- key: 'actions',
- fixed: 'right',
- width: 150,
- render: (record) => {
- return (
-
- }
- onClick={() =>
- navigate(
- `/dashboard/production/printers/control?printerId=${record._id}`
- )
- }
- />
-
- Actions
-
-
- )
- }
- }
- ]
-
- const [columnVisibility, setColumnVisibility] = useState(
- columns.reduce((acc, col) => {
- if (col.key) {
- acc[col.key] = true
- }
- return acc
- }, {})
- )
-
- const getFilterDropdown = ({
- setSelectedKeys,
- selectedKeys,
- confirm,
- clearFilters,
- propertyName
- }) => {
- return (
-
-
-
- setSelectedKeys(e.target.value ? [e.target.value] : [])
- }
- onPressEnter={() => confirm()}
- style={{ width: 200, display: 'block' }}
- />
- {
- clearFilters()
- confirm()
- }}
- icon={}
- />
- confirm()}
- icon={}
- />
-
-
- )
- }
-
- const getPrinterActionItems = (printerId) => {
- return {
- items: [
- {
- label: 'Control',
- key: 'control',
- icon:
- },
- {
- type: 'divider'
- },
- {
- label: 'Info',
- key: 'info',
- icon:
- }
- ],
- onClick: ({ key }) => {
- if (key === 'info') {
- navigate(`/dashboard/production/printers/info?printerId=${printerId}`)
- } else if (key === 'control') {
- navigate(
- `/dashboard/production/printers/control?printerId=${printerId}`
- )
- }
- }
- }
- }
-
- const getViewDropdownItems = () => {
- const columnItems = columns
- .filter((col) => col.key && col.title !== '')
- .map((col) => (
- {
- setColumnVisibility((prev) => ({
- ...prev,
- [col.key]: e.target.checked
- }))
- }}
- >
- {col.title}
-
- ))
-
- return (
-
-
- {columnItems}
-
-
- )
- }
-
- const visibleColumns = columns.filter(
- (col) => !col.key || columnVisibility[col.key]
- )
+ // Column visibility state, persisted in sessionStorage via custom hook
+ const [columnVisibility, setColumnVisibility] = useColumnVisibility('printer')
const actionItems = {
items: [
@@ -291,13 +59,12 @@ const Printers = () => {
Actions
-
- View
-
+
{
{
+ const location = useLocation()
+ const navigate = useNavigate()
+ const action = new URLSearchParams(location.search).get(actionParam)
+
+ // Execute action and clear from URL
+ useEffect(() => {
+ if (action && actions[action]) {
+ // Execute the action
+ const result = actions[action]()
+
+ // Call optional callback
+ if (onAction) {
+ onAction(action, result)
+ }
+
+ // Clear action from URL if requested
+ if (clearAfterExecute) {
+ const searchParams = new URLSearchParams(location.search)
+ searchParams.delete(actionParam)
+ const newSearch = searchParams.toString()
+ const newPath = location.pathname + (newSearch ? `?${newSearch}` : '')
+ navigate(newPath, { replace: true })
+ }
+ }
+ }, [
+ action,
+ actions,
+ actionParam,
+ clearAfterExecute,
+ onAction,
+ location.pathname,
+ location.search,
+ navigate
+ ])
+
+ // Return null as this is a utility component
+ return null
+}
+
+ActionHandler.propTypes = {
+ actions: PropTypes.objectOf(PropTypes.func),
+ actionParam: PropTypes.string,
+ clearAfterExecute: PropTypes.bool,
+ onAction: PropTypes.func
+}
+
+export default ActionHandler
diff --git a/src/components/Dashboard/common/ColumnViewButton.jsx b/src/components/Dashboard/common/ColumnViewButton.jsx
new file mode 100644
index 0000000..6e5826f
--- /dev/null
+++ b/src/components/Dashboard/common/ColumnViewButton.jsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import ViewButton from './ViewButton'
+import { getModelByName } from '../../../database/ObjectModels'
+
+const ColumnViewButton = ({
+ type,
+ loading = false,
+ collapseState = {},
+ updateCollapseState = () => {},
+ ...buttonProps
+}) => {
+ // Get the model by name
+ const model = getModelByName(type)
+
+ // Get the properties that correspond to the model's columns
+ const columnProperties =
+ model.columns
+ ?.map((columnName) => {
+ const property = model.properties.find(
+ (prop) => prop.name === columnName
+ )
+ if (property) {
+ return {
+ key: property.name,
+ label: property.label
+ }
+ }
+ })
+ .filter(Boolean) || []
+
+ return (
+
+ )
+}
+
+ColumnViewButton.propTypes = {
+ type: PropTypes.string.isRequired,
+ loading: PropTypes.bool,
+ collapseState: PropTypes.object,
+ updateCollapseState: PropTypes.func
+}
+
+export default ColumnViewButton
diff --git a/src/components/Icons/EmailDisplay.jsx b/src/components/Dashboard/common/EmailDisplay.jsx
similarity index 87%
rename from src/components/Icons/EmailDisplay.jsx
rename to src/components/Dashboard/common/EmailDisplay.jsx
index 2d4a950..809539f 100644
--- a/src/components/Icons/EmailDisplay.jsx
+++ b/src/components/Dashboard/common/EmailDisplay.jsx
@@ -1,9 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Typography, Flex, Button, Tooltip } from 'antd'
-import NewMailIcon from './NewMailIcon'
+import NewMailIcon from '../../Icons/NewMailIcon'
// import CopyIcon from './CopyIcon'
-import CopyButton from '../Dashboard/common/CopyButton'
+import CopyButton from './CopyButton'
const { Text, Link } = Typography
@@ -19,7 +19,9 @@ const EmailDisplay = ({ email, showCopy = true, showLink = false }) => {
) : (
<>
- {email}
+
+ {email}
+
}
diff --git a/src/components/Dashboard/common/IdDisplay.jsx b/src/components/Dashboard/common/IdDisplay.jsx
index 689281f..2020e3b 100644
--- a/src/components/Dashboard/common/IdDisplay.jsx
+++ b/src/components/Dashboard/common/IdDisplay.jsx
@@ -23,10 +23,20 @@ const IdDisplay = ({
const model = getModelByName(type)
const prefix = model.prefix
- const hyperlink = model.url(id)
+
const IconComponent = model.icon
const icon =
+ var hyperlink = null
+
+ const defaultModelActions = model.actions.filter(
+ (action) => action.default == true
+ )
+
+ if (defaultModelActions.length >= 1) {
+ hyperlink = defaultModelActions[0].url(id)
+ }
+
if (!id) {
return n/a
}
@@ -41,79 +51,75 @@ const IdDisplay = ({
return (
- {showHyperlink &&
- (showSpotlight ? (
-
- ) : null
- }
- trigger={['hover', 'click']}
- placement='topLeft'
- arrow={false}
- style={{ padding: 0 }}
+ {(() => {
+ const content = (
+
+ {icon}
+ {displayId}
+
+ )
+
+ const textElement = (
+
+ {content}
+
+ )
+
+ // If hyperlink is enabled
+ if (showHyperlink && hyperlink != null) {
+ const linkElement = (
{
- if (showHyperlink) {
- navigate(hyperlink)
- }
- }}
+ onClick={() => navigate(hyperlink)}
style={{ marginRight: 6 }}
>
-
-
- {icon}
- {displayId}
-
-
+ {textElement}
-
- ) : (
- {
- if (showHyperlink) {
- navigate(hyperlink)
- }
- }}
- >
-
-
- {icon}
- {displayId}
-
-
-
- ))}
+ )
+
+ if (showSpotlight) {
+ return (
+
+ ) : null
+ }
+ trigger={['hover', 'click']}
+ placement='topLeft'
+ arrow={false}
+ style={{ padding: 0 }}
+ >
+ {linkElement}
+
+ )
+ }
+ return linkElement
+ }
+
+ // If hyperlink is disabled
+ if (showSpotlight) {
+ return (
+
+ ) : null
+ }
+ trigger={['hover', 'click']}
+ placement='topLeft'
+ arrow={false}
+ >
+ {textElement}
+
+ )
+ }
+ return textElement
+ })()}
- {!showHyperlink &&
- (showSpotlight ? (
-
- ) : null
- }
- trigger={['hover', 'click']}
- placement='topLeft'
- arrow={false}
- >
-
-
- {icon}
- {displayId}
-
-
-
- ) : (
-
-
- {icon}
- {displayId}
-
-
- ))}
{showCopy && (
{value}
+ return {value}
} else {
return n/a
}
@@ -156,7 +156,7 @@ const ObjectProperty = ({
}
case 'object': {
if (value && value.name) {
- return {value.name}
+ return {value.name}
} else {
return n/a
}
diff --git a/src/components/Dashboard/common/ObjectTable.jsx b/src/components/Dashboard/common/ObjectTable.jsx
index 2dc0e1e..4f0ffbd 100644
--- a/src/components/Dashboard/common/ObjectTable.jsx
+++ b/src/components/Dashboard/common/ObjectTable.jsx
@@ -15,32 +15,50 @@ import {
Col,
Descriptions,
Flex,
- Spin
+ Spin,
+ Button,
+ Input,
+ Space,
+ Tooltip
} from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'
import { useMediaQuery } from 'react-responsive'
-import axios from 'axios'
+import { useContext } from 'react'
+import { ApiServerContext } from '../context/ApiServerContext'
import config from '../../../config'
import loglevel from 'loglevel'
+import {
+ getModelProperties,
+ getModelByName,
+ getPropertyValue
+} from '../../../database/ObjectModels'
+import ObjectProperty from './ObjectProperty'
+import XMarkIcon from '../../Icons/XMarkIcon'
+import CheckIcon from '../../Icons/CheckIcon'
+import { useNavigate } from 'react-router-dom'
+import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
const logger = loglevel.getLogger('DasboardTable')
logger.setLevel(config.logLevel)
const ObjectTable = forwardRef(
(
{
- columns,
+ type,
url,
pageSize = 25,
scrollHeight = 'calc(var(--unit-100vh) - 270px)',
onDataChange,
authenticated,
initialPage = 1,
- cards = false
+ cards = false,
+ visibleColumns = {}
},
ref
) => {
+ const { fetchTableData } = useContext(ApiServerContext)
const isMobile = useMediaQuery({ maxWidth: 768 })
+ const navigate = useNavigate()
var adjustedScrollHeight = scrollHeight
if (isMobile) {
adjustedScrollHeight = 'calc(var(--unit-100vh) - 316px)'
@@ -50,8 +68,8 @@ const ObjectTable = forwardRef(
}
const [, contextHolder] = message.useMessage()
const tableRef = useRef(null)
- const [filters, setFilters] = useState({})
- const [sorter, setSorter] = useState({})
+ const [tableFilter, setTableFilter] = useState({})
+ const [tableSorter, setTableSorter] = useState({})
const [initialized, setInitialized] = useState(false)
// Table state
@@ -59,7 +77,6 @@ const ObjectTable = forwardRef(
const [hasMore, setHasMore] = useState(true)
const [loading, setLoading] = useState(true)
const [lazyLoading, setLazyLoading] = useState(false)
- const [totalPages, setTotalPages] = useState(0)
const createSkeletonData = useCallback(() => {
return Array(pageSize)
@@ -71,30 +88,31 @@ const ObjectTable = forwardRef(
}, [pageSize])
const fetchData = useCallback(
- async (pageNum = 1) => {
+ async (pageNum = 1, filter = null, sorter = null) => {
+ if (filter == null) {
+ filter = tableFilter
+ } else {
+ setTableFilter(filter)
+ }
+ if (sorter == null) {
+ sorter = tableSorter
+ } else {
+ setTableSorter({
+ field: sorter.field,
+ order: sorter.order
+ })
+ }
+ console.log('filter 2', filter)
try {
- const response = await axios.get(url, {
- params: {
- page: pageNum,
- limit: pageSize,
- ...filters,
- sort: sorter.field,
- order: sorter.order
- },
- headers: {
- Accept: 'application/json'
- },
- withCredentials: true
+ const result = await fetchTableData(type, {
+ page: pageNum,
+ limit: pageSize,
+ filter,
+ sorter,
+ onDataChange
})
- const newData = response.data
- const totalCount = parseInt(
- response.headers['x-total-count'] || '0',
- 10
- )
- setTotalPages(Math.ceil(totalCount / pageSize))
-
- setHasMore(newData.length >= pageSize)
+ setHasMore(result.hasMore)
setPages((prev) => {
const existingPageIndex = prev.findIndex(
@@ -104,20 +122,16 @@ const ObjectTable = forwardRef(
if (existingPageIndex !== -1) {
// Update existing page
const newPages = [...prev]
- newPages[existingPageIndex] = { pageNum, items: newData }
+ newPages[existingPageIndex] = { pageNum, items: result.data }
return newPages
}
// If page doesn't exist, return unchanged
return prev
})
- if (onDataChange) {
- onDataChange(newData)
- }
-
setLoading(false)
setLazyLoading(false)
- return newData
+ return result.data
} catch (error) {
setPages((prev) =>
prev.map((page) => ({
@@ -130,7 +144,7 @@ const ObjectTable = forwardRef(
throw error
}
},
- [url, pageSize, filters, sorter, onDataChange]
+ [url, pageSize, tableFilter, tableSorter, onDataChange, fetchTableData]
)
const loadNextPage = useCallback(() => {
@@ -233,40 +247,34 @@ const ObjectTable = forwardRef(
)
}, [])
- const goToPage = useCallback(
- (pageNum) => {
- if (pageNum > 0 && pageNum <= totalPages) {
- const pagesToLoad = [pageNum - 1, pageNum, pageNum + 1].filter(
- (p) => p > 0 && p <= totalPages
- )
- return Promise.all(pagesToLoad.map((p) => fetchData(p)))
+ const loadPage = useCallback(
+ async (pageNum, filter = null, sorter = null) => {
+ // Create initial page with skeletons
+ setPages([{ pageNum: pageNum, items: createSkeletonData() }])
+
+ const items = await fetchData(pageNum, filter, sorter)
+
+ if (items.length >= 25) {
+ setPages((prev) => [
+ ...prev,
+ { pageNum: pageNum + 1, items: createSkeletonData() }
+ ])
+ await fetchData(pageNum + 1, filter, sorter)
}
},
- [fetchData, totalPages]
+ [createSkeletonData, fetchData]
)
const loadInitialPage = useCallback(async () => {
- // Create initial page with skeletons
- setPages([{ pageNum: initialPage, items: createSkeletonData() }])
-
- const items = await fetchData(initialPage)
-
- if (items.length >= 25) {
- setPages((prev) => [
- ...prev,
- { pageNum: initialPage + 1, items: createSkeletonData() }
- ])
- await fetchData(initialPage + 1)
- }
- }, [initialPage, createSkeletonData, fetchData])
+ loadPage(initialPage)
+ }, [initialPage, loadPage])
useImperativeHandle(ref, () => ({
reload,
setData: (newData) => {
setPages([{ pageNum: 1, items: newData }])
},
- updateData,
- goToPage
+ updateData
}))
useEffect(() => {
@@ -276,33 +284,207 @@ const ObjectTable = forwardRef(
}
}, [authenticated, loadInitialPage, initialPage, pages, initialized])
+ const getFilterDropdown = ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ propertyName
+ }) => {
+ return (
+
+
+
+ setSelectedKeys(e.target.value ? [e.target.value] : [])
+ }
+ onPressEnter={() => confirm()}
+ style={{ width: 200, display: 'block' }}
+ />
+ {
+ clearFilters()
+ confirm()
+ }}
+ icon={}
+ />
+ confirm()}
+ icon={}
+ />
+
+
+ )
+ }
+
const handleTableChange = (pagination, filters, sorter) => {
const newFilters = {}
+
Object.entries(filters).forEach(([key, value]) => {
if (value && value.length > 0) {
newFilters[key] = value[0]
}
})
- setFilters(newFilters)
- setSorter({
+
+ setPages([])
+ setLoading(true)
+ loadPage(initialPage, newFilters, {
field: sorter.field,
order: sorter.order
})
- setPages([])
- fetchData(1)
}
- const columnsWithSkeleton = columns.map((col) => ({
- ...col,
- render: (text, record) => {
- if (record.isSkeleton) {
- return (
-
- )
- }
- return col.render ? col.render(text, record) : text
+ const modelProperties = getModelProperties(type)
+ const model = getModelByName(type)
+
+ // Table columns from model properties
+ const columnsWithSkeleton = [
+ {
+ title: model.icon,
+ key: 'icon',
+ width: 45,
+ fixed: 'left',
+ render: model.icon
}
- }))
+ ]
+
+ // Add columns in the order specified by model.columns
+ model.columns.forEach((colName) => {
+ const prop = modelProperties.find((p) => p.name === colName)
+ if (prop) {
+ // Check if column should be visible based on visibleColumns prop
+ if (
+ Object.keys(visibleColumns).length > 0 &&
+ visibleColumns[prop.name] === false
+ ) {
+ return // Skip this column if it's not visible
+ }
+
+ var fixed = prop.columnFixed || undefined
+ var width = 200
+
+ switch (prop.type) {
+ case 'text':
+ width = 200
+ break
+ case 'number':
+ width = 100
+ break
+ case 'dateTime':
+ width = 200
+ break
+ case 'state':
+ width = 200
+ break
+ case 'id':
+ width = 180
+ break
+ default:
+ break
+ }
+ // Check if this property should be filterable based on model.filters
+ const isFilterable = model.filters && model.filters.includes(prop.name)
+
+ // Check if this property should be sortable based on model.sorters
+ const isSortable = model.sorters && model.sorters.includes(prop.name)
+
+ const columnConfig = {
+ sorter: isSortable,
+ title: prop.label,
+ dataIndex: prop.name,
+ width: prop.columnWidth || width,
+ fixed: fixed,
+ key: prop.name,
+ render: (text, record) => {
+ if (record.isSkeleton) {
+ return (
+
+ )
+ }
+ return (
+
+ )
+ }
+ }
+
+ // Add filter configuration if the property is filterable
+ if (isFilterable) {
+ columnConfig.filterDropdown = ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters
+ }) =>
+ getFilterDropdown({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ propertyName: prop.label
+ })
+ // Remove local filtering - let the server handle it
+ columnConfig.filtered = false
+ }
+
+ columnsWithSkeleton.push(columnConfig)
+ }
+ })
+
+ if (model.actions.length > 0) {
+ const rowActions = model.actions.filter((action) => action.row == true)
+ if (rowActions.length > 0) {
+ columnsWithSkeleton.push({
+ title: (
+
+ {'Actions'}
+
+ ),
+ key: 'actions',
+ fixed: 'right',
+ width: 80 + rowActions.length * 40, // Adjust width based on number of actions
+ render: (record) => {
+ return (
+
+ {rowActions.map((action, index) => (
+
+
+ )
+ }
+ type={'text'}
+ size={'small'}
+ onClick={() => {
+ if (action.onClick) {
+ action.onClick(record)
+ } else if (action.url) {
+ navigate(action.url(record._id))
+ } else {
+ navigate(model.url(record._id))
+ }
+ }}
+ />
+
+ ))}
+
+ )
+ }
+ })
+ }
+ }
// Flatten pages array for table display
const tableData = pages.flatMap((page) => page.items)
@@ -355,72 +537,31 @@ const ObjectTable = forwardRef(
style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
ref={cardsContainerRef}
>
- {tableData.map((record) => {
- // Special case for columns[0] if needed
- let icon = null
- if (columns[0].key === 'icon' && columns[0].render) {
- const renderedIcon = columns[0].render()
- icon = React.cloneElement(renderedIcon, {
- style: {
- fontSize: 32,
- ...(renderedIcon.props.style || {})
- }
- })
- }
- let actions = null
- const endColumn = columns.length - 1
- if (
- columns[endColumn].key === 'actions' &&
- columns[endColumn].render
- ) {
- actions = columns[endColumn].render(record)
- }
- return (
- (
+
+
-
-
- {icon}
-
- {columns
- .filter(
- (col) => col.key !== 'icon' && col.key !== 'actions'
- )
- .map((col) => {
- let value
- if (col.render && col.dataIndex) {
- value = col.render(record[col.dataIndex], record)
- } else if (col.render && !col.dataIndex) {
- value = col.render(record)
- } else {
- value = String(record[col.dataIndex] ?? '')
- }
- return (
-
- {value}
-
- )
- })}
-
- {actions}
-
-
-
- )
- })}
+
+
+ {modelProperties.map((prop) => (
+
+
+
+ ))}
+
+
+
+
+ ))}
)
}
@@ -456,7 +597,7 @@ const ObjectTable = forwardRef(
ObjectTable.displayName = 'ObjectTable'
ObjectTable.propTypes = {
- columns: PropTypes.arrayOf(PropTypes.object).isRequired,
+ type: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
pageSize: PropTypes.number,
scrollHeight: PropTypes.string,
@@ -464,7 +605,8 @@ ObjectTable.propTypes = {
authenticated: PropTypes.bool.isRequired,
initialPage: PropTypes.number,
cards: PropTypes.bool,
- cardRenderer: PropTypes.func
+ cardRenderer: PropTypes.func,
+ visibleColumns: PropTypes.object
}
export default ObjectTable
diff --git a/src/components/Icons/UrlDisplay.jsx b/src/components/Dashboard/common/UrlDisplay.jsx
similarity index 94%
rename from src/components/Icons/UrlDisplay.jsx
rename to src/components/Dashboard/common/UrlDisplay.jsx
index 93089e9..926f1f7 100644
--- a/src/components/Icons/UrlDisplay.jsx
+++ b/src/components/Dashboard/common/UrlDisplay.jsx
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Typography, Flex, Button, Tooltip } from 'antd'
-import LinkIcon from './LinkIcon'
-import CopyButton from '../Dashboard/common/CopyButton'
+import LinkIcon from '../../Icons/LinkIcon'
+import CopyButton from './CopyButton'
const { Text, Link } = Typography
diff --git a/src/components/Dashboard/common/ViewButton.jsx b/src/components/Dashboard/common/ViewButton.jsx
index efcba58..307c632 100644
--- a/src/components/Dashboard/common/ViewButton.jsx
+++ b/src/components/Dashboard/common/ViewButton.jsx
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
const ViewButton = ({
loading = false,
- sections = [],
+ properties = [],
collapseState = {},
updateCollapseState = () => {},
...buttonProps
@@ -15,15 +15,15 @@ const ViewButton = ({
return (
- {sections.map((section) => (
+ {properties.map((property) => (
{
- updateCollapseState(section.key, e.target.checked)
+ updateCollapseState(property.key, e.target.checked)
}}
>
- {section.label}
+ {property.label}
))}
@@ -42,7 +42,7 @@ const ViewButton = ({
ViewButton.propTypes = {
loading: PropTypes.bool,
- sections: PropTypes.arrayOf(
+ properties: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string.isRequired,
label: PropTypes.string.isRequired
diff --git a/src/components/Dashboard/context/ApiServerContext.js b/src/components/Dashboard/context/ApiServerContext.js
index cbcc39d..44499bb 100644
--- a/src/components/Dashboard/context/ApiServerContext.js
+++ b/src/components/Dashboard/context/ApiServerContext.js
@@ -224,7 +224,7 @@ const ApiServerProvider = ({ children }) => {
// Update filament information
const updateObjectInfo = async (id, type, value) => {
- const updateUrl = `${config.backendUrl}/${type}s/${id}`
+ const updateUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
logger.debug('Updating info for ' + id)
try {
const response = await axios.put(updateUrl, value, {
@@ -249,6 +249,99 @@ const ApiServerProvider = ({ children }) => {
}
}
+ // Fetch table data with pagination, filtering, and sorting
+ const fetchTableData = async (type, params = {}) => {
+ const {
+ page = 1,
+ limit = 25,
+ filter = {},
+ sorter = {},
+ onDataChange
+ } = params
+
+ logger.debug('Fetching table data from:', type, {
+ page,
+ limit,
+ filter,
+ sorter
+ })
+
+ try {
+ const response = await axios.get(
+ `${config.backendUrl}/${type.toLowerCase()}s`,
+ {
+ params: {
+ page,
+ limit,
+ ...filter,
+ sort: sorter.field,
+ order: sorter.order
+ },
+ headers: {
+ Accept: 'application/json'
+ },
+ withCredentials: true
+ }
+ )
+
+ const newData = response.data
+ const totalCount = parseInt(response.headers['x-total-count'] || '0', 10)
+ const totalPages = Math.ceil(totalCount / limit)
+ const hasMore = newData.length >= limit
+
+ if (onDataChange) {
+ onDataChange(newData)
+ }
+
+ return {
+ data: newData,
+ totalCount,
+ totalPages,
+ hasMore,
+ page
+ }
+ } catch (error) {
+ logger.error('Failed to fetch table data:', error)
+ throw error
+ }
+ }
+
+ // Download GCode file content
+ const handleDownloadContent = async (id, type, fileName) => {
+ if (!token) {
+ return
+ }
+ try {
+ const response = await axios.get(
+ `${config.backendUrl}/${type.toLowerCase()}s/${id}/content`,
+ {
+ headers: {
+ Accept: 'application/json'
+ },
+ withCredentials: true
+ }
+ )
+
+ const fileURL = window.URL.createObjectURL(new Blob([response.data]))
+ const fileLink = document.createElement('a')
+ fileLink.href = fileURL
+ fileLink.setAttribute('download', fileName)
+ document.body.appendChild(fileLink)
+ fileLink.click()
+ fileLink.parentNode.removeChild(fileLink)
+ } catch (error) {
+ logger.error('Failed to download GCode file content:', error)
+ if (error.response) {
+ messageApi.error('Error downloading GCode file:', error.response.status)
+ } else {
+ messageApi.error(
+ 'An unexpected error occurred while downloading. Please try again later.'
+ )
+ }
+ throw error
+ }
+ }
+
return (
{
onUpdateEvent,
offUpdateEvent,
fetchObjectInfo,
+ fetchTableData,
fetchLoading,
- showError
+ showError,
+ handleDownloadContent
}}
>
{contextHolder}
diff --git a/src/components/Dashboard/hooks/useColumnVisibility.js b/src/components/Dashboard/hooks/useColumnVisibility.js
index 5051889..e3d49b1 100644
--- a/src/components/Dashboard/hooks/useColumnVisibility.js
+++ b/src/components/Dashboard/hooks/useColumnVisibility.js
@@ -1,15 +1,21 @@
import { useState, useEffect } from 'react'
+import { getModelByName } from '../../../database/ObjectModels'
-const useColumnVisibility = (componentName, columns) => {
+const useColumnVisibility = (type) => {
const getInitialVisibility = () => {
- const stored = sessionStorage.getItem(`${componentName}_columnVisibility`)
+ const stored = sessionStorage.getItem(`${type}_columnVisibility`)
if (stored) {
return JSON.parse(stored)
}
// Default visibility - all columns visible
- return columns.reduce((acc, col) => {
- if (col.key) {
- acc[col.key] = true
+ const model = getModelByName(type)
+ const columns = model.columns || []
+ return columns.reduce((acc, columnName) => {
+ const property = model.properties?.find(
+ (prop) => prop.name === columnName
+ )
+ if (property) {
+ acc[property.name] = true
}
return acc
}, {})
@@ -19,10 +25,10 @@ const useColumnVisibility = (componentName, columns) => {
useEffect(() => {
sessionStorage.setItem(
- `${componentName}_columnVisibility`,
+ `${type}_columnVisibility`,
JSON.stringify(columnVisibility)
)
- }, [columnVisibility, componentName])
+ }, [columnVisibility, type])
const updateColumnVisibility = (key, value) => {
setColumnVisibility((prev) => ({
diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js
index f5b1e91..0fb8e26 100644
--- a/src/database/ObjectModels.js
+++ b/src/database/ObjectModels.js
@@ -4,6 +4,7 @@ import { Spool } from './models/Spool'
import { GCodeFile } from './models/GCodeFile'
import { Job } from './models/Job'
import { Product } from './models/Product'
+import { Part } from './models/Part.js'
import { Vendor } from './models/Vendor'
import { SubJob } from './models/SubJob'
import { Initial } from './models/Initial'
@@ -25,6 +26,7 @@ export const objectModels = [
GCodeFile,
Job,
Product,
+ Part,
Vendor,
SubJob,
Initial,
@@ -47,6 +49,7 @@ export {
GCodeFile,
Job,
Product,
+ Part,
Vendor,
SubJob,
Initial,
diff --git a/src/database/models/AuditLog.js b/src/database/models/AuditLog.js
index 00b2b12..ac868dc 100644
--- a/src/database/models/AuditLog.js
+++ b/src/database/models/AuditLog.js
@@ -1,9 +1,20 @@
import AuditLogIcon from '../../components/Icons/AuditLogIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const AuditLog = {
name: 'auditlog',
label: 'Audit Log',
prefix: 'ADL',
icon: AuditLogIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/auditlogs/info?auditLogId=${_id}`
+ }
+ ],
url: () => `#`
}
diff --git a/src/database/models/Filament.js b/src/database/models/Filament.js
index 839f9fa..1dae231 100644
--- a/src/database/models/Filament.js
+++ b/src/database/models/Filament.js
@@ -1,16 +1,41 @@
import FilamentIcon from '../../components/Icons/FilamentIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Filament = {
name: 'filament',
label: 'Filament',
prefix: 'FIL',
icon: FilamentIcon,
- url: (id) => `/dashboard/management/filaments/info?filamentId=${id}`,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/filaments/info?filamentId=${_id}`
+ }
+ ],
+ columns: [
+ '_id',
+ 'name',
+ 'type',
+ 'color',
+ 'vendor',
+ 'vendor._id',
+ 'cost',
+ 'density',
+ 'diameter',
+ 'createdAt',
+ 'updatedAt'
+ ],
+ filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor', 'vendor._id'],
+ sorters: ['name', 'createdAt', 'type', 'vendor', 'cost', 'updatedAt'],
properties: [
{
name: '_id',
label: 'ID',
-
+ columnFixed: 'left',
type: 'id',
objectType: 'filament',
showCopy: true
@@ -18,28 +43,25 @@ export const Filament = {
{
name: 'createdAt',
label: 'Created At',
-
type: 'dateTime',
readOnly: true
},
{
name: 'name',
label: 'Name',
-
+ columnFixed: 'left',
required: true,
type: 'text'
},
{
name: 'updatedAt',
label: 'Updated At',
-
type: 'dateTime',
readOnly: true
},
{
name: 'vendor',
label: 'Vendor',
-
required: true,
type: 'object',
objectType: 'vendor'
@@ -55,34 +77,35 @@ export const Filament = {
{
name: 'type',
label: 'Material',
-
required: true,
+ columnWidth: 150,
type: 'material'
},
{
name: 'cost',
label: 'Cost',
-
+ columnWidth: 150,
required: true,
type: 'currency'
},
{
name: 'color',
label: 'Color',
-
+ columnWidth: 150,
required: true,
type: 'color'
},
{
name: 'diameter',
label: 'Diameter',
-
+ columnWidth: 150,
required: true,
type: 'mm'
},
{
name: 'density',
label: 'Density',
+ columnWidth: 150,
required: true,
type: 'density'
},
diff --git a/src/database/models/FilamentStock.js b/src/database/models/FilamentStock.js
index 1b490a3..a31a2ad 100644
--- a/src/database/models/FilamentStock.js
+++ b/src/database/models/FilamentStock.js
@@ -1,9 +1,21 @@
import FilamentStockIcon from '../../components/Icons/FilamentStockIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const FilamentStock = {
name: 'filamentstock',
label: 'Filament Stock',
prefix: 'FLS',
icon: FilamentStockIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) =>
+ `/dashboard/inventory/filamentstocks/info?filamentStockId=${_id}`
+ }
+ ],
url: (id) => `/dashboard/inventory/filamentstocks/info?filamentStockId=${id}`
}
diff --git a/src/database/models/GCodeFile.js b/src/database/models/GCodeFile.js
index 905e9a0..96b526b 100644
--- a/src/database/models/GCodeFile.js
+++ b/src/database/models/GCodeFile.js
@@ -1,17 +1,49 @@
import GCodeFileIcon from '../../components/Icons/GCodeFileIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const GCodeFile = {
name: 'gcodeFile',
label: 'GCode File',
prefix: 'GCF',
icon: GCodeFileIcon,
- url: (id) => `/dashboard/production/gcodefiles/info?gcodeFileId=${id}`,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/production/gcodefiles/info?gcodeFileId=${_id}`
+ },
+ {
+ name: 'download',
+ label: 'Download',
+ row: true,
+ url: (_id) =>
+ `/dashboard/production/gcodefiles/info?gcodeFileId=${_id}&action=download`
+ }
+ ],
+
+ columns: [
+ 'name',
+ '_id',
+ 'filament',
+ 'gcodeFileInfo.estimatedPrintingTimeNormalMode',
+ 'gcodeFileInfo.sparseInfillDensity',
+ 'gcodeFileInfo.sparseInfillPattern',
+ 'gcodeFileInfo.nozzleTemperature',
+ 'gcodeFileInfo.hotPlateTemp',
+ 'updatedAt'
+ ],
+ filters: ['_id', 'name', 'updatedAt'],
+ sorters: ['name', 'createdAt', 'updatedAt'],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
- objectType: 'gcodefile',
+ objectType: 'gcodeFile',
+ columnFixed: 'left',
value: null,
showCopy: true
},
@@ -25,6 +57,7 @@ export const GCodeFile = {
{
name: 'name',
label: 'Name',
+ columnFixed: 'left',
type: 'text',
value: null,
required: true
@@ -61,6 +94,7 @@ export const GCodeFile = {
{
name: 'gcodeFileInfo.sparseInfillDensity',
label: 'Infill Density',
+ columnWidth: 150,
type: 'number',
readOnly: true
},
@@ -86,14 +120,16 @@ export const GCodeFile = {
},
{
name: 'gcodeFileInfo.nozzleTemperature',
- label: 'Hotend Temperature',
+ label: 'Hotend Temp',
+ columnWidth: 150,
value: null,
type: 'number',
readOnly: true
},
{
name: 'gcodeFileInfo.hotPlateTemp',
- label: 'Bed Temperature',
+ label: 'Bed Temp',
+ columnWidth: 150,
value: null,
type: 'number',
readOnly: true
diff --git a/src/database/models/Initial.js b/src/database/models/Initial.js
index e1c3b0a..ea53200 100644
--- a/src/database/models/Initial.js
+++ b/src/database/models/Initial.js
@@ -1,9 +1,20 @@
import QuestionCircleIcon from '../../components/Icons/QuestionCircleIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Initial = {
name: 'initial',
label: 'Initial',
prefix: 'INT',
icon: QuestionCircleIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/initials/info?initialId=${_id}`
+ }
+ ],
url: () => `#`
}
diff --git a/src/database/models/Job.js b/src/database/models/Job.js
index d1ad0b8..002577f 100644
--- a/src/database/models/Job.js
+++ b/src/database/models/Job.js
@@ -1,16 +1,37 @@
import JobIcon from '../../components/Icons/JobIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Job = {
name: 'job',
label: 'Job',
prefix: 'JOB',
icon: JobIcon,
- url: (id) => `/dashboard/production/jobs/info?jobId=${id}`,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/production/jobs/info?jobId=${_id}`
+ }
+ ],
+ columns: [
+ '_id',
+ 'gcodeFile',
+ 'gcodeFile._id',
+ 'state',
+ 'quantity',
+ 'createdAt'
+ ],
+ filters: ['state', '_id', 'gcodeFile._id', 'quantity'],
+ sorters: ['createdAt', 'state', 'quantity', '_id'],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
+ columnFixed: 'left',
objectType: 'job',
showCopy: true
},
@@ -23,12 +44,14 @@ export const Job = {
showProgress: true,
showId: false,
showQuantity: false,
+ columnWidth: 150,
readOnly: true
},
{
name: 'gcodeFile',
label: 'GCode File',
type: 'object',
+ columnFixed: 'left',
objectType: 'gcodeFile',
readOnly: true
},
@@ -43,6 +66,7 @@ export const Job = {
name: 'quantity',
label: 'Quantity',
type: 'number',
+ columnWidth: 125,
readOnly: true
},
{
diff --git a/src/database/models/Note.js b/src/database/models/Note.js
index 72d2615..4544c43 100644
--- a/src/database/models/Note.js
+++ b/src/database/models/Note.js
@@ -1,9 +1,20 @@
import NoteIcon from '../../components/Icons/NoteIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Note = {
name: 'note',
label: 'Note',
prefix: 'NTE',
icon: NoteIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/notes/info?noteId=${_id}`
+ }
+ ],
url: () => `#`
}
diff --git a/src/database/models/NoteType.js b/src/database/models/NoteType.js
index 68049af..dc1cc05 100644
--- a/src/database/models/NoteType.js
+++ b/src/database/models/NoteType.js
@@ -1,9 +1,59 @@
import NoteTypeIcon from '../../components/Icons/NoteTypeIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const NoteType = {
- name: 'notetype',
+ name: 'noteType',
label: 'Note Type',
prefix: 'NTY',
icon: NoteTypeIcon,
- url: (id) => `/dashboard/management/notetypes/info?noteTypeId=${id}`
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/notetypes/info?noteTypeId=${_id}`
+ }
+ ],
+ columns: ['name', '_id', 'color', 'active', 'createdAt', 'updatedAt'],
+ filters: ['name', '_id', 'color', 'active'],
+ sorters: ['name', 'color', 'active', 'createdAt', 'updatedAt'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ type: 'id',
+ objectType: 'noteType',
+ showCopy: true
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ required: true,
+ type: 'text'
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'color',
+ label: 'Color',
+ type: 'color'
+ },
+ {
+ name: 'active',
+ label: 'Active',
+ type: 'bool'
+ }
+ ]
}
diff --git a/src/database/models/Part.js b/src/database/models/Part.js
new file mode 100644
index 0000000..1652474
--- /dev/null
+++ b/src/database/models/Part.js
@@ -0,0 +1,64 @@
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
+import PartIcon from '../../components/Icons/PartIcon'
+
+export const Part = {
+ name: 'part',
+ label: 'Part',
+ prefix: 'PRT',
+ icon: PartIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/parts/info?partId=${_id}`
+ }
+ ],
+ columns: ['name', '_id', 'product', 'product._id', 'createdAt'],
+ filters: ['name', '_id', 'product', 'product._id'],
+ sorters: ['name', 'email', 'role', 'createdAt', '_id'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ columnFixed: 'left',
+ type: 'id',
+ objectType: 'user',
+ showCopy: true
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ columnFixed: 'left',
+ required: true,
+ type: 'text'
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true
+ },
+
+ {
+ name: 'product',
+ label: 'Product',
+ type: 'object',
+ objectType: 'product'
+ },
+ {
+ name: 'product._id',
+ label: 'Product ID',
+ type: 'id',
+ objectType: 'product'
+ }
+ ]
+}
diff --git a/src/database/models/PartStock.js b/src/database/models/PartStock.js
index f5c5f1b..9355acd 100644
--- a/src/database/models/PartStock.js
+++ b/src/database/models/PartStock.js
@@ -1,9 +1,20 @@
import PartStockIcon from '../../components/Icons/PartStockIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const PartStock = {
name: 'partstock',
label: 'Part Stock',
prefix: 'PTS',
icon: PartStockIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/partstocks/info?partStockId=${_id}`
+ }
+ ],
url: (id) => `/dashboard/management/partstocks/info?partStockId=${id}`
}
diff --git a/src/database/models/Printer.js b/src/database/models/Printer.js
index 9a3b13f..d384225 100644
--- a/src/database/models/Printer.js
+++ b/src/database/models/Printer.js
@@ -1,11 +1,25 @@
import PrinterIcon from '../../components/Icons/PrinterIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Printer = {
name: 'printer',
label: 'Printer',
prefix: 'PRN',
icon: PrinterIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/production/printers/info?printerId=${_id}`
+ }
+ ],
url: (id) => `/dashboard/production/printers/info?printerId=${id}`,
+ columns: ['name', '_id', 'state', 'tags', 'connectedAt'],
+ filters: ['name', '_id', 'state', 'tags'],
+ sorters: ['name', 'state', 'connectedAt', '_id'],
properties: [
{
name: '_id',
@@ -24,7 +38,9 @@ export const Printer = {
name: 'name',
label: 'Name',
required: true,
- type: 'text'
+ type: 'text',
+ columnWidth: 200,
+ columnFixed: 'left'
},
{
name: 'state',
diff --git a/src/database/models/Product.js b/src/database/models/Product.js
index ef1c150..ac0c75a 100644
--- a/src/database/models/Product.js
+++ b/src/database/models/Product.js
@@ -1,9 +1,47 @@
import ProductIcon from '../../components/Icons/ProductIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Product = {
name: 'product',
label: 'Product',
prefix: 'PRD',
icon: ProductIcon,
- url: (id) => `/dashboard/management/products/info?productId=${id}`
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/products/info?productId=${_id}`
+ }
+ ],
+ url: (id) => `/dashboard/management/products/info?productId=${id}`,
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ type: 'id',
+ objectType: 'printer',
+ showCopy: true
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ required: true,
+ type: 'text'
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true
+ }
+ ]
}
diff --git a/src/database/models/ProductStock.js b/src/database/models/ProductStock.js
index 2c9b66a..cef7cb0 100644
--- a/src/database/models/ProductStock.js
+++ b/src/database/models/ProductStock.js
@@ -1,9 +1,21 @@
import ProductStockIcon from '../../components/Icons/ProductStockIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const ProductStock = {
name: 'productstock',
label: 'Product Stock',
prefix: 'PDS',
icon: ProductStockIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) =>
+ `/dashboard/management/productstocks/info?productStockId=${_id}`
+ }
+ ],
url: (id) => `/dashboard/management/productstocks/info?productStockId=${id}`
}
diff --git a/src/database/models/Spool.js b/src/database/models/Spool.js
index 6d348ca..560d062 100644
--- a/src/database/models/Spool.js
+++ b/src/database/models/Spool.js
@@ -1,9 +1,20 @@
import FilamentIcon from '../../components/Icons/FilamentIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Spool = {
name: 'spool',
label: 'Spool',
prefix: 'SPL',
icon: FilamentIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/inventory/spool/info?spoolId=${_id}`
+ }
+ ],
url: (id) => `/dashboard/inventory/spool/info?spoolId=${id}`
}
diff --git a/src/database/models/StockAudit.js b/src/database/models/StockAudit.js
index f124222..0c95b1b 100644
--- a/src/database/models/StockAudit.js
+++ b/src/database/models/StockAudit.js
@@ -1,9 +1,20 @@
import StockAuditIcon from '../../components/Icons/StockAuditIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const StockAudit = {
name: 'stockaudit',
label: 'Stock Audit',
prefix: 'SAU',
icon: StockAuditIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${_id}`
+ }
+ ],
url: (id) => `/dashboard/inventory/stockaudits/info?stockAuditId=${id}`
}
diff --git a/src/database/models/StockEvent.js b/src/database/models/StockEvent.js
index 73af09c..bd8a85d 100644
--- a/src/database/models/StockEvent.js
+++ b/src/database/models/StockEvent.js
@@ -1,9 +1,20 @@
import StockEventIcon from '../../components/Icons/StockEventIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const StockEvent = {
name: 'stockevent',
label: 'Stock Event',
prefix: 'SEV',
icon: StockEventIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/inventory/stockevents/info?stockEventId=${_id}`
+ }
+ ],
url: () => `#`
}
diff --git a/src/database/models/SubJob.js b/src/database/models/SubJob.js
index f49291e..af1186b 100644
--- a/src/database/models/SubJob.js
+++ b/src/database/models/SubJob.js
@@ -1,9 +1,20 @@
import SubJobIcon from '../../components/Icons/SubJobIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const SubJob = {
name: 'subjob',
label: 'Sub Job',
prefix: 'SJB',
icon: SubJobIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/production/subjobs/info?subJobId=${_id}`
+ }
+ ],
url: () => `#`
}
diff --git a/src/database/models/User.js b/src/database/models/User.js
index 276521c..d72f529 100644
--- a/src/database/models/User.js
+++ b/src/database/models/User.js
@@ -1,9 +1,75 @@
import PersonIcon from '../../components/Icons/PersonIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const User = {
name: 'user',
label: 'User',
prefix: 'USR',
icon: PersonIcon,
- url: (id) => `/dashboard/management/users/info?userId=${id}`
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/users/info?userId=${_id}`
+ }
+ ],
+ url: (id) => `/dashboard/management/users/info?userId=${id}`,
+ columns: ['name', '_id', 'username', 'email', 'role', 'createdAt'],
+ filters: ['name', '_id', 'email', 'role'],
+ sorters: ['name', 'email', 'role', 'createdAt', '_id'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ columnFixed: 'left',
+ type: 'id',
+ objectType: 'user',
+ showCopy: true
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ columnFixed: 'left',
+ required: true,
+ type: 'text'
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true
+ },
+
+ {
+ name: 'firstName',
+ label: 'First Name',
+ type: 'text'
+ },
+ {
+ name: 'username',
+ label: 'Username',
+ required: true,
+ type: 'text'
+ },
+ {
+ name: 'lastName',
+ label: 'Last Name',
+ type: 'text'
+ },
+ {
+ name: 'email',
+ label: 'Email',
+ columnWidth: 300,
+ type: 'email'
+ }
+ ]
}
diff --git a/src/database/models/Vendor.js b/src/database/models/Vendor.js
index 7331400..9ce6131 100644
--- a/src/database/models/Vendor.js
+++ b/src/database/models/Vendor.js
@@ -1,16 +1,30 @@
import VendorIcon from '../../components/Icons/VendorIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Vendor = {
name: 'vendor',
label: 'Vendor',
prefix: 'VEN',
icon: VendorIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/vendors/info?vendorId=${_id}`
+ }
+ ],
url: (id) => `/dashboard/management/vendors/info?vendorId=${id}`,
+ columns: ['name', '_id', 'country', 'email', 'createdAt'],
+ filters: ['name', '_id', 'country', 'email'],
+ sorters: ['name', 'country', 'email', 'createdAt', '_id'],
properties: [
{
name: '_id',
label: 'ID',
-
+ columnFixed: 'left',
type: 'id',
objectType: 'vendor',
showCopy: true
@@ -18,13 +32,13 @@ export const Vendor = {
{
name: 'createdAt',
label: 'Created At',
-
type: 'dateTime',
readOnly: true
},
{
name: 'name',
label: 'Name',
+ columnFixed: 'left',
required: true,
type: 'text'
},
@@ -51,6 +65,7 @@ export const Vendor = {
{
name: 'email',
label: 'Email',
+ columnWidth: 300,
type: 'email',
readOnly: false,
required: false
@@ -65,6 +80,7 @@ export const Vendor = {
{
name: 'website',
label: 'Website',
+ columnWidth: 300,
type: 'url',
readOnly: false,
required: false