feat(WEB): 견적 시스템 개선, 엑셀 다운로드, PDF 생성 기능 추가

견적 시스템:
- QuoteRegistrationV2: 할인 모달, 거래명세서 모달, vatType 필드 추가
- DiscountModal: 할인율/할인금액 상호 계산 모달
- QuoteTransactionModal: 거래명세서 미리보기 모달
- LocationDetailPanel, LocationListPanel 개선

템플릿 기능:
- UniversalListPage: 엑셀 다운로드 기능 추가 (전체/선택 다운로드)
- DocumentViewer: PDF 생성 기능 개선

신규 API:
- /api/pdf/generate: Puppeteer 기반 PDF 생성 엔드포인트

UI 개선:
- 입력 컴포넌트 placeholder 스타일 개선 (opacity 50%)
- 각종 리스트 컴포넌트 정렬/필터링 개선

패키지 추가:
- html2canvas, jspdf, puppeteer, dom-to-image-more

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-27 19:49:03 +09:00
parent c4644489e7
commit afd7bda269
35 changed files with 3493 additions and 946 deletions

View File

@@ -7,12 +7,12 @@ import {
UserCheck,
AlertCircle,
Calendar,
Download,
Plus,
FileText,
Edit,
Search,
} from 'lucide-react';
import type { ExcelColumn } from '@/lib/utils/excel-download';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -351,10 +351,23 @@ export function AttendanceManagement() {
router.push(`/ko/hr/documents/new?type=${data.reasonType}`);
}, [router]);
const handleExcelDownload = useCallback(() => {
console.log('Excel download');
// TODO: 엑셀 다운로드 기능 구현
}, []);
// ===== 엑셀 컬럼 정의 =====
const excelColumns: ExcelColumn<AttendanceRecord>[] = useMemo(() => [
{ header: '부서', key: 'department' },
{ header: '직책', key: 'position' },
{ header: '이름', key: 'employeeName' },
{ header: '직급', key: 'rank' },
{ header: '기준일', key: 'baseDate' },
{ header: '출근', key: 'checkIn', transform: (value) => value ? String(value).substring(0, 5) : '-' },
{ header: '퇴근', key: 'checkOut', transform: (value) => value ? String(value).substring(0, 5) : '-' },
{ header: '휴게', key: 'breakTime', transform: (value) => value ? String(value) : '-' },
{ header: '연장근무', key: 'overtimeHours', transform: (value) => value ? String(value) : '-' },
{ header: '상태', key: 'status', transform: (value) => ATTENDANCE_STATUS_LABELS[value as AttendanceStatus] },
{ header: '사유', key: 'reason', transform: (value) => {
const reason = value as AttendanceRecord['reason'];
return reason?.label || '-';
}},
], []);
const handleReasonClick = useCallback((record: AttendanceRecord) => {
if (record.reason?.documentId) {
@@ -458,12 +471,15 @@ export function AttendanceManagement() {
searchPlaceholder: '이름, 부서 검색...',
// 엑셀 다운로드 설정 (클라이언트 사이드 필터링이므로 filteredData 사용)
excelDownload: {
columns: excelColumns,
filename: '근태현황',
sheetName: '근태',
},
extraFilters: (
<div className="flex items-center gap-2 flex-wrap">
<Button variant="outline" onClick={handleExcelDownload}>
<Download className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleAddReason}>
<FileText className="w-4 h-4 mr-2" />
@@ -667,7 +683,7 @@ export function AttendanceManagement() {
startDate,
endDate,
handleAddAttendance,
handleExcelDownload,
excelColumns,
handleAddReason,
handleReasonClick,
handleEditAttendance,