Files
sam-react-prod/src/components/quotes/DiscountModal.tsx
유병철 81affdc441 feat: ESLint 정리 및 전체 코드 품질 개선
- eslint.config.mjs 규칙 강화 및 정리
- 전역 unused import/변수 제거 (312개 파일)
- next.config.ts, middleware, proxy route 개선
- CopyableCell molecule 추가
- 회계/결재/HR/생산/건설/품질/영업 등 전 도메인 lint 정리
- IntegratedListTemplateV2, DataTable, MobileCard 등 공통 컴포넌트 개선
- execute-server-action 에러 핸들링 보강
2026-03-11 10:27:10 +09:00

232 lines
7.2 KiB
TypeScript

/**
* 할인하기 모달
*
* - 공급가액 표시 (읽기 전용)
* - 할인율 입력 (% 단위, 소수점 첫째자리까지)
* - 할인금액 입력 (원 단위)
* - 할인 후 공급가액 자동 계산
* - 할인율 ↔ 할인금액 상호 계산
*/
"use client";
import { useState, useEffect, useCallback } from "react";
import { formatNumber } from "@/lib/utils/amount";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "../ui/dialog";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
// =============================================================================
// Props
// =============================================================================
interface DiscountModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
/** 공급가액 (할인 전 금액) */
supplyAmount: number;
/** 기존 할인율 (%) */
initialDiscountRate?: number;
/** 기존 할인금액 (원) */
initialDiscountAmount?: number;
/** 적용 콜백 */
onApply: (discountRate: number, discountAmount: number) => void;
}
// =============================================================================
// 컴포넌트
// =============================================================================
export function DiscountModal({
open,
onOpenChange,
supplyAmount,
initialDiscountRate = 0,
initialDiscountAmount = 0,
onApply,
}: DiscountModalProps) {
// ---------------------------------------------------------------------------
// 상태
// ---------------------------------------------------------------------------
const [discountRate, setDiscountRate] = useState<string>("");
const [discountAmount, setDiscountAmount] = useState<string>("");
// ---------------------------------------------------------------------------
// 초기화
// ---------------------------------------------------------------------------
useEffect(() => {
if (open) {
// 모달이 열릴 때 초기값 설정
if (initialDiscountRate > 0) {
setDiscountRate(initialDiscountRate.toString());
setDiscountAmount(initialDiscountAmount.toString());
} else if (initialDiscountAmount > 0) {
setDiscountAmount(initialDiscountAmount.toString());
const rate = supplyAmount > 0 ? (initialDiscountAmount / supplyAmount) * 100 : 0;
setDiscountRate(rate.toFixed(1));
} else {
setDiscountRate("");
setDiscountAmount("");
}
}
}, [open, initialDiscountRate, initialDiscountAmount, supplyAmount]);
// ---------------------------------------------------------------------------
// 핸들러
// ---------------------------------------------------------------------------
// 할인율 변경 → 할인금액 자동 계산
const handleDiscountRateChange = useCallback(
(value: string) => {
// 숫자와 소수점만 허용
const sanitized = value.replace(/[^0-9.]/g, "");
// 소수점이 여러 개인 경우 첫 번째만 유지
const parts = sanitized.split(".");
const formatted = parts.length > 2
? parts[0] + "." + parts.slice(1).join("")
: sanitized;
setDiscountRate(formatted);
const rate = parseFloat(formatted) || 0;
if (rate >= 0 && rate <= 100) {
const amount = Math.round(supplyAmount * (rate / 100));
setDiscountAmount(amount.toString());
}
},
[supplyAmount]
);
// 할인금액 변경 → 할인율 자동 계산
const handleDiscountAmountChange = useCallback(
(value: string) => {
// 숫자만 허용
const sanitized = value.replace(/[^0-9]/g, "");
setDiscountAmount(sanitized);
const amount = parseInt(sanitized) || 0;
if (supplyAmount > 0 && amount >= 0 && amount <= supplyAmount) {
const rate = (amount / supplyAmount) * 100;
setDiscountRate(rate.toFixed(1));
}
},
[supplyAmount]
);
// 적용
const handleApply = useCallback(() => {
const rate = parseFloat(discountRate) || 0;
const amount = parseInt(discountAmount) || 0;
onApply(rate, amount);
onOpenChange(false);
}, [discountRate, discountAmount, onApply, onOpenChange]);
// 취소
const handleCancel = useCallback(() => {
onOpenChange(false);
}, [onOpenChange]);
// ---------------------------------------------------------------------------
// 계산
// ---------------------------------------------------------------------------
const discountedAmount = supplyAmount - (parseInt(discountAmount) || 0);
// ---------------------------------------------------------------------------
// 렌더링
// ---------------------------------------------------------------------------
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[400px]">
<DialogHeader>
<DialogTitle className="text-center"></DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
{/* 공급가액 (읽기 전용) */}
<div className="space-y-2">
<Label></Label>
<Input
value={formatNumber(supplyAmount)}
disabled
className="bg-gray-50 text-right font-medium"
/>
</div>
{/* 할인율 */}
<div className="space-y-2">
<Label></Label>
<div className="relative">
<Input
type="text"
placeholder="0"
value={discountRate}
onChange={(e) => handleDiscountRateChange(e.target.value)}
className="pr-8 text-right"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500">
%
</span>
</div>
</div>
{/* 할인금액 */}
<div className="space-y-2">
<Label></Label>
<div className="relative">
<Input
type="text"
placeholder="0"
value={discountAmount ? formatNumber(parseInt(discountAmount)) : ""}
onChange={(e) => handleDiscountAmountChange(e.target.value.replace(/,/g, ""))}
className="pr-8 text-right"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500">
</span>
</div>
</div>
{/* 할인 후 공급가액 (읽기 전용) */}
<div className="space-y-2">
<Label> </Label>
<Input
value={formatNumber(discountedAmount)}
disabled
className="bg-gray-50 text-right font-bold text-blue-600"
/>
</div>
</div>
<DialogFooter className="gap-2">
<Button
variant="outline"
onClick={handleCancel}
className="flex-1"
>
</Button>
<Button
onClick={handleApply}
className="flex-1 bg-gray-800 hover:bg-gray-900 text-white"
>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}