'use client' import React, { useState } from 'react' import { Card, CardContent } from '@/components/ui/card' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' import { Checkbox } from '@/components/ui/checkbox' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Textarea } from '@/components/ui/textarea' import { Alert, AlertDescription } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { updateSubcontractChecklist } from '../service' import { toast } from 'sonner' import { AlertTriangle, CheckCircle, XCircle, HelpCircle, Save } from 'lucide-react' interface SubcontractChecklistData { // 1. 계약서면발급 contractDocumentIssuance: { workOrderBeforeStart: boolean entrustmentDetails: boolean deliveryDetails: boolean inspectionMethod: boolean subcontractPayment: boolean materialProvision: boolean priceAdjustment: boolean } // 2. 부당하도급대금결정행위 unfairSubcontractPricing: { priceReductionWithBasis: boolean noNegotiationAfterLowestBid: boolean noDeceptionInPricing: boolean noUniformPriceReduction: boolean noDiscriminatoryTreatment: boolean } // 점검결과 inspectionResult: 'compliant' | 'violation' | 'suspected_violation' // 귀책부서 (위반/위반의심 시 필수) responsibleDepartment?: string // 원인 (위반/위반의심 시 필수) cause?: string causeOther?: string // 대책 (위반/위반의심 시 필수) countermeasure?: string countermeasureOther?: string } interface SubcontractChecklistProps { contractId: number onDataChange: (data: SubcontractChecklistData) => void readOnly?: boolean initialData?: SubcontractChecklistData } export function SubcontractChecklist({ contractId, onDataChange, readOnly = false, initialData }: SubcontractChecklistProps) { // 기본 데이터 구조 const defaultData: SubcontractChecklistData = { contractDocumentIssuance: { workOrderBeforeStart: false, entrustmentDetails: false, deliveryDetails: false, inspectionMethod: false, subcontractPayment: false, materialProvision: false, priceAdjustment: false, }, unfairSubcontractPricing: { priceReductionWithBasis: false, noNegotiationAfterLowestBid: false, noDeceptionInPricing: false, noUniformPriceReduction: false, noDiscriminatoryTreatment: false, }, inspectionResult: 'compliant', } // initialData와 기본값을 깊이 병합 const mergedInitialData = React.useMemo(() => { if (!initialData) return defaultData return { contractDocumentIssuance: { ...defaultData.contractDocumentIssuance, ...(initialData.contractDocumentIssuance || {}), }, unfairSubcontractPricing: { ...defaultData.unfairSubcontractPricing, ...(initialData.unfairSubcontractPricing || {}), }, inspectionResult: initialData.inspectionResult || defaultData.inspectionResult, responsibleDepartment: initialData.responsibleDepartment, cause: initialData.cause, causeOther: initialData.causeOther, countermeasure: initialData.countermeasure, countermeasureOther: initialData.countermeasureOther, } }, [initialData]) const [isEnabled, setIsEnabled] = useState(true) const [data, setData] = useState(mergedInitialData) // 점검결과 자동 계산 함수 const calculateInspectionResult = ( contractDocumentIssuance: SubcontractChecklistData['contractDocumentIssuance'], unfairSubcontractPricing: SubcontractChecklistData['unfairSubcontractPricing'] ): 'compliant' | 'violation' | 'suspected_violation' => { // 1. 계약서면발급의 모든 항목이 체크되어야 함 const allContractItemsChecked = Object.values(contractDocumentIssuance).every(checked => checked) // 2. 부당하도급대금결정행위에서 'X' 항목 체크 확인 const hasUnfairPricingViolation = Object.values(unfairSubcontractPricing).some(checked => !checked) if (!allContractItemsChecked) { return 'violation' } else if (hasUnfairPricingViolation) { return 'suspected_violation' } return 'compliant' } const handleContractDocumentChange = (field: keyof SubcontractChecklistData['contractDocumentIssuance'], checked: boolean) => { setData(prev => { const newContractDocumentIssuance = { ...prev.contractDocumentIssuance, [field]: checked } const newInspectionResult = calculateInspectionResult(newContractDocumentIssuance, prev.unfairSubcontractPricing) return { ...prev, contractDocumentIssuance: newContractDocumentIssuance, inspectionResult: newInspectionResult } }) } const handleUnfairPricingChange = (field: keyof SubcontractChecklistData['unfairSubcontractPricing'], checked: boolean) => { setData(prev => { const newUnfairSubcontractPricing = { ...prev.unfairSubcontractPricing, [field]: checked } const newInspectionResult = calculateInspectionResult(prev.contractDocumentIssuance, newUnfairSubcontractPricing) return { ...prev, unfairSubcontractPricing: newUnfairSubcontractPricing, inspectionResult: newInspectionResult } }) } const handleFieldChange = (field: keyof SubcontractChecklistData, value: string) => { setData(prev => ({ ...prev, [field]: value })) } // 데이터 변경 시 부모 컴포넌트에 전달 (저장 시에만) const handleSave = async () => { try { // validation 체크 const errors = [] // 위반 또는 위반의심인 경우 필수 필드 체크 if (data.inspectionResult === 'violation' || data.inspectionResult === 'suspected_violation') { if (!data.responsibleDepartment) errors.push('귀책부서') if (!data.cause) errors.push('원인') if (!data.countermeasure) errors.push('대책') // 기타 선택 시 추가 입력 필드 체크 if (data.cause === '기타' && !data.causeOther) errors.push('원인 기타 입력') if (data.countermeasure === '기타' && !data.countermeasureOther) errors.push('대책 기타 입력') } if (errors.length > 0) { toast.error(`다음 항목을 입력해주세요: ${errors.join(', ')}`) return } await updateSubcontractChecklist(contractId, data) onDataChange(data) toast.success('하도급법 체크리스트가 저장되었습니다.') } catch (error) { console.error('Error saving subcontract checklist:', error) toast.error('하도급법 체크리스트 저장 중 오류가 발생했습니다.') } } const getInspectionResultInfo = () => { switch (data.inspectionResult) { case 'compliant': return { icon: , label: '준수', color: 'bg-green-100 text-green-800', description: '1. 계약서면발급의 모든 항목에 체크, 2. 부당하도급에서 X항목에 체크한 상태' } case 'violation': return { icon: , label: '위반', color: 'bg-red-100 text-red-800', description: '1. 계약서면발급의 모든 항목 중 1개 이상 미체크 한 경우' } case 'suspected_violation': return { icon: , label: '위반의심', color: 'bg-yellow-100 text-yellow-800', description: '2. 부당하도급에서 O항목에 체크한 경우' } default: // 기본값으로 준수 상태 반환 return { icon: , label: '준수', color: 'bg-green-100 text-green-800', description: '점검 결과가 유효하지 않습니다.' } } } const resultInfo = getInspectionResultInfo() const isViolationOrSuspected = data.inspectionResult === 'violation' || data.inspectionResult === 'suspected_violation' const causeOptions = [ { value: '서면미교부_현업부서 하도급법 이해 부족', label: '서면미교부_현업부서 하도급법 이해 부족' }, { value: '서면미교부_기존계약 만료前 계약연장에 대한 사전조치 소홀', label: '서면미교부_기존계약 만료前 계약연장에 대한 사전조치 소홀' }, { value: '서면미교부_긴급작업時 先작업합의서 체결 절차 未인지', label: '서면미교부_긴급작업時 先작업합의서 체결 절차 未인지' }, { value: '부당가격인하_예산부족 等 원가절감 필요성 대두', label: '부당가격인하_예산부족 等 원가절감 필요성 대두' }, { value: '부당가격인하_하도급법 이해부족 및 금액 협의과정에 대한 근거 미흡', label: '부당가격인하_하도급법 이해부족 및 금액 협의과정에 대한 근거 미흡' }, { value: '기타', label: '기타' } ] const countermeasureOptions = [ { value: '서면미교부_준법지원을 통한 현업부서 계몽활동 실시', label: '서면미교부_준법지원을 통한 현업부서 계몽활동 실시' }, { value: '서면미교부_계약만료일정 별도 관리 및 사전점검', label: '서면미교부_계약만료일정 별도 관리 및 사전점검' }, { value: '서면미교부_작업착수前 先작업합의서 체결토록 현업에 가이드', label: '서면미교부_작업착수前 先작업합의서 체결토록 현업에 가이드' }, { value: '부당가격인하_최종 협의된 견적서 접수/보관 必', label: '부당가격인하_최종 협의된 견적서 접수/보관 必' }, { value: '부당가격인하_합의서 체결시 \'자율적 의사결정\' 等 문구 삽입', label: '부당가격인하_합의서 체결시 \'자율적 의사결정\' 等 문구 삽입' }, { value: '부당가격인하_수의계약時 금액 협의과정에 대한 근거 확보 (회의록, 메일, 당초/변경 견적서 等)', label: '부당가격인하_수의계약時 금액 협의과정에 대한 근거 확보 (회의록, 메일, 당초/변경 견적서 等)' }, { value: '기타', label: '기타' } ] return (
하도급법 자율점검 체크리스트 {resultInfo.label}
{/* 체크박스 */}
setIsEnabled(checked as boolean)} disabled={readOnly} /> 하도급법 자율점검 체크리스트 활성화
{/* 점검결과 표시 */}
{resultInfo.icon} {resultInfo.label}
{resultInfo.description}
{/* 1. 계약서면발급 */} 1. 계약서면발급
본 계약에 해당하는 항목을 아래 안내사항에 따라 'O'인 경우 체크하세요.
handleContractDocumentChange('workOrderBeforeStart', checked as boolean)} disabled={!isEnabled || readOnly} />

