fix: 품목관리 수정 기능 버그 수정 및 Sales 페이지 추가

## 품목관리 수정 버그 수정
- FG(제품) 수정 시 품목명 반영 안되는 문제 해결
  - productName → name 필드 매핑 추가
  - FG 품목코드 = 품목명 동기화 로직 추가
- Materials(SM, RM, CS) 수정페이지 진입 오류 해결
- UNIQUE 제약조건 위반 오류 해결

## Sales 페이지
- 거래처관리 (client-management-sales-admin) 페이지 구현
- 견적관리 (quote-management) 페이지 구현
- 관련 컴포넌트 및 훅 추가

## 기타
- 회원가입 페이지 차단 처리
- 디버깅용 콘솔 로그 추가 (PUT 요청/응답 확인용)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-04 20:52:42 +09:00
parent 42f80e2b16
commit 751e65f59b
52 changed files with 8869 additions and 1088 deletions

View File

@@ -0,0 +1,247 @@
/**
* 거래처 상세 보기 컴포넌트
*
* 스크린샷 기준 4개 섹션:
* 1. 기본 정보
* 2. 연락처 정보
* 3. 결제 정보
* 4. 악성채권 정보 (있는 경우 빨간 테두리)
*/
"use client";
import { useRouter } from "next/navigation";
import { Button } from "../ui/button";
import { Badge } from "../ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import {
Building2,
Phone,
CreditCard,
AlertTriangle,
ArrowLeft,
Pencil,
Trash2,
MapPin,
Mail,
} from "lucide-react";
import { Client } from "../../hooks/useClientList";
interface ClientDetailProps {
client: Client;
onBack: () => void;
onEdit: () => void;
onDelete: () => void;
}
// 상세 항목 표시 컴포넌트
function DetailItem({
label,
value,
icon,
valueClassName,
}: {
label: string;
value: React.ReactNode;
icon?: React.ReactNode;
valueClassName?: string;
}) {
return (
<div>
<p className="text-xs text-muted-foreground mb-1">{label}</p>
<div className={`flex items-center gap-2 ${valueClassName || ""}`}>
{icon}
<span className="font-medium">{value || "-"}</span>
</div>
</div>
);
}
export function ClientDetail({
client,
onBack,
onEdit,
onDelete,
}: ClientDetailProps) {
const router = useRouter();
// 금액 포맷
const formatCurrency = (amount: string) => {
if (!amount) return "-";
const num = Number(amount);
return `${num.toLocaleString()}`;
};
return (
<div className="space-y-6">
{/* 헤더 */}
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div className="flex items-center gap-3">
<Building2 className="h-6 w-6 text-primary" />
<h1 className="text-2xl font-bold">{client.name}</h1>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={onBack}>
<ArrowLeft className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={onEdit}>
<Pencil className="h-4 w-4 mr-2" />
</Button>
<Button variant="destructive" onClick={onDelete}>
<Trash2 className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
{/* 1. 기본 정보 */}
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Building2 className="h-5 w-5 text-primary" />
<CardTitle> </CardTitle>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<DetailItem label="CODE" value={client.code} />
<DetailItem label="사업자번호" value={client.businessNo} />
<DetailItem
label="거래처 유형"
value={
<Badge
variant={
client.clientType === "매출"
? "default"
: client.clientType === "매입"
? "secondary"
: "outline"
}
>
{client.clientType}
</Badge>
}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<DetailItem label="거래처명" value={client.name} />
<DetailItem label="대표자" value={client.representative} />
<DetailItem
label="상태"
value={
<Badge
variant={client.status === "활성" ? "default" : "secondary"}
className={
client.status === "활성"
? "bg-green-100 text-green-800"
: ""
}
>
{client.status}
</Badge>
}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<DetailItem
label="주소"
value={client.address}
icon={<MapPin className="h-4 w-4 text-muted-foreground" />}
/>
<DetailItem label="업태" value={client.businessType} />
<DetailItem label="종목" value={client.businessItem} />
</div>
{client.memo && (
<DetailItem label="비고" value={client.memo} />
)}
</CardContent>
</Card>
{/* 2. 연락처 정보 */}
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Phone className="h-5 w-5 text-primary" />
<CardTitle> </CardTitle>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<DetailItem
label="전화"
value={client.phone}
icon={<Phone className="h-4 w-4 text-muted-foreground" />}
/>
<DetailItem
label="휴대전화"
value={client.mobile}
icon={<Phone className="h-4 w-4 text-muted-foreground" />}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<DetailItem label="팩스" value={client.fax} />
<DetailItem
label="이메일"
value={client.email}
icon={<Mail className="h-4 w-4 text-muted-foreground" />}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<DetailItem label="담당자명" value={client.managerName} />
<DetailItem label="담당자 연락처" value={client.managerTel} />
</div>
</CardContent>
</Card>
{/* 3. 결제 정보 */}
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<CreditCard className="h-5 w-5 text-primary" />
<CardTitle> </CardTitle>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<DetailItem label="매입 결제일" value={client.purchasePaymentDay} />
<DetailItem label="매출 결제일" value={client.salesPaymentDay} />
</div>
</CardContent>
</Card>
{/* 4. 악성채권 정보 (있는 경우에만 표시) */}
{client.badDebt && (
<Card className="border-red-300 bg-red-50/30">
<CardHeader>
<div className="flex items-center gap-2">
<AlertTriangle className="h-5 w-5 text-red-500" />
<CardTitle className="text-red-700"> </CardTitle>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<DetailItem
label="악성채권 금액"
value={formatCurrency(client.badDebtAmount)}
valueClassName="text-red-600 font-bold"
/>
<DetailItem label="수령일" value={client.badDebtReceiveDate} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<DetailItem label="종료일" value={client.badDebtEndDate} />
<DetailItem label="진행 상태" value={client.badDebtProgress} />
</div>
</CardContent>
</Card>
)}
</div>
);
}