/**
* 결재 템플릿 유틸리티 함수
*
* 기능:
* - 템플릿 이름으로 조회
* - 변수 치환 ({{변수명}})
* - HTML 변환 유틸리티 (테이블, 리스트)
*/
'use server';
import db from '@/db/db';
import { eq } from 'drizzle-orm';
import { approvalTemplates } from '@/db/schema/knox/approvals';
/**
* 템플릿 이름으로 조회
*
* @param name - 템플릿 이름 (한국어)
* @returns 템플릿 객체 또는 null
*/
export async function getApprovalTemplateByName(name: string) {
try {
const [template] = await db
.select()
.from(approvalTemplates)
.where(eq(approvalTemplates.name, name))
.limit(1);
return template || null;
} catch (error) {
console.error(`[Template Utils] Failed to get template: ${name}`, error);
return null;
}
}
/**
* 템플릿 변수 치환
*
* {{변수명}} 형태의 변수를 실제 값으로 치환
*
* **중요**: 변수명의 앞뒤 공백은 자동으로 제거됩니다.
* - 템플릿: `{{ 변수명 }}` → `{{변수명}}`으로 정규화
* - 변수 키: ` 변수명 ` → `변수명`으로 정규화
*
* @param content - 템플릿 HTML 내용
* @param variables - 변수 매핑 객체
* @returns 치환된 HTML
*
* @example
* ```typescript
* const content = "
{{ 이름 }}님, 안녕하세요
";
* const variables = { " 이름 ": "홍길동" }; // 공백 있어도 OK
* const result = await replaceTemplateVariables(content, variables);
* // "홍길동님, 안녕하세요
"
* ```
*/
export async function replaceTemplateVariables(
content: string,
variables: Record
): Promise {
let result = content;
// 변수 키를 trim하여 정규화된 맵 생성
const normalizedVariables: Record = {};
Object.entries(variables).forEach(([key, value]) => {
normalizedVariables[key.trim()] = value;
});
// 템플릿에서 {{ 변수명 }} 패턴을 찾아 치환
// 공백을 허용하는 정규식: {{\s*변수명\s*}}
Object.entries(normalizedVariables).forEach(([key, value]) => {
// 변수명 앞뒤에 공백이 있을 수 있으므로 \s*를 추가
const pattern = new RegExp(
`\\{\\{\\s*${escapeRegex(key)}\\s*\\}\\}`,
'g'
);
result = result.replace(pattern, value);
});
// 치환되지 않은 변수 로그 (디버깅용)
const remainingVariables = result.match(/\{\{[^}]+\}\}/g);
if (remainingVariables && remainingVariables.length > 0) {
console.warn(
'[Template Utils] Unmatched variables found:',
remainingVariables
);
}
return result;
}
/**
* 정규식 특수문자 이스케이프
*/
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* 배열 데이터를 HTML 테이블로 변환 (공통 유틸)
*
* @param data - 테이블 데이터 배열
* @param columns - 컬럼 정의 (키, 라벨)
* @returns HTML 테이블 문자열
*
* @example
* ```typescript
* const data = [
* { name: "홍길동", age: 30 },
* { name: "김철수", age: 25 }
* ];
* const columns = [
* { key: "name", label: "이름" },
* { key: "age", label: "나이" }
* ];
* const html = await htmlTableConverter(data, columns);
* ```
*/
export async function htmlTableConverter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: Array>,
columns: Array<{ key: string; label: string }>
): Promise {
if (!data || data.length === 0) {
return '데이터가 없습니다.
';
}
const headerRow = columns
.map(
(col) =>
`${col.label} | `
)
.join('');
const bodyRows = data
.map((row) => {
const cells = columns
.map((col) => {
const value = row[col.key];
const displayValue =
value !== undefined && value !== null ? String(value) : '-';
return `${displayValue} | `;
})
.join('');
return `${cells}
`;
})
.join('');
return `
`;
}
/**
* 배열을 HTML 리스트로 변환
*
* @param items - 리스트 아이템 배열
* @param ordered - 순서 있는 리스트 여부 (기본: false)
* @returns HTML 리스트 문자열
*
* @example
* ```typescript
* const items = ["첫 번째", "두 번째", "세 번째"];
* const html = await htmlListConverter(items, true);
* ```
*/
export async function htmlListConverter(
items: string[],
ordered: boolean = false
): Promise {
if (!items || items.length === 0) {
return '항목이 없습니다.
';
}
const listItems = items
.map((item) => `${item}`)
.join('');
const tag = ordered ? 'ol' : 'ul';
const listStyle = ordered
? 'list-style-type: decimal; list-style-position: inside; margin: 16px 0; padding-left: 20px;'
: 'list-style-type: disc; list-style-position: inside; margin: 16px 0; padding-left: 20px;';
return `<${tag} style="${listStyle}">${listItems}${tag}>`;
}
/**
* 키-값 쌍을 HTML 정의 목록으로 변환
*
* @param items - 키-값 쌍 배열
* @returns HTML dl 태그
*
* @example
* ```typescript
* const items = [
* { label: "협력업체명", value: "ABC 주식회사" },
* { label: "사업자등록번호", value: "123-45-67890" }
* ];
* const html = await htmlDescriptionList(items);
* ```
*/
export async function htmlDescriptionList(
items: Array<{ label: string; value: string }>
): Promise {
if (!items || items.length === 0) {
return '정보가 없습니다.
';
}
const listItems = items
.map(
(item) => `
${item.label}
${item.value}
`
)
.join('');
return `${listItems}
`;
}