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