From 6870320ab4c1e626dafa14ef287932357f8c6928 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 8 Mar 2026 01:28:09 +0000 Subject: [PATCH] Implemented materials. --- .../Dashboard/Management/Materials.jsx | 300 ++++-------------- .../Management/Materials/MaterialInfo.jsx | 199 ++++++++++++ .../Management/Materials/NewMaterial.jsx | 8 +- .../Dashboard/common/ObjectProperty.jsx | 28 -- .../Dashboard/context/HistoryContext.jsx | 14 +- src/database/ObjectModels.js | 3 + src/database/models/Filament.js | 14 +- src/database/models/Material.js | 117 +++++++ src/routes/ManagementRoutes.jsx | 6 + 9 files changed, 409 insertions(+), 280 deletions(-) create mode 100644 src/components/Dashboard/Management/Materials/MaterialInfo.jsx create mode 100644 src/database/models/Material.js diff --git a/src/components/Dashboard/Management/Materials.jsx b/src/components/Dashboard/Management/Materials.jsx index 8bb8694..0623da2 100644 --- a/src/components/Dashboard/Management/Materials.jsx +++ b/src/components/Dashboard/Management/Materials.jsx @@ -1,218 +1,26 @@ -// src/materials.js - -import { useEffect, useState, useContext, useCallback } from 'react' -import { useNavigate } from 'react-router-dom' -import axios from 'axios' -import { - Table, - Button, - Flex, - Space, - Modal, - Dropdown, - Spin -} from 'antd' -import { createStyles } from 'antd-style' -import { LoadingOutlined } from '@ant-design/icons' -import { useMessageContext } from '../context/MessageContext' - -import { AuthContext } from '../context/AuthContext' +import { useRef, useState } from 'react' +import { Button, Flex, Space, Modal, Dropdown } from 'antd' import NewMaterial from './Materials/NewMaterial' -import IdDisplay from '../common/IdDisplay' -import MaterialIcon from '../../Icons/MaterialIcon' -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 TimeDisplay from '../common/TimeDisplay' - -import config from '../../../config' - -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' +import ExportListButton from '../common/ExportListButton' const Materials = () => { - const { showError } = useMessageContext() - const navigate = useNavigate() - const { styles } = useStyle() - - const [materialsData, setMaterialsData] = useState([]) - const [page, setPage] = useState(1) - const [hasMore, setHasMore] = useState(true) - const [loading, setLoading] = useState(true) - const [lazyLoading, setLazyLoading] = useState(false) const [newMaterialOpen, setNewMaterialOpen] = useState(false) + const tableRef = useRef() - const { authenticated } = useContext(AuthContext) + const [viewMode, setViewMode] = useViewMode('material') - const fetchMaterialsData = useCallback( - async (pageNum = 1, append = false) => { - try { - const response = await axios.get(`${config.backendUrl}/materials`, { - params: { - page: pageNum, - limit: 25 - }, - headers: { - Accept: 'application/json' - }, - withCredentials: true - }) - - const newData = response.data - setHasMore(newData.length === 25) // If we get less than 25 items, we've reached the end - - if (append) { - setMaterialsData((prev) => [...prev, ...newData]) - } else { - setMaterialsData(newData) - } - - setLoading(false) - setLazyLoading(false) - } catch (error) { - if (error.response) { - showError( - `Error updating material details: ${error.response.status}` - ) - } else { - showError( - 'An unexpected error occurred. Please try again later.' - ) - } - setLoading(false) - setLazyLoading(false) - } - }, - [showError] - ) - - useEffect(() => { - if (authenticated) { - fetchMaterialsData() - } - }, [authenticated, fetchMaterialsData]) - - const handleScroll = useCallback( - (e) => { - const { target } = e - const scrollHeight = target.scrollHeight - const scrollTop = target.scrollTop - const clientHeight = target.clientHeight - - // If we're near the bottom (within 100px) and not currently loading - if ( - scrollHeight - scrollTop - clientHeight < 100 && - !lazyLoading && - hasMore - ) { - setLazyLoading(true) - const nextPage = page + 1 - setPage(nextPage) - fetchMaterialsData(nextPage, true) - } - }, - [page, lazyLoading, hasMore, fetchMaterialsData] - ) - - const getMaterialActionItems = (id) => { - return { - items: [ - { - label: 'Info', - key: 'info', - icon: - } - ], - onClick: ({ key }) => { - if (key === 'info') { - navigate(`/dashboard/management/materials/info?materialId=${id}`) - } - } - } - } - - const columns = [ - { - title: '', - dataIndex: '', - key: 'icon', - width: 40, - fixed: 'left', - render: () => - }, - { - title: 'Name', - dataIndex: 'name', - key: 'name', - width: 200, - fixed: 'left' - }, - { - title: 'ID', - dataIndex: '_id', - key: 'id', - width: 180, - render: (text) => - }, - { - title: 'Category', - dataIndex: 'category', - key: 'category', - width: 150 - }, - { - title: 'Created At', - dataIndex: 'createdAt', - key: 'createdAt', - width: 180, - render: (createdAt) => { - if (createdAt) { - return - } else { - return 'n/a' - } - } - }, - { - title: 'Actions', - key: 'actions', - fixed: 'right', - width: 150, - render: (text, record) => { - return ( - - - - - ) - } - } - ] + const [columnVisibility, setColumnVisibility] = + useColumnVisibility('material') const actionItems = { items: [ @@ -230,7 +38,7 @@ const Materials = () => { ], onClick: ({ key }) => { if (key === 'reloadList') { - fetchMaterialsData() + tableRef.current?.reload() } else if (key === 'newMaterial') { setNewMaterialOpen(true) } @@ -240,43 +48,53 @@ const Materials = () => { return ( <> - - - - - - }} - scroll={{ y: 'calc(100vh - 270px)' }} - onScroll={handleScroll} + + + + + + + + + +