4. 주문 관리

1. 주문 목록

🎯 주문 관리 기능을 간단히 다뤄보기

실제 쇼핑몰을 만들 때는 여러가지 상태 정보가 필요하기 때문에 주문 관리가 제일 복잡(특히 백엔드)

  • 송장 번호

  • 취소시 환불

  • 부분 취소

OrderListPage 구현

주문 목록을 확인하는 페이지 조금 양이 많은 사용자 또는 카테고리 목록

export default function OrderListPage() {
  const { orders, loading, error } = useFetchOrders();

  // loading, error 처리 

  return (
    <Container>
      <h2>Orders</h2>
      <table>
        <thead>
          <tr>
            <th>주문일</th>
            <th>주문자</th>
            <th>상품</th>
            <th>총 가격</th>
            <th>상태</th>
            <th>행동</th>
          </tr>
        </thead>
        <tbody>
          {orders.map((order) => (
            <tr key={order.id}>
              <td>{order.orderedAt}</td>
              <td>{order.orderer.name}</td>
              <td>{order.title}</td>
              <td>
                {numberFormat(order.totalPrice)}

              </td>
              <td>{STATUS_MESSAGES[order.status]}</td> 
              <td>
                <Link to={`/orders/${order.id}`}>
                  자세히
                </Link>
                <Link to={`/orders/${order.id}/edit`}>
                  상태 변경
                </Link>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </Container>
  );
}
  • STATUS_MESSAGES : 언어를 서버에서 변경해 줄 수도 있지만 다국어 지원 등이 불편하기 때문에 이렇게 처리

  • 다국어 지원을 위한 라이브러리도 존재

contants.ts 파일에서 상수 관리

주문 상태를 어떻게 표시할지 상수만 모은 contants.ts 파일에서 정의

export const STATUS_MESSAGES: Record<string, string> = {
  paid: '결제 완료',
  ready: '배송 준비',
  shipping: '배송 중',
  complete: '배송 완료',
  canceled: '취소',
};
  • 배송 준비 : 택배를 보내려고 포장하는 상태

  • 배송 중 : 택배 기사에게 전달 후

  • 취소 : 관리자가 실수했을 때 처리

    • 어떤 일이 있었는지 로그가 필요

useFetchOrders hook 생성

import useFetch from './useFetch';

import { OrderSummary } from '../types';

export default function useFetchOrders() {
  const { data, error, loading } = useFetch<{
    orders: OrderSummary[];
  }>('/orders');

  return {
    orders: data?.orders ?? [],
    error,
    loading,
  };
}

2. 주문 상세

🎯 주문 상세 정보 확인

OrderDetailPage 구현

export default function OrderDetailPage() {
  const params = useParams();

  const { order, loading, error } = useFetchOrder({
    orderId: String(params.id),
  });

  // loading, error 처리
    
  return (
    <Container>
      <h2>Order Detail</h2>
      <dl>
        <dt>주문일시</dt>
        <dd>{order.orderedAt}</dd>
        <dt>주문자</dt>
        <dd>{order.orderer.name}</dd>
        <dt>상품</dt>
        <dd>
          <ul>
            {order.lineItems.map((lineItem) => (
              <li key={lineItem.id}>
                {lineItem.product.name}
                <Options options={lineItem.options} />
              </li>
            ))}
          </ul>
        </dd>
        <dt>총 가격</dt>
        <dd>
          {numberFormat(order.totalPrice)}

        </dd>
        <dt>배송 정보</dt>
        <dd>
          <p>받는 사람:{order.receiver.name}</p>
          <p>연락처:{order.receiver.phoneNumber}</p>
          <p>
            {order.receiver.address1}
            {order.receiver.address2}
            (우편번호: {order.receiver.postalCode})
          </p>
        </dd>
        <dt>결제 정보</dt>
        <dd>
          <p>주문번호: {order.payment.merchantId}</p>
          <p>결제고유번호: {order.payment.transactionId}</p>
        </dd>
        <dt>상태</dt>
        <dd>{STATUS_MESSAGES[order.status]}</dd>
      </dl>
      <Link to={`/orders/${order.id}/edit`}>
        상태 변경
      </Link>
    </Container>
  );
}
  • STATUS_MESSAGES 사용

  • <Link>로 처리된 상태 변경은 버튼으로 구현해도 좋음

  • 상태 변경주문 목록 버튼 구현하기

  • 상태에는 송장 번호가 같이 나오면 좋음(배송 추적 가능)

useFetchOrder hook 생성

기존과 비슷하게 작성

  • orderId를 인자로 받음

3. 주문 상태 변경

🎯 단순하게 주문 상태만 변경해보기

👩🏻‍💼 고객 지원 업무(CS)를 위해서는 주문을 자세히 다룰 수 있는 기능이 필요

  • 주소를 잘못 입력했을 경우

  • 이사 간 주소로 변경할 경우

  • 배송 시작했을 경우 송장 번호 추가 (배송 시작 버튼을 누르면 송장 번호를 입력)

  • 이후 배송 중 상태로 변경

필요에 따라 B/E 개발자와 논의해 계속해서 확장해 나갈 것 ⇒ 단순 CRUD를 넘어서 비즈니스에 대한 이해가 필요한 부분

OrderEditPage 구현

export default function OrderEditPage() {
  const params = useParams();

  const orderId = String(params.id);

  const navigate = useNavigate();

  const {
    order, loading, error, updateOrder,
  } = useFetchOrder({
    orderId,
  });

  type FormValues = {
    status: string;
  };

  const { handleSubmit, control } = useForm<FormValues>();

  const onSubmit = async (data: FormValues) => {
    await updateOrder({
      status: data.status,
    });
    navigate(`/orders/${orderId}`);
  };
  
  // loading, error 처리

  return (
    <Container>
      <h2>Order Status Transition</h2>
      <dl>
        <dt>주문일시</dt>
        <dd>{order.orderedAt}</dd>
        <dt>주문자</dt>
        <dd>{order.orderer.name}</dd>
        <dt>상품</dt>
        <dd>{order.title}</dd>
        <dt>총 가격</dt>
        <dd>
          {numberFormat(order.totalPrice)}

        </dd>
      </dl>
      <form onSubmit={handleSubmit(onSubmit)}>
        <Controller
          control={control}
          name="status"
          defaultValue={order.status}
          render={({ field: { onChange, value } }) => (
            <div>
              <label htmlFor="input-status">상태</label>
              <select
                id="input-status"
                value={value}
                onChange={onChange}
              >
                {Object.keys(STATUS_MESSAGES).map((status) => (
                  <option key={status} value={status}>
                    {STATUS_MESSAGES[status]}
                  </option>
                ))}
              </select>
            </div>
          )}
        />
        <Button type="submit">
          변경
        </Button>
      </form>
    </Container>
  );
}

useFetchOrder hook에 updateOrder 추가

export default function useFetchOrder({ orderId }: {
  orderId: string;
}) {
  const {
    data, error, loading, mutate,
  } = useFetch<OrderDetail>(`/orders/${orderId}`);

  return {
    order: data,
    error,
    loading,
    async updateOrder({ status }: {
      status: string;
    }) {
      await apiService.updateOrder({ orderId, status });

      mutate();
    },
  };
}

Last updated