+
@@ -448,6 +417,7 @@ export function WorkOrderCreate() {
)}
+ {validationErrors.selectedOrder &&
{validationErrors.selectedOrder}
}
)}
@@ -459,21 +429,29 @@ export function WorkOrderCreate() {
setFormData({ ...formData, client: e.target.value })}
+ onChange={(e) => {
+ setFormData({ ...formData, client: e.target.value });
+ clearFieldError('client');
+ }}
placeholder={mode === 'linked' ? '수주를 선택하면 자동 입력됩니다' : '발주처 입력'}
disabled={mode === 'linked'}
- className="bg-white"
+ className={`bg-white ${validationErrors.client ? 'border-red-500' : ''}`}
/>
+ {validationErrors.client &&
{validationErrors.client}
}
setFormData({ ...formData, projectName: e.target.value })}
+ onChange={(e) => {
+ setFormData({ ...formData, projectName: e.target.value });
+ clearFieldError('projectName');
+ }}
placeholder={mode === 'linked' ? '수주를 선택하면 자동 입력됩니다' : '현장명 입력'}
disabled={mode === 'linked'}
- className="bg-white"
+ className={`bg-white ${validationErrors.projectName ? 'border-red-500' : ''}`}
/>
+ {validationErrors.projectName &&
{validationErrors.projectName}
}
@@ -506,10 +484,13 @@ export function WorkOrderCreate() {
+ {validationErrors.processId &&
{validationErrors.processId}
}
공정코드: {getSelectedProcessCode()}
@@ -529,8 +511,13 @@ export function WorkOrderCreate() {
setFormData({ ...formData, shipmentDate: date })}
+ onChange={(date) => {
+ setFormData({ ...formData, shipmentDate: date });
+ clearFieldError('shipmentDate');
+ }}
+ className={validationErrors.shipmentDate ? 'border-red-500' : ''}
/>
+ {validationErrors.shipmentDate && {validationErrors.shipmentDate}
}
@@ -717,7 +704,7 @@ export function WorkOrderCreate() {
/>
- ), [mode, formData, validationErrors, processOptions, isLoadingProcesses, assigneeNames, getSelectedProcessCode, manualItems, showItemSearch, itemSearchQuery, itemSearchResults, isSearchingItems]);
+ ), [mode, formData, validationErrors, processOptions, isLoadingProcesses, assigneeNames, getSelectedProcessCode, manualItems, showItemSearch, itemSearchQuery, itemSearchResults, isSearchingItems, clearFieldError]);
return (
<>
@@ -751,4 +738,4 @@ export function WorkOrderCreate() {
/>
>
);
-}
\ No newline at end of file
+}
diff --git a/src/components/production/WorkOrders/WorkOrderEdit.tsx b/src/components/production/WorkOrders/WorkOrderEdit.tsx
index c7d80cd8..1eaabe58 100644
--- a/src/components/production/WorkOrders/WorkOrderEdit.tsx
+++ b/src/components/production/WorkOrders/WorkOrderEdit.tsx
@@ -30,7 +30,6 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
-import { Alert, AlertDescription } from '@/components/ui/alert';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { AssigneeSelectModal } from './AssigneeSelectModal';
import { toast } from 'sonner';
@@ -52,17 +51,6 @@ interface EditableItem extends WorkOrderItem {
editQuantity?: number;
}
-// Validation 에러 타입
-interface ValidationErrors {
- [key: string]: string;
-}
-
-// 필드명 매핑
-const FIELD_NAME_MAP: Record
= {
- processId: '공정',
- scheduledDate: '출고예정일',
-};
-
interface FormData {
// 기본 정보 (읽기 전용)
client: string;
@@ -101,7 +89,7 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
const [isAssigneeModalOpen, setIsAssigneeModalOpen] = useState(false);
const [deleteTargetItemId, setDeleteTargetItemId] = useState(null);
const [assigneeNames, setAssigneeNames] = useState([]);
- const [validationErrors, setValidationErrors] = useState({});
+ const [validationErrors, setValidationErrors] = useState>({});
const [isLoading, setIsLoading] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
const [processOptions, setProcessOptions] = useState([]);
@@ -213,7 +201,7 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
// 폼 제출
const handleSubmit = async (): Promise<{ success: boolean; error?: string }> => {
// Validation 체크
- const errors: ValidationErrors = {};
+ const errors: Record = {};
if (!formData.processId) {
errors.processId = '공정을 선택해주세요';
@@ -226,7 +214,8 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
// 에러가 있으면 상태 업데이트 후 리턴
if (Object.keys(errors).length > 0) {
setValidationErrors(errors);
- window.scrollTo({ top: 0, behavior: 'smooth' });
+ const firstError = Object.values(errors)[0];
+ toast.error(firstError);
return { success: false, error: '입력 정보를 확인해주세요.' };
}
@@ -344,35 +333,6 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
// 폼 컨텐츠 렌더링 (기획서 4열 그리드)
const renderFormContent = useCallback(() => (
- {/* Validation 에러 표시 */}
- {Object.keys(validationErrors).length > 0 && (
-
-
-
-
⚠️
-
-
- 입력 내용을 확인해주세요 ({Object.keys(validationErrors).length}개 오류)
-
-
- {Object.entries(validationErrors).map(([field, message]) => {
- const fieldName = FIELD_NAME_MAP[field] || field;
- return (
- -
- •
-
- {fieldName}: {message}
-
-
- );
- })}
-
-
-
-
-
- )}
-
{/* 기본 정보 (기획서 4열 구성) */}
기본 정보
@@ -391,10 +351,15 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
+ {validationErrors.processId && {validationErrors.processId}
}
@@ -442,8 +408,15 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
setFormData({ ...formData, scheduledDate: date })}
+ onChange={(date) => {
+ setFormData({ ...formData, scheduledDate: date });
+ if (validationErrors.scheduledDate) {
+ setValidationErrors(prev => { const { scheduledDate: _, ...rest } = prev; return rest; });
+ }
+ }}
+ className={validationErrors.scheduledDate ? 'border-red-500' : ''}
/>
+ {validationErrors.scheduledDate && {validationErrors.scheduledDate}
}
@@ -671,4 +644,4 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
/>
>
);
-}
\ No newline at end of file
+}
diff --git a/src/components/production/WorkerScreen/index.tsx b/src/components/production/WorkerScreen/index.tsx
index 422a3bca..335835af 100644
--- a/src/components/production/WorkerScreen/index.tsx
+++ b/src/components/production/WorkerScreen/index.tsx
@@ -348,15 +348,28 @@ export default function WorkerScreen() {
// 작업지시별 단계 진행 캐시: { [workOrderId]: StepProgressItem[] }
const [stepProgressMap, setStepProgressMap] = useState
>({});
- // 데이터 로드
+ // 데이터 로드 (작업목록 + 공정목록 + 부서목록 병렬)
const loadData = useCallback(async () => {
setIsLoading(true);
try {
- const result = await getMyWorkOrders();
- if (result.success) {
- setWorkOrders(result.data);
+ const [workOrderResult, processResult, deptResult] = await Promise.all([
+ getMyWorkOrders(),
+ getProcessList({ size: 100 }),
+ getDepartments(),
+ ]);
+
+ if (workOrderResult.success) {
+ setWorkOrders(workOrderResult.data);
} else {
- toast.error(result.error || '작업 목록 조회에 실패했습니다.');
+ toast.error(workOrderResult.error || '작업 목록 조회에 실패했습니다.');
+ }
+
+ if (processResult.success && processResult.data?.items) {
+ setProcessListCache(processResult.data.items);
+ }
+
+ if (deptResult.success) {
+ setDepartmentList(deptResult.data);
}
} catch (error) {
if (isNextRedirectError(error)) throw error;
@@ -369,10 +382,6 @@ export default function WorkerScreen() {
useEffect(() => {
loadData();
- // 부서 목록 로드
- getDepartments().then((res) => {
- if (res.success) setDepartmentList(res.data);
- });
}, [loadData]);
// 부서 선택 시 해당 부서 사용자 목록 로드
@@ -455,21 +464,6 @@ export default function WorkerScreen() {
// 공정 목록 캐시
const [processListCache, setProcessListCache] = useState([]);
- // 공정 목록 조회 (최초 1회)
- useEffect(() => {
- const fetchProcessList = async () => {
- try {
- const result = await getProcessList({ size: 100 });
- if (result.success && result.data?.items) {
- setProcessListCache(result.data.items);
- }
- } catch (error) {
- console.error('Failed to fetch process list:', error);
- }
- };
- fetchProcessList();
- }, []);
-
// 활성 공정 목록 (탭용) - 공정관리에서 등록된 활성 공정만
const processTabs = useMemo(() => {
return processListCache.filter((p) => p.status === '사용중');
@@ -1478,6 +1472,9 @@ export default function WorkerScreen() {
{/* 공정별 탭 (공정관리 API 기반 동적 생성) */}
+ {isLoading ? (
+
+ ) : (
setActiveTab(v)}
@@ -1708,11 +1705,12 @@ export default function WorkerScreen() {
))}
+ )}
{/* 하단 고정 버튼 */}
{(hasWipItems || activeProcessSettings.needsWorkLog || activeProcessSettings.hasDocumentTemplate) && (
-