Phase 2 완료 (4개): - 노무관리, 단가관리(건설), 입금, 출금 Phase 3 라우팅 구조 변경 완료 (22개): - 거래처(영업), 팝업관리, 공정관리, 게시판관리, 대손추심, Q&A - 현장관리, 실행내역, 견적관리, 견적(테스트) - 입찰관리, 이슈관리, 현장설명회, 견적서(건설) - 협력업체, 시공관리, 기성관리, 품목관리(건설) - 회계 도메인: 거래처, 매출, 세금계산서, 매입 신규 컴포넌트: - ErrorCard: 에러 페이지 UI 통일 - ServerErrorPage: V2 페이지 에러 처리 필수 - V2 Client 컴포넌트 및 Config 파일들 총 47개 상세 페이지 중 28개 완료, 19개 제외/불필요 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
175 lines
5.2 KiB
TypeScript
175 lines
5.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Label } from '@/components/ui/label';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { Calendar } from '@/components/ui/calendar';
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from '@/components/ui/popover';
|
|
import { CalendarIcon } from 'lucide-react';
|
|
import { format } from 'date-fns';
|
|
import { ko } from 'date-fns/locale';
|
|
import { cn } from '@/lib/utils';
|
|
import type {
|
|
ReasonInfoDialogProps,
|
|
ReasonFormData,
|
|
ReasonType,
|
|
} from './types';
|
|
import { REASON_TYPE_LABELS } from './types';
|
|
|
|
const initialFormData: ReasonFormData = {
|
|
employeeId: '',
|
|
baseDate: format(new Date(), 'yyyy-MM-dd'),
|
|
reasonType: '',
|
|
};
|
|
|
|
export function ReasonInfoDialog({
|
|
open,
|
|
onOpenChange,
|
|
employees,
|
|
onSubmit,
|
|
}: ReasonInfoDialogProps) {
|
|
const [formData, setFormData] = useState<ReasonFormData>(initialFormData);
|
|
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
|
|
|
|
// 데이터 초기화
|
|
useEffect(() => {
|
|
if (open) {
|
|
setFormData(initialFormData);
|
|
setSelectedDate(new Date());
|
|
}
|
|
}, [open]);
|
|
|
|
// 입력 변경 핸들러
|
|
const handleChange = (field: keyof ReasonFormData, value: string) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
// 날짜 변경 핸들러
|
|
const handleDateChange = (date: Date | undefined) => {
|
|
setSelectedDate(date);
|
|
if (date) {
|
|
setFormData(prev => ({ ...prev, baseDate: format(date, 'yyyy-MM-dd') }));
|
|
}
|
|
};
|
|
|
|
// 등록 (문서 작성 화면으로 이동)
|
|
const handleSubmit = () => {
|
|
onSubmit(formData);
|
|
onOpenChange(false);
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>사유 정보</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-4">
|
|
{/* 대상 선택 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-sm font-medium min-w-[80px]">대상</Label>
|
|
<Select
|
|
value={formData.employeeId}
|
|
onValueChange={(value) => handleChange('employeeId', value)}
|
|
>
|
|
<SelectTrigger className="w-[200px]">
|
|
<SelectValue placeholder="선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{employees.map((employee) => (
|
|
<SelectItem key={employee.id} value={employee.id}>
|
|
{[employee.department, employee.position, employee.rank, employee.name].filter(Boolean).join(' / ')}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 기준일 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-sm font-medium min-w-[80px]">기준일</Label>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
className={cn(
|
|
'w-[200px] justify-start text-left font-normal',
|
|
!selectedDate && 'text-muted-foreground'
|
|
)}
|
|
>
|
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
{selectedDate ? format(selectedDate, 'yyyy-MM-dd') : '날짜 선택'}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0" align="end">
|
|
<Calendar
|
|
mode="single"
|
|
selected={selectedDate}
|
|
onSelect={handleDateChange}
|
|
locale={ko}
|
|
initialFocus
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
|
|
{/* 유형 선택 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-sm font-medium min-w-[80px]">유형</Label>
|
|
<Select
|
|
value={formData.reasonType}
|
|
onValueChange={(value) => handleChange('reasonType', value)}
|
|
>
|
|
<SelectTrigger className="w-[200px]">
|
|
<SelectValue placeholder="선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{Object.entries(REASON_TYPE_LABELS).map(([value, label]) => (
|
|
<SelectItem key={value} value={value}>
|
|
{label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => onOpenChange(false)}
|
|
className="bg-gray-900 text-white hover:bg-gray-800"
|
|
>
|
|
취소
|
|
</Button>
|
|
<Button
|
|
onClick={handleSubmit}
|
|
className="bg-gray-900 text-white hover:bg-gray-800"
|
|
>
|
|
등록
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|