feat(WEB): 공정관리/작업지시/작업자화면 기능 강화 및 템플릿 개선
- 공정관리: ProcessDetail/ProcessForm/ProcessList 개선, StepDetail/StepForm 신규 추가 - 작업지시: WorkOrderDetail/Edit/List UI 개선, 작업지시서 문서 추가 - 작업자화면: WorkerScreen 대폭 개선, MaterialInputModal/WorkLogModal 수정, WorkItemCard 신규 - 영업주문: 주문 상세 페이지 개선 - 입고관리: 상세/actions 수정 - 템플릿: IntegratedDetailTemplate/IntegratedListTemplateV2/UniversalListPage 기능 확장 - UI: confirm-dialog 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
332
src/components/process-management/StepForm.tsx
Normal file
332
src/components/process-management/StepForm.tsx
Normal file
@@ -0,0 +1,332 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 단계 등록/수정 폼 컴포넌트
|
||||
*
|
||||
* 기획서 스크린샷 2 기준:
|
||||
* - 기본 정보: 단계코드(자동), 단계명, 필수여부, 승인여부, 검사여부, 상태
|
||||
* - 연결 정보: 유형(팝업/없음), 도달(Select)
|
||||
* - 완료 정보: 유형(선택 완료 시 완료/클릭 시 완료)
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { toast } from 'sonner';
|
||||
import type { ProcessStep, StepConnectionType, StepCompletionType } from '@/types/process';
|
||||
import {
|
||||
STEP_CONNECTION_TYPE_OPTIONS,
|
||||
STEP_COMPLETION_TYPE_OPTIONS,
|
||||
STEP_CONNECTION_TARGET_OPTIONS,
|
||||
} from '@/types/process';
|
||||
import { createProcessStep, updateProcessStep } from './actions';
|
||||
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
||||
|
||||
const stepCreateConfig: DetailConfig = {
|
||||
title: '단계',
|
||||
description: '새로운 단계를 등록합니다',
|
||||
basePath: '',
|
||||
fields: [],
|
||||
actions: {
|
||||
showBack: true,
|
||||
showEdit: false,
|
||||
showDelete: false,
|
||||
showSave: true,
|
||||
submitLabel: '등록',
|
||||
},
|
||||
};
|
||||
|
||||
const stepEditConfig: DetailConfig = {
|
||||
...stepCreateConfig,
|
||||
title: '단계',
|
||||
description: '단계 정보를 수정합니다',
|
||||
actions: {
|
||||
...stepCreateConfig.actions,
|
||||
submitLabel: '저장',
|
||||
},
|
||||
};
|
||||
|
||||
interface StepFormProps {
|
||||
mode: 'create' | 'edit';
|
||||
processId: string;
|
||||
initialData?: ProcessStep;
|
||||
}
|
||||
|
||||
export function StepForm({ mode, processId, initialData }: StepFormProps) {
|
||||
const router = useRouter();
|
||||
const isEdit = mode === 'edit';
|
||||
|
||||
// 기본 정보
|
||||
const [stepName, setStepName] = useState(initialData?.stepName || '');
|
||||
const [isRequired, setIsRequired] = useState(
|
||||
initialData?.isRequired ? '필수' : '선택'
|
||||
);
|
||||
const [needsApproval, setNeedsApproval] = useState(
|
||||
initialData?.needsApproval ? '필요' : '불필요'
|
||||
);
|
||||
const [needsInspection, setNeedsInspection] = useState(
|
||||
initialData?.needsInspection ? '필요' : '불필요'
|
||||
);
|
||||
const [isActive, setIsActive] = useState(
|
||||
initialData?.isActive !== false ? '사용' : '미사용'
|
||||
);
|
||||
|
||||
// 연결 정보
|
||||
const [connectionType, setConnectionType] = useState<StepConnectionType>(
|
||||
initialData?.connectionType || '없음'
|
||||
);
|
||||
const [connectionTarget, setConnectionTarget] = useState(
|
||||
initialData?.connectionTarget || ''
|
||||
);
|
||||
|
||||
// 완료 정보
|
||||
const [completionType, setCompletionType] = useState<StepCompletionType>(
|
||||
initialData?.completionType || '클릭 시 완료'
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// 제출
|
||||
const handleSubmit = async (): Promise<{ success: boolean; error?: string }> => {
|
||||
if (!stepName.trim()) {
|
||||
toast.error('단계명을 입력해주세요.');
|
||||
return { success: false, error: '단계명을 입력해주세요.' };
|
||||
}
|
||||
|
||||
const stepData: Omit<ProcessStep, 'id'> = {
|
||||
stepCode: initialData?.stepCode || `STP-${Date.now().toString().slice(-6)}`,
|
||||
stepName: stepName.trim(),
|
||||
isRequired: isRequired === '필수',
|
||||
needsApproval: needsApproval === '필요',
|
||||
needsInspection: needsInspection === '필요',
|
||||
isActive: isActive === '사용',
|
||||
order: initialData?.order || 0,
|
||||
connectionType,
|
||||
connectionTarget: connectionType === '팝업' ? connectionTarget : undefined,
|
||||
completionType,
|
||||
};
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isEdit && initialData?.id) {
|
||||
const result = await updateProcessStep(processId, initialData.id, stepData);
|
||||
if (result.success) {
|
||||
toast.success('단계가 수정되었습니다.');
|
||||
router.push(`/ko/master-data/process-management/${processId}`);
|
||||
return { success: true };
|
||||
} else {
|
||||
toast.error(result.error || '수정에 실패했습니다.');
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
} else {
|
||||
const result = await createProcessStep(processId, stepData);
|
||||
if (result.success) {
|
||||
toast.success('단계가 등록되었습니다.');
|
||||
router.push(`/ko/master-data/process-management/${processId}`);
|
||||
return { success: true };
|
||||
} else {
|
||||
toast.error(result.error || '등록에 실패했습니다.');
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
toast.error('처리 중 오류가 발생했습니다.');
|
||||
return { success: false, error: '처리 중 오류가 발생했습니다.' };
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
router.push(`/ko/master-data/process-management/${processId}`);
|
||||
};
|
||||
|
||||
const renderFormContent = useCallback(
|
||||
() => (
|
||||
<div className="space-y-6">
|
||||
{/* 기본 정보 */}
|
||||
<Card>
|
||||
<CardHeader className="bg-muted/50">
|
||||
<CardTitle className="text-base">기본 정보</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label>단계코드</Label>
|
||||
<Input
|
||||
value={initialData?.stepCode || '자동생성'}
|
||||
disabled
|
||||
className="bg-muted"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="stepName">단계명 *</Label>
|
||||
<Input
|
||||
id="stepName"
|
||||
value={stepName}
|
||||
onChange={(e) => setStepName(e.target.value)}
|
||||
placeholder="예: 자재투입"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>필수여부</Label>
|
||||
<Select value={isRequired} onValueChange={setIsRequired}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="필수">필수</SelectItem>
|
||||
<SelectItem value="선택">선택</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>승인여부</Label>
|
||||
<Select value={needsApproval} onValueChange={setNeedsApproval}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="필요">필요</SelectItem>
|
||||
<SelectItem value="불필요">불필요</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>검사여부</Label>
|
||||
<Select value={needsInspection} onValueChange={setNeedsInspection}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="필요">필요</SelectItem>
|
||||
<SelectItem value="불필요">불필요</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>상태</Label>
|
||||
<Select value={isActive} onValueChange={setIsActive}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="사용">사용</SelectItem>
|
||||
<SelectItem value="미사용">미사용</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 연결 정보 */}
|
||||
<Card>
|
||||
<CardHeader className="bg-muted/50">
|
||||
<CardTitle className="text-base">연결 정보</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label>유형</Label>
|
||||
<Select
|
||||
value={connectionType}
|
||||
onValueChange={(v) => setConnectionType(v as StepConnectionType)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{STEP_CONNECTION_TYPE_OPTIONS.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>도달</Label>
|
||||
<Select value={connectionTarget} onValueChange={setConnectionTarget}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="선택하세요" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{STEP_CONNECTION_TARGET_OPTIONS.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 완료 정보 */}
|
||||
<Card>
|
||||
<CardHeader className="bg-muted/50">
|
||||
<CardTitle className="text-base">완료 정보</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label>유형</Label>
|
||||
<Select
|
||||
value={completionType}
|
||||
onValueChange={(v) => setCompletionType(v as StepCompletionType)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{STEP_COMPLETION_TYPE_OPTIONS.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
[
|
||||
stepName,
|
||||
isRequired,
|
||||
needsApproval,
|
||||
needsInspection,
|
||||
isActive,
|
||||
connectionType,
|
||||
connectionTarget,
|
||||
completionType,
|
||||
initialData?.stepCode,
|
||||
]
|
||||
);
|
||||
|
||||
const config = isEdit ? stepEditConfig : stepCreateConfig;
|
||||
|
||||
return (
|
||||
<IntegratedDetailTemplate
|
||||
config={config}
|
||||
mode={isEdit ? 'edit' : 'create'}
|
||||
isLoading={false}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmit}
|
||||
renderForm={renderFormContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user