import React, { useState, useEffect, useRef } from 'react' import { useLocation } from 'react-router-dom' import axios from 'axios' import { Descriptions, Spin, Space, Button, message, Typography, Card, Flex, Form, Input, Checkbox, InputNumber, Switch, Tag, Collapse } from 'antd' import { LoadingOutlined, CaretRightOutlined } from '@ant-design/icons' import IdText from '../../common/IdText.jsx' import { StlViewer } from 'react-stl-viewer' import ReloadIcon from '../../../Icons/ReloadIcon' import EditIcon from '../../../Icons/EditIcon.jsx' import XMarkIcon from '../../../Icons/XMarkIcon.jsx' import CheckIcon from '../../../Icons/CheckIcon.jsx' import useCollapseState from '../../hooks/useCollapseState' import TimeDisplay from '../../common/TimeDisplay.jsx' const { Title } = Typography const PartInfo = () => { const [partData, setPartData] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const location = useLocation() const [messageApi, contextHolder] = message.useMessage() const partId = new URLSearchParams(location.search).get('partId') const [marginOrPrice, setMarginOrPrice] = useState(false) const [useGlobalPricing, setUseGlobalPricing] = useState(true) const [collapseState, updateCollapseState] = useCollapseState('PartInfo', { info: true, preview: true }) const [partForm] = Form.useForm() const [partFormValues, setPartFormValues] = useState({}) // Add a ref to store the object URL const objectUrlRef = useRef(null) // Add a ref to store the array buffer const arrayBufferRef = useRef(null) const [isEditing, setIsEditing] = useState(false) const [fetchLoading, setFetchLoading] = useState(true) const [partFileObjectId, setPartFileObjectId] = useState(null) const [stlLoadError, setStlLoadError] = useState(null) useEffect(() => { async function fetchData() { await fetchPartDetails() setTimeout(async () => { await fetchPartContent() }, 1000) } if (partId) { fetchData() } }, [partId]) useEffect(() => { if (partData) { partForm.setFieldsValue({ name: partData.name || '', price: partData.price || null, margin: partData.margin || null, marginOrPrice: partData.marginOrPrice, useGlobalPricing: partData.useGlobalPricing, createdAt: partData.createdAt || null, updatedAt: partData.updatedAt || null }) setPartFormValues(partData) } }, [partData, partForm]) useEffect(() => { setMarginOrPrice(partFormValues.marginOrPrice) setUseGlobalPricing(partFormValues.useGlobalPricing) }, [partFormValues]) const fetchPartDetails = async () => { try { setFetchLoading(true) const response = await axios.get( `http://localhost:8080/parts/${partId}`, { headers: { Accept: 'application/json' }, withCredentials: true } ) setPartData(response.data) setError(null) } catch (err) { setError('Failed to fetch part details') console.log(err) messageApi.error('Failed to fetch part details') } finally { setFetchLoading(false) } } const fetchPartContent = async () => { if (fetchLoading == true) { return } try { setFetchLoading(true) // Cleanup previous object URL if it exists if (objectUrlRef.current) { URL.revokeObjectURL(objectUrlRef.current) objectUrlRef.current = null } const response = await axios.get( `http://localhost:8080/parts/${partId}/content`, { withCredentials: true, responseType: 'blob' } ) // Check file size before processing const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB if (response.data.size > MAX_FILE_SIZE) { throw new Error( `File size exceeds ${MAX_FILE_SIZE / (1024 * 1024)}MB limit` ) } // Convert blob to array buffer for better memory management const arrayBuffer = await response.data.arrayBuffer() // Store array buffer in ref for later cleanup arrayBufferRef.current = arrayBuffer // Create a new blob from the array buffer const blob = new Blob([arrayBuffer], { type: response.data.type }) try { // Create and store object URL const objectUrl = URL.createObjectURL(blob) objectUrlRef.current = objectUrl // Update state with the new object URL setPartFileObjectId(objectUrl) setStlLoadError(null) setError(null) } catch (allocErr) { setStlLoadError( 'Failed to load STL file: Array buffer allocation failed' ) console.error('STL allocation error:', allocErr) } } catch (err) { setError('Failed to fetch part content') console.log(err) messageApi.error('Failed to fetch part content') } finally { setFetchLoading(false) } } const startEditing = () => { updateCollapseState('info', true) setIsEditing(true) } const cancelEditing = () => { partForm.setFieldsValue({ name: partData?.name || '' }) setIsEditing(false) } const updateInfo = async () => { try { const values = await partForm.validateFields() setLoading(true) await axios.put(`http://localhost:8080/parts/${partId}`, values, { headers: { 'Content-Type': 'application/json' }, withCredentials: true }) // Update the local state with the new values setPartData({ ...partData, ...values }) setIsEditing(false) messageApi.success('Part information updated successfully') } catch (err) { if (err.errorFields) { // This is a form validation error return } console.error('Failed to update part information:', err) messageApi.error('Failed to update part information') } finally { fetchPartDetails() setLoading(false) } } if (fetchLoading) { return (
{error || 'Part not found'}
} onClick={fetchPartDetails}> Retry