Skip to content
Snippets Groups Projects
Commit 13e3a3d7 authored by Minseo Lee's avatar Minseo Lee
Browse files

feat: adminpage with socket

parent 4ffa398e
No related branches found
No related tags found
No related merge requests found
...@@ -10,5 +10,9 @@ const ALERT = Object.freeze({ ...@@ -10,5 +10,9 @@ const ALERT = Object.freeze({
REQ_WRONG: '잘못된 접근입니다' REQ_WRONG: '잘못된 접근입니다'
}); });
const STATUS = Object.freeze({
PENDING: 'Pending',
COMPLETED: 'Completed'
});
export { CONFIRM, ALERT }; export { CONFIRM, ALERT, STATUS };
...@@ -36,6 +36,5 @@ ...@@ -36,6 +36,5 @@
.mr-4 { margin-right: 16px; } .mr-4 { margin-right: 16px; }
.bold { .bold { font-weight: bold; }
font-weight: bold; .del { text-decoration: line-through; }
}
import { io } from "socket.io-client";
import { SOCKET_URL } from "../utils/api";
class AdminSocket {
private ioInstance = io(`${SOCKET_URL}/admin`, { withCredentials: true });
constructor() {
this.ioInstance.on('connect', () => {
console.log('WS: admin connected');
});
this.ioInstance.on('auth', (e) => {
console.log('WS: admin auth', e);
});
}
onOrder(listener: (a: any) => void) {
return this.ioInstance.on('order', listener);
}
disconnect() {
this.ioInstance.disconnect();
}
}
export default AdminSocket;
...@@ -21,6 +21,13 @@ class Connector { ...@@ -21,6 +21,13 @@ class Connector {
return this.isAdmin; return this.isAdmin;
} }
private checkSession(status: number) {
if (status === RESPONSE_STATUS.UNAUTH) {
this.setLoginInstance(false);
window.location.href = '/login';
}
}
async login<T>(payload?: any): Promise<T> { async login<T>(payload?: any): Promise<T> {
const postRequest = await fetchData<T>('/user/login', FETCH_METHOD.POST, payload); const postRequest = await fetchData<T>('/user/login', FETCH_METHOD.POST, payload);
if (postRequest.status === RESPONSE_STATUS.OK) { if (postRequest.status === RESPONSE_STATUS.OK) {
...@@ -36,21 +43,21 @@ class Connector { ...@@ -36,21 +43,21 @@ class Connector {
async get<T>(url: string, payload?: any): Promise<T> { async get<T>(url: string, payload?: any): Promise<T> {
const getRequest = await fetchData<T>(url, FETCH_METHOD.GET, payload); const getRequest = await fetchData<T>(url, FETCH_METHOD.GET, payload);
if (getRequest.status === RESPONSE_STATUS.UNAUTH) { this.checkSession(getRequest.status);
this.setLoginInstance(false);
window.location.href = '/login';
}
return getRequest.response; return getRequest.response;
} }
async post<T>(url: string, payload?: any): Promise<T> { async post<T>(url: string, payload?: any): Promise<T> {
const postRequest = await fetchData<T>(url, FETCH_METHOD.POST, payload); const postRequest = await fetchData<T>(url, FETCH_METHOD.POST, payload);
if (postRequest.status === RESPONSE_STATUS.UNAUTH) { this.checkSession(postRequest.status);
this.setLoginInstance(false);
window.location.href = '/login';
}
return postRequest.response; return postRequest.response;
} }
async patch<T>(url: string, payload?: any): Promise<T> {
const patchRequest = await fetchData<T>(url, FETCH_METHOD.PATCH, payload);
this.checkSession(patchRequest.status);
return patchRequest.response;
}
} }
export default Connector; export default Connector;
...@@ -93,6 +93,7 @@ const Header: GFCWithProp<Props> = ({ headerName, connector }) => { ...@@ -93,6 +93,7 @@ const Header: GFCWithProp<Props> = ({ headerName, connector }) => {
if (_loc === 'menu') setReturnEl(<MenuHeader headerName={headerName} connector={connector} />); if (_loc === 'menu') setReturnEl(<MenuHeader headerName={headerName} connector={connector} />);
if (_loc === 'cart') setReturnEl(<CartHeader />); if (_loc === 'cart') setReturnEl(<CartHeader />);
if (_loc === 'history') setReturnEl(<HistoryHeader />); if (_loc === 'history') setReturnEl(<HistoryHeader />);
if (_loc === 'admin') setReturnEl(<HomeHeader connector={connector} />);
}, [location, headerName]); }, [location, headerName]);
return ( return (
......
.container {
display: flex;
flex-direction: column;
gap: 24px;
padding: 48px;
line-height: 1.2;
}
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import APP_ROUTE from "../../_app/config/route"; import APP_ROUTE from "../../_app/config/route";
import { ALERT } from "../../common/constants"; import { ALERT, STATUS } from "../../common/constants";
import AdminSocket from "../../common/instances/AdminSocket";
import S from './AdminPage.module.css';
import AdminOrderBox from "./modules/admin-order-box/AdminOrderBox";
import type { OrderedItemModel } from "./config/type";
import type { GFC } from "../../common/types/fc"; import type { GFC } from "../../common/types/fc";
const AdminPage: GFC = ({ connector }) => { const AdminPage: GFC = ({ connector }) => {
const navigator = useNavigate(); const navigator = useNavigate();
let adminSocket: AdminSocket|null = null;
const [orderList, setOrderList] = useState<Array<OrderedItemModel>>([]);
useEffect(() => { useEffect(() => {
if (!connector.current?.getIsAdmin()) { if (!connector.current?.getIsAdmin()) {
...@@ -16,10 +25,55 @@ const AdminPage: GFC = ({ connector }) => { ...@@ -16,10 +25,55 @@ const AdminPage: GFC = ({ connector }) => {
navigator(APP_ROUTE.LOGIN); navigator(APP_ROUTE.LOGIN);
return; return;
} }
adminSocket = new AdminSocket();
adminSocket.onOrder((e) => {
setOrderList((prev) => {
let sortedArray = [...prev];
sortedArray.push(e);
const idStatusMap = new Map();
sortedArray.forEach(item => {
if (item.status === STATUS.COMPLETED) {
idStatusMap.set(item._id, true);
} else if (!idStatusMap.has(item._id)) {
idStatusMap.set(item._id, false);
}
});
sortedArray = sortedArray.filter(item => {
return item.status === STATUS.COMPLETED || !idStatusMap.get(item._id);
});
sortedArray.sort((a, b) => {
if (a.status === STATUS.COMPLETED && b.status !== STATUS.COMPLETED) {
return 1;
} else if (b.status === STATUS.COMPLETED && a.status !== STATUS.COMPLETED) {
return -1;
}
return 0;
});
return sortedArray;
});
});
}, [connector]); }, [connector]);
return ( return (
<div>admin</div> <div className={S['container']}>
{
orderList.length && orderList.map((d, i) => {
return (
<AdminOrderBox connector={connector} items={d.items}
waitingCount={d.waitingCount} takeout={d.takeout}
createdTime={d.createdTime} status={d.status}
_id={d._id} key={`aob-c1-${i}`}
/>
);
})
}
</div>
); );
}; };
......
import type { OrderedItemModel } from "./type";
const ADMIN_DUMMY: OrderedItemModel = {
"userId": "656b8ec725f8b6df24ab910c",
"shopId": "3",
"items": [
{
"menuId": "6562292377dd1a645a7e960a",
"quantity": 2
},
{
"menuId": "6562292377dd1a645a7e9609",
"quantity": 1
}
],
"paymentMethod": "MONEY",
"waitingCount": 39,
"takeout": true,
"totalPrice": 22000,
"createdTime": "2023-12-02T23:07:47.000Z",
"status": "Pending",
"_id": "123"
};
export { ADMIN_DUMMY };
import type { CartItemPostModel } from "../../cart-page/config/type";
type OrderedItemModel = CartItemPostModel & {
status: string;
_id: string;
}
export type { OrderedItemModel };
.container {
width: 100%;
min-height: 48px;
padding: 16px;
border: 1px solid var(--gray-2);
border-radius: 16px;
}
import { useEffect, useState } from "react";
import { ALERT, STATUS } from "../../../../common/constants";
import S from './AdminOrderBox.module.css';
import type Connector from "../../../../common/instances/Connector";
import type { MenuPageData } from "../../../menu-page/config/type";
import type { OrderedItemModel } from "../../config/type";
import type { FC, MutableRefObject } from "react";
interface Props {
connector: MutableRefObject<Connector|null>;
items: OrderedItemModel['items'];
waitingCount: OrderedItemModel['waitingCount'];
takeout: OrderedItemModel['takeout'];
createdTime: OrderedItemModel['createdTime'];
status: OrderedItemModel['status'];
_id: OrderedItemModel['_id'];
}
const AdminOrderBox: FC<Props> = ({ connector, items, waitingCount, takeout, createdTime, status, _id }) => {
const [itemName, setItemName] = useState<string[]>([]);
const handleClickButton = (_id: string) => {
connector.current!.patch('/admin/order', {
orderId: _id,
newStatus: 'Completed'
});
};
useEffect(() => {
void (async () => {
try {
const response = await Promise.allSettled(
items.map((d) =>
connector.current!.get<MenuPageData>(`/menu/${d.menuId}`)
)
);
const itemNames = response.map(r => {
if (r.status === 'fulfilled' && r.value.name) {
return r.value.name;
}
return null;
}).filter(name => name !== null) as string[];
setItemName(itemNames);
} catch (e) {
alert(ALERT.REQ_FAIL);
}
})();
}, [items]);
return (
<div className={[S['container'], status === STATUS.COMPLETED ? 'del' : ''].join(' ')}>
<h1>대기 번호: {waitingCount}</h1>
<div>
{
items.map((d, i) => {
return (
<div key={`aob-c2-${i}`}>
<p>{itemName[i]} {d.quantity}</p>
</div>
);
})
}
</div>
<h2>주문 시각: {createdTime}</h2>
<h2>포장 여부: {takeout ? '포장' : '매장'}</h2>
{
status !== STATUS.COMPLETED && <button onClick={() => handleClickButton(_id)}>조리 완료</button>
}
</div>
);
};
export default AdminOrderBox;
...@@ -17,7 +17,7 @@ interface CartItemPostModel { ...@@ -17,7 +17,7 @@ interface CartItemPostModel {
takeout: boolean; takeout: boolean;
waitingCount: number; waitingCount: number;
totalPrice: number; totalPrice: number;
createdTime: Date; createdTime: string;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment