feat(WEB): 입력 컴포넌트 공통화 및 UI 개선

- 숫자/통화/전화번호/사업자번호 등 특수 입력 컴포넌트 추가
- MobileCard 컴포넌트 통합 (ListMobileCard 제거)
- IntegratedListTemplateV2 페이지네이션 버그 수정 (NaN 이슈)
- IntegratedDetailTemplate 타이틀 중복 수정
- 문서 시스템 컴포넌트 추가
- 헤더 벨 아이콘 포커스 스타일 개선

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-21 20:56:17 +09:00
parent cfa72fe19b
commit 835c06ce94
190 changed files with 8575 additions and 2354 deletions

View File

@@ -48,8 +48,9 @@ import {
type UniversalListConfig,
type TabOption,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { DocumentDetailModal } from '@/components/approval/DocumentDetail';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
// import { DocumentDetailModal } from '@/components/approval/DocumentDetail';
import { DocumentDetailModalV2 as DocumentDetailModal } from '@/components/approval/DocumentDetail';
import type {
DocumentType,
ProposalDocumentData,

View File

@@ -5,6 +5,7 @@ import { Plus, X, Upload, FileText, ExternalLink } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { CurrencyInput } from '@/components/ui/currency-input';
import {
Select,
SelectContent,
@@ -146,11 +147,10 @@ export function ExpenseReportForm({ data, onChange }: ExpenseReportFormProps) {
/>
</TableCell>
<TableCell>
<Input
type="number"
<CurrencyInput
placeholder="금액을 입력해주세요"
value={item.amount || ''}
onChange={(e) => handleItemChange(index, 'amount', Number(e.target.value) || 0)}
value={item.amount || 0}
onChange={(value) => handleItemChange(index, 'amount', value ?? 0)}
/>
</TableCell>
<TableCell>

View File

@@ -5,6 +5,7 @@ import { Mic, Upload, X, FileText, ExternalLink } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { CurrencyInput } from '@/components/ui/currency-input';
import { Textarea } from '@/components/ui/textarea';
import type { ProposalData, UploadedFile } from './types';
@@ -131,12 +132,11 @@ export function ProposalForm({ data, onChange }: ProposalFormProps) {
{/* 예상 비용 */}
<div className="space-y-2">
<Label htmlFor="estimatedCost"> </Label>
<Input
<CurrencyInput
id="estimatedCost"
type="number"
placeholder="금액을 입력해주세요"
value={data.estimatedCost || ''}
onChange={(e) => onChange({ ...data, estimatedCost: Number(e.target.value) || 0 })}
value={data.estimatedCost || 0}
onChange={(value) => onChange({ ...data, estimatedCost: value ?? 0 })}
/>
</div>
</div>

View File

@@ -0,0 +1,93 @@
'use client';
import { DocumentViewer } from '@/components/document-system';
import { ProposalDocument } from './ProposalDocument';
import { ExpenseReportDocument } from './ExpenseReportDocument';
import { ExpenseEstimateDocument } from './ExpenseEstimateDocument';
import type {
DocumentType,
DocumentDetailModalProps,
ProposalDocumentData,
ExpenseReportDocumentData,
ExpenseEstimateDocumentData,
} from './types';
/**
* 문서 상세 모달 V2
*
* DocumentViewer를 사용하여 통합 UI 제공
* - 줌/드래그 기능 추가
* - 모드에 따른 버튼 자동 설정
*/
export function DocumentDetailModalV2({
open,
onOpenChange,
documentType,
data,
mode = 'inbox',
documentStatus,
onEdit,
onCopy,
onApprove,
onReject,
onSubmit,
}: DocumentDetailModalProps) {
// 문서 타입별 제목
const getDocumentTitle = () => {
switch (documentType) {
case 'proposal':
return '품의서';
case 'expenseReport':
return '지출결의서';
case 'expenseEstimate':
return '지출 예상 내역서';
default:
return '문서';
}
};
// 모드에 따른 프리셋 결정
const getPreset = () => {
// 기안함 모드 + 임시저장 상태: 복제, 상신, 인쇄
if (mode === 'draft' && documentStatus === 'draft') {
return 'approval-draft' as const;
}
// 결재함 모드: 수정, 반려, 승인, 인쇄
if (mode === 'inbox') {
return 'approval-inbox' as const;
}
// 그 외 (참조함 등): 인쇄만
return 'readonly' as const;
};
// 문서 콘텐츠 렌더링
const renderDocument = () => {
switch (documentType) {
case 'proposal':
return <ProposalDocument data={data as ProposalDocumentData} />;
case 'expenseReport':
return <ExpenseReportDocument data={data as ExpenseReportDocumentData} />;
case 'expenseEstimate':
return <ExpenseEstimateDocument data={data as ExpenseEstimateDocumentData} />;
default:
return null;
}
};
return (
<DocumentViewer
title={getDocumentTitle()}
subtitle={`${getDocumentTitle()} 상세`}
preset={getPreset()}
open={open}
onOpenChange={onOpenChange}
onEdit={onEdit}
onCopy={onCopy}
onApprove={onApprove}
onReject={onReject}
onSubmit={onSubmit}
>
{renderDocument()}
</DocumentViewer>
);
}

View File

@@ -207,4 +207,7 @@ export function DocumentDetailModal({
export type { DocumentType, DocumentDetailModalProps } from './types';
export { ProposalDocument } from './ProposalDocument';
export { ExpenseReportDocument } from './ExpenseReportDocument';
export { ExpenseEstimateDocument } from './ExpenseEstimateDocument';
export { ExpenseEstimateDocument } from './ExpenseEstimateDocument';
// V2 - DocumentViewer 기반
export { DocumentDetailModalV2 } from './DocumentDetailModalV2';

View File

@@ -36,8 +36,9 @@ import {
UniversalListPage,
type UniversalListConfig,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { DocumentDetailModal } from '@/components/approval/DocumentDetail';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
// import { DocumentDetailModal } from '@/components/approval/DocumentDetail';
import { DocumentDetailModalV2 as DocumentDetailModal } from '@/components/approval/DocumentDetail';
import type {
DocumentType,
ProposalDocumentData,

View File

@@ -42,8 +42,9 @@ import {
type TabOption,
} from '@/components/templates/UniversalListPage';
import { DateRangeSelector } from '@/components/molecules/DateRangeSelector';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { DocumentDetailModal } from '@/components/approval/DocumentDetail';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
// import { DocumentDetailModal } from '@/components/approval/DocumentDetail';
import { DocumentDetailModalV2 as DocumentDetailModal } from '@/components/approval/DocumentDetail';
import type { DocumentType, ProposalDocumentData, ExpenseReportDocumentData, ExpenseEstimateDocumentData } from '@/components/approval/DocumentDetail/types';
import type {
ReferenceTabType,