※ 단가, 물량 등을 정하지 못하는 경우 정하는 기일을 기재

handleContractDocumentChange('entrustmentDetails', checked as boolean)} disabled={!isEnabled || readOnly} />
handleContractDocumentChange('deliveryDetails', checked as boolean)} disabled={!isEnabled || readOnly} />

예: 삼성의 검사완료(승인) 후 목적물 인도 등

handleContractDocumentChange('inspectionMethod', checked as boolean)} disabled={!isEnabled || readOnly} />

예: 작업완료 후 삼성담당자 입회하에 검사를 실시하고 10일 이내 검사결과 통보

handleContractDocumentChange('subcontractPayment', checked as boolean)} disabled={!isEnabled || readOnly} />
handleContractDocumentChange('materialProvision', checked as boolean)} disabled={!isEnabled || readOnly} />

해당사항 없을 시에도 기재로 간주

handleContractDocumentChange('priceAdjustment', checked as boolean)} disabled={!isEnabled || readOnly} />
{/* 2. 부당하도급대금결정행위 */} 2. 부당하도급대금결정행위
본 계약에 해당하는 항목을 아래 안내사항에 따라 'O'인 경우 체크하세요.
※ 'X' 항목에 다음 안내사항이 자동 표기됩니다:

안내사항:

  • • 단가 인하時 객관/타당한 근거에 의해 산출하고 협력사와 합의
  • • 최저가 경쟁입찰 후 입찰자와 대금인하 협상 불가
  • • 협력사에 발주량 등 거래조건에 착오를 일으키게 하거나 타 사업자 견적 또는 거짓 견적을 보여주는 등 기만하여 대금을 결정할 수 없음
  • • 정당한 이유 없이 일률적 비율로 단가 인하 불가
  • • 정당한 이유 없이 특정 사업자를 차별 취급 하도록 대금 결정 불가
handleUnfairPricingChange('priceReductionWithBasis', checked as boolean)} disabled={!isEnabled || readOnly} />
handleUnfairPricingChange('noNegotiationAfterLowestBid', checked as boolean)} disabled={!isEnabled || readOnly} />
handleUnfairPricingChange('noDeceptionInPricing', checked as boolean)} disabled={!isEnabled || readOnly} />
handleUnfairPricingChange('noUniformPriceReduction', checked as boolean)} disabled={!isEnabled || readOnly} />
handleUnfairPricingChange('noDiscriminatoryTreatment', checked as boolean)} disabled={!isEnabled || readOnly} />
{/* 위반/위반의심 시 추가 정보 */} {isViolationOrSuspected && ( 위반/위반의심 상세 정보
점검결과가 위반 또는 위반의심인 경우 아래 정보를 필수로 입력해주세요.