- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션 - 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts) - ApiErrorContext 추가로 전역 에러 처리 개선 - HR EmployeeForm 컴포넌트 개선 - 참조함(ReferenceBox) 기능 수정 - juil 테스트 URL 페이지 추가 - claudedocs 문서 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
153 lines
5.0 KiB
TypeScript
153 lines
5.0 KiB
TypeScript
|
|
import { promises as fs } from 'fs';
|
|
import path from 'path';
|
|
import JuilTestUrlsClient, { UrlCategory, UrlItem } from './JuilTestUrlsClient';
|
|
|
|
// 아이콘 매핑
|
|
const iconMap: Record<string, string> = {
|
|
'기본': '🏠',
|
|
'시스템': '💻',
|
|
'대시보드': '📊',
|
|
};
|
|
|
|
function getIcon(title: string): string {
|
|
for (const [key, icon] of Object.entries(iconMap)) {
|
|
if (title.includes(key)) return icon;
|
|
}
|
|
return '📄';
|
|
}
|
|
|
|
function parseTableRow(line: string): UrlItem | null {
|
|
// | 페이지 | URL | 상태 | 형식 파싱
|
|
const parts = line.split('|').map(p => p.trim()).filter(p => p);
|
|
|
|
if (parts.length < 2) return null;
|
|
if (parts[0] === '페이지' || parts[0].startsWith('---')) return null;
|
|
|
|
const name = parts[0].replace(/\*\*/g, ''); // **bold** 제거
|
|
const url = parts[1].replace(/`/g, ''); // backtick 제거
|
|
const status = parts[2] || undefined;
|
|
|
|
// URL이 /ko로 시작하는지 확인
|
|
if (!url.startsWith('/ko')) return null;
|
|
|
|
return { name, url, status };
|
|
}
|
|
|
|
function parseMdFile(content: string): { categories: UrlCategory[]; lastUpdated: string } {
|
|
const lines = content.split('\n');
|
|
const categories: UrlCategory[] = [];
|
|
let currentCategory: UrlCategory | null = null;
|
|
let currentSubCategory: { title: string; items: UrlItem[] } | null = null;
|
|
let lastUpdated = 'N/A';
|
|
|
|
// Last Updated 추출
|
|
const updateMatch = content.match(/Last Updated:\s*(\d{4}-\d{2}-\d{2})/);
|
|
if (updateMatch) {
|
|
lastUpdated = updateMatch[1];
|
|
}
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i].trim();
|
|
|
|
// ## 카테고리 (메인 섹션)
|
|
if (line.startsWith('## ') && !line.includes('클릭 가능한') && !line.includes('전체 URL') && !line.includes('백엔드 메뉴')) {
|
|
// 이전 카테고리 저장
|
|
if (currentCategory) {
|
|
if (currentSubCategory) {
|
|
currentCategory.subCategories = currentCategory.subCategories || [];
|
|
currentCategory.subCategories.push(currentSubCategory);
|
|
currentSubCategory = null;
|
|
}
|
|
categories.push(currentCategory);
|
|
}
|
|
|
|
const title = line.replace('## ', '').replace(/[🏠👥💰📦🏭⚙️📝📋💵]/g, '').trim();
|
|
currentCategory = {
|
|
title,
|
|
icon: getIcon(title),
|
|
items: [],
|
|
subCategories: [],
|
|
};
|
|
currentSubCategory = null;
|
|
}
|
|
|
|
// ### 서브 카테고리
|
|
else if (line.startsWith('### ') && currentCategory) {
|
|
// 이전 서브카테고리 저장
|
|
if (currentSubCategory) {
|
|
currentCategory.subCategories = currentCategory.subCategories || [];
|
|
currentCategory.subCategories.push(currentSubCategory);
|
|
}
|
|
|
|
const subTitle = line.replace('### ', '').trim();
|
|
// "메인 페이지"는 서브카테고리가 아니라 메인 아이템으로
|
|
if (subTitle === '메인 페이지') {
|
|
currentSubCategory = null;
|
|
} else {
|
|
currentSubCategory = {
|
|
title: subTitle,
|
|
items: [],
|
|
};
|
|
}
|
|
}
|
|
|
|
// 테이블 행 파싱
|
|
else if (line.startsWith('|') && currentCategory) {
|
|
const item = parseTableRow(line);
|
|
if (item) {
|
|
if (currentSubCategory) {
|
|
currentSubCategory.items.push(item);
|
|
} else {
|
|
currentCategory.items.push(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 마지막 카테고리 저장
|
|
if (currentCategory) {
|
|
if (currentSubCategory) {
|
|
currentCategory.subCategories = currentCategory.subCategories || [];
|
|
currentCategory.subCategories.push(currentSubCategory);
|
|
}
|
|
categories.push(currentCategory);
|
|
}
|
|
|
|
// 빈 서브카테고리 제거
|
|
categories.forEach(cat => {
|
|
cat.subCategories = cat.subCategories?.filter(sub => sub.items.length > 0);
|
|
});
|
|
|
|
return { categories, lastUpdated };
|
|
}
|
|
|
|
export default async function TestUrlsPage() {
|
|
// md 파일 경로
|
|
const mdFilePath = path.join(
|
|
process.cwd(),
|
|
'claudedocs',
|
|
'[REF] juil-pages-test-urls.md'
|
|
);
|
|
|
|
let urlData: UrlCategory[] = [];
|
|
let lastUpdated = 'N/A';
|
|
|
|
try {
|
|
const fileContent = await fs.readFile(mdFilePath, 'utf-8');
|
|
const parsed = parseMdFile(fileContent);
|
|
urlData = parsed.categories;
|
|
lastUpdated = parsed.lastUpdated;
|
|
} catch (error) {
|
|
console.error('Failed to read md file:', error);
|
|
// 파일 읽기 실패 시 빈 데이터
|
|
urlData = [];
|
|
}
|
|
|
|
return <JuilTestUrlsClient initialData={urlData} lastUpdated={lastUpdated} />;
|
|
}
|
|
|
|
// 캐싱 비활성화 - 항상 최신 md 파일 읽기
|
|
export const dynamic = 'force-dynamic';
|
|
export const revalidate = 0;
|