본 내용의 코드는 전부 위 링크에 있으니 참고바랍니다
로그인 페이지 (LoginPage.js)
로그인 POST
- URL에 /admin 입력시 자동으로 /login의 LoginPage로 이동
- ID와 PW가 승인될 경우 response.status 값 200과 함께 AdminPage로 이동
- 제어 컴포넌트로 로그인 객체 생성 및 POST
...
// ID, PW
const [inputs, setInputs] = useState({
id: "",
password: "",
});
const { id, password } = inputs;
// input 객체 생성
const onChange = (e) => {
const { value, name } = e.target;
setInputs({
...inputs,
[name]: value,
});
};
// REST API 4-1: login
const handleClick = (e) => {
e.preventDefault();
axios
.post(`${hostURL}/api/check`, inputs)
.then((response) => {
// 로그인 승인이 됐을 경우
if (response.data.result === 'True') {
navigator("/admin", { state: response.status });
}
// 로그인 승인이 안 됐을 경우
else {
alert("아이디 또는 비밀번호가 일치하지 않습니다.");
}
})
.catch((error) => {
console.log(error);
});
};
...
관리자 페이지: 주문 목록 (AdminPage.js, OrderListPage.js)
AdminPage.js
- useLocation을 통해 response.status 값을 받아와서 한 번 더 로그인 승인 확인
- 상단 헤더의 토글 버튼으로 주문목록 페이지와 상품/후기 목록 페이지 사이를 전환
- 하위 컴포넌트 OrderListPage와 ProductListPage는 toggle status에 따라 출력
...
const [title, setTitle] = useState("주문 목록");
const navigate = useNavigate();
const location = useLocation();
// prevent direct access
useEffect(() => {
// 로그인 정보가 틀릴 경우 로그인 페이지로 이동
if (location.state !== 200) {
navigate("/login");
}
}, [location.state, navigate]);
...
return
...
{/* toggle button */}
<label className={styles.switch}>
<input type="checkbox" onClick={() => { title === "주문 목록" ? setTitle("상품/후기 목록") : setTitle("주문 목록") }}></input>
<span className={styles.slider} id={styles.round}></span>
</label>
...
{/* Order List Page */}
<div style={title === "주문 목록" ? { display: "block" } : { display: "none" }}>
<OrderListPage status={location.state} />
</div>
{/* Product List Page */}
<div style={title === "상품/후기 목록" ? { display: "block" } : { display: "none" }}>
<ProductListPage status={location.state} />
</div>
...
전 주문 목록 GET
- useEffect를 통해 최초 렌더링 시에만 작동
- useState를 통해 result 상태를 GET한 전 주문 목록으로 set
- useState를 통해 전 주문정보를 GET할 경우 loading 상태 false로 변경
- reloadFlag 값이 dependency로서 변할 경우 전 주문 목록을 다시 불러옴 (배송 상태 수정 시 활용)
...
// REST API 1-1: get all orders
useEffect(() => {
if (props.status === 200) {
axios
.get(`${hostURL}/api/admin/orders`)
.then((response) => {
setResult(response.data);
setIsLoading(false);
})
.catch((error) => {
console.log(error);
});
}
}, [props.status, reloadFlag]);
...
주문 목록 검색
- 주문 목록을 필터할 기준들을 담고 있는 객체 생성
- filter된 주문 목록을 mapping하여 나열
- mapping할 때 key는 orderId로 설정
...
// 회차, 배송상태, 상품명, 상품개수, 주문자명, 전화번호
const [inputs, setInputs] = useState({
testOrderSelected: "전체",
statusSelected: "배송상태",
productSelected: "상품명",
countSelected: "상품개수",
searchName: "",
searchNumber: "",
});
const { testOrderSelected, statusSelected, productSelected, countSelected, searchName, searchNumber } = inputs;
// input 객체 생성
const onChange = (e) => {
const { value, name } = e.target;
setInputs({
...inputs,
[name]: value,
});
};
...
// filtered result
const filteredResult = [...result]
// 최신 순 정렬
.sort((a, b) => {
if (a.orderDate > b.orderDate) return -1;
if (a.orderDate < b.orderDate) return 1;
return 0;
})
// 회차 기준
.filter((order) => {
switch (testOrderSelected) {
case "4회차":
return (
order.orderDate >= "2023-09-18T20:59:00" &&
order.orderDate <= "2023-09-24T23:59:59"
);
case "3회차":
return (
order.orderDate >= "2023-09-11T20:59:00" &&
order.orderDate <= "2023-09-17T23:59:59"
);
case "2회차":
return (
order.orderDate >= "2023-09-04T20:59:00" &&
order.orderDate <= "2023-09-10T23:59:59"
);
case "1회차":
return (
order.orderDate >= "2023-08-28T20:59:00" &&
order.orderDate <= "2023-09-03T23:59:59"
);
default:
return true;
}
})
// 배송상태 기준
.filter((order) => {
if (statusSelected === "배송상태") {
return true;
}
return order.orderStatus === filterDict.statusDict[statusSelected];
})
// 상품명 기준
.filter((order) => {
if (productSelected === "상품명") {
return true;
}
return order.item.name === filterDict.productDict[productSelected];
})
// 상품개수 기준
.filter((order) => {
if (countSelected === "상품개수") {
return true;
}
return order.count === filterDict.countDict[countSelected];
})
// 주문자명 검색
.filter((order) => {
if (searchName === "") {
return true;
}
return order.name
.toString()
.replace(" ", "")
.toLocaleLowerCase()
.includes(searchName.toLocaleLowerCase().replace(" ", ""));
})
// 주문자 전화번호 검색
.filter((order) => {
if (searchNumber === "") {
return true;
}
return order.number
.toString()
.replace(" ", "")
.toLocaleLowerCase()
.includes(searchNumber.toLocaleLowerCase().replace(" ", ""));
});
...
return (
<div className={styles.order_main}>
{/* 필터 목록 */}
<div className={styles.firstSelectBox}>
{/* 회차 */}
<select onChange={onChange} name="testOrderSelected" value={testOrderSelected}>
{Object.keys(filterDict.testOrderDict).map((testOrder) => (
<option value={testOrder} key={testOrder}>
{testOrder}
</option>
))}
</select>
{/* 배송상태 */}
<select onChange={onChange} name="statusSelected" value={statusSelected}>
{Object.keys(filterDict.statusDict).map((status) => (
<option value={status} key={status}>
{status}
</option>
))}
</select>
...
{/* 검색 결과 */}
{filteredResult.map((order) => (...))}
...
주문 목록 요약
- 주문 개수, 상품 개수, 비용, 매출에 관한 정보를 요약하여 출력
- 세 자리마다 쉼표 표시
...
// product count
let productCount = 0;
for (let i = 0; i < filteredResult.length; i++) {
productCount += filteredResult[i].count;
}
// total cost
let cost = 0;
for (let i = 0; i < filteredResult.length; i++) {
cost += filteredResult[i].item.originalPrice * filteredResult[i].count;
}
// total revenue
let revenue = 0;
for (let i = 0; i < filteredResult.length; i++) {
revenue += filteredResult[i].orderPrice;
}
...
return
...
{/* order & payment count */}
<div className={styles.orderCount}>
<span>
주문: {filteredResult.length}개 / 상품: {productCount}개
</span>
</div>
<div className={styles.paymentCount}>
<span>
비용: {cost.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}원 / 매출: {revenue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}원
</span>
</div>
...
배송 상태 PATCH
- props를 orderId, orderStatus, reloadFlag, setReloadFlag로 지닌 하위 컴포넌트 StatusButton 생성
- orderStatus에 따라 배송 상태 버튼 style을 다르게 설정
// OrderListPage.js
return
...
{/* delivery */}
<StatusButton
orderId={order.orderId}
orderStatus={order.orderStatus}
reloadFlag={reloadFlag}
setReloadFlag={setReloadFlag}
/>
...
// StatusButton.js
...
const { orderId, orderStatus, reloadFlag, setReloadFlag } = props;
...
// intial button style
const initialStyle = {
WAITING: ButtonStyle,
DELIVERING: ButtonStyle,
ARRIVED: ButtonStyle,
CANCELED: ButtonStyle
};
switch (orderStatus) {
case "WAITING":
initialStyle.WAITING = clickedButtonStyle;
break;
case "DELIVERING":
initialStyle.DELIVERING = clickedButtonStyle;
break;
case "ARRIVED":
initialStyle.ARRIVED = clickedButtonStyle;
break;
case "CANCELED":
initialStyle.CANCELED = clickedButtonStyle;
break;
default:
break;
}
const [styleInput, setStyleInput] = useState(initialStyle);
const { WAITING, DELIVERING, ARRIVED, CANCELED } = styleInput;
...
- 다른 배송 상태 버튼 클릭 시 배송 상태 변경
- 이에 따라 모든 배송 상태 버튼들의 style도 같이 변동
- reloadFlag 값이 변함에 따라 전 주문 목록 reload
// StatusButton.js
...
// REST API 1-4: change order status
const onClick = (e) => {
// 버튼 Style 초기화
setStyleInput({
WAITING: ButtonStyle,
DELIVERING: ButtonStyle,
ARRIVED: ButtonStyle,
CANCELED: ButtonStyle
});
// 배송 상태 수정
axios
.patch(`${hostURL}/api/admin/orders/${orderId}`, { status: e.target.value })
.then((response) => {
// 클릭된 배송 상태만 Style 변동
setStyleInput((prev) => ({...prev, [e.target.value]: clickedButtonStyle}));
// reloadFlag가 변함에 따라 전 주문 목록 reload
reloadFlag === 0 ? setReloadFlag(1) : setReloadFlag(0);
})
.catch((error) => {
console.log(error);
});
};
return (
<div style={orderDelivery}>
<button
onClick={onClick}
style={WAITING}
value="WAITING"
>
입금대기
</button>
<button
onClick={onClick}
style={DELIVERING}
value="DELIVERING"
>
배송중
</button>
<button
onClick={onClick}
style={ARRIVED}
value="ARRIVED"
>
배송완료
</button>
<button
onClick={onClick}
style={CANCELED}
value="CANCELED"
>
주문취소
</button>
</div>
);
...
'React' 카테고리의 다른 글
React로 커머스 사이트 만들기 (8)-1: AWS S3 배포 (0) | 2023.09.24 |
---|---|
React로 커머스 사이트 만들기 (7): 관리자 페이지 (2) (0) | 2023.09.24 |
React로 커머스 사이트 만들기 (5): 주문인증/주문조회 페이지 (0) | 2023.09.24 |
React로 커머스 사이트 만들기 (4): 구매 Modal & 주문완료 페이지 (0) | 2023.09.24 |
React로 커머스 사이트 만들기 (3): 홈페이지 & 상품페이지 (2) | 2023.09.23 |