feat(WEB): 작업일지/검사성적서 버튼 공정 레벨 설정 연동

- Process 타입에 documentTemplateId, needsWorkLog 필드 추가 (ProcessStep에서 이동)
- ProcessForm에 중간검사 양식/작업일지 설정 UI 추가
- StepForm에서 해당 UI 제거
- WorkerScreen 하단 버튼 조건부 렌더링: needsWorkLog/documentTemplateId 기반
This commit is contained in:
2026-02-11 09:51:38 +09:00
parent df668316c9
commit 973c3a9018
5 changed files with 200 additions and 96 deletions

View File

@@ -37,7 +37,9 @@ import {
updateProcess,
getDepartmentOptions,
getProcessSteps,
getDocumentTemplates,
type DepartmentOption,
type DocumentTemplateOption,
} from './actions';
interface ProcessFormProps {
@@ -67,6 +69,18 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
);
const [isLoading, setIsLoading] = useState(false);
// 중간검사/작업일지 설정 (Process 레벨)
const [documentTemplateId, setDocumentTemplateId] = useState<number | undefined>(
initialData?.documentTemplateId
);
const [needsWorkLog, setNeedsWorkLog] = useState(
initialData?.needsWorkLog ?? false
);
const [workLogTemplateId, setWorkLogTemplateId] = useState<number | undefined>(
initialData?.workLogTemplateId
);
const [documentTemplates, setDocumentTemplates] = useState<DocumentTemplateOption[]>([]);
// 품목 분류 규칙 (기존 로직 유지)
const [classificationRules, setClassificationRules] = useState<ClassificationRule[]>(
initialData?.classificationRules || []
@@ -124,15 +138,21 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
);
}, []);
// 부서 목록 + 단계 목록 로드
// 부서 목록 + 문서양식 목록 로드
useEffect(() => {
const loadDepartments = async () => {
const loadInitialData = async () => {
setIsDepartmentsLoading(true);
const departments = await getDepartmentOptions();
const [departments, templates] = await Promise.all([
getDepartmentOptions(),
getDocumentTemplates(),
]);
setDepartmentOptions(departments);
if (templates.success && templates.data) {
setDocumentTemplates(templates.data);
}
setIsDepartmentsLoading(false);
};
loadDepartments();
loadInitialData();
}, []);
useEffect(() => {
@@ -300,6 +320,9 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
processType,
processCategory: processCategory || undefined,
department,
documentTemplateId: documentTemplateId || undefined,
needsWorkLog,
workLogTemplateId: needsWorkLog ? workLogTemplateId : undefined,
classificationRules: classificationRules.map((rule) => ({
registrationType: rule.registrationType,
ruleType: rule.ruleType,
@@ -463,6 +486,64 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
</Select>
</div>
</div>
{/* Row 3: 중간검사양식 | 작업일지여부 | 작업일지양식 */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mt-6">
<div className="space-y-2">
<Label> </Label>
<Select
key={`doc-template-${documentTemplateId ?? 'none'}`}
value={documentTemplateId ? String(documentTemplateId) : ''}
onValueChange={(v) => setDocumentTemplateId(v ? Number(v) : undefined)}
>
<SelectTrigger>
<SelectValue placeholder="선택하세요" />
</SelectTrigger>
<SelectContent>
{documentTemplates.map((tmpl) => (
<SelectItem key={tmpl.id} value={String(tmpl.id)}>
{tmpl.name} ({tmpl.category})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label> </Label>
<Select
value={needsWorkLog ? '사용' : '미사용'}
onValueChange={(v) => setNeedsWorkLog(v === '사용')}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="사용"></SelectItem>
<SelectItem value="미사용"></SelectItem>
</SelectContent>
</Select>
</div>
{needsWorkLog && (
<div className="space-y-2">
<Label> </Label>
<Select
key={`worklog-template-${workLogTemplateId ?? 'none'}`}
value={workLogTemplateId ? String(workLogTemplateId) : ''}
onValueChange={(v) => setWorkLogTemplateId(v ? Number(v) : undefined)}
>
<SelectTrigger>
<SelectValue placeholder="선택하세요" />
</SelectTrigger>
<SelectContent>
{documentTemplates.map((tmpl) => (
<SelectItem key={tmpl.id} value={String(tmpl.id)}>
{tmpl.name} ({tmpl.category})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div>
</CardContent>
</Card>
@@ -679,6 +760,10 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
manager,
useProductionDate,
isActive,
documentTemplateId,
needsWorkLog,
workLogTemplateId,
documentTemplates,
classificationRules,
steps,
isStepsLoading,

View File

@@ -9,7 +9,7 @@
* - 완료 정보: 유형(선택 완료 시 완료/클릭 시 완료)
*/
import { useState, useCallback, useEffect } from 'react';
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';
@@ -37,8 +37,7 @@ import {
STEP_CONNECTION_TARGET_OPTIONS,
DEFAULT_INSPECTION_SETTING,
} from '@/types/process';
import { createProcessStep, updateProcessStep, getDocumentTemplates } from './actions';
import type { DocumentTemplateOption } from './actions';
import { createProcessStep, updateProcessStep } from './actions';
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
import { InspectionSettingModal } from './InspectionSettingModal';
import { InspectionPreviewModal } from './InspectionPreviewModal';
@@ -105,12 +104,6 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
initialData?.completionType || '클릭 시 완료'
);
// 문서양식 선택
const [documentTemplateId, setDocumentTemplateId] = useState<number | undefined>(
initialData?.documentTemplateId
);
const [documentTemplates, setDocumentTemplates] = useState<DocumentTemplateOption[]>([]);
// 검사 설정
const [inspectionSetting, setInspectionSetting] = useState<InspectionSetting>(
initialData?.inspectionSetting || DEFAULT_INSPECTION_SETTING
@@ -125,17 +118,6 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
// 검사여부가 "사용"인지 확인
const isInspectionEnabled = needsInspection === '사용';
// 검사여부가 "사용"이면 문서양식 목록 조회
useEffect(() => {
if (isInspectionEnabled && documentTemplates.length === 0) {
getDocumentTemplates().then((result) => {
if (result.success && result.data) {
setDocumentTemplates(result.data);
}
});
}
}, [isInspectionEnabled, documentTemplates.length]);
// 제출
const handleSubmit = async (): Promise<{ success: boolean; error?: string }> => {
if (!stepName.trim()) {
@@ -149,7 +131,6 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
isRequired: isRequired === '필수',
needsApproval: needsApproval === '필요',
needsInspection: needsInspection === '사용',
documentTemplateId: isInspectionEnabled ? documentTemplateId : undefined,
isActive: isActive === '사용',
order: initialData?.order || 0,
connectionType,
@@ -269,33 +250,6 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
</Select>
</div>
</div>
{/* 문서양식 선택 (검사여부가 "사용"일 때만 표시) */}
{isInspectionEnabled && (
<div className="mt-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
<div className="space-y-2">
<Label> ( 릿)</Label>
<Select
key={`doc-template-${documentTemplateId ?? 'none'}`}
value={documentTemplateId ? String(documentTemplateId) : ''}
onValueChange={(v) => setDocumentTemplateId(v ? Number(v) : undefined)}
>
<SelectTrigger>
<SelectValue placeholder="문서양식을 선택하세요" />
</SelectTrigger>
<SelectContent>
{documentTemplates.map((tmpl) => (
<SelectItem key={tmpl.id} value={String(tmpl.id)}>
{tmpl.name} ({tmpl.category})
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
. MNG에서 .
</p>
</div>
</div>
)}
</CardContent>
</Card>
@@ -384,8 +338,6 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
completionType,
initialData?.stepCode,
isInspectionEnabled,
documentTemplateId,
documentTemplates,
]
);

View File

@@ -21,6 +21,11 @@ interface ApiProcess {
process_category: string | null;
use_production_date: boolean;
work_log_template: string | null;
document_template_id: number | null;
document_template?: { id: number; name: string; category: string } | null;
needs_work_log: boolean;
work_log_template_id: number | null;
work_log_template_relation?: { id: number; name: string; category: string } | null;
required_workers: number;
equipment_info: string | null;
work_steps: string[] | null;
@@ -88,6 +93,11 @@ function transformApiToFrontend(apiData: ApiProcess): Process {
processCategory: apiData.process_category ?? undefined,
useProductionDate: apiData.use_production_date ?? false,
workLogTemplate: apiData.work_log_template ?? undefined,
documentTemplateId: apiData.document_template_id ?? undefined,
documentTemplateName: apiData.document_template?.name ?? undefined,
needsWorkLog: apiData.needs_work_log ?? false,
workLogTemplateId: apiData.work_log_template_id ?? undefined,
workLogTemplateName: apiData.work_log_template_relation?.name ?? undefined,
classificationRules: [...patternRules, ...individualRules],
requiredWorkers: apiData.required_workers,
equipmentInfo: apiData.equipment_info ?? undefined,
@@ -179,6 +189,9 @@ function transformFrontendToApi(data: ProcessFormData): Record<string, unknown>
process_category: data.processCategory || null,
use_production_date: data.useProductionDate ?? false,
work_log_template: data.workLogTemplate || null,
document_template_id: data.documentTemplateId || null,
needs_work_log: data.needsWorkLog ?? false,
work_log_template_id: data.workLogTemplateId || null,
required_workers: data.requiredWorkers,
equipment_info: data.equipmentInfo || null,
work_steps: data.workSteps ? data.workSteps.split(',').map((s) => s.trim()).filter(Boolean) : [],
@@ -567,12 +580,6 @@ interface ApiProcessStep {
is_required: boolean;
needs_approval: boolean;
needs_inspection: boolean;
document_template_id: number | null;
document_template?: {
id: number;
name: string;
category: string;
} | null;
is_active: boolean;
sort_order: number;
connection_type: string | null;
@@ -590,8 +597,6 @@ function transformStepApiToFrontend(apiStep: ApiProcessStep): ProcessStep {
isRequired: apiStep.is_required,
needsApproval: apiStep.needs_approval,
needsInspection: apiStep.needs_inspection,
documentTemplateId: apiStep.document_template_id ?? undefined,
documentTemplateName: apiStep.document_template?.name ?? undefined,
isActive: apiStep.is_active,
order: apiStep.sort_order,
connectionType: (apiStep.connection_type as ProcessStep['connectionType']) || '없음',
@@ -647,7 +652,6 @@ export async function createProcessStep(
is_required: data.isRequired,
needs_approval: data.needsApproval,
needs_inspection: data.needsInspection,
document_template_id: data.documentTemplateId || null,
is_active: data.isActive,
connection_type: data.connectionType || null,
connection_target: data.connectionTarget || null,
@@ -672,7 +676,6 @@ export async function updateProcessStep(
if (data.isRequired !== undefined) apiData.is_required = data.isRequired;
if (data.needsApproval !== undefined) apiData.needs_approval = data.needsApproval;
if (data.needsInspection !== undefined) apiData.needs_inspection = data.needsInspection;
if (data.documentTemplateId !== undefined) apiData.document_template_id = data.documentTemplateId || null;
if (data.isActive !== undefined) apiData.is_active = data.isActive;
if (data.connectionType !== undefined) apiData.connection_type = data.connectionType || null;
if (data.connectionTarget !== undefined) apiData.connection_target = data.connectionTarget || null;