feat: 생산/품질/자재/출고/주문 관리 페이지 구현

- 생산관리: 대시보드, 작업지시, 작업실적, 작업자화면
- 품질관리: 검사관리 (리스트/등록/상세)
- 자재관리: 입고관리, 재고현황
- 출고관리: 출하관리 (리스트/등록/상세/수정)
- 주문관리: 수주관리, 생산의뢰
- 기존 컴포넌트 개선: CardTransactionInquiry, VendorDetail, QuoteRegistration
- IntegratedListTemplateV2 개선
- 공통 컴포넌트 분석 문서 추가

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-23 21:13:07 +09:00
parent 2ebcea0255
commit f0e8e51d06
108 changed files with 21156 additions and 84 deletions

View File

@@ -40,6 +40,14 @@ import {
SelectValue,
} from '@/components/ui/select';
import { toast } from 'sonner';
import { Alert, AlertDescription } from '@/components/ui/alert';
// 필드명 매핑
const FIELD_NAME_MAP: Record<string, string> = {
effectiveDate: "적용일",
purchasePrice: "입고가",
salesPrice: "판매단가",
};
import type {
PricingData,
@@ -105,7 +113,7 @@ export function PricingFormClient({
const [unit, setUnit] = useState(displayUnit);
// 에러 상태
const [errors, setErrors] = useState<Record<string, boolean>>({});
const [errors, setErrors] = useState<Record<string, string>>({});
// 다이얼로그 상태
const [showHistoryDialog, setShowHistoryDialog] = useState(false);
@@ -187,11 +195,11 @@ export function PricingFormClient({
// 유효성 검사
const validateForm = useCallback(() => {
const newErrors: Record<string, boolean> = {};
if (!effectiveDate) newErrors.effectiveDate = true;
const newErrors: Record<string, string> = {};
if (!effectiveDate) newErrors.effectiveDate = "적용일을 선택해주세요";
if (purchasePrice <= 0 && salesPrice <= 0) {
newErrors.purchasePrice = true;
newErrors.salesPrice = true;
newErrors.purchasePrice = "입고가 또는 판매단가 중 최소 하나를 입력해주세요";
newErrors.salesPrice = "입고가 또는 판매단가 중 최소 하나를 입력해주세요";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
@@ -200,10 +208,14 @@ export function PricingFormClient({
// 저장 처리
const handleSave = async (isRevision = false, revisionReason = '') => {
if (!validateForm()) {
toast.error('필수 항목을 입력해주세요.');
// 페이지 상단으로 스크롤
window.scrollTo({ top: 0, behavior: 'smooth' });
return;
}
// 에러 초기화
setErrors({});
// 수정 모드이고 리비전 있으면 수정 이력 다이얼로그
if (isEditMode && initialData && !isRevision &&
(initialData.currentRevision > 0 || initialData.isFinal)) {
@@ -301,6 +313,35 @@ export function PricingFormClient({
</div>
</div>
{/* Validation 에러 표시 */}
{Object.keys(errors).length > 0 && (
<Alert className="bg-red-50 border-red-200 mb-6">
<AlertDescription className="text-red-900">
<div className="flex items-start gap-2">
<span className="text-lg"></span>
<div className="flex-1">
<strong className="block mb-2">
({Object.keys(errors).length} )
</strong>
<ul className="space-y-1 text-sm">
{Object.entries(errors).map(([field, message]) => {
const fieldName = FIELD_NAME_MAP[field] || field;
return (
<li key={field} className="flex items-start gap-1">
<span></span>
<span>
<strong>{fieldName}</strong>: {message}
</span>
</li>
);
})}
</ul>
</div>
</div>
</AlertDescription>
</Alert>
)}
{/* 상태 표시 (수정 모드) */}
{isEditMode && initialData && (
<div className="mb-4 flex gap-2 justify-end">
@@ -383,7 +424,7 @@ export function PricingFormClient({
value={effectiveDate}
onChange={(e) => {
setEffectiveDate(e.target.value);
setErrors((prev) => ({ ...prev, effectiveDate: false }));
setErrors((prev) => { const n = {...prev}; delete n.effectiveDate; return n; });
}}
className={errors.effectiveDate ? 'border-red-500' : ''}
/>
@@ -439,7 +480,7 @@ export function PricingFormClient({
value={purchasePrice || ''}
onChange={(e) => {
setPurchasePrice(parseInt(e.target.value) || 0);
setErrors((prev) => ({ ...prev, purchasePrice: false, salesPrice: false }));
setErrors((prev) => { const n = {...prev}; delete n.purchasePrice; delete n.salesPrice; return n; });
}}
placeholder="0"
className={errors.purchasePrice ? 'border-red-500 pr-12' : 'pr-12'}
@@ -619,7 +660,7 @@ export function PricingFormClient({
value={salesPrice || ''}
onChange={(e) => {
handleSalesPriceChange(parseInt(e.target.value) || 0);
setErrors((prev) => ({ ...prev, purchasePrice: false, salesPrice: false }));
setErrors((prev) => { const n = {...prev}; delete n.purchasePrice; delete n.salesPrice; return n; });
}}
placeholder="0"
className={errors.salesPrice ? 'border-red-500 pr-12' : 'pr-12'}