반응형
안녕하세요! 오늘은 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. 데이터 관리
- 상품 목록은 배열로 관리
- 각 상품은 고유 인덱스로 식별
- 상품 삭제 시 자동으로 총액 재계산
개선 가능한 부분
이 마트 계산기는 다음과 같은 방향으로 확장할 수 있습니다:
- 기능 확장
- 상품 코드/바코드 지원
- 자주 사용하는 상품 즐겨찾기
- 결제 수단 선택
- 영수증 출력
- UI/UX 개선
- 다크 모드 지원
- 반응형 디자인 강화
- 키보드 단축키 지원
- 상품 검색 기능
- 데이터 관리
- 로컬 스토리지 저장
- 상품 데이터베이스 연동
- 거래 내역 관리
전체 소스 코드
전체 구현 코드는 아래와 같습니다:
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 컴포넌트 구성을 실전적으로 학습할 수 있었습니다. 이 코드를 기반으로 여러분의 필요에 맞게 기능을 확장하고 개선할 수 있습니다.
참고 자료
- React 공식 문서: https://react.dev
- Tailwind CSS 문서: https://tailwindcss.com
- shadcn/ui 컴포넌트: https://ui.shadcn.com
반응형