feat: 레이아웃/출하/생산/회계/대시보드 전반 개선
- HeaderFavoritesBar 대폭 개선 - Sidebar/AuthenticatedLayout 소폭 수정 - ShipmentCreate, VehicleDispatch 출하 관련 개선 - WorkOrderCreate/Edit, WorkerScreen 생산 관련 개선 - InspectionCreate 자재 입고검사 개선 - DailyReport, VendorDetail 회계 수정 - CEO 대시보드: CardManagement/DailyProduction/DailyAttendance 섹션 개선 - useCEODashboard, expense transformer 정비 - DocumentViewer, PDF generate route 소폭 수정 - bill-prototype 개발 페이지 추가 - mockData 불필요 데이터 제거
This commit is contained in:
@@ -16,7 +16,6 @@ import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -138,7 +137,7 @@ export function ShipmentCreate() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [validationErrors, setValidationErrors] = useState<string[]>([]);
|
||||
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
||||
|
||||
// 아코디언 상태
|
||||
const [accordionValue, setAccordionValue] = useState<string[]>([]);
|
||||
@@ -226,7 +225,9 @@ export function ShipmentCreate() {
|
||||
setProductGroups([]);
|
||||
setOtherParts([]);
|
||||
}
|
||||
if (validationErrors.length > 0) setValidationErrors([]);
|
||||
if (validationErrors.lotNo) {
|
||||
setValidationErrors(prev => { const { lotNo: _, ...rest } = prev; return rest; });
|
||||
}
|
||||
}, [validationErrors]);
|
||||
|
||||
// 배송방식에 따라 운임비용 '없음' 고정 여부 판단
|
||||
@@ -245,7 +246,13 @@ export function ShipmentCreate() {
|
||||
} else {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
}
|
||||
if (validationErrors.length > 0) setValidationErrors([]);
|
||||
if (validationErrors[field]) {
|
||||
setValidationErrors(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[field];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 배차 정보 핸들러
|
||||
@@ -289,12 +296,16 @@ export function ShipmentCreate() {
|
||||
}, [router]);
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const errors: string[] = [];
|
||||
if (!formData.lotNo) errors.push('로트번호는 필수 선택 항목입니다.');
|
||||
if (!formData.scheduledDate) errors.push('출고예정일은 필수 입력 항목입니다.');
|
||||
if (!formData.deliveryMethod) errors.push('배송방식은 필수 선택 항목입니다.');
|
||||
const errors: Record<string, string> = {};
|
||||
if (!formData.lotNo) errors.lotNo = '로트번호는 필수 선택 항목입니다.';
|
||||
if (!formData.scheduledDate) errors.scheduledDate = '출고예정일은 필수 입력 항목입니다.';
|
||||
if (!formData.deliveryMethod) errors.deliveryMethod = '배송방식은 필수 선택 항목입니다.';
|
||||
setValidationErrors(errors);
|
||||
return errors.length === 0;
|
||||
if (Object.keys(errors).length > 0) {
|
||||
const firstError = Object.values(errors)[0];
|
||||
toast.error(firstError);
|
||||
}
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = useCallback(async (): Promise<{ success: boolean; error?: string }> => {
|
||||
@@ -349,30 +360,6 @@ export function ShipmentCreate() {
|
||||
// 폼 컨텐츠 렌더링
|
||||
const renderFormContent = useCallback((_props: { formData: Record<string, unknown>; onChange: (key: string, value: unknown) => void; mode: string; errors: Record<string, string> }) => (
|
||||
<div className="space-y-6">
|
||||
{/* Validation 에러 표시 */}
|
||||
{validationErrors.length > 0 && (
|
||||
<Alert className="bg-red-50 border-red-200">
|
||||
<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">
|
||||
입력 내용을 확인해주세요 ({validationErrors.length}개 오류)
|
||||
</strong>
|
||||
<ul className="space-y-1 text-sm">
|
||||
{validationErrors.map((err, index) => (
|
||||
<li key={index} className="flex items-start gap-1">
|
||||
<span>•</span>
|
||||
<span>{err}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* 카드 1: 기본 정보 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -393,7 +380,7 @@ export function ShipmentCreate() {
|
||||
onValueChange={handleLotChange}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className={validationErrors.lotNo ? 'border-red-500' : ''}>
|
||||
<SelectValue placeholder="로트 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -404,6 +391,7 @@ export function ShipmentCreate() {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{validationErrors.lotNo && <p className="text-sm text-red-500">{validationErrors.lotNo}</p>}
|
||||
</div>
|
||||
{/* 현장명 - LOT 선택 시 자동 매핑 */}
|
||||
<div>
|
||||
@@ -432,7 +420,9 @@ export function ShipmentCreate() {
|
||||
value={formData.scheduledDate}
|
||||
onChange={(date) => handleInputChange('scheduledDate', date)}
|
||||
disabled={isSubmitting}
|
||||
className={validationErrors.scheduledDate ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.scheduledDate && <p className="text-sm text-red-500">{validationErrors.scheduledDate}</p>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>출고일</Label>
|
||||
@@ -449,7 +439,7 @@ export function ShipmentCreate() {
|
||||
onValueChange={(value) => handleInputChange('deliveryMethod', value)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger className={validationErrors.deliveryMethod ? 'border-red-500' : ''}>
|
||||
<SelectValue placeholder="배송방식 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -460,6 +450,7 @@ export function ShipmentCreate() {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{validationErrors.deliveryMethod && <p className="text-sm text-red-500">{validationErrors.deliveryMethod}</p>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>운임비용</Label>
|
||||
@@ -748,9 +739,7 @@ export function ShipmentCreate() {
|
||||
isLoading={false}
|
||||
onCancel={handleCancel}
|
||||
renderForm={(_props: { formData: Record<string, unknown>; onChange: (key: string, value: unknown) => void; mode: string; errors: Record<string, string> }) => (
|
||||
<Alert className="bg-red-50 border-red-200">
|
||||
<AlertDescription className="text-red-900">{error}</AlertDescription>
|
||||
</Alert>
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-900">{error}</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user