From 6afa11dcb7a065a615f1695a9a10293a05f77570 Mon Sep 17 00:00:00 2001 From: psyeon1120 Date: Fri, 1 Dec 2023 02:21:11 +0900 Subject: [PATCH] =?UTF-8?q?[PDW-20]=20feat:=20=EB=B9=84=ED=92=88=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 3 + src/components/capsule/DropBox.js | 11 +- src/components/card/EquipmentInfo.js | 2 - src/components/searchBar/ManageSearchBar.js | 4 +- src/pages/admin/car/CarManage.js | 18 +- src/pages/admin/car/CarManageAdd.js | 2 +- src/pages/admin/office/OfficeManageAdd.js | 2 +- src/pages/admin/resource/ResourceManageAdd.js | 2 +- src/pages/admin/user/UserManage.js | 3 +- src/pages/basic/equipment/EquipmentAdd.js | 372 ++++++++++++++++++ src/pages/basic/equipment/EquipmentList.js | 13 +- 11 files changed, 402 insertions(+), 30 deletions(-) create mode 100644 src/pages/basic/equipment/EquipmentAdd.js diff --git a/src/App.js b/src/App.js index 099b8fa..a0105fd 100644 --- a/src/App.js +++ b/src/App.js @@ -28,6 +28,7 @@ import CarManageAdd from "./pages/admin/car/CarManageAdd"; import CarManageDetail from "./pages/admin/car/CarManageDetail"; import OfficeManageAdd from "./pages/admin/office/OfficeManageAdd"; import EquipmentList from "./pages/basic/equipment/EquipmentList"; +import EquipmentAdd from "./pages/basic/equipment/EquipmentAdd"; function App() { @@ -58,6 +59,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/capsule/DropBox.js b/src/components/capsule/DropBox.js index c374607..bea8442 100644 --- a/src/components/capsule/DropBox.js +++ b/src/components/capsule/DropBox.js @@ -25,6 +25,7 @@ const Select = styled.select` const ManagerSelect = styled(Select)` margin-right: 10px; + border: ${props => props.color ? `2px solid ${props.color}` : '1px solid #717171'}; ` const NoBorderSelect = styled(Select)` @@ -52,15 +53,7 @@ export function SelectToggleInModal(props) { export function DropBox(props) { return ( - - ); -} - -export function ManagerDropBox(props) { - return ( - + {props.items} ); diff --git a/src/components/card/EquipmentInfo.js b/src/components/card/EquipmentInfo.js index 64a5635..df373ec 100644 --- a/src/components/card/EquipmentInfo.js +++ b/src/components/card/EquipmentInfo.js @@ -11,13 +11,11 @@ import EmptyImg from "assets/images/EmptyImg.svg" // 카드 박스 export const Card = styled.div` height: 350px; - width: calc(33.33% - 15px); background: white; border-radius: 8px; border: 1px solid #E6E6E6; box-sizing: border-box; padding: 15px; - margin: 0 0 15px 0; ` // 상단줄 컨테이너 diff --git a/src/components/searchBar/ManageSearchBar.js b/src/components/searchBar/ManageSearchBar.js index 6f01b38..97fa236 100644 --- a/src/components/searchBar/ManageSearchBar.js +++ b/src/components/searchBar/ManageSearchBar.js @@ -2,7 +2,7 @@ import React from "react"; import styled from "styled-components"; import SearchInputImage from "../../assets/images/SearchInput.svg" import SearchButtonImage from "../../assets/images/SearchPlus.svg" -import {ManagerDropBox} from "../capsule/DropBox"; +import {DropBox} from "../capsule/DropBox"; const Container = styled.div` background: none; @@ -74,7 +74,7 @@ function ManageSearchBar(props) { let dropBoxes = []; if (props.selectOptions !== null) props.selectOptions.forEach((option, index) => - dropBoxes.push() + dropBoxes.push() ) return dropBoxes } diff --git a/src/pages/admin/car/CarManage.js b/src/pages/admin/car/CarManage.js index 96a96fc..2572547 100644 --- a/src/pages/admin/car/CarManage.js +++ b/src/pages/admin/car/CarManage.js @@ -17,8 +17,12 @@ function CarManage(props) { Authorization: getToken() } }) - .then((Response) => { setCars(Response.data.data.content) }) - .catch((error) => {basicError(error)}) + .then((Response) => { + setCars(Response.data.data.content) + }) + .catch((error) => { + basicError(error) + }) }; const searchCars = (e) => { @@ -29,16 +33,16 @@ function CarManage(props) { getCars(""); }, []) - const moveToAdd = () => { + const moveToAdd = () => { window.location.href = `/admin/cars/add` } return ( - + 차량 관리 - + @@ -52,7 +56,7 @@ function CarManage(props) { - { cars.length === 0 ? + {cars.length === 0 ? 차량 내역이 없습니다. @@ -73,7 +77,7 @@ function CarManage(props) { - + ); } diff --git a/src/pages/admin/car/CarManageAdd.js b/src/pages/admin/car/CarManageAdd.js index cce375f..98d1f43 100644 --- a/src/pages/admin/car/CarManageAdd.js +++ b/src/pages/admin/car/CarManageAdd.js @@ -217,7 +217,7 @@ function CarManageAdd(props) { alert("책임자를 선택해주세요."); return; } - if (imageFile !== null) { // 이미지 파일 선택했을 때 + if (imageFile !== null && !isUpload) { // 이미지 파일 선택했을 때 ImageUrlAxios.get(`?ext=${imageFile.type.split("/", 2)[1]}&dir=car`) .then((Response) => { setImgUrl(Response.data); diff --git a/src/pages/admin/office/OfficeManageAdd.js b/src/pages/admin/office/OfficeManageAdd.js index 455526f..c05960c 100644 --- a/src/pages/admin/office/OfficeManageAdd.js +++ b/src/pages/admin/office/OfficeManageAdd.js @@ -217,7 +217,7 @@ function OfficeManageAdd(props) { // 이미지 람다 호출 const getImageUrl = () => { // todo: 검사 다 하기 - if (imageFile !== null) { // 이미지 파일 선택했을 때 + if (imageFile !== null && !isUpload) { // 이미지 파일 선택했을 때 && 업로드 안했을 때 ImageUrlAxios.get(`?ext=${imageFile.type.split("/", 2)[1]}&dir=office`) .then((Response) => { setImgUrl(Response.data); diff --git a/src/pages/admin/resource/ResourceManageAdd.js b/src/pages/admin/resource/ResourceManageAdd.js index b120914..8134c74 100644 --- a/src/pages/admin/resource/ResourceManageAdd.js +++ b/src/pages/admin/resource/ResourceManageAdd.js @@ -217,7 +217,7 @@ function ResourceManageAdd(props) { alert("책임자를 선택해주세요."); return; } - if (imageFile !== null) { // 이미지 파일 선택했을 때 + if (imageFile !== null && !isUpload) { // 이미지 파일 선택했을 때 ImageUrlAxios.get(`?ext=${imageFile.type.split("/", 2)[1]}&dir=resource`) .then((Response) => { setImgUrl(Response.data); diff --git a/src/pages/admin/user/UserManage.js b/src/pages/admin/user/UserManage.js index 7482af6..854bc93 100644 --- a/src/pages/admin/user/UserManage.js +++ b/src/pages/admin/user/UserManage.js @@ -25,7 +25,6 @@ function UserManage(props) { const currentAffiliation = useRef(""); const currentDepartment = useRef(""); const currentSearchWord = useRef(""); - let departments; // 유저 목록 조회 const getUserList = (word, affiliation, department) => { @@ -51,7 +50,7 @@ function UserManage(props) { } }) .then((response) => { - departments = response.data.data.departmentList + let departments = response.data.data.departmentList if (departmentOptionList.length === 0) { departmentOptionList.push() departments.map((department) => diff --git a/src/pages/basic/equipment/EquipmentAdd.js b/src/pages/basic/equipment/EquipmentAdd.js new file mode 100644 index 0000000..df7c9b9 --- /dev/null +++ b/src/pages/basic/equipment/EquipmentAdd.js @@ -0,0 +1,372 @@ +import React, {useEffect, useRef, useState} from "react"; +import styled from "styled-components"; +import {RightContainer, TitleText, WhiteContainer} from "components/rightContainer/RightContainer"; +import {ManageAddButton, ManageAddButtonImage} from "components/searchBar/ManageSearchBar"; +import {getToken} from "utils/IsLoginUtil"; +import {basicError} from "utils/ErrorHandlerUtil"; +import {EquipmentsAxios, ImageUrlAxios} from "api/AxiosApi"; +import SearchButtonImage from "assets/images/SearchPlus.svg" +import EmptyImg from "assets/images/EmptyImg.svg" +import {ExitBtn} from "components/modal/BigModal"; +import axios from "axios"; +import {useParams} from "react-router-dom"; +import {getImgKey} from "utils/ImageUtil"; +import {DropBox} from "components/capsule/DropBox"; + +const MarginWhiteContainer = styled(WhiteContainer)` + padding: 40px; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: space-between; +` + +const ColumnContainer = styled.div` + height: 40px; + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 40px; +` + +const ShortColumnContainer = styled(ColumnContainer)` + width: 50%; +` + +const ImgColumnContainer = styled(ShortColumnContainer)` + margin-bottom: 0; + height: 150px; + width: 300px; +` + +const TitleLabel = styled.label` + color: #8741CB; + font-size: 20px; + width: 100px; + min-width: 100px; + text-align: left; +` + +const InfoInput = styled.input.attrs({type: 'text'})` + flex: 1; + height: 20px; + border-radius: 8px; + border: 2px solid #E6E6E6; + font-size: 20px; + padding: 10px; +` + +const DescriptionContainer = styled(ColumnContainer)` + height: 60px; +` + +const DescriptionInput = styled.textarea` + flex: 1; + height: 100%; + border-radius: 8px; + border: 2px solid #E6E6E6; + font-size: 20px; + padding: 10px; +` + +const AddButtonContainer = styled.div` + width: 100%; + height: fit-content; + display: flex; + justify-content: flex-end; +` + +const ImageAddContainer = styled.div` + width: 100px; + height: 40px; + margin-left: 10px; +` + +const ImageAddButton = styled.button` + min-width: 100px; + height: 100%; + border-radius: 8px; + border: none; + filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.25)); + font-size: 16px; +` + +const ImageInfoContainer = styled.div` + width: 320px; + min-width: 320px; + height: 180px; + border-radius: 8px; + border: 2px solid #E6E6E6; + overflow: clip; + position: relative; +` + +const AbExitBtn = styled(ExitBtn)` + position: absolute; + top: 0; + right: 0; + color: #8741CB; +` + +const PreviewImage = styled.img` + width: 320px; + height: 180px; + object-fit: contain; +` + +function EquipmentManageAdd(props) { + let {equipmentId} = useParams(); + + const initialValue = { + name: null, + quantity: null, + place: null, + description: null, + category: null + }; + const [inputValues, setInputValues] = useState(initialValue); + const {name, quantity, place, description, category} = inputValues; + const [categoryOptionList, setCategoryOptionList] = useState([]); + const [imageSrc, setImgSrc] = useState(null); + const [imageFile, setImageFile] = useState(null); + const [imageUrl, setImgUrl] = useState(null); + const [isUpload, setIsUpload] = useState(false); + const [equipmentInfo, setEquipmentInfo] = useState(null) + const imageInput = useRef(null); + + const onChangeInput = event => { + const {value, name: inputName} = event.target; + setInputValues({...inputValues, [inputName]: value}); + } + + const changeImageFile = () => { + imageInput.current.click(); + }; + + const handleChange = (e) => { + setImageFile(e.target.files[0]) + const reader = new FileReader(); + reader.readAsDataURL(e.target.files[0]); + reader.onloadend = () => { + setImgSrc(reader.result); + }; + }; + + // 비품 기존 정보 호출 + const getEquipmentInfo = () => { + EquipmentsAxios.get(`/${equipmentId}`, { + headers: { + Authorization: getToken() + } + }) + .then((Response) => { + setEquipmentInfo(Response.data.data); + setInputValues({ + ...inputValues, + name: Response.data.data.name, + quantity: Response.data.data.quantity, + category: Response.data.data.category, + place: Response.data.data.location, + description: Response.data.data.description + }); + setImgUrl(Response.data.data.imgUrl); + setImgSrc(Response.data.data.imgUrl); + }) + .catch((Error) => { + basicError(Error) + window.history.back() + }) + } + useEffect(() => { + getCategoryList(); + if (equipmentId !== undefined) { + getEquipmentInfo(); + } + }, []) + + // 카테고리 리스트 검색 + const getCategoryList = () => { + EquipmentsAxios.get(`categories`, { + headers: { + Authorization: getToken() + } + }) + .then((Response) => { + let categories = Response.data.data.categoryNames + if (categoryOptionList.length === 0) { + setCategoryOptionList((prevList) => [...prevList, ]) + categories.map((category) => + setCategoryOptionList((prevList) => [...prevList, + ])) + } + }) + .catch((error) => { + basicError(error) + }); + }; + + const deleteImageFile = () => { + setImageFile(null); + setIsUpload(false); + imageInput.current.value = ""; + setImgSrc(null) + setImgUrl(null) + }; + + // 이미지 람다 호출 + const getImageUrl = () => { + // todo: 검사 다 하기 + if (imageFile !== null && !isUpload) { // 이미지 파일 선택했을 때 + ImageUrlAxios.get(`?ext=${imageFile.type.split("/", 2)[1]}&dir=equipment`) + .then((Response) => { + setImgUrl(Response.data); + }) + .catch((error) => { + console.log(error) + }); + } else { + if (equipmentInfo !== null) { // 수정일 때 + editEquipment(); + } else { + addEquipment(); + } + } + } + + // s3에 이미지 업로드 + const uploadImage = () => { + axios.put(imageUrl.presignedUrl, imageFile, { + headers: { + 'Content-Type': 'multipart/form-data' // Content-Type 헤더 설정 + } + }) + .then(function (rep) { + setIsUpload(true); + }) + .catch(function (err) { + console.log(err) + alert("이미지 등록에 실패했습니다. 다시 시도해주세요."); + }); + } + + useEffect(() => { + if (imageUrl !== null && imageFile !== null) + uploadImage(); + }, [imageUrl]); + + useEffect(() => { + if (isUpload) { + if (equipmentInfo !== null) editEquipment(); + else addEquipment(); + } + }, [isUpload]); + + const addEquipment = () => { + EquipmentsAxios.post(``, { + category: category, + description: description, + quantity: quantity, + location: place, + name: name, + imgKey: imageFile === null ? null : `equipment/${imageUrl.imageKey}`, + }, + { + headers: { + Authorization: getToken() + }, + }) + .then((Response) => { + alert("비품 등록이 완료되었습니다."); + window.location.href = `/equipments` + }) + .catch((error) => { + basicError(error) + }); + }; + + const editEquipment = () => { + EquipmentsAxios.patch(`/${equipmentId}`, { + category: category, + description: description, + quantity: quantity, + location: place, + name: name, + imgKey: imageFile === null ? getImgKey(imageUrl) : `equipment/${imageUrl.imageKey}`, + }, + { + headers: { + Authorization: getToken() + }, + }) + .then((Response) => { + alert("비품 수정이 완료되었습니다."); + window.location.href = `/equipments` + }) + .catch((error) => { + basicError(error) + }); + } + + return ( + + {equipmentInfo === null ? "비품 추가" : "비품 수정"} + + + + 비품명 + + + + + 수량 + + + + + 보관장소 + + + + + 카테고리 + + + + + 설명 + + + + + 첨부사진 + + + {imageSrc !== null && ×} + + + 파일선택 + + + + + + + + {equipmentInfo === null ? "비품 추가" : "비품 수정"} + + + + + + ); +} + +export default EquipmentManageAdd; diff --git a/src/pages/basic/equipment/EquipmentList.js b/src/pages/basic/equipment/EquipmentList.js index 7753c0a..c8aa113 100644 --- a/src/pages/basic/equipment/EquipmentList.js +++ b/src/pages/basic/equipment/EquipmentList.js @@ -11,10 +11,9 @@ import SearchButtonImage from "../../../assets/images/SearchPlus.svg"; import {ManageAddButton, ManageAddButtonImage} from "../../../components/searchBar/ManageSearchBar"; const CardList = styled.div` - width: 100%; - display: flex; - flex-wrap: wrap; - justify-content: space-between; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(calc(33.33% - 15px), 1fr)); + grid-gap: 15px; ` const Container = styled(WhiteContainer)` @@ -55,13 +54,17 @@ function EquipmentList(props) { searchEquipment(); }, []); + const moveToAdd = () => { + window.location.href = `/equipments/add` + } + return ( 비품 내역 - + 신규 비품 추가