feat: 리포트 타입 에러 수정

This commit is contained in:
shin
2026-03-12 18:47:42 +09:00
parent a3aae8aeda
commit 930727a5cb
140 changed files with 27027 additions and 9323 deletions
+441 -121
View File
@@ -1,8 +1,3 @@
/**
* 리포트 관리 시스템 타입 정의
*/
// 리포트 템플릿
export interface ReportTemplate {
template_id: string;
template_name_kor: string;
@@ -21,12 +16,12 @@ export interface ReportTemplate {
updated_by: string | null;
}
// 리포트 마스터
export interface ReportMaster {
report_id: string;
report_name_kor: string;
report_name_eng: string | null;
template_id: string | null;
template_name: string | null;
report_type: string;
company_code: string | null;
description: string | null;
@@ -37,7 +32,6 @@ export interface ReportMaster {
updated_by: string | null;
}
// 리포트 레이아웃
export interface ReportLayout {
layout_id: string;
report_id: string;
@@ -49,14 +43,13 @@ export interface ReportLayout {
margin_left: number;
margin_right: number;
components: ComponentConfig[];
pages?: ReportPage[]; // 새 페이지 구조 (옵셔널, 하위 호환성)
pages?: ReportPage[];
created_at: string;
created_by: string | null;
updated_at: string | null;
updated_by: string | null;
}
// 리포트 쿼리
export interface ReportQuery {
query_id: string;
report_id: string;
@@ -64,7 +57,7 @@ export interface ReportQuery {
query_type: "MASTER" | "DETAIL";
sql_query: string;
parameters: string[];
external_connection_id: number | null; // 외부 DB 연결 ID
external_connection_id: number | null;
display_order: number;
created_at: string;
created_by: string | null;
@@ -72,7 +65,6 @@ export interface ReportQuery {
updated_by: string | null;
}
// 외부 DB 연결 (간단한 버전)
export interface ExternalConnection {
id: number;
connection_name: string;
@@ -81,29 +73,24 @@ export interface ExternalConnection {
is_active: string;
}
// 워터마크 설정
export interface WatermarkConfig {
enabled: boolean;
type: "text" | "image";
// 텍스트 워터마크
text?: string;
fontSize?: number;
fontColor?: string;
// 이미지 워터마크
imageUrl?: string;
// 공통 설정
opacity: number; // 0~1
opacity: number;
style: "diagonal" | "center" | "tile";
rotation?: number; // 대각선일 때 각도 (기본 -45)
rotation?: number;
}
// 페이지 설정
export interface ReportPage {
page_id: string;
page_name: string;
page_order: number;
width: number; // mm
height: number; // mm
width: number;
height: number;
orientation: "portrait" | "landscape";
margins: {
top: number;
@@ -115,16 +102,14 @@ export interface ReportPage {
components: ComponentConfig[];
}
// 레이아웃 설정 (페이지 기반)
export interface ReportLayoutConfig {
pages: ReportPage[];
watermark?: WatermarkConfig; // 전체 페이지 공유 워터마크
watermark?: WatermarkConfig;
}
// 컴포넌트 설정
export interface ComponentConfig {
id: string;
type: string; // "text", "label", "table", "image", "divider", "signature", "stamp"
type: string;
x: number;
y: number;
width: number;
@@ -139,7 +124,15 @@ export interface ComponentConfig {
borderColor?: string;
borderRadius?: number;
textAlign?: string;
verticalAlign?: "top" | "middle" | "bottom";
padding?: number;
lineHeight?: number;
letterSpacing?: number;
textDecoration?: "none" | "underline" | "line-through";
fontStyle?: "normal" | "italic";
textTransform?: "none" | "uppercase" | "lowercase";
textOverflow?: "clip" | "ellipsis" | "wrap";
textPreset?: string;
queryId?: string;
fieldName?: string;
defaultValue?: string;
@@ -147,118 +140,174 @@ export interface ComponentConfig {
visible?: boolean;
printable?: boolean;
conditional?: string;
locked?: boolean; // 잠금 여부 (편집/이동/삭제 방지)
groupId?: string; // 그룹 ID (같은 그룹 ID를 가진 컴포넌트는 함께 움직임)
// 이미지 전용
imageUrl?: string; // 이미지 URL 또는 업로드된 파일 경로
imageFile?: File; // 업로드된 이미지 파일 (클라이언트 측에서만 사용)
objectFit?: "contain" | "cover" | "fill" | "none"; // 이미지 맞춤 방식
// 구분선 전용
orientation?: "horizontal" | "vertical"; // 구분선 방향
lineStyle?: "solid" | "dashed" | "dotted" | "double"; // 선 스타일
lineWidth?: number; // 구분선 두께 (borderWidth와 별도)
lineColor?: string; // 구분선 색상 (borderColor와 별도)
// 서명/도장 전용
showLabel?: boolean; // 레이블 표시 여부 ("서명:", "(인)")
labelText?: string; // 커스텀 레이블 텍스트
labelPosition?: "top" | "left" | "bottom" | "right"; // 레이블 위치
personName?: string; // 도장란 이름 (예: "홍길동")
// 테이블 전용
conditionalRule?: ConditionalRule;
conditionalRules?: ConditionalRule[];
gridConditionalRule?: ConditionalRule;
gridConditionalRules?: ConditionalRule[];
locked?: boolean;
groupId?: string;
imageUrl?: string;
imageFile?: File;
objectFit?: "contain" | "cover" | "fill" | "none";
imageOpacity?: number;
imageBorderRadius?: number;
imageCaption?: string;
imageCaptionPosition?: "top" | "bottom";
imageCaptionFontSize?: number;
imageCaptionColor?: string;
imageCaptionAlign?: "left" | "center" | "right";
imageAlt?: string;
imageRotation?: number;
imageFlipH?: boolean;
imageFlipV?: boolean;
imageCropX?: number;
imageCropY?: number;
imageCropWidth?: number;
imageCropHeight?: number;
orientation?: "horizontal" | "vertical";
lineStyle?: "solid" | "dashed" | "dotted" | "double";
lineWidth?: number;
lineColor?: string;
showLabel?: boolean;
labelText?: string;
labelPosition?: "top" | "left" | "bottom" | "right";
personName?: string;
tableColumns?: Array<{
field: string; // 필드명
header: string; // 헤더 표시명
width?: number; // 컬럼 너비 (px)
align?: "left" | "center" | "right"; // 정렬
field: string;
header: string;
width?: number;
align?: "left" | "center" | "right";
mappingType?: "field" | "formula";
formula?: string;
summaryType?: "SUM" | "AVG" | "COUNT" | "NONE";
/** 컬럼 표시 여부 — false일 때 렌더링에서 제외 (기본: true) */
visible?: boolean;
/** 숫자 포맷 — "none" | "comma" | "currency" (기본: "none") */
numberFormat?: "none" | "comma" | "currency";
/** 통화 단위 (numberFormat="currency"일 때, 기본: "원") */
currencySuffix?: string;
}>;
headerBackgroundColor?: string; // 헤더 배경색
headerTextColor?: string; // 헤더 텍스트 색상
showBorder?: boolean; // 테두리 표시
rowHeight?: number; // 행 높이 (px)
// 페이지 번호 전용
pageNumberFormat?: "number" | "numberTotal" | "koreanNumber"; // 페이지 번호 포맷
// 카드 컴포넌트 전용
cardTitle?: string; // 카드 제목
showFooter?: boolean;
exportSummaryId?: string;
headerBackgroundColor?: string;
headerTextColor?: string;
showBorder?: boolean;
rowHeight?: number;
pageNumberFormat?: "number" | "numberTotal" | "koreanNumber";
cardTitle?: string;
cardItems?: Array<{
label: string; // 항목 라벨 (예: "회사명")
value: string; // 항목 값 (예: "당사 주식회사") 또는 기본값
fieldName?: string; // 쿼리 필드명 (바인딩용)
label: string;
value: string;
fieldName?: string;
}>;
labelWidth?: number; // 라벨 컬럼 너비 (px)
showCardBorder?: boolean; // 카드 테두리 표시 여부
showCardTitle?: boolean; // 카드 제목 표시 여부
titleFontSize?: number; // 제목 폰트 크기
labelFontSize?: number; // 라벨 폰트 크기
valueFontSize?: number; // 값 폰트 크기
titleColor?: string; // 제목 색상
labelColor?: string; // 라벨 색상
valueColor?: string; // 값 색상
// 계산 컴포넌트 전용
labelWidth?: number;
showCardBorder?: boolean;
showCardTitle?: boolean;
titleFontSize?: number;
labelFontSize?: number;
valueFontSize?: number;
titleColor?: string;
labelColor?: string;
valueColor?: string;
calcItems?: Array<{
label: string; // 항목 라벨 (예: "공급가액")
value: number | string; // 항목 값 또는 기본값
operator: "+" | "-" | "x" | "÷"; // 연산자
fieldName?: string; // 쿼리 필드명 (바인딩용)
label: string;
value: number | string;
operator: "+" | "-" | "x" | "÷";
fieldName?: string;
}>;
resultLabel?: string; // 결과 라벨 (예: "합계 금액")
resultColor?: string; // 결과 색상
resultFontSize?: number; // 결과 폰트 크기
showCalcBorder?: boolean; // 테두리 표시 여부
numberFormat?: "none" | "comma" | "currency"; // 숫자 포맷 (없음, 천단위, 원화)
currencySuffix?: string; // 통화 접미사 (예: "원")
// 바코드 컴포넌트 전용
barcodeType?: "CODE128" | "CODE39" | "EAN13" | "EAN8" | "UPC" | "QR"; // 바코드 타입
barcodeValue?: string; // 고정값
barcodeFieldName?: string; // 쿼리 필드 바인딩
showBarcodeText?: boolean; // 바코드 아래 텍스트 표시 (1D만)
barcodeColor?: string; // 바코드 색상
barcodeBackground?: string; // 배경 색상
barcodeMargin?: number; // 여백
qrErrorCorrectionLevel?: "L" | "M" | "Q" | "H"; // QR 오류 보정 수준
// QR코드 다중 필드 (JSON 형식)
resultLabel?: string;
resultColor?: string;
resultFontSize?: number;
showCalcBorder?: boolean;
numberFormat?: "none" | "comma" | "currency";
currencySuffix?: string;
barcodeType?: "CODE128" | "CODE39" | "EAN13" | "EAN8" | "UPC" | "QR";
barcodeValue?: string;
barcodeFieldName?: string;
showBarcodeText?: boolean;
barcodeColor?: string;
barcodeBackground?: string;
barcodeMargin?: number;
qrErrorCorrectionLevel?: "L" | "M" | "Q" | "H";
qrDataFields?: Array<{
fieldName: string; // 쿼리 필드명
label: string; // JSON 키 이름
fieldName: string;
label: string;
}>;
qrUseMultiField?: boolean; // 다중 필드 사용 여부
qrIncludeAllRows?: boolean; // 모든 행 포함 (배열 JSON)
// 체크박스 컴포넌트 전용
checkboxChecked?: boolean; // 체크 상태 (고정값)
checkboxFieldName?: string; // 쿼리 필드 바인딩 (truthy/falsy 값)
checkboxLabel?: string; // 체크박스 옆 레이블 텍스트
checkboxSize?: number; // 체크박스 크기 (px)
checkboxColor?: string; // 체크 색상
checkboxBorderColor?: string; // 테두리 색상
checkboxLabelPosition?: "left" | "right"; // 레이블 위치
qrUseMultiField?: boolean;
qrIncludeAllRows?: boolean;
checkboxChecked?: boolean;
checkboxFieldName?: string;
checkboxLabel?: string;
checkboxSize?: number;
checkboxColor?: string;
checkboxBorderColor?: string;
checkboxLabelPosition?: "left" | "right";
chartQueryId?: string;
chartLabelField?: string;
chartValueField?: string;
chartColors?: string[];
chartInnerRadius?: number;
chartShowLegend?: boolean;
chartTitle?: string;
/** 테이블 컴포넌트의 데이터 소스 테이블/뷰명 */
dataTableName?: string;
/** 테이블 모드에서 미리보기 행 수 */
tableRowCount?: number;
content?: string;
text?: string;
conditionalStyles?: Array<{ value: string; backgroundColor?: string; fontColor?: string }>;
// 비주얼 쿼리 빌더 (table 컴포넌트 전용)
visualQuery?: VisualQuery;
visualDataSource?: VisualDataSource;
// 카드 레이아웃 설정 (card 컴포넌트 전용 - v3)
cardLayoutConfig?: CardLayoutConfig;
// 그리드 양식 모드 (table 컴포넌트 전용)
gridMode?: boolean;
gridCells?: GridCell[];
gridRowCount?: number;
gridColCount?: number;
gridColWidths?: number[];
gridRowHeights?: number[];
gridHeaderRows?: number;
gridHeaderCols?: number;
/** 격자 모드 하단 요약(푸터) 행 수 */
gridFooterRows?: number;
/** 격자 모드 푸터 표시 여부 */
gridShowFooter?: boolean;
}
// 리포트 상세
export interface ReportDetail {
report: ReportMaster;
layout: ReportLayout | null;
queries: ReportQuery[];
menuObjids?: number[]; // 연결된 메뉴 ID 목록
menuObjids?: number[];
}
// 리포트 목록 응답
export interface GetReportsResponse {
items: ReportMaster[];
total: number;
page: number;
limit: number;
typeSummary: Array<{ type: string; count: number }>;
allTypes: string[];
recentActivity: Array<{ date: string; count: number }>;
recentTotal: number;
}
// 리포트 목록 조회 파라미터
export interface GetReportsParams {
page?: number;
limit?: number;
searchText?: string;
searchField?: "report_name" | "created_by" | "report_type" | "updated_at" | "created_at";
startDate?: string;
endDate?: string;
reportType?: string;
useYn?: string;
sortBy?: string;
sortOrder?: "ASC" | "DESC";
}
// 리포트 생성 요청
export interface CreateReportRequest {
reportNameKor: string;
reportNameEng?: string;
@@ -268,7 +317,6 @@ export interface CreateReportRequest {
companyCode?: string;
}
// 리포트 수정 요청
export interface UpdateReportRequest {
reportNameKor?: string;
reportNameEng?: string;
@@ -277,9 +325,8 @@ export interface UpdateReportRequest {
useYn?: string;
}
// 레이아웃 저장 요청
export interface SaveLayoutRequest {
layoutConfig: ReportLayoutConfig; // 페이지 기반 구조
layoutConfig: ReportLayoutConfig;
queries?: Array<{
id: string;
name: string;
@@ -288,31 +335,304 @@ export interface SaveLayoutRequest {
parameters: string[];
externalConnectionId?: number;
}>;
menuObjids?: number[]; // 연결할 메뉴 ID 목록
// 하위 호환성 (deprecated)
canvasWidth?: number;
canvasHeight?: number;
pageOrientation?: string;
marginTop?: number;
marginBottom?: number;
marginLeft?: number;
marginRight?: number;
components?: ComponentConfig[];
menuObjids?: number[];
}
// 템플릿 목록 응답
export interface GetTemplatesResponse {
system: ReportTemplate[];
custom: ReportTemplate[];
}
// 템플릿 생성 요청
export interface CreateTemplateRequest {
templateNameKor: string;
templateNameEng?: string;
templateType: string;
description?: string;
layoutConfig?: any;
defaultQueries?: any;
layoutConfig?: Record<string, unknown>;
defaultQueries?: Array<Record<string, unknown>>;
}
export interface GetReportsByMenuResponse {
items: ReportMaster[];
total: number;
}
export interface ConditionalRule {
queryId: string;
field: string;
operator: "eq" | "ne" | "gt" | "lt" | "gte" | "lte" | "contains" | "notEmpty" | "empty";
value: string;
action: "show" | "hide";
}
export interface VisualQueryFormulaColumn {
alias: string;
header: string;
expression: string;
}
export interface VisualQuery {
tableName: string;
limit?: number;
columns: string[];
formulaColumns: VisualQueryFormulaColumn[];
}
// ─────────────────────────────────────────────────────────────────────────────
// 그리드 양식 셀 타입 정의 (복잡한 테이블 — 셀 병합, 고정텍스트/데이터 혼합)
// ─────────────────────────────────────────────────────────────────────────────
export interface GridCell {
id: string;
row: number;
col: number;
rowSpan?: number;
colSpan?: number;
cellType: "static" | "field" | "formula";
value?: string;
field?: string;
formula?: string;
align?: "left" | "center" | "right";
verticalAlign?: "top" | "middle" | "bottom";
fontWeight?: "normal" | "bold";
fontSize?: number;
backgroundColor?: string;
textColor?: string;
borderStyle?: "thin" | "medium" | "thick" | "none";
numberFormat?: "none" | "comma" | "currency";
currencySuffix?: string;
merged?: boolean;
mergedBy?: string;
/** 셀 요약(집계) 유형 — 격자 모드에서 푸터 행 셀에 적용 */
summaryType?: "SUM" | "AVG" | "COUNT" | "NONE";
}
// ─────────────────────────────────────────────────────────────────────────────
// 카드 레이아웃 v3 타입 정의
// ─────────────────────────────────────────────────────────────────────────────
/** 카드 내부 요소 타입 */
export type CardElementType =
| "header" | "dataCell" | "divider" | "badge"
| "image" | "number" | "date" | "link" | "status" | "spacer" | "staticText";
/** 데이터 셀 방향 */
export type CellDirection = "vertical" | "horizontal";
/** 카드 요소 공통 인터페이스 */
export interface CardElementBase {
id: string;
type: CardElementType;
colspan?: number;
rowspan?: number;
direction?: CellDirection;
}
/** 헤더 요소 */
export interface CardHeaderElement extends CardElementBase {
type: "header";
icon?: string;
iconColor?: string;
title: string;
titleColor?: string;
titleFontSize?: number;
fontSize?: number;
color?: string;
fontWeight?: string;
}
/** 데이터 셀 요소 */
export interface CardDataCellElement extends CardElementBase {
type: "dataCell";
direction: CellDirection;
label: string;
columnName?: string;
inputType?: "text" | "date" | "number" | "select" | "readonly";
required?: boolean;
placeholder?: string;
selectOptions?: string[];
labelWidth?: number;
labelFontSize?: number;
labelColor?: string;
valueFontSize?: number;
valueColor?: string;
labelFontWeight?: string;
}
/** 구분선 요소 */
export interface CardDividerElement extends CardElementBase {
type: "divider";
style?: "solid" | "dashed" | "dotted";
color?: string;
thickness?: number;
}
/** 뱃지 요소 */
export interface CardBadgeElement extends CardElementBase {
type: "badge";
label?: string;
columnName?: string;
colorMap?: Record<string, string>;
bgColor?: string;
textColor?: string;
}
/** 이미지 요소 */
export interface CardImageElement extends CardElementBase {
type: "image";
columnName?: string;
altText?: string;
objectFit?: "contain" | "cover" | "fill";
height?: number;
}
/** 숫자/금액 요소 */
export interface CardNumberElement extends CardElementBase {
type: "number";
label: string;
columnName?: string;
numberFormat?: "none" | "comma" | "currency";
currencySuffix?: string;
labelFontSize?: number;
labelColor?: string;
valueFontSize?: number;
valueColor?: string;
}
/** 날짜 요소 */
export interface CardDateElement extends CardElementBase {
type: "date";
label: string;
columnName?: string;
dateFormat?: string;
labelFontSize?: number;
labelColor?: string;
valueFontSize?: number;
valueColor?: string;
}
/** 링크 요소 */
export interface CardLinkElement extends CardElementBase {
type: "link";
label: string;
columnName?: string;
linkText?: string;
openInNewTab?: boolean;
}
/** 상태 요소 */
export interface CardStatusElement extends CardElementBase {
type: "status";
columnName?: string;
statusMappings?: Array<{ value: string; label: string; color: string }>;
}
/** 빈 공간 요소 */
export interface CardSpacerElement extends CardElementBase {
type: "spacer";
height?: number;
}
/** 고정 텍스트 요소 */
export interface CardStaticTextElement extends CardElementBase {
type: "staticText";
text: string;
fontSize?: number;
color?: string;
fontWeight?: "normal" | "bold";
textAlign?: "left" | "center" | "right";
}
/** 통합 요소 타입 */
export type CardElement =
| CardHeaderElement
| CardDataCellElement
| CardDividerElement
| CardBadgeElement
| CardImageElement
| CardNumberElement
| CardDateElement
| CardLinkElement
| CardStatusElement
| CardSpacerElement
| CardStaticTextElement;
/** 카드 행 정의 */
export interface CardLayoutRow {
id: string;
gridColumns: number;
elements: CardElement[];
height?: string;
gap?: string;
marginBottom?: string;
}
/** 카드 레이아웃 전체 설정 */
export interface CardLayoutConfig {
tableName?: string;
primaryKey?: string;
rows: CardLayoutRow[];
padding?: string;
gap?: string;
borderStyle?: string;
borderColor?: string;
backgroundColor?: string;
headerTitleFontSize?: number;
headerTitleColor?: string;
labelFontSize?: number;
labelColor?: string;
valueFontSize?: number;
valueColor?: string;
dividerThickness?: number;
dividerColor?: string;
accentBorderColor?: string;
accentBorderWidth?: number;
borderRadius?: string;
borderWidth?: number;
headerFontWeight?: "normal" | "bold";
valueFontWeight?: "normal" | "bold";
}
// ─────────────────────────────────────────────────────────────────────────────
// 그리드 설정
// ─────────────────────────────────────────────────────────────────────────────
export interface GridConfig {
visible: boolean;
snapToGrid: boolean;
cellWidth: number;
cellHeight: number;
columns: number;
rows: number;
gridColor: string;
gridOpacity: number;
}
// ─────────────────────────────────────────────────────────────────────────────
// 비주얼 데이터 소스 (마스터-디테일 구조)
// ─────────────────────────────────────────────────────────────────────────────
export interface VisualColumn {
name: string;
label?: string;
dataType?: string;
selected?: boolean;
}
export interface JoinKeyPair {
masterColumn: string;
detailColumn: string;
autoDetected?: boolean;
}
export interface VisualDetailSource {
id: string;
tableName: string;
columns: VisualColumn[];
joinKeys: JoinKeyPair[];
}
export interface VisualDataSource {
master: { tableName: string; columns: VisualColumn[] };
details: VisualDetailSource[];
}