본문 바로가기

카테고리 없음

[React] React로 마트 계산기 만들기

반응형

 

안녕하세요! 오늘은 React와 Tailwind CSS를 사용하여 실용적인 마트 계산기를 만드는 방법을 알아보겠습니다. 이 프로젝트를 통해 실제 상황에서 사용할 수 있는 계산기를 구현하면서 React의 상태 관리와 UI 구성 방법을 배워볼 수 있습니다.

주요 기능

  • 상품 정보 입력 (상품명, 가격, 수량, 할인율)
  • 상품 목록 관리 (추가/삭제)
  • 할인율 적용 및 계산
  • 총액 자동 계산
  • 한국 원화 형식으로 금액 표시

사용된 기술

  • React.js
  • Tailwind CSS
  • shadcn/ui (Card, Button, Input 컴포넌트)
  • Lucide React (아이콘)

기능 구현

1. 상태 관리 설정

먼저 필요한 상태들을 정의합니다:

const [items, setItems] = useState([]); // 상품 목록
const [currentItem, setCurrentItem] = useState({
  name: '',
  price: '',
  quantity: '1',
  discount: '0'
}); // 현재 입력 중인 상품
const [total, setTotal] = useState(0); // 총액

2. 상품 추가 기능

const addItem = () => {
  if (currentItem.name && currentItem.price) {
    const price = parseFloat(currentItem.price);
    const quantity = parseInt(currentItem.quantity) || 1;
    const discount = parseFloat(currentItem.discount) || 0;

    const subtotal = (price * quantity) * (1 - discount / 100);

    const newItem = {
      ...currentItem,
      price: parseFloat(currentItem.price),
      quantity: parseInt(currentItem.quantity),
      discount: parseFloat(currentItem.discount),
      subtotal: subtotal
    };

    setItems([...items, newItem]);
    setCurrentItem({
      name: '',
      price: '',
      quantity: '1',
      discount: '0'
    });

    calculateTotal([...items, newItem]);
  }
};

3. 금액 계산 및 포맷팅

const calculateTotal = (currentItems) => {
  const newTotal = currentItems.reduce((sum, item) => sum + item.subtotal, 0);
  setTotal(newTotal);
};

const formatCurrency = (amount) => {
  return new Intl.NumberFormat('ko-KR', {
    style: 'currency',
    currency: 'KRW'
  }).format(amount);
};

UI 구현

1. 입력 폼 구현

상품 정보를 입력받는 폼을 그리드 레이아웃으로 구성합니다:

<div className="grid grid-cols-12 gap-2 mb-4">
  <Input
    className="col-span-3"
    placeholder="상품명"
    value={currentItem.name}
    onChange={(e) => setCurrentItem({...currentItem, name: e.target.value})}
  />
  <Input
    className="col-span-3"
    placeholder="가격"
    type="number"
    value={currentItem.price}
    onChange={(e) => setCurrentItem({...currentItem, price: e.target.value})}
  />
  {/* 수량과 할인율 입력 필드 */}
  <Button className="col-span-2" onClick={addItem}>
    <Plus className="mr-1 h-4 w-4" /> 추가
  </Button>
</div>

2. 상품 목록 테이블

HTML table 요소를 사용하여 상품 목록을 표시합니다:

<table className="w-full">
  <thead className="bg-gray-50">
    <tr>
      <th className="py-2 px-4 text-left">상품명</th>
      <th className="py-2 px-4 text-right">가격</th>
      <th className="py-2 px-4 text-right">수량</th>
      <th className="py-2 px-4 text-right">할인</th>
      <th className="py-2 px-4 text-right">소계</th>
      <th className="py-2 px-4 w-[50px]"></th>
    </tr>
  </thead>
  <tbody>
    {items.map((item, index) => (
      <tr key={index} className="border-t">
        {/* 상품 정보 표시 */}
      </tr>
    ))}
  </tbody>
</table>

3. 총액 표시

계산된 총액을 시각적으로 강조하여 표시합니다:

<div className="mt-4 flex justify-end">
  <div className="bg-blue-50 p-4 rounded-lg">
    <span className="text-lg font-semibold mr-2">총액:</span>
    <span className="text-2xl font-bold text-blue-600">
      {formatCurrency(total)}
    </span>
  </div>
</div>

주요 기능 설명

1. 상품 입력 처리

  • 사용자가 입력한 상품 정보를 실시간으로 상태에 반영
  • 필수 필드(상품명, 가격) 검증
  • 수량과 할인율의 기본값 설정

2. 계산 로직

  • 개별 상품의 소계 = 가격 × 수량 × (1 - 할인율/100)
  • 총액 = 모든 상품의 소계 합계
  • 할인율은 퍼센트 단위로 적용

3. 데이터 관리

  • 상품 목록은 배열로 관리
  • 각 상품은 고유 인덱스로 식별
  • 상품 삭제 시 자동으로 총액 재계산

개선 가능한 부분

이 마트 계산기는 다음과 같은 방향으로 확장할 수 있습니다:

  1. 기능 확장
    • 상품 코드/바코드 지원
    • 자주 사용하는 상품 즐겨찾기
    • 결제 수단 선택
    • 영수증 출력
  2. UI/UX 개선
    • 다크 모드 지원
    • 반응형 디자인 강화
    • 키보드 단축키 지원
    • 상품 검색 기능
  3. 데이터 관리
    • 로컬 스토리지 저장
    • 상품 데이터베이스 연동
    • 거래 내역 관리

