From 3f04849bb9fbfe76ad0c3c97defeaf2bbff3423b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B5=ED=9D=AC=20=EA=B9=80?= Date: Wed, 22 Jan 2025 03:52:49 +0900 Subject: [PATCH] =?UTF-8?q?=EC=83=81=ED=92=88=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_admin/products/editProduct.jsx | 1 + src/components/forms/product.jsx | 10 +- src/components/upload/UploadMultiFile.jsx | 173 ++++++++++++------ 3 files changed, 120 insertions(+), 64 deletions(-) diff --git a/src/components/_admin/products/editProduct.jsx b/src/components/_admin/products/editProduct.jsx index d0d85ee..98cb0a8 100644 --- a/src/components/_admin/products/editProduct.jsx +++ b/src/components/_admin/products/editProduct.jsx @@ -29,6 +29,7 @@ export default function EditProduct({ brands, categories, slug, shops, isVendor
({ const now = new Date(); export default function ProductForm({ categories, + isEdit = false, currentProduct, categoryLoading = false, isInitialized = false, @@ -80,10 +81,6 @@ export default function ProductForm({ } ); - useEffect(() => { - console.log('currentProduct', currentProduct) - }, [currentProduct]) - const NewProductSchema = Yup.object().shape({ name: Yup.string().required('Product name is required'), code: Yup.string().required('Product code is required'), @@ -212,7 +209,6 @@ export default function ProductForm({ formik.setFieldValue('slug', slug); // set the value of slug in the formik state formik.handleChange(event); // handle the change in formik }; - return ( @@ -620,6 +616,8 @@ export default function ProductForm({ accept="image/*" files={values?.images} loading={loading} + isEdit={isEdit} + metaDescription={currentProduct?.metaDescription} onDrop={handleDrop} onRemove={handleRemove} onRemoveAll={handleRemoveAll} @@ -668,7 +666,7 @@ export default function ProductForm({ /> )}
-
+
{isInitialized ? ( ) : ( diff --git a/src/components/upload/UploadMultiFile.jsx b/src/components/upload/UploadMultiFile.jsx index ec5c38d..83c64c5 100644 --- a/src/components/upload/UploadMultiFile.jsx +++ b/src/components/upload/UploadMultiFile.jsx @@ -39,10 +39,11 @@ UploadMultiFile.propTypes = { isInitialized: PropTypes.bool.isRequired, isEdit: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired, - onAnnotationsChange: PropTypes.func.isRequired + onAnnotationsChange: PropTypes.func.isRequired, + metaDescription: PropTypes.string }; -export default function UploadMultiFile({ onAnnotationsChange, ...props }) { +export default function UploadMultiFile({ onAnnotationsChange, metaDescription, ...props }) { const { error, files, onRemove, blob, isEdit, onRemoveAll, loading, sx, ...other } = props; const [modalOpen, setModalOpen] = useState(false); @@ -53,6 +54,9 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { const [image, setImage] = useState(''); const [predictions, setPredictions] = useState([]); // Roboflow 결과 저장 + const [selectedAnnotation, setSelectedAnnotation] = useState(null); + const [editPrice, setEditPrice] = useState(''); + const [showAnnotations, setShowAnnotations] = useState(false); const canvasRef = useRef(null); // 캔버스 참조 const hasFile = files.length > 0; const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ @@ -80,40 +84,43 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { const blob = await response.blob(); // API 호출 - const formData = new FormData(); - formData.append('file', blob); - const apiEndpoint = `https://detect.roboflow.com/picup/1?api_key=s9OJq0UPljSqkPsJY6xP`; + if (!isEdit) { + const formData = new FormData(); + formData.append('file', blob); - try { - const apiResponse = await fetch(apiEndpoint, { - method: 'POST', - body: formData - }); + const apiEndpoint = `https://detect.roboflow.com/picup/1?api_key=s9OJq0UPljSqkPsJY6xP`; - const result = await apiResponse.json(); - console.log('Roboflow Predictions:', result); - - if (result.predictions) { - setPredictions(result.predictions); - - // 바운딩 박스와 라벨 표시 - result.predictions.forEach((prediction) => { - const { x, y, width, height, class: label, confidence } = prediction; - - // 바운딩 박스 - ctx.strokeStyle = 'red'; - ctx.lineWidth = 1; - ctx.strokeRect(x - 50, y - 80, width, height); - - // 라벨 - ctx.fillStyle = 'red'; - ctx.font = '16px Arial'; - ctx.fillText(`${label} (${(confidence * 100).toFixed(2)}%)`, x - 50, y - 80 > 10 ? y - 5 : 10); + try { + const apiResponse = await fetch(apiEndpoint, { + method: 'POST', + body: formData }); + + const result = await apiResponse.json(); + console.log('Roboflow Predictions:', result); + + if (result.predictions) { + setPredictions(result.predictions); + + // 바운딩 박스와 라벨 표시 + result.predictions.forEach((prediction) => { + const { x, y, width, height, class: label, confidence } = prediction; + + // 바운딩 박스 + ctx.strokeStyle = 'red'; + ctx.lineWidth = 1; + ctx.strokeRect(x - 50, y - 80, width, height); + + // 라벨 + ctx.fillStyle = 'red'; + ctx.font = '16px Arial'; + ctx.fillText(`${label} (${(confidence * 100).toFixed(2)}%)`, x - 50, y - 80 > 10 ? y - 5 : 10); + }); + } + } catch (error) { + console.error('Error detecting objects:', error); } - } catch (error) { - console.error('Error detecting objects:', error); } }; }; @@ -121,6 +128,7 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { // AI 상품 검출 트리거 const objectDetect = () => { detectWithRoboflow(); + setShowAnnotations(true); }; // 캔버스 클릭 시 실행되는 핸들러 @@ -133,30 +141,40 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { setPriceModalOpen(true); // 금액 입력 모달 열기 }; - // 금액 입력 후 저장 - // const handleSavePrice = () => { - // const updatedAnnotations = [...annotations, { x: clientPosition.x, y: clientPosition.y, price }]; - // setAnnotations(updatedAnnotations); - // setPriceModalOpen(false); // 모달 닫기 - // setPrice(''); // 입력 초기화 - - // if (onAnnotationsChange) { - // onAnnotationsChange(updatedAnnotations); // 부모 컴포넌트에 알림 - // } - - // }; - const handleSavePrice = () => { - const newId = annotations.length > 0 ? annotations[annotations.length - 1].id + 1 : 1; + if (selectedAnnotation) { + const updatedAnnotations = annotations.map((ann) => + ann.id === selectedAnnotation.id ? { ...ann, price: editPrice } : ann + ); + setAnnotations(updatedAnnotations); + } else { + const newId = annotations.length > 0 ? annotations[annotations.length - 1].id + 1 : 1; + const updatedAnnotations = [...annotations, { id: newId, x: clientPosition.x, y: clientPosition.y, price }]; + setAnnotations(updatedAnnotations); + } - const updatedAnnotations = [...annotations, { id: newId, x: clientPosition.x, y: clientPosition.y, price }]; + setPriceModalOpen(false); + setPrice(''); + setSelectedAnnotation(null); + if (onAnnotationsChange) { + onAnnotationsChange(annotations); + } + }; + const handleEditAnnotation = (annotation) => { + setSelectedAnnotation(annotation); + setEditPrice(annotation.price); + setPriceModalOpen(true); + }; + + const handleDeleteAnnotation = (id) => { + const updatedAnnotations = annotations.filter((ann) => ann.id !== id); setAnnotations(updatedAnnotations); - setPriceModalOpen(false); // 모달 닫기 - setPrice(''); // 입력 초기화 + setPriceModalOpen(false); + setSelectedAnnotation(null); if (onAnnotationsChange) { - onAnnotationsChange(updatedAnnotations); // 부모 컴포넌트에 알림 + onAnnotationsChange(updatedAnnotations); } }; @@ -166,6 +184,21 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { } }, [annotations, onAnnotationsChange, other.metaDescription]); + useEffect(() => { + if (metaDescription) { + try { + const parsedAnnotations = JSON.parse(metaDescription); + setAnnotations(parsedAnnotations); + } catch (error) { + console.error('Failed to parse metaDescription:', error); + } + } + }, [metaDescription]); + + + console.log(showAnnotations) + + return ( { setModalOpen(true); - setImage(file.url); + if (isEdit) { + setImage(files[0].url); + } else { + setImage(file.url); + } }} src={!file.blob ? file.url : file.blob} sx={{ @@ -271,6 +308,7 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { sx={{ position: 'absolute;', right: 0, top: 0 }} onClick={() => { setModalOpen(false); + setShowAnnotations(false); setImage(''); }} > @@ -280,7 +318,7 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { sx={{ position: 'absolute', bottom: 0, left: '50%', transform: 'translate(-50%, -30px)' }} onClick={() => objectDetect()} > - AI 상품 검출 + {isEdit ? '상품 보기' : 'AI 상품 검출 '} {/* 금액 입력 모달 */} setPriceModalOpen(false)}> @@ -300,14 +338,27 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { Enter Price setPrice(e.target.value)} + value={editPrice || price} + onChange={(e) => { + isEdit ? setEditPrice(e.target.value) : setPrice(e.target.value); + }} placeholder="Enter price" style={{ padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }} /> - + + + {selectedAnnotation && ( + + )} + priceTagHandler(event)} /> @@ -315,7 +366,9 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { {annotations.map((annotation, idx) => ( handleEditAnnotation(annotation)} // 클릭 시 수정 모달 오픈 sx={{ + display:showAnnotations ? 'block' : 'none', position: 'absolute', top: `${annotation.y * 100}%`, // 상대값을 퍼센트로 변환 left: `${annotation.x * 100}%`, @@ -323,10 +376,14 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) { backgroundColor: 'rgba(0,0,0,0.7)', color: '#fff', padding: '2px 5px', - borderRadius: '4px' + borderRadius: '4px', + cursor: 'pointer', + '&:hover': { + backgroundColor: 'rgba(0,0,0,0.9)' + } }} > - { annotation.id}: {annotation.price} + {annotation.id}: {annotation.price} ))}