DatePicker 공통화: - date-picker.tsx 공통 컴포넌트 신규 추가 - 전체 폼 컴포넌트 DatePicker 통일 적용 (50+ 파일) - DateRangeSelector 개선 공정관리: - RuleModal 대폭 리팩토링 (-592줄 → 간소화) - ProcessForm, StepForm 개선 - ProcessDetail 수정, actions 확장 작업자화면: - WorkerScreen 기능 대폭 확장 (+543줄) - WorkItemCard 개선 - types 확장 회계/인사/영업/품질: - BadDebtDetail, BillDetail, DepositDetail, SalesDetail 등 DatePicker 적용 - EmployeeForm, VacationDialog 등 DatePicker 적용 - OrderRegistration, QuoteRegistration DatePicker 적용 - InspectionCreate, InspectionDetail DatePicker 적용 공사관리/CEO대시보드: - BiddingDetail, ContractDetail, HandoverReport 등 DatePicker 적용 - ScheduleDetailModal, TodayIssueSection 개선 기타: - WorkOrderCreate/Edit/Detail/List 개선 - ShipmentCreate/Edit, ReceivingDetail 개선 - calendar, calendarEvents 수정 - datepicker 마이그레이션 체크리스트 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
694 lines
26 KiB
TypeScript
694 lines
26 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useCallback, useMemo } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Plus, X, Eye, Stamp } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { DatePicker } from '@/components/ui/date-picker';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { QuantityInput } from '@/components/ui/quantity-input';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
|
import { handoverReportConfig } from './handoverReportConfig';
|
|
import { toast } from 'sonner';
|
|
import type {
|
|
HandoverReportDetail,
|
|
HandoverReportFormData,
|
|
HandoverStatus,
|
|
ConstructionManager,
|
|
} from './types';
|
|
import {
|
|
HANDOVER_STATUS_LABELS,
|
|
CONSTRUCTION_PM_OPTIONS,
|
|
MANAGER_OPTIONS,
|
|
getEmptyHandoverReportFormData,
|
|
handoverReportDetailToFormData,
|
|
} from './types';
|
|
import { updateHandoverReport, deleteHandoverReport } from './actions';
|
|
import { HandoverReportDocumentModal } from './modals/HandoverReportDocumentModal';
|
|
import {
|
|
ElectronicApprovalModal,
|
|
type ElectronicApproval,
|
|
getEmptyElectronicApproval,
|
|
} from '../common';
|
|
import { formatNumber } from '@/utils/formatAmount';
|
|
|
|
interface HandoverReportDetailFormProps {
|
|
mode: 'view' | 'edit';
|
|
reportId: string;
|
|
initialData?: HandoverReportDetail;
|
|
}
|
|
|
|
export default function HandoverReportDetailForm({
|
|
mode,
|
|
reportId,
|
|
initialData,
|
|
}: HandoverReportDetailFormProps) {
|
|
const router = useRouter();
|
|
const isViewMode = mode === 'view';
|
|
const isEditMode = mode === 'edit';
|
|
|
|
// 폼 데이터
|
|
const [formData, setFormData] = useState<HandoverReportFormData>(
|
|
initialData ? handoverReportDetailToFormData(initialData) : getEmptyHandoverReportFormData()
|
|
);
|
|
|
|
// 로딩 상태
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
// 모달 상태
|
|
const [showDocumentModal, setShowDocumentModal] = useState(false);
|
|
const [showApprovalModal, setShowApprovalModal] = useState(false);
|
|
|
|
// 전자결재 데이터
|
|
const [approvalData, setApprovalData] = useState<ElectronicApproval>(
|
|
getEmptyElectronicApproval()
|
|
);
|
|
|
|
// 폼 필드 변경
|
|
const handleFieldChange = useCallback(
|
|
(field: keyof HandoverReportFormData, value: string | number | boolean) => {
|
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
},
|
|
[]
|
|
);
|
|
|
|
// 저장 핸들러 (IntegratedDetailTemplate용)
|
|
const handleSubmit = useCallback(async (): Promise<{ success: boolean; error?: string }> => {
|
|
try {
|
|
const result = await updateHandoverReport(reportId, formData);
|
|
if (result.success) {
|
|
toast.success('수정이 완료되었습니다.');
|
|
router.push(`/ko/construction/project/contract/handover-report/${reportId}?mode=view`);
|
|
router.refresh();
|
|
return { success: true };
|
|
}
|
|
return { success: false, error: result.error || '저장에 실패했습니다.' };
|
|
} catch (error) {
|
|
return { success: false, error: error instanceof Error ? error.message : '저장에 실패했습니다.' };
|
|
}
|
|
}, [router, reportId, formData]);
|
|
|
|
// 삭제 핸들러 (IntegratedDetailTemplate용)
|
|
const handleDelete = useCallback(async (): Promise<{ success: boolean; error?: string }> => {
|
|
try {
|
|
const result = await deleteHandoverReport(reportId);
|
|
if (result.success) {
|
|
toast.success('인수인계보고서가 삭제되었습니다.');
|
|
router.push('/ko/construction/project/contract/handover-report');
|
|
router.refresh();
|
|
return { success: true };
|
|
}
|
|
return { success: false, error: result.error || '삭제에 실패했습니다.' };
|
|
} catch (error) {
|
|
return { success: false, error: error instanceof Error ? error.message : '삭제에 실패했습니다.' };
|
|
}
|
|
}, [router, reportId]);
|
|
|
|
// 인수인계보고서 보기 핸들러
|
|
const handleViewDocument = useCallback(() => {
|
|
setShowDocumentModal(true);
|
|
}, []);
|
|
|
|
// 전자결재 핸들러
|
|
const handleApproval = useCallback(() => {
|
|
setShowApprovalModal(true);
|
|
}, []);
|
|
|
|
// 전자결재 저장
|
|
const handleApprovalSave = useCallback((approval: ElectronicApproval) => {
|
|
setApprovalData(approval);
|
|
setShowApprovalModal(false);
|
|
toast.success('전자결재 정보가 저장되었습니다.');
|
|
}, []);
|
|
|
|
// 커스텀 헤더 액션 (view 모드에서 인수인계보고서 보기, 전자결재 버튼)
|
|
const customHeaderActions = useMemo(() => {
|
|
if (!isViewMode) return null;
|
|
return (
|
|
<>
|
|
<Button variant="outline" onClick={handleViewDocument} size="sm">
|
|
<Eye className="h-4 w-4 md:mr-2" />
|
|
<span className="hidden md:inline">인수인계보고서 보기</span>
|
|
</Button>
|
|
<Button variant="outline" onClick={handleApproval} size="sm">
|
|
<Stamp className="h-4 w-4 md:mr-2" />
|
|
<span className="hidden md:inline">전자결재</span>
|
|
</Button>
|
|
</>
|
|
);
|
|
}, [isViewMode, handleViewDocument, handleApproval]);
|
|
|
|
// 공사담당자 추가
|
|
const handleAddManager = useCallback(() => {
|
|
const newManager: ConstructionManager = {
|
|
id: String(Date.now()),
|
|
name: '',
|
|
nonPerformanceReason: '',
|
|
};
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
constructionManagers: [...prev.constructionManagers, newManager],
|
|
}));
|
|
}, []);
|
|
|
|
// 공사담당자 삭제
|
|
const handleRemoveManager = useCallback((managerId: string) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
constructionManagers: prev.constructionManagers.filter((m) => m.id !== managerId),
|
|
}));
|
|
}, []);
|
|
|
|
// 공사담당자 변경
|
|
const handleManagerChange = useCallback(
|
|
(managerId: string, field: keyof ConstructionManager, value: string | boolean) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
constructionManagers: prev.constructionManagers.map((m) =>
|
|
m.id === managerId ? { ...m, [field]: value } : m
|
|
),
|
|
}));
|
|
},
|
|
[]
|
|
);
|
|
|
|
// 장비 외 실행금액 변경
|
|
const handleEquipmentCostChange = useCallback(
|
|
(field: 'shippingCost' | 'highAltitudeWork' | 'publicExpense', value: number) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
externalEquipmentCost: {
|
|
...prev.externalEquipmentCost,
|
|
[field]: value,
|
|
},
|
|
}));
|
|
},
|
|
[]
|
|
);
|
|
|
|
// 계약 ITEM 비고 변경
|
|
const handleContractItemRemarkChange = useCallback((itemId: string, remark: string) => {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
contractItems: prev.contractItems.map((item) =>
|
|
item.id === itemId ? { ...item, remark } : item
|
|
),
|
|
}));
|
|
}, []);
|
|
|
|
// 폼 내용 렌더링 함수 (IntegratedDetailTemplate용)
|
|
const renderFormContent = () => (
|
|
<div className="space-y-6">
|
|
{/* 인수인계 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg">인수인계 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* 보고서번호 */}
|
|
<div className="space-y-2">
|
|
<Label>보고서번호</Label>
|
|
<Input
|
|
value={formData.reportNumber}
|
|
onChange={(e) => handleFieldChange('reportNumber', e.target.value)}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* 계약담당자 */}
|
|
<div className="space-y-2">
|
|
<Label>계약담당자</Label>
|
|
<Input
|
|
value={formData.contractManagerName}
|
|
onChange={(e) => handleFieldChange('contractManagerName', e.target.value)}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* 거래처명 */}
|
|
<div className="space-y-2">
|
|
<Label>거래처명</Label>
|
|
<Input
|
|
value={formData.partnerName}
|
|
onChange={(e) => handleFieldChange('partnerName', e.target.value)}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* 현장명 */}
|
|
<div className="space-y-2">
|
|
<Label>회사명</Label>
|
|
<Input
|
|
value={formData.siteName}
|
|
onChange={(e) => handleFieldChange('siteName', e.target.value)}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* 계약일자 */}
|
|
<div className="space-y-2">
|
|
<Label>계약일자</Label>
|
|
<DatePicker
|
|
value={formData.contractDate}
|
|
onChange={(date) => handleFieldChange('contractDate', date)}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* 개소 */}
|
|
<div className="space-y-2">
|
|
<Label>개소</Label>
|
|
<QuantityInput
|
|
value={formData.totalSites}
|
|
onChange={(value) => handleFieldChange('totalSites', value ?? 0)}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* 계약기간 */}
|
|
<div className="space-y-2">
|
|
<Label>계약기간</Label>
|
|
<div className="flex items-center gap-2">
|
|
<DatePicker
|
|
value={formData.contractStartDate}
|
|
onChange={(date) => handleFieldChange('contractStartDate', date)}
|
|
disabled={isViewMode}
|
|
/>
|
|
<span>~</span>
|
|
<DatePicker
|
|
value={formData.contractEndDate}
|
|
onChange={(date) => handleFieldChange('contractEndDate', date)}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 계약금액 (공급가액) */}
|
|
<div className="space-y-2">
|
|
<Label>계약금액 (공급가액)</Label>
|
|
<Input
|
|
type="text"
|
|
value={formatNumber(formData.contractAmount)}
|
|
onChange={(e) => {
|
|
const value = e.target.value.replace(/[^0-9]/g, '');
|
|
handleFieldChange('contractAmount', parseInt(value) || 0);
|
|
}}
|
|
disabled={isViewMode}
|
|
/>
|
|
</div>
|
|
|
|
{/* 공사PM */}
|
|
<div className="space-y-2">
|
|
<Label>공사PM</Label>
|
|
<Select
|
|
value={formData.constructionPMId}
|
|
onValueChange={(value) => {
|
|
handleFieldChange('constructionPMId', value);
|
|
const pm = CONSTRUCTION_PM_OPTIONS.find((p) => p.value === value);
|
|
if (pm) {
|
|
handleFieldChange('constructionPMName', pm.label);
|
|
}
|
|
}}
|
|
disabled={isViewMode}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{CONSTRUCTION_PM_OPTIONS.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 상태 */}
|
|
<div className="space-y-2">
|
|
<Label>상태</Label>
|
|
<RadioGroup
|
|
value={formData.status}
|
|
onValueChange={(value) => handleFieldChange('status', value as HandoverStatus)}
|
|
disabled={isViewMode}
|
|
className="flex gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="pending" id="pending" />
|
|
<Label htmlFor="pending" className="font-normal cursor-pointer">
|
|
{HANDOVER_STATUS_LABELS.pending}
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="completed" id="completed" />
|
|
<Label htmlFor="completed" className="font-normal cursor-pointer">
|
|
{HANDOVER_STATUS_LABELS.completed}
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 공사담당자 */}
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
|
|
<CardTitle className="text-lg">공사담당자</CardTitle>
|
|
{isEditMode && (
|
|
<Button variant="outline" size="sm" onClick={handleAddManager}>
|
|
<Plus className="h-4 w-4 mr-1" />
|
|
추가
|
|
</Button>
|
|
)}
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-[60px] text-center">번호</TableHead>
|
|
<TableHead className="w-[200px]">공사담당자</TableHead>
|
|
<TableHead>미이행 사유</TableHead>
|
|
{isEditMode && <TableHead className="w-[60px]"></TableHead>}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{formData.constructionManagers.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={isEditMode ? 4 : 3} className="text-center text-muted-foreground py-8">
|
|
등록된 공사담당자가 없습니다.
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
formData.constructionManagers.map((manager, index) => (
|
|
<TableRow key={manager.id}>
|
|
<TableCell className="text-center">{index + 1}</TableCell>
|
|
<TableCell>
|
|
{isEditMode ? (
|
|
<Select
|
|
value={manager.name}
|
|
onValueChange={(value) => handleManagerChange(manager.id, 'name', value)}
|
|
>
|
|
<SelectTrigger className="w-[150px]">
|
|
<SelectValue placeholder="이름" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{MANAGER_OPTIONS.map((option) => (
|
|
<SelectItem key={option.value} value={option.label}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
) : (
|
|
manager.name
|
|
)}
|
|
</TableCell>
|
|
<TableCell>
|
|
{isEditMode ? (
|
|
<Input
|
|
value={manager.nonPerformanceReason}
|
|
onChange={(e) =>
|
|
handleManagerChange(manager.id, 'nonPerformanceReason', e.target.value)
|
|
}
|
|
placeholder="미이행 사유 입력"
|
|
/>
|
|
) : (
|
|
manager.nonPerformanceReason || '-'
|
|
)}
|
|
</TableCell>
|
|
{isEditMode && (
|
|
<TableCell className="text-center">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={() => handleRemoveManager(manager.id)}
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 계약 ITEM */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg">계약 ITEM</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-[60px] text-center">번호</TableHead>
|
|
<TableHead>명칭</TableHead>
|
|
<TableHead>제품</TableHead>
|
|
<TableHead className="text-right">수량</TableHead>
|
|
<TableHead>비고</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{formData.contractItems.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={5} className="text-center text-muted-foreground py-8">
|
|
등록된 계약 ITEM이 없습니다.
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
formData.contractItems.map((item) => (
|
|
<TableRow key={item.id}>
|
|
<TableCell className="text-center">{item.no}</TableCell>
|
|
<TableCell>{item.name}</TableCell>
|
|
<TableCell>{item.product}</TableCell>
|
|
<TableCell className="text-right">{formatNumber(item.quantity)}</TableCell>
|
|
<TableCell>
|
|
{isEditMode ? (
|
|
<Input
|
|
value={item.remark}
|
|
onChange={(e) => handleContractItemRemarkChange(item.id, e.target.value)}
|
|
placeholder="비고 입력"
|
|
/>
|
|
) : (
|
|
item.remark || '-'
|
|
)}
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 상세 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg">상세 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-6">
|
|
{/* 2차 배관 유무 / 도장 & 코킹 유무 */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{/* 2차 배관 유무 */}
|
|
<div className="space-y-2">
|
|
<Label>2차 배관 유무</Label>
|
|
<div className="flex items-center gap-4">
|
|
<RadioGroup
|
|
value={formData.hasSecondaryPiping ? 'included' : 'not_included'}
|
|
onValueChange={(value) => {
|
|
handleFieldChange('hasSecondaryPiping', value === 'included');
|
|
if (value !== 'included') {
|
|
handleFieldChange('secondaryPipingNote', '');
|
|
}
|
|
}}
|
|
disabled={isViewMode}
|
|
className="flex gap-4 shrink-0"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="not_included" id="piping_not" />
|
|
<Label htmlFor="piping_not" className="font-normal cursor-pointer">
|
|
미포함
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="included" id="piping_yes" />
|
|
<Label htmlFor="piping_yes" className="font-normal cursor-pointer">
|
|
포함
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
<Input
|
|
value={formData.secondaryPipingNote}
|
|
onChange={(e) => handleFieldChange('secondaryPipingNote', e.target.value)}
|
|
disabled={isViewMode || !formData.hasSecondaryPiping}
|
|
placeholder="2차 배관 내용 입력"
|
|
className={`flex-1 transition-opacity duration-200 ${
|
|
formData.hasSecondaryPiping ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
}`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 도장 & 코킹 유무 */}
|
|
<div className="space-y-2">
|
|
<Label>도장 & 코킹 유무</Label>
|
|
<div className="flex items-center gap-4">
|
|
<RadioGroup
|
|
value={formData.hasCoating ? 'included' : 'not_included'}
|
|
onValueChange={(value) => {
|
|
handleFieldChange('hasCoating', value === 'included');
|
|
if (value !== 'included') {
|
|
handleFieldChange('coatingNote', '');
|
|
}
|
|
}}
|
|
disabled={isViewMode}
|
|
className="flex gap-4 shrink-0"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="not_included" id="coating_not" />
|
|
<Label htmlFor="coating_not" className="font-normal cursor-pointer">
|
|
미포함
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="included" id="coating_yes" />
|
|
<Label htmlFor="coating_yes" className="font-normal cursor-pointer">
|
|
포함
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
<Input
|
|
value={formData.coatingNote}
|
|
onChange={(e) => handleFieldChange('coatingNote', e.target.value)}
|
|
disabled={isViewMode || !formData.hasCoating}
|
|
placeholder="도장 & 코킹 내용 입력"
|
|
className={`flex-1 transition-opacity duration-200 ${
|
|
formData.hasCoating ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
}`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 장비 외 실행금액 */}
|
|
<div className="space-y-2">
|
|
<Label>장비 외 실행금액</Label>
|
|
<div className="flex flex-wrap items-center gap-4">
|
|
<div className="flex items-center gap-2">
|
|
<Checkbox
|
|
checked={formData.externalEquipmentCost.shippingCost > 0}
|
|
onCheckedChange={(checked) =>
|
|
handleEquipmentCostChange('shippingCost', checked ? 1500000 : 0)
|
|
}
|
|
disabled={isViewMode}
|
|
/>
|
|
<Label className="font-normal">운반비</Label>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Checkbox
|
|
checked={formData.externalEquipmentCost.highAltitudeWork > 0}
|
|
onCheckedChange={(checked) =>
|
|
handleEquipmentCostChange('highAltitudeWork', checked ? 800000 : 0)
|
|
}
|
|
disabled={isViewMode}
|
|
/>
|
|
<Label className="font-normal">고소작업대</Label>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Checkbox
|
|
checked={formData.externalEquipmentCost.publicExpense > 0}
|
|
onCheckedChange={(checked) =>
|
|
handleEquipmentCostChange('publicExpense', checked ? 10000000 : 0)
|
|
}
|
|
disabled={isViewMode}
|
|
/>
|
|
<Label className="font-normal">공과잡</Label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 특이사항 */}
|
|
<div className="space-y-2">
|
|
<Label>특이사항</Label>
|
|
<Textarea
|
|
value={formData.specialNotes}
|
|
onChange={(e) => handleFieldChange('specialNotes', e.target.value)}
|
|
disabled={isViewMode}
|
|
rows={4}
|
|
placeholder="특이사항을 입력하세요"
|
|
/>
|
|
</div>
|
|
|
|
{/* 녹음 버튼 */}
|
|
{isEditMode && (
|
|
<div className="flex justify-end">
|
|
<Button variant="outline">녹음</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<IntegratedDetailTemplate
|
|
config={handoverReportConfig}
|
|
mode={mode}
|
|
initialData={{}}
|
|
itemId={reportId}
|
|
isLoading={false}
|
|
onSubmit={handleSubmit}
|
|
onDelete={isViewMode ? handleDelete : undefined}
|
|
headerActions={customHeaderActions}
|
|
renderView={() => renderFormContent()}
|
|
renderForm={() => renderFormContent()}
|
|
/>
|
|
|
|
{/* 인수인계보고서 보기 모달 (특수 기능) */}
|
|
{initialData && (
|
|
<HandoverReportDocumentModal
|
|
open={showDocumentModal}
|
|
onOpenChange={setShowDocumentModal}
|
|
report={initialData}
|
|
/>
|
|
)}
|
|
|
|
{/* 전자결재 모달 (특수 기능) */}
|
|
<ElectronicApprovalModal
|
|
isOpen={showApprovalModal}
|
|
onClose={() => setShowApprovalModal(false)}
|
|
approval={approvalData}
|
|
onSave={handleApprovalSave}
|
|
/>
|
|
</>
|
|
);
|
|
} |