495 lines
15 KiB
JavaScript
495 lines
15 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
import axios from 'axios'
|
|
import {
|
|
Descriptions,
|
|
Spin,
|
|
Space,
|
|
Button,
|
|
message,
|
|
Badge,
|
|
Typography,
|
|
Flex,
|
|
Form,
|
|
Input,
|
|
InputNumber,
|
|
ColorPicker,
|
|
Select,
|
|
Modal,
|
|
Collapse
|
|
} from 'antd'
|
|
import {
|
|
LoadingOutlined,
|
|
ReloadOutlined,
|
|
EditOutlined,
|
|
CheckOutlined,
|
|
CloseOutlined,
|
|
DeleteOutlined,
|
|
CaretRightOutlined
|
|
} from '@ant-design/icons'
|
|
import IdText from '../../common/IdText'
|
|
import moment from 'moment'
|
|
import VendorSelect from '../../common/VendorSelect'
|
|
import useCollapseState from '../../hooks/useCollapseState'
|
|
|
|
const { Title, Link } = Typography
|
|
|
|
const FilamentInfo = () => {
|
|
const [filamentData, setFilamentData] = useState(null)
|
|
const [fetchLoading, setFetchLoading] = useState(true)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
|
|
const location = useLocation()
|
|
const [messageApi, contextHolder] = message.useMessage()
|
|
const filamentId = new URLSearchParams(location.search).get('filamentId')
|
|
const [isEditing, setIsEditing] = useState(false)
|
|
const [form] = Form.useForm()
|
|
const navigate = useNavigate()
|
|
const [collapseState, updateCollapseState] = useCollapseState(
|
|
'FilamentInfo',
|
|
{
|
|
info: true,
|
|
details: true
|
|
}
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (filamentId) {
|
|
fetchFilamentDetails()
|
|
}
|
|
}, [filamentId])
|
|
|
|
useEffect(() => {
|
|
if (filamentData) {
|
|
form.setFieldsValue({
|
|
name: filamentData.name || '',
|
|
brand: filamentData.brand || '',
|
|
type: filamentData.type || '',
|
|
cost: filamentData.cost || null,
|
|
color: filamentData.color || '#000000',
|
|
diameter: filamentData.diameter || null,
|
|
density: filamentData.density || null,
|
|
url: filamentData.url || '',
|
|
barcode: filamentData.barcode || '',
|
|
emptySpoolWeight: filamentData.emptySpoolWeight || ''
|
|
})
|
|
}
|
|
}, [filamentData, form])
|
|
|
|
const fetchFilamentDetails = async () => {
|
|
try {
|
|
setFetchLoading(true)
|
|
const response = await axios.get(
|
|
`http://localhost:8080/filaments/${filamentId}`,
|
|
{
|
|
headers: {
|
|
Accept: 'application/json'
|
|
},
|
|
withCredentials: true
|
|
}
|
|
)
|
|
setFilamentData(response.data)
|
|
setError(null)
|
|
} catch (err) {
|
|
setError('Failed to fetch filament details')
|
|
messageApi.error('Failed to fetch filament details')
|
|
} finally {
|
|
setFetchLoading(false)
|
|
}
|
|
}
|
|
|
|
const startEditing = () => {
|
|
setIsEditing(true)
|
|
}
|
|
|
|
const cancelEditing = () => {
|
|
// Reset form values to original data
|
|
if (filamentData) {
|
|
form.setFieldsValue({
|
|
name: filamentData.name || '',
|
|
brand: filamentData.brand || '',
|
|
type: filamentData.type || '',
|
|
cost: filamentData.cost || null,
|
|
color: filamentData.color || '#000000',
|
|
diameter: filamentData.diameter || null,
|
|
density: filamentData.density || null,
|
|
url: filamentData.url || '',
|
|
barcode: filamentData.barcode || '',
|
|
emptySpoolWeight: filamentData.emptySpoolWeight || ''
|
|
})
|
|
}
|
|
setIsEditing(false)
|
|
}
|
|
|
|
const updateFilamentInfo = async () => {
|
|
try {
|
|
const values = await form.validateFields()
|
|
setLoading(true)
|
|
|
|
await axios.put(
|
|
`http://localhost:8080/filaments/${filamentId}`,
|
|
{
|
|
name: values.name,
|
|
vendor: values.vendor,
|
|
type: values.type,
|
|
cost: values.cost,
|
|
color: values.color,
|
|
diameter: values.diameter,
|
|
density: values.density,
|
|
url: values.url,
|
|
barcode: values.barcode,
|
|
emptySpoolWeight: values.emptySpoolWeight
|
|
},
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
withCredentials: true
|
|
}
|
|
)
|
|
|
|
// Update the local state with the new values
|
|
setFilamentData({ ...filamentData, ...values })
|
|
setIsEditing(false)
|
|
messageApi.success('Filament information updated successfully')
|
|
} catch (err) {
|
|
if (err.errorFields) {
|
|
// This is a form validation error
|
|
return
|
|
}
|
|
console.error('Failed to update filament information:', err)
|
|
messageApi.error('Failed to update filament information')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleDelete = async () => {
|
|
try {
|
|
setLoading(true)
|
|
await axios.delete(`http://localhost:8080/filaments/${filamentId}`, {
|
|
withCredentials: true
|
|
})
|
|
messageApi.success('Filament deleted successfully')
|
|
navigate('/dashboard/filaments')
|
|
} catch (err) {
|
|
console.error('Failed to delete filament:', err)
|
|
messageApi.error('Failed to delete filament')
|
|
} finally {
|
|
setLoading(false)
|
|
setIsDeleteModalOpen(false)
|
|
}
|
|
}
|
|
|
|
if (fetchLoading) {
|
|
return (
|
|
<div style={{ textAlign: 'center', padding: '20px' }}>
|
|
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error || !filamentData) {
|
|
return (
|
|
<Space
|
|
direction='vertical'
|
|
style={{ width: '100%', textAlign: 'center' }}
|
|
>
|
|
<p>{error || 'Filament not found'}</p>
|
|
<Button icon={<ReloadOutlined />} onClick={fetchFilamentDetails}>
|
|
Retry
|
|
</Button>
|
|
</Space>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div style={{ height: '100%', minHeight: 0, overflowY: 'auto' }}>
|
|
{contextHolder}
|
|
<Collapse
|
|
ghost
|
|
activeKey={collapseState.info ? ['1'] : []}
|
|
onChange={(keys) => updateCollapseState('info', keys.length > 0)}
|
|
expandIcon={({ isActive }) => (
|
|
<CaretRightOutlined
|
|
rotate={isActive ? 90 : 0}
|
|
style={{ paddingTop: '9px' }}
|
|
/>
|
|
)}
|
|
>
|
|
<Collapse.Panel
|
|
header={
|
|
<Flex
|
|
align='center'
|
|
justify='space-between'
|
|
style={{ width: '100%' }}
|
|
>
|
|
<Title level={5} style={{ margin: 0 }}>
|
|
Filament Information
|
|
</Title>
|
|
<Space>
|
|
<Button
|
|
icon={<DeleteOutlined />}
|
|
danger
|
|
onClick={() => setIsDeleteModalOpen(true)}
|
|
loading={loading}
|
|
/>
|
|
{isEditing ? (
|
|
<>
|
|
<Button
|
|
icon={<CheckOutlined />}
|
|
type='primary'
|
|
onClick={updateFilamentInfo}
|
|
loading={loading}
|
|
/>
|
|
<Button
|
|
icon={<CloseOutlined />}
|
|
onClick={cancelEditing}
|
|
disabled={loading}
|
|
/>
|
|
</>
|
|
) : (
|
|
<Button icon={<EditOutlined />} onClick={startEditing} />
|
|
)}
|
|
</Space>
|
|
</Flex>
|
|
}
|
|
key='1'
|
|
>
|
|
<Form
|
|
form={form}
|
|
layout='vertical'
|
|
initialValues={{
|
|
name: filamentData.name || '',
|
|
vendor: filamentData.vendor || { id: null, name: '' },
|
|
type: filamentData.type || '',
|
|
cost: filamentData.cost || null,
|
|
color: filamentData.color || '#000000',
|
|
diameter: filamentData.diameter || null,
|
|
density: filamentData.density || null,
|
|
url: filamentData.url || '',
|
|
barcode: filamentData.barcode || ''
|
|
}}
|
|
>
|
|
<Descriptions bordered column={2}>
|
|
<Descriptions.Item label='ID' span={1}>
|
|
{filamentData.id ? (
|
|
<IdText id={filamentData.id} type={'filament'} />
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Created At'>
|
|
{moment(filamentData.createdAt).format('YYYY-MM-DD HH:mm:ss')}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Name'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='name'
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: 'Please enter a filament name'
|
|
},
|
|
{ max: 100, message: 'Name cannot exceed 100 characters' }
|
|
]}
|
|
style={{ margin: 0 }}
|
|
>
|
|
<Input placeholder='Enter filament name' />
|
|
</Form.Item>
|
|
) : (
|
|
filamentData.name || 'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Updated At'>
|
|
{moment(filamentData.updatedAt).format('YYYY-MM-DD HH:mm:ss')}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Vendor'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='vendor'
|
|
rules={[
|
|
{ required: true, message: 'Please enter a vendor' }
|
|
]}
|
|
style={{ margin: 0 }}
|
|
>
|
|
<VendorSelect />
|
|
</Form.Item>
|
|
) : (
|
|
filamentData.vendor.name || 'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Vendor ID'>
|
|
<IdText
|
|
id={filamentData.vendor.id}
|
|
type={'vendor'}
|
|
showHyperlink={true}
|
|
/>
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Material'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='type'
|
|
style={{ margin: 0 }}
|
|
rules={[
|
|
{ required: true, message: 'Please select a material' }
|
|
]}
|
|
>
|
|
<Select>
|
|
<Select.Option value='PLA'>PLA</Select.Option>
|
|
<Select.Option value='PETG'>PETG</Select.Option>
|
|
<Select.Option value='ABS'>ABS</Select.Option>
|
|
<Select.Option value='ASA'>ASA</Select.Option>
|
|
<Select.Option value='HIPS'>HIPS</Select.Option>
|
|
<Select.Option value='TPU'>TPU</Select.Option>
|
|
</Select>
|
|
</Form.Item>
|
|
) : (
|
|
filamentData.type || 'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Cost'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='cost'
|
|
style={{ margin: 0 }}
|
|
rules={[{ required: true, message: 'Please enter a cost' }]}
|
|
>
|
|
<InputNumber
|
|
prefix='£'
|
|
suffix='/kg'
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</Form.Item>
|
|
) : filamentData.cost ? (
|
|
`£${filamentData.cost}/kg`
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Color'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='color'
|
|
style={{ margin: 0 }}
|
|
rules={[
|
|
{ required: true, message: 'Please select a color' }
|
|
]}
|
|
>
|
|
<ColorPicker />
|
|
</Form.Item>
|
|
) : (
|
|
<Badge color={filamentData.color} text={filamentData.color} />
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Diameter'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='diameter'
|
|
style={{ margin: 0 }}
|
|
rules={[
|
|
{ required: true, message: 'Please enter a diameter' }
|
|
]}
|
|
>
|
|
<InputNumber suffix='mm' style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
) : filamentData.diameter ? (
|
|
`${filamentData.diameter}mm`
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Density'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='density'
|
|
style={{ margin: 0 }}
|
|
rules={[
|
|
{ required: true, message: 'Please enter a density' }
|
|
]}
|
|
>
|
|
<InputNumber suffix='g/cm³' style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
) : filamentData.density ? (
|
|
`${filamentData.density}g/cm³`
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='URL'>
|
|
{isEditing ? (
|
|
<Form.Item name='url' style={{ margin: 0 }}>
|
|
<Input placeholder='Enter URL' />
|
|
</Form.Item>
|
|
) : filamentData.url ? (
|
|
<Link href={filamentData.url} target='_blank'>
|
|
{filamentData.url}
|
|
</Link>
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Barcode'>
|
|
{isEditing ? (
|
|
<Form.Item name='barcode' style={{ margin: 0 }}>
|
|
<Input placeholder='Enter barcode' />
|
|
</Form.Item>
|
|
) : (
|
|
filamentData.barcode || 'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
</Form>
|
|
</Collapse.Panel>
|
|
</Collapse>
|
|
|
|
<Collapse
|
|
ghost
|
|
activeKey={collapseState.details ? ['2'] : []}
|
|
onChange={(keys) => updateCollapseState('details', keys.length > 0)}
|
|
expandIcon={({ isActive }) => (
|
|
<CaretRightOutlined
|
|
rotate={isActive ? 90 : 0}
|
|
style={{ paddingTop: '2px' }}
|
|
/>
|
|
)}
|
|
>
|
|
<Collapse.Panel
|
|
header={
|
|
<Title level={5} style={{ margin: 0 }}>
|
|
Additional Details
|
|
</Title>
|
|
}
|
|
key='2'
|
|
>
|
|
{/* Add any additional details sections here */}
|
|
</Collapse.Panel>
|
|
</Collapse>
|
|
|
|
<Modal
|
|
title='Delete Filament'
|
|
open={isDeleteModalOpen}
|
|
onOk={handleDelete}
|
|
onCancel={() => setIsDeleteModalOpen(false)}
|
|
confirmLoading={loading}
|
|
>
|
|
<p>Are you sure you want to delete this filament?</p>
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default FilamentInfo
|