상품 수정 삭제 가능
This commit is contained in:
parent
05e565a02c
commit
3f04849bb9
@ -29,6 +29,7 @@ export default function EditProduct({ brands, categories, slug, shops, isVendor
|
|||||||
<div>
|
<div>
|
||||||
<ProductForm
|
<ProductForm
|
||||||
shops={shops}
|
shops={shops}
|
||||||
|
isEdit={true}
|
||||||
brands={brands}
|
brands={brands}
|
||||||
categories={categories}
|
categories={categories}
|
||||||
currentProduct={data?.data}
|
currentProduct={data?.data}
|
||||||
|
@ -49,6 +49,7 @@ const LabelStyle = styled(Typography)(({ theme }) => ({
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
export default function ProductForm({
|
export default function ProductForm({
|
||||||
categories,
|
categories,
|
||||||
|
isEdit = false,
|
||||||
currentProduct,
|
currentProduct,
|
||||||
categoryLoading = false,
|
categoryLoading = false,
|
||||||
isInitialized = false,
|
isInitialized = false,
|
||||||
@ -80,10 +81,6 @@ export default function ProductForm({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('currentProduct', currentProduct)
|
|
||||||
}, [currentProduct])
|
|
||||||
|
|
||||||
const NewProductSchema = Yup.object().shape({
|
const NewProductSchema = Yup.object().shape({
|
||||||
name: Yup.string().required('Product name is required'),
|
name: Yup.string().required('Product name is required'),
|
||||||
code: Yup.string().required('Product code 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.setFieldValue('slug', slug); // set the value of slug in the formik state
|
||||||
formik.handleChange(event); // handle the change in formik
|
formik.handleChange(event); // handle the change in formik
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<FormikProvider value={formik}>
|
<FormikProvider value={formik}>
|
||||||
@ -620,6 +616,8 @@ export default function ProductForm({
|
|||||||
accept="image/*"
|
accept="image/*"
|
||||||
files={values?.images}
|
files={values?.images}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
isEdit={isEdit}
|
||||||
|
metaDescription={currentProduct?.metaDescription}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
onRemove={handleRemove}
|
onRemove={handleRemove}
|
||||||
onRemoveAll={handleRemoveAll}
|
onRemoveAll={handleRemoveAll}
|
||||||
@ -668,7 +666,7 @@ export default function ProductForm({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'none' }}>
|
<div style={{display:'none'}}>
|
||||||
{isInitialized ? (
|
{isInitialized ? (
|
||||||
<Skeleton variant="text" width={140} />
|
<Skeleton variant="text" width={140} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -39,10 +39,11 @@ UploadMultiFile.propTypes = {
|
|||||||
isInitialized: PropTypes.bool.isRequired,
|
isInitialized: PropTypes.bool.isRequired,
|
||||||
isEdit: PropTypes.bool.isRequired,
|
isEdit: PropTypes.bool.isRequired,
|
||||||
loading: 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 { error, files, onRemove, blob, isEdit, onRemoveAll, loading, sx, ...other } = props;
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
@ -53,6 +54,9 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
|
|
||||||
const [image, setImage] = useState('');
|
const [image, setImage] = useState('');
|
||||||
const [predictions, setPredictions] = useState([]); // Roboflow 결과 저장
|
const [predictions, setPredictions] = useState([]); // Roboflow 결과 저장
|
||||||
|
const [selectedAnnotation, setSelectedAnnotation] = useState(null);
|
||||||
|
const [editPrice, setEditPrice] = useState('');
|
||||||
|
const [showAnnotations, setShowAnnotations] = useState(false);
|
||||||
const canvasRef = useRef(null); // 캔버스 참조
|
const canvasRef = useRef(null); // 캔버스 참조
|
||||||
const hasFile = files.length > 0;
|
const hasFile = files.length > 0;
|
||||||
const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({
|
const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({
|
||||||
@ -80,40 +84,43 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
|
|
||||||
// API 호출
|
// 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 apiEndpoint = `https://detect.roboflow.com/picup/1?api_key=s9OJq0UPljSqkPsJY6xP`;
|
||||||
const apiResponse = await fetch(apiEndpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await apiResponse.json();
|
try {
|
||||||
console.log('Roboflow Predictions:', result);
|
const apiResponse = await fetch(apiEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
if (result.predictions) {
|
body: formData
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 상품 검출 트리거
|
// AI 상품 검출 트리거
|
||||||
const objectDetect = () => {
|
const objectDetect = () => {
|
||||||
detectWithRoboflow();
|
detectWithRoboflow();
|
||||||
|
setShowAnnotations(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 캔버스 클릭 시 실행되는 핸들러
|
// 캔버스 클릭 시 실행되는 핸들러
|
||||||
@ -133,30 +141,40 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
setPriceModalOpen(true); // 금액 입력 모달 열기
|
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 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);
|
setAnnotations(updatedAnnotations);
|
||||||
setPriceModalOpen(false); // 모달 닫기
|
setPriceModalOpen(false);
|
||||||
setPrice(''); // 입력 초기화
|
setSelectedAnnotation(null);
|
||||||
|
|
||||||
if (onAnnotationsChange) {
|
if (onAnnotationsChange) {
|
||||||
onAnnotationsChange(updatedAnnotations); // 부모 컴포넌트에 알림
|
onAnnotationsChange(updatedAnnotations);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -166,6 +184,21 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
}
|
}
|
||||||
}, [annotations, onAnnotationsChange, other.metaDescription]);
|
}, [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 (
|
return (
|
||||||
<Box sx={{ width: '100%', ...sx }}>
|
<Box sx={{ width: '100%', ...sx }}>
|
||||||
<DropZoneStyle
|
<DropZoneStyle
|
||||||
@ -247,7 +280,11 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
component="img"
|
component="img"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
setImage(file.url);
|
if (isEdit) {
|
||||||
|
setImage(files[0].url);
|
||||||
|
} else {
|
||||||
|
setImage(file.url);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
src={!file.blob ? file.url : file.blob}
|
src={!file.blob ? file.url : file.blob}
|
||||||
sx={{
|
sx={{
|
||||||
@ -271,6 +308,7 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
sx={{ position: 'absolute;', right: 0, top: 0 }}
|
sx={{ position: 'absolute;', right: 0, top: 0 }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
|
setShowAnnotations(false);
|
||||||
setImage('');
|
setImage('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -280,7 +318,7 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
sx={{ position: 'absolute', bottom: 0, left: '50%', transform: 'translate(-50%, -30px)' }}
|
sx={{ position: 'absolute', bottom: 0, left: '50%', transform: 'translate(-50%, -30px)' }}
|
||||||
onClick={() => objectDetect()}
|
onClick={() => objectDetect()}
|
||||||
>
|
>
|
||||||
AI 상품 검출
|
{isEdit ? '상품 보기' : 'AI 상품 검출 '}
|
||||||
</Button>
|
</Button>
|
||||||
{/* 금액 입력 모달 */}
|
{/* 금액 입력 모달 */}
|
||||||
<Modal open={priceModalOpen} onClose={() => setPriceModalOpen(false)}>
|
<Modal open={priceModalOpen} onClose={() => setPriceModalOpen(false)}>
|
||||||
@ -300,14 +338,27 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
<Typography variant="h6">Enter Price</Typography>
|
<Typography variant="h6">Enter Price</Typography>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={price}
|
value={editPrice || price}
|
||||||
onChange={(e) => setPrice(e.target.value)}
|
onChange={(e) => {
|
||||||
|
isEdit ? setEditPrice(e.target.value) : setPrice(e.target.value);
|
||||||
|
}}
|
||||||
placeholder="Enter price"
|
placeholder="Enter price"
|
||||||
style={{ padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
style={{ padding: '8px', borderRadius: '4px', border: '1px solid #ddd' }}
|
||||||
/>
|
/>
|
||||||
<Button variant="contained" onClick={handleSavePrice}>
|
<Stack direction="row" spacing={2}>
|
||||||
Save
|
<Button variant="contained" onClick={handleSavePrice}>
|
||||||
</Button>
|
Save
|
||||||
|
</Button>
|
||||||
|
{selectedAnnotation && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
onClick={() => handleDeleteAnnotation(selectedAnnotation.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
<canvas ref={canvasRef} style={{ width: '100%' }} onClick={(event) => priceTagHandler(event)} />
|
<canvas ref={canvasRef} style={{ width: '100%' }} onClick={(event) => priceTagHandler(event)} />
|
||||||
@ -315,7 +366,9 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
{annotations.map((annotation, idx) => (
|
{annotations.map((annotation, idx) => (
|
||||||
<Typography
|
<Typography
|
||||||
key={idx}
|
key={idx}
|
||||||
|
onClick={() => handleEditAnnotation(annotation)} // 클릭 시 수정 모달 오픈
|
||||||
sx={{
|
sx={{
|
||||||
|
display:showAnnotations ? 'block' : 'none',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: `${annotation.y * 100}%`, // 상대값을 퍼센트로 변환
|
top: `${annotation.y * 100}%`, // 상대값을 퍼센트로 변환
|
||||||
left: `${annotation.x * 100}%`,
|
left: `${annotation.x * 100}%`,
|
||||||
@ -323,10 +376,14 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
|||||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
padding: '2px 5px',
|
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}
|
||||||
</Typography>
|
</Typography>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
Loading…
Reference in New Issue
Block a user