refactor: UI 컴포넌트 추상화 및 입금/출금 등록 버튼 추가
- 입금관리, 출금관리 리스트에 등록 버튼 추가 - skeleton, confirm-dialog, empty-state, status-badge UI 컴포넌트 추가 - document-system 컴포넌트 추상화 (ApprovalLine, DocumentHeader 등) - 여러 페이지 컴포넌트 리팩토링 및 코드 정리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,8 @@ export interface ApprovalPerson {
|
||||
}
|
||||
|
||||
export interface ApprovalLineProps {
|
||||
/** 결재란 유형: 3열(작성/승인) 또는 4열(작성/검토/승인) */
|
||||
type?: '3col' | '4col';
|
||||
/** 결재란 유형: 2열(담당/부서장), 3열(작성/승인), 4열(작성/검토/승인) */
|
||||
type?: '2col' | '3col' | '4col';
|
||||
/** 외부 송부 시 숨김 */
|
||||
visible?: boolean;
|
||||
/** 문서 모드: internal(내부), external(외부송부) */
|
||||
@@ -43,6 +43,12 @@ export interface ApprovalLineProps {
|
||||
reviewer?: string;
|
||||
approver?: string;
|
||||
};
|
||||
/** 컬럼 헤더 라벨 커스텀 (기본값: 작성/검토/승인) */
|
||||
columnLabels?: {
|
||||
writer?: string;
|
||||
reviewer?: string;
|
||||
approver?: string;
|
||||
};
|
||||
/** 추가 className */
|
||||
className?: string;
|
||||
}
|
||||
@@ -60,6 +66,7 @@ export function ApprovalLine({
|
||||
reviewer: '생산',
|
||||
approver: '품질',
|
||||
},
|
||||
columnLabels,
|
||||
className,
|
||||
}: ApprovalLineProps) {
|
||||
// 외부 송부 모드이거나 visible이 false면 렌더링 안함
|
||||
@@ -68,6 +75,14 @@ export function ApprovalLine({
|
||||
}
|
||||
|
||||
const is4Col = type === '4col';
|
||||
const is2Col = type === '2col';
|
||||
|
||||
// 기본 컬럼 라벨
|
||||
const labels = {
|
||||
writer: columnLabels?.writer ?? (is2Col ? '담당' : '작성'),
|
||||
reviewer: columnLabels?.reviewer ?? '검토',
|
||||
approver: columnLabels?.approver ?? (is2Col ? '부서장' : '승인'),
|
||||
};
|
||||
|
||||
return (
|
||||
<table className={cn('border-collapse text-xs', className)}>
|
||||
@@ -84,15 +99,15 @@ export function ApprovalLine({
|
||||
</div>
|
||||
</td>
|
||||
<td className="w-16 p-2 text-center font-medium bg-gray-100 border border-gray-300">
|
||||
작성
|
||||
{labels.writer}
|
||||
</td>
|
||||
{is4Col && (
|
||||
<td className="w-16 p-2 text-center font-medium bg-gray-100 border border-gray-300">
|
||||
검토
|
||||
{labels.reviewer}
|
||||
</td>
|
||||
)}
|
||||
<td className="w-16 p-2 text-center font-medium bg-gray-100 border border-gray-300">
|
||||
승인
|
||||
{labels.approver}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 건설 문서용 결재란 컴포넌트
|
||||
*
|
||||
* @example
|
||||
* // 4열 결재란 (작성 + 승인 3개)
|
||||
* <ConstructionApprovalTable
|
||||
* approvers={{
|
||||
* writer: { name: '홍길동', department: '영업부' },
|
||||
* approver1: { name: '김부장', department: '기획부' },
|
||||
* approver2: { name: '이이사', department: '개발부' },
|
||||
* approver3: { name: '박대표', department: '경영부' },
|
||||
* }}
|
||||
* />
|
||||
*/
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface ConstructionApprover {
|
||||
name?: string;
|
||||
department?: string;
|
||||
}
|
||||
|
||||
export interface ConstructionApprovalTableProps {
|
||||
/** 결재자 정보 */
|
||||
approvers?: {
|
||||
writer?: ConstructionApprover;
|
||||
approver1?: ConstructionApprover;
|
||||
approver2?: ConstructionApprover;
|
||||
approver3?: ConstructionApprover;
|
||||
};
|
||||
/** 컬럼 헤더 라벨 커스텀 */
|
||||
columnLabels?: {
|
||||
writer?: string;
|
||||
approver1?: string;
|
||||
approver2?: string;
|
||||
approver3?: string;
|
||||
};
|
||||
/** 추가 className */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ConstructionApprovalTable({
|
||||
approvers = {},
|
||||
columnLabels = {},
|
||||
className,
|
||||
}: ConstructionApprovalTableProps) {
|
||||
const labels = {
|
||||
writer: columnLabels.writer ?? '작성',
|
||||
approver1: columnLabels.approver1 ?? '승인',
|
||||
approver2: columnLabels.approver2 ?? '승인',
|
||||
approver3: columnLabels.approver3 ?? '승인',
|
||||
};
|
||||
|
||||
return (
|
||||
<table className={cn('border-collapse border border-gray-300 text-sm', className)}>
|
||||
<tbody>
|
||||
{/* 헤더 행 */}
|
||||
<tr>
|
||||
<th
|
||||
rowSpan={3}
|
||||
className="border border-gray-300 px-2 py-1 bg-gray-50 text-center w-8 align-middle"
|
||||
>
|
||||
<span className="writing-vertical">결<br />재</span>
|
||||
</th>
|
||||
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">
|
||||
{labels.writer}
|
||||
</th>
|
||||
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">
|
||||
{labels.approver1}
|
||||
</th>
|
||||
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">
|
||||
{labels.approver2}
|
||||
</th>
|
||||
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">
|
||||
{labels.approver3}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
{/* 이름 행 */}
|
||||
<tr>
|
||||
<td className="border border-gray-300 px-3 py-2 text-center h-10">
|
||||
{approvers.writer?.name || ''}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-2 text-center h-10">
|
||||
{approvers.approver1?.name || ''}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-2 text-center h-10">
|
||||
{approvers.approver2?.name || ''}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-2 text-center h-10">
|
||||
{approvers.approver3?.name || ''}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* 부서 행 */}
|
||||
<tr>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
{approvers.writer?.department || '부서명'}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
{approvers.approver1?.department || '부서명'}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
{approvers.approver2?.department || '부서명'}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
{approvers.approver3?.department || '부서명'}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -28,6 +28,15 @@
|
||||
*
|
||||
* // 외부 송부 (결재선 숨김)
|
||||
* <DocumentHeader title="견적서" mode="external" />
|
||||
*
|
||||
* // 품질검사 레이아웃 (2줄 제목)
|
||||
* <DocumentHeader
|
||||
* title="스크린"
|
||||
* subtitle="중간검사 성적서"
|
||||
* layout="quality"
|
||||
* logo={{ text: 'KD', subtext: '경동기업\nKYUNGDONG COMPANY' }}
|
||||
* customApproval={<QualityApprovalTable type="4col" approvers={approvers} />}
|
||||
* />
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
@@ -56,10 +65,12 @@ export interface DocumentHeaderProps {
|
||||
topInfo?: ReactNode;
|
||||
/** 결재란 설정 (null이면 숨김) */
|
||||
approval?: Omit<ApprovalLineProps, 'mode'> | null;
|
||||
/** 커스텀 결재란 컴포넌트 (approval 대신 사용) */
|
||||
customApproval?: ReactNode;
|
||||
/** 문서 모드: internal(내부), external(외부송부) */
|
||||
mode?: 'internal' | 'external';
|
||||
/** 레이아웃 유형 */
|
||||
layout?: 'default' | 'centered' | 'simple';
|
||||
layout?: 'default' | 'centered' | 'simple' | 'quality' | 'construction' | 'quote';
|
||||
/** 추가 className */
|
||||
className?: string;
|
||||
}
|
||||
@@ -71,12 +82,13 @@ export function DocumentHeader({
|
||||
logo,
|
||||
topInfo,
|
||||
approval,
|
||||
customApproval,
|
||||
mode = 'internal',
|
||||
layout = 'default',
|
||||
className,
|
||||
}: DocumentHeaderProps) {
|
||||
const isExternal = mode === 'external';
|
||||
const showApproval = approval !== null && !isExternal;
|
||||
const showApproval = (approval !== null || customApproval) && !isExternal;
|
||||
|
||||
// 간단한 레이아웃 (제목만)
|
||||
if (layout === 'simple') {
|
||||
@@ -93,6 +105,79 @@ export function DocumentHeader({
|
||||
);
|
||||
}
|
||||
|
||||
// 품질검사 레이아웃 (로고 + 2줄 제목 + 결재란, 테두리 없음)
|
||||
if (layout === 'quality') {
|
||||
return (
|
||||
<div className={cn('flex justify-between items-start mb-4', className)}>
|
||||
{/* 좌측: 로고 영역 */}
|
||||
{logo && (
|
||||
<div className="flex items-center gap-2">
|
||||
{logo.imageUrl ? (
|
||||
<img src={logo.imageUrl} alt={logo.text} className="h-8" />
|
||||
) : (
|
||||
<div className="text-2xl font-bold">{logo.text}</div>
|
||||
)}
|
||||
{logo.subtext && (
|
||||
<div className="text-xs text-gray-600 whitespace-pre-line">{logo.subtext}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 중앙: 2줄 제목 */}
|
||||
<div className="text-center">
|
||||
<div className="text-xl font-bold">{title}</div>
|
||||
{subtitle && (
|
||||
<div className="text-xl font-bold tracking-[0.2rem]">{subtitle}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 우측: 결재란 */}
|
||||
{showApproval && (
|
||||
customApproval || (approval && <ApprovalLine {...approval} mode={mode} />)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 건설 문서 레이아웃 (좌측 정렬 제목 + 결재란)
|
||||
if (layout === 'construction') {
|
||||
return (
|
||||
<div className={cn('flex justify-between items-start mb-6', className)}>
|
||||
{/* 좌측: 제목 및 문서정보 */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-2">{title}</h1>
|
||||
{(documentCode || subtitle) && (
|
||||
<div className="text-sm text-gray-600">
|
||||
{documentCode && <span>문서번호: {documentCode}</span>}
|
||||
{documentCode && subtitle && <span> | </span>}
|
||||
{subtitle && <span>{subtitle}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 우측: 결재란 */}
|
||||
{showApproval && (
|
||||
customApproval || (approval && <ApprovalLine {...approval} mode={mode} />)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 견적/발주 문서 레이아웃 (중앙 제목 + 우측 로트번호/결재란)
|
||||
if (layout === 'quote') {
|
||||
return (
|
||||
<div className={cn('flex items-center justify-between mb-5 pb-4 border-b-2 border-black', className)}>
|
||||
{/* 중앙: 제목 */}
|
||||
<div className="flex-1 text-center">
|
||||
<h1 className="text-4xl font-bold tracking-[8px]">{title}</h1>
|
||||
</div>
|
||||
|
||||
{/* 우측: 로트번호 + 결재란 (customApproval로 전달) */}
|
||||
{showApproval && customApproval}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 중앙 정렬 레이아웃 (견적서 스타일)
|
||||
if (layout === 'centered') {
|
||||
return (
|
||||
@@ -107,8 +192,10 @@ export function DocumentHeader({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showApproval && approval && (
|
||||
<ApprovalLine {...approval} mode={mode} className="ml-4" />
|
||||
{showApproval && (
|
||||
<div className="ml-4">
|
||||
{customApproval || (approval && <ApprovalLine {...approval} mode={mode} />)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -150,8 +237,8 @@ export function DocumentHeader({
|
||||
{topInfo}
|
||||
</div>
|
||||
)}
|
||||
{showApproval && approval && (
|
||||
<ApprovalLine {...approval} mode={mode} />
|
||||
{showApproval && (
|
||||
customApproval || (approval && <ApprovalLine {...approval} mode={mode} />)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
121
src/components/document-system/components/LotApprovalTable.tsx
Normal file
121
src/components/document-system/components/LotApprovalTable.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 로트번호 + 결재란 통합 컴포넌트
|
||||
*
|
||||
* @example
|
||||
* <LotApprovalTable
|
||||
* lotNumber="KQ#-SC-250122-01"
|
||||
* approvers={{
|
||||
* writer: { name: '전진', department: '판매/전진' },
|
||||
* reviewer: { name: '', department: '회계' },
|
||||
* approver: { name: '', department: '생산' },
|
||||
* }}
|
||||
* />
|
||||
*/
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface LotApprover {
|
||||
name?: string;
|
||||
department?: string;
|
||||
}
|
||||
|
||||
export interface LotApprovalTableProps {
|
||||
/** 로트번호 */
|
||||
lotNumber: string;
|
||||
/** 로트번호 라벨 (기본값: '로트번호') */
|
||||
lotLabel?: string;
|
||||
/** 결재자 정보 */
|
||||
approvers?: {
|
||||
writer?: LotApprover;
|
||||
reviewer?: LotApprover;
|
||||
approver?: LotApprover;
|
||||
};
|
||||
/** 컬럼 헤더 라벨 커스텀 */
|
||||
columnLabels?: {
|
||||
writer?: string;
|
||||
reviewer?: string;
|
||||
approver?: string;
|
||||
};
|
||||
/** 추가 className */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function LotApprovalTable({
|
||||
lotNumber,
|
||||
lotLabel = '로트번호',
|
||||
approvers = {},
|
||||
columnLabels = {},
|
||||
className,
|
||||
}: LotApprovalTableProps) {
|
||||
const labels = {
|
||||
writer: columnLabels.writer ?? '작성',
|
||||
reviewer: columnLabels.reviewer ?? '검토',
|
||||
approver: columnLabels.approver ?? '승인',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn('border-2 border-black bg-white', className)}>
|
||||
{/* 로트번호 행 */}
|
||||
<div className="grid grid-cols-[100px_1fr] border-b-2 border-black">
|
||||
<div className="bg-gray-200 border-r-2 border-black px-2 py-2 text-center font-bold text-xs flex items-center justify-center">
|
||||
{lotLabel}
|
||||
</div>
|
||||
<div className="bg-white px-2 py-2 text-center font-bold text-sm flex items-center justify-center">
|
||||
{lotNumber}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 결재란 */}
|
||||
<div className="grid grid-cols-[60px_1fr]">
|
||||
{/* 결재 세로 셀 */}
|
||||
<div className="border-r border-black flex items-center justify-center bg-white row-span-3">
|
||||
<span className="text-xs font-semibold">결<br />재</span>
|
||||
</div>
|
||||
|
||||
{/* 결재 내용 */}
|
||||
<div>
|
||||
{/* 헤더 행 */}
|
||||
<div className="grid grid-cols-3 border-b border-black">
|
||||
<div className="border-r border-black px-3 py-2 text-center font-semibold text-xs bg-white">
|
||||
{labels.writer}
|
||||
</div>
|
||||
<div className="border-r border-black px-3 py-2 text-center font-semibold text-xs bg-white">
|
||||
{labels.reviewer}
|
||||
</div>
|
||||
<div className="px-3 py-2 text-center font-semibold text-xs bg-white">
|
||||
{labels.approver}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 서명 행 */}
|
||||
<div className="grid grid-cols-3 border-b border-black">
|
||||
<div className="border-r border-black px-3 py-2 text-center text-xs h-12 flex items-center justify-center bg-white">
|
||||
{approvers.writer?.name || ''}
|
||||
</div>
|
||||
<div className="border-r border-black px-3 py-2 text-center text-xs h-12 flex items-center justify-center bg-white">
|
||||
{approvers.reviewer?.name || ''}
|
||||
</div>
|
||||
<div className="px-3 py-2 text-center text-xs h-12 flex items-center justify-center bg-white">
|
||||
{approvers.approver?.name || ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 부서 행 */}
|
||||
<div className="grid grid-cols-3">
|
||||
<div className="border-r border-black px-2 py-1 text-center text-xs font-semibold bg-white">
|
||||
{approvers.writer?.department || ''}
|
||||
</div>
|
||||
<div className="border-r border-black px-2 py-1 text-center text-xs font-semibold bg-white">
|
||||
{approvers.reviewer?.department || ''}
|
||||
</div>
|
||||
<div className="px-2 py-1 text-center text-xs font-semibold bg-white">
|
||||
{approvers.approver?.department || ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 품질검사 문서용 결재란 컴포넌트
|
||||
*
|
||||
* @example
|
||||
* // 2열 타입 (수입검사 성적서)
|
||||
* <QualityApprovalTable
|
||||
* type="2col"
|
||||
* approvers={{ writer: '노원호' }}
|
||||
* reportDate="2025-07-15"
|
||||
* />
|
||||
*
|
||||
* // 4열 타입 (중간검사 성적서)
|
||||
* <QualityApprovalTable
|
||||
* type="4col"
|
||||
* approvers={{ writer: '전진', reviewer: '', approver: '' }}
|
||||
* departments={{ writer: '판매/전진', reviewer: '생산', approver: '품질' }}
|
||||
* />
|
||||
*/
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface QualityApprovers {
|
||||
writer?: string;
|
||||
reviewer?: string;
|
||||
approver?: string;
|
||||
}
|
||||
|
||||
export interface QualityDepartments {
|
||||
writer?: string;
|
||||
reviewer?: string;
|
||||
approver?: string;
|
||||
}
|
||||
|
||||
export interface QualityApprovalTableProps {
|
||||
/** 결재란 타입: 2col(담당/부서장), 4col(결재/작성/검토/승인) */
|
||||
type?: '2col' | '4col';
|
||||
/** 결재자 정보 */
|
||||
approvers?: QualityApprovers;
|
||||
/** 부서 정보 (4col 전용) */
|
||||
departments?: QualityDepartments;
|
||||
/** 보고일자 (2col 전용) */
|
||||
reportDate?: string;
|
||||
/** 추가 className */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function QualityApprovalTable({
|
||||
type = '4col',
|
||||
approvers = {},
|
||||
departments = {
|
||||
writer: '판매/전진',
|
||||
reviewer: '생산',
|
||||
approver: '품질',
|
||||
},
|
||||
reportDate,
|
||||
className,
|
||||
}: QualityApprovalTableProps) {
|
||||
// 2열 타입 (수입검사 성적서 - 담당/부서장)
|
||||
if (type === '2col') {
|
||||
return (
|
||||
<div className={cn('text-right', className)}>
|
||||
<table className="text-xs border-collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-2 py-1 bg-gray-100 w-12">담당</td>
|
||||
<td className="border border-gray-400 px-2 py-1 w-16">부서장</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-2 py-1 bg-gray-100">결재</td>
|
||||
<td className="border border-gray-400 px-2 py-1 h-8">{approvers.writer || ''}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{reportDate && (
|
||||
<div className="text-xs text-right mt-1">접고일자: {reportDate}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 4열 타입 (중간검사 성적서 - 결재/작성/검토/승인 + 부서)
|
||||
return (
|
||||
<table className={cn('text-xs border-collapse', className)}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-2 py-1 bg-gray-100 w-8 text-center" rowSpan={3}>
|
||||
<div className="flex flex-col items-center">
|
||||
<span>결</span><span>재</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="border border-gray-400 px-2 py-1 bg-gray-100 w-14 text-center">작성</td>
|
||||
<td className="border border-gray-400 px-2 py-1 bg-gray-100 w-14 text-center">검토</td>
|
||||
<td className="border border-gray-400 px-2 py-1 bg-gray-100 w-14 text-center">승인</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-2 py-1 h-8 text-center font-medium">
|
||||
{approvers.writer || ''}
|
||||
</td>
|
||||
<td className="border border-gray-400 px-2 py-1 h-8 text-center">
|
||||
{approvers.reviewer || ''}
|
||||
</td>
|
||||
<td className="border border-gray-400 px-2 py-1 h-8 text-center">
|
||||
{approvers.approver || ''}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-2 py-1 text-center bg-gray-50">
|
||||
{departments.writer || '판매/전진'}
|
||||
</td>
|
||||
<td className="border border-gray-400 px-2 py-1 text-center bg-gray-50">
|
||||
{departments.reviewer || '생산'}
|
||||
</td>
|
||||
<td className="border border-gray-400 px-2 py-1 text-center bg-gray-50">
|
||||
{departments.approver || '품질'}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
106
src/components/document-system/components/SignatureSection.tsx
Normal file
106
src/components/document-system/components/SignatureSection.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 서명/도장 영역 컴포넌트
|
||||
*
|
||||
* @example
|
||||
* // 기본 사용 (도장 영역 포함)
|
||||
* <SignatureSection
|
||||
* date="2025년 01월 22일"
|
||||
* companyName="경동기업"
|
||||
* showStamp={true}
|
||||
* />
|
||||
*
|
||||
* // 커스텀 문구
|
||||
* <SignatureSection
|
||||
* label="상기와 같이 견적합니다."
|
||||
* date="2025년 01월 22일"
|
||||
* companyName="경동기업"
|
||||
* role="공급자"
|
||||
* />
|
||||
*/
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface SignatureSectionProps {
|
||||
/** 상단 안내 문구 (기본값: '상기와 같이 견적합니다.') */
|
||||
label?: string;
|
||||
/** 날짜 */
|
||||
date?: string;
|
||||
/** 회사명 */
|
||||
companyName?: string;
|
||||
/** 역할 라벨 (기본값: '공급자') */
|
||||
role?: string;
|
||||
/** 도장 영역 표시 여부 */
|
||||
showStamp?: boolean;
|
||||
/** 도장 내부 텍스트 */
|
||||
stampText?: string;
|
||||
/** 도장 이미지 URL */
|
||||
stampImageUrl?: string;
|
||||
/** 정렬 (기본값: 'right') */
|
||||
align?: 'left' | 'center' | 'right';
|
||||
/** 추가 className */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SignatureSection({
|
||||
label = '상기와 같이 견적합니다.',
|
||||
date,
|
||||
companyName,
|
||||
role = '공급자',
|
||||
showStamp = true,
|
||||
stampText = '(인감\n날인)',
|
||||
stampImageUrl,
|
||||
align = 'right',
|
||||
className,
|
||||
}: SignatureSectionProps) {
|
||||
const alignClass = {
|
||||
left: 'text-left',
|
||||
center: 'text-center',
|
||||
right: 'text-right',
|
||||
}[align];
|
||||
|
||||
return (
|
||||
<div className={cn('mt-8', alignClass, className)}>
|
||||
<div className="inline-block text-left">
|
||||
{/* 안내 문구 */}
|
||||
{label && (
|
||||
<div className="mb-4 text-sm">
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 날짜 + 회사명 + 도장 */}
|
||||
<div className="flex items-center gap-5">
|
||||
<div>
|
||||
{date && (
|
||||
<div className="text-sm mb-1">{date}</div>
|
||||
)}
|
||||
{companyName && (
|
||||
<div className="text-base font-semibold">
|
||||
{role}: {companyName} (인)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 도장 영역 */}
|
||||
{showStamp && (
|
||||
<div className="border-2 border-black w-20 h-20 relative inline-block ml-5">
|
||||
{stampImageUrl ? (
|
||||
<img
|
||||
src={stampImageUrl}
|
||||
alt="도장"
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-[10px] text-gray-400 text-center whitespace-pre-line leading-tight">
|
||||
{stampText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,9 +3,27 @@ export { ApprovalLine } from './ApprovalLine';
|
||||
export { DocumentHeader } from './DocumentHeader';
|
||||
export { SectionHeader } from './SectionHeader';
|
||||
export { InfoTable } from './InfoTable';
|
||||
export { QualityApprovalTable } from './QualityApprovalTable';
|
||||
export { ConstructionApprovalTable } from './ConstructionApprovalTable';
|
||||
export { LotApprovalTable } from './LotApprovalTable';
|
||||
export { SignatureSection } from './SignatureSection';
|
||||
|
||||
// Types
|
||||
export type { ApprovalPerson, ApprovalLineProps } from './ApprovalLine';
|
||||
export type { DocumentHeaderLogo, DocumentHeaderProps } from './DocumentHeader';
|
||||
export type { SectionHeaderProps } from './SectionHeader';
|
||||
export type { InfoTableCell, InfoTableProps } from './InfoTable';
|
||||
export type {
|
||||
QualityApprovers,
|
||||
QualityDepartments,
|
||||
QualityApprovalTableProps,
|
||||
} from './QualityApprovalTable';
|
||||
export type {
|
||||
ConstructionApprover,
|
||||
ConstructionApprovalTableProps,
|
||||
} from './ConstructionApprovalTable';
|
||||
export type {
|
||||
LotApprover,
|
||||
LotApprovalTableProps,
|
||||
} from './LotApprovalTable';
|
||||
export type { SignatureSectionProps } from './SignatureSection';
|
||||
|
||||
@@ -7,6 +7,10 @@ export {
|
||||
DocumentHeader,
|
||||
SectionHeader,
|
||||
InfoTable,
|
||||
QualityApprovalTable,
|
||||
ConstructionApprovalTable,
|
||||
LotApprovalTable,
|
||||
SignatureSection,
|
||||
} from './components';
|
||||
|
||||
// Hooks
|
||||
@@ -25,6 +29,14 @@ export type {
|
||||
SectionHeaderProps,
|
||||
InfoTableCell,
|
||||
InfoTableProps,
|
||||
QualityApprovers,
|
||||
QualityDepartments,
|
||||
QualityApprovalTableProps,
|
||||
ConstructionApprover,
|
||||
ConstructionApprovalTableProps,
|
||||
LotApprover,
|
||||
LotApprovalTableProps,
|
||||
SignatureSectionProps,
|
||||
} from './components';
|
||||
|
||||
export type {
|
||||
|
||||
Reference in New Issue
Block a user