상품 수정 삭제 가능
This commit is contained in:
parent
05e565a02c
commit
3f04849bb9
@ -29,6 +29,7 @@ export default function EditProduct({ brands, categories, slug, shops, isVendor
|
||||
<div>
|
||||
<ProductForm
|
||||
shops={shops}
|
||||
isEdit={true}
|
||||
brands={brands}
|
||||
categories={categories}
|
||||
currentProduct={data?.data}
|
||||
|
@ -49,6 +49,7 @@ const LabelStyle = styled(Typography)(({ theme }) => ({
|
||||
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 (
|
||||
<Stack spacing={3}>
|
||||
<FormikProvider value={formik}>
|
||||
@ -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({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'none' }}>
|
||||
<div style={{display:'none'}}>
|
||||
{isInitialized ? (
|
||||
<Skeleton variant="text" width={140} />
|
||||
) : (
|
||||
|
@ -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 (
|
||||
<Box sx={{ width: '100%', ...sx }}>
|
||||
<DropZoneStyle
|
||||
@ -247,7 +280,11 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
||||
component="img"
|
||||
onClick={() => {
|
||||
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 상품 검출 '}
|
||||
</Button>
|
||||
{/* 금액 입력 모달 */}
|
||||
<Modal open={priceModalOpen} onClose={() => setPriceModalOpen(false)}>
|
||||
@ -300,14 +338,27 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
||||
<Typography variant="h6">Enter Price</Typography>
|
||||
<input
|
||||
type="text"
|
||||
value={price}
|
||||
onChange={(e) => 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' }}
|
||||
/>
|
||||
<Button variant="contained" onClick={handleSavePrice}>
|
||||
Save
|
||||
</Button>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Button variant="contained" onClick={handleSavePrice}>
|
||||
Save
|
||||
</Button>
|
||||
{selectedAnnotation && (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={() => handleDeleteAnnotation(selectedAnnotation.id)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
<canvas ref={canvasRef} style={{ width: '100%' }} onClick={(event) => priceTagHandler(event)} />
|
||||
@ -315,7 +366,9 @@ export default function UploadMultiFile({ onAnnotationsChange, ...props }) {
|
||||
{annotations.map((annotation, idx) => (
|
||||
<Typography
|
||||
key={idx}
|
||||
onClick={() => 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}
|
||||
</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
|
Loading…
Reference in New Issue
Block a user