diff --git a/src/App.jsx b/src/App.jsx
index 549170d..46e1028 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -174,7 +174,7 @@ const AppContent = () => {
path='*'
element={
}
diff --git a/src/components/Dashboard/Inventory/FilamentStocks.jsx b/src/components/Dashboard/Inventory/FilamentStocks.jsx
index 600ea1f..68baf6e 100644
--- a/src/components/Dashboard/Inventory/FilamentStocks.jsx
+++ b/src/components/Dashboard/Inventory/FilamentStocks.jsx
@@ -11,7 +11,11 @@ import {
Modal,
message,
Dropdown,
- Typography
+ Typography,
+ Popover,
+ Checkbox,
+ Input,
+ Spin
} from 'antd'
import { createStyles } from 'antd-style'
import { LoadingOutlined } from '@ant-design/icons'
@@ -27,6 +31,9 @@ import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
import FilamentStockState from '../common/FilamentStockState'
import TimeDisplay from '../common/TimeDisplay'
+import XMarkIcon from '../../Icons/XMarkIcon'
+import CheckIcon from '../../Icons/CheckIcon'
+import useColumnVisibility from '../hooks/useColumnVisibility'
import config from '../../../config'
@@ -57,38 +64,105 @@ const FilamentStocks = () => {
const { socket } = useContext(SocketContext)
const [filamentStocksData, setFilamentStocksData] = useState([])
+ const [page, setPage] = useState(1)
+ const [hasMore, setHasMore] = useState(true)
+ const [loading, setLoading] = useState(true)
+ const [lazyLoading, setLazyLoading] = useState(false)
+
+ const [filters, setFilters] = useState({})
+ const [sorter, setSorter] = useState({
+ field: 'createdAt',
+ order: 'descend'
+ })
const [newFilamentStockOpen, setNewFilamentStockOpen] = useState(false)
-
- const [loading, setLoading] = useState(true)
const [initialized, setInitialized] = useState(false)
const { authenticated } = useContext(AuthContext)
- const fetchFilamentStocksData = useCallback(async () => {
- try {
- const response = await axios.get(`${config.backendUrl}/filamentstocks`, {
- headers: {
- Accept: 'application/json'
- },
- withCredentials: true // Important for including cookies
- })
- setFilamentStocksData(response.data)
- setLoading(false)
- } catch (err) {
- messageApi.info(err)
- }
- }, [messageApi])
+ const getFilterDropdown = ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ propertyName
+ }) => {
+ return (
+
+
+
+ setSelectedKeys(e.target.value ? [e.target.value] : [])
+ }
+ onPressEnter={() => confirm()}
+ style={{ width: 200, display: 'block' }}
+ />
+
+
+ )
+ }
+
+ const fetchFilamentStocksData = useCallback(
+ async (pageNum = 1, append = false) => {
+ try {
+ const response = await axios.get(
+ `${config.backendUrl}/filamentstocks`,
+ {
+ 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) {
+ setFilamentStocksData((prev) => [...prev, ...newData])
+ } else {
+ setFilamentStocksData(newData)
+ }
+
+ setLoading(false)
+ setLazyLoading(false)
+ } catch (err) {
+ messageApi.error('Error fetching filament stocks:', err)
+ setLoading(false)
+ setLazyLoading(false)
+ }
+ },
+ [messageApi, filters, sorter]
+ )
useEffect(() => {
- // Fetch initial data
if (authenticated) {
fetchFilamentStocksData()
}
}, [authenticated, fetchFilamentStocksData])
useEffect(() => {
- // Add WebSocket event listener for real-time updates
if (socket && !initialized) {
setInitialized(true)
socket.on('notify_filamentstock_update', (updateData) => {
@@ -134,6 +208,43 @@ const FilamentStocks = () => {
}
}
+ 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)
+ fetchFilamentStocksData(nextPage, true)
+ }
+ },
+ [page, lazyLoading, hasMore, fetchFilamentStocksData]
+ )
+
+ 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
+ })
+ fetchFilamentStocksData(1)
+ }
+
// Column definitions
const columns = [
{
@@ -150,6 +261,20 @@ const FilamentStocks = () => {
key: 'name',
width: 200,
fixed: 'left',
+ sorter: true,
+ filterDropdown: ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters
+ }) =>
+ getFilterDropdown({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ propertyName: 'filament name'
+ }),
render: (filament) => {filament.name}
},
{
@@ -171,7 +296,8 @@ const FilamentStocks = () => {
title: 'Current (g)',
dataIndex: 'currentNetWeight',
key: 'currentNetWeight',
- width: 120,
+ width: 140,
+ sorter: true,
render: (currentNetWeight) => (
{currentNetWeight.toFixed(2) + 'g'}
)
@@ -180,7 +306,8 @@ const FilamentStocks = () => {
title: 'Starting (g)',
dataIndex: 'startingNetWeight',
key: 'startingNetWeight',
- width: 120,
+ width: 140,
+ sorter: true,
render: (startingNetWeight) => (
{startingNetWeight.toFixed(2) + 'g'}
)
@@ -190,6 +317,8 @@ const FilamentStocks = () => {
dataIndex: 'createdAt',
key: 'createdAt',
width: 180,
+ sorter: true,
+ defaultSortOrder: 'descend',
render: (createdAt) => {
if (createdAt) {
return
@@ -203,6 +332,7 @@ const FilamentStocks = () => {
dataIndex: 'updatedAt',
key: 'updatedAt',
width: 180,
+ sorter: true,
render: (updatedAt) => {
if (updatedAt) {
return
@@ -236,6 +366,39 @@ const FilamentStocks = () => {
}
]
+ const getViewDropdownItems = () => {
+ const columnItems = columns
+ .filter((col) => col.key && col.title !== '')
+ .map((col) => (
+ {
+ updateColumnVisibility(col.key, e.target.checked)
+ }}
+ >
+ {col.title}
+
+ ))
+
+ return (
+
+
+ {columnItems}
+
+
+ )
+ }
+
+ const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
+ 'FilamentStocks',
+ columns
+ )
+
+ const visibleColumns = columns.filter(
+ (col) => !col.key || columnVisibility[col.key]
+ )
+
const actionItems = {
items: [
{
@@ -252,7 +415,8 @@ const FilamentStocks = () => {
],
onClick: ({ key }) => {
if (key === 'reloadList') {
- fetchFilamentStocksData()
+ setPage(1)
+ fetchFilamentStocksData(1)
} else if (key === 'newFilamentStock') {
setNewFilamentStockOpen(true)
}
@@ -263,19 +427,32 @@ const FilamentStocks = () => {
<>
{contextHolder}
-
-
- Actions
-
-
+
+
+
+ Actions
+
+
+ View
+
+
+ {lazyLoading && } />}
+
}}
scroll={{ y: 'calc(100vh - 270px)' }}
+ onScroll={handleScroll}
+ onChange={handleTableChange}
+ showSorterTooltip={false}
/>
{
const { socket } = useContext(SocketContext)
const [initialized, setInitialized] = useState(false)
+ // Helper function to convert text to camelCase
+ const toCamelCase = (text) => {
+ return text
+ .toLowerCase()
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
+ return index === 0 ? word.toLowerCase() : word.toUpperCase()
+ })
+ .replace(/\s+/g, '')
+ }
+
const [stockEventsData, setStockEventsData] = useState([])
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
@@ -61,7 +74,10 @@ const StockEvents = () => {
const [lazyLoading, setLazyLoading] = useState(false)
const [filters, setFilters] = useState({})
- const [sorter, setSorter] = useState({})
+ const [sorter, setSorter] = useState({
+ field: 'createdAt',
+ order: 'descend'
+ })
// Column definitions for visibility
const columns = [
@@ -87,40 +103,27 @@ const StockEvents = () => {
dataIndex: 'type',
key: 'type',
width: 200,
- sorter: (a, b) => a.type.localeCompare(b.type),
- filters: [
- { text: 'Sub Job', value: 'Sub Job' },
- { text: 'Audit Adjustment', value: 'Audit Adjustment' },
- { text: 'Initial', value: 'Initial' }
- ],
- onFilter: (value, record) => {
- const recordType = record.type.toLowerCase()
- if (recordType === 'subjob') {
- return value === 'Sub Job'
- } else if (recordType === 'audit') {
- return value === 'Audit Adjustment'
- }
- return (
- value === recordType.charAt(0).toUpperCase() + recordType.slice(1)
- )
- },
- render: (type) => {
- switch (type.toLowerCase()) {
- case 'subjob':
- return 'Sub Job'
- case 'audit':
- return 'Audit Adjustment'
- default:
- return type.charAt(0).toUpperCase() + type.slice(1)
- }
- }
+ sorter: true,
+ filterDropdown: ({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters
+ }) =>
+ getFilterDropdown({
+ setSelectedKeys,
+ selectedKeys,
+ confirm,
+ clearFilters,
+ propertyName: 'type'
+ })
},
{
title: ,
dataIndex: 'value',
key: 'value',
width: 100,
- sorter: (a, b) => a.value - b.value,
+ sorter: true,
render: (value, record) => {
const formattedValue = value.toFixed(2) + record.unit
return (
@@ -180,8 +183,8 @@ const StockEvents = () => {
dataIndex: 'createdAt',
key: 'createdAt',
width: 180,
+ sorter: true,
defaultSortOrder: 'descend',
- sorter: (a, b) => moment(a.createdAt).unix() - moment(b.createdAt).unix(),
render: (createdAt) => {
if (createdAt) {
return
@@ -195,7 +198,7 @@ const StockEvents = () => {
dataIndex: 'updatedAt',
key: 'updatedAt',
width: 180,
- sorter: (a, b) => moment(a.updatedAt).unix() - moment(b.updatedAt).unix(),
+ sorter: true,
render: (updatedAt) => {
if (updatedAt) {
return
@@ -206,15 +209,47 @@ const StockEvents = () => {
}
]
- const [columnVisibility, setColumnVisibility] = useState(
- columns.reduce((acc, col) => {
- if (col.key) {
- acc[col.key] = true
- }
- return acc
- }, {})
+ const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
+ 'StockEvents',
+ columns
)
+ 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 { authenticated } = useContext(AuthContext)
const fetchStockEventsData = useCallback(
@@ -224,6 +259,7 @@ const StockEvents = () => {
params: {
page: pageNum,
limit: 25,
+ type: filters.type,
...filters,
sort: sorter.field,
order: sorter.order
@@ -341,7 +377,12 @@ const StockEvents = () => {
const newFilters = {}
Object.entries(filters).forEach(([key, value]) => {
if (value && value.length > 0) {
- newFilters[key] = value[0]
+ // Convert type filter to camelCase
+ if (key === 'type') {
+ newFilters[key] = toCamelCase(value[0])
+ } else {
+ newFilters[key] = value[0]
+ }
}
})
setPage(1)
@@ -350,6 +391,8 @@ const StockEvents = () => {
field: sorter.field,
order: sorter.order
})
+ // Trigger a new fetch with the updated filters
+ fetchStockEventsData(1)
}
const getViewDropdownItems = () => {
@@ -360,10 +403,7 @@ const StockEvents = () => {
checked={columnVisibility[col.key]}
key={col.key}
onChange={(e) => {
- setColumnVisibility((prev) => ({
- ...prev,
- [col.key]: e.target.checked
- }))
+ updateColumnVisibility(col.key, e.target.checked)
}}
>
{col.title}
diff --git a/src/components/Dashboard/Management/Settings.jsx b/src/components/Dashboard/Management/Settings.jsx
index 4990b2c..ad0dfb0 100644
--- a/src/components/Dashboard/Management/Settings.jsx
+++ b/src/components/Dashboard/Management/Settings.jsx
@@ -91,7 +91,7 @@ const Settings = () => {