전체 소스 코드

전체 구현 코드는 아래와 같습니다:

import React, { useState } from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Trash2, Plus } from 'lucide-react';

const MartCalculator = () => {
  const [items, setItems] = useState([]);
  const [currentItem, setCurrentItem] = useState({
    name: '',
    price: '',
    quantity: '1',
    discount: '0'
  });
  const [total, setTotal] = useState(0);

  const addItem = () => {
    if (currentItem.name && currentItem.price) {
      const price = parseFloat(currentItem.price);
      const quantity = parseInt(currentItem.quantity) || 1;
      const discount = parseFloat(currentItem.discount) || 0;
      
      const subtotal = (price * quantity) * (1 - discount / 100);
      
      const newItem = {
        ...currentItem,
        price: parseFloat(currentItem.price),
        quantity: parseInt(currentItem.quantity),
        discount: parseFloat(currentItem.discount),
        subtotal: subtotal
      };

      setItems([...items, newItem]);
      setCurrentItem({
        name: '',
        price: '',
        quantity: '1',
        discount: '0'
      });

      calculateTotal([...items, newItem]);
    }
  };

  const removeItem = (index) => {
    const newItems = items.filter((_, i) => i !== index);
    setItems(newItems);
    calculateTotal(newItems);
  };

  const calculateTotal = (currentItems) => {
    const newTotal = currentItems.reduce((sum, item) => sum + item.subtotal, 0);
    setTotal(newTotal);
  };

  const formatCurrency = (amount) => {
    return new Intl.NumberFormat('ko-KR', {
      style: 'currency',
      currency: 'KRW'
    }).format(amount);
  };

  return (
    <Card className="w-full max-w-3xl mx-auto">
      <div className="p-6">
        <h2 className="text-2xl font-bold mb-6">마트 계산기</h2>
        
        {/* 상품 입력 폼 */}
        <div className="grid grid-cols-12 gap-2 mb-4">
          <Input
            className="col-span-3"
            placeholder="상품명"
            value={currentItem.name}
            onChange={(e) => setCurrentItem({...currentItem, name: e.target.value})}
          />
          <Input
            className="col-span-3"
            placeholder="가격"
            type="number"
            value={currentItem.price}
            onChange={(e) => setCurrentItem({...currentItem, price: e.target.value})}
          />
          <Input
            className="col-span-2"
            placeholder="수량"
            type="number"
            value={currentItem.quantity}
            onChange={(e) => setCurrentItem({...currentItem, quantity: e.target.value})}
          />
          <Input
            className="col-span-2"
            placeholder="할인 %"
            type="number"
            value={currentItem.discount}
            onChange={(e) => setCurrentItem({...currentItem, discount: e.target.value})}
          />
          <Button className="col-span-2" onClick={addItem}>
            <Plus className="mr-1 h-4 w-4" /> 추가
          </Button>
        </div>

        {/* 상품 목록 테이블 */}
        <div className="border rounded-lg overflow-hidden">
          <table className="w-full">
            <thead className="bg-gray-50">
              <tr>
                <th className="py-2 px-4 text-left">상품명</th>
                <th className="py-2 px-4 text-right">가격</th>
                <th className="py-2 px-4 text-right">수량</th>
                <th className="py-2 px-4 text-right">할인</th>
                <th className="py-2 px-4 text-right">소계</th>
                <th className="py-2 px-4 w-[50px]"></th>
              </tr>
            </thead>
            <tbody>
              {items.map((item, index) => (
                <tr key={index} className="border-t">
                  <td className="py-2 px-4">{item.name}</td>
                  <td className="py-2 px-4 text-right">{formatCurrency(item.price)}</td>
                  <td className="py-2 px-4 text-right">{item.quantity}</td>
                  <td className="py-2 px-4 text-right">{item.discount}%</td>
                  <td className="py-2 px-4 text-right">{formatCurrency(item.subtotal)}</td>
                  <td className="py-2 px-4">
                    <Button 
                      variant="ghost" 
                      size="icon"
                      onClick={() => removeItem(index)}
                    >
                      <Trash2 className="h-4 w-4" />
                    </Button>
                  </td>
                </tr>
              ))}
              {items.length === 0 && (
                <tr className="border-t">
                  <td colSpan={6} className="py-8 text-center text-gray-500">
                    상품이 없습니다.
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>

        {/* 총액 표시 */}
        <div className="mt-4 flex justify-end">
          <div className="bg-blue-50 p-4 rounded-lg">
            <span className="text-lg font-semibold mr-2">총액:</span>
            <span className="text-2xl font-bold text-blue-600">
              {formatCurrency(total)}
            </span>
          </div>
        </div>
      </div>
    </Card>
  );
};

export default MartCalculator;

결론

이 프로젝트를 통해 우리는 실제 사용 가능한 마트 계산기를 구현해보았습니다. React의 상태 관리, 이벤트 처리, 그리고 UI 컴포넌트 구성을 실전적으로 학습할 수 있었습니다. 이 코드를 기반으로 여러분의 필요에 맞게 기능을 확장하고 개선할 수 있습니다.

참고 자료

반응형