Files
sam-react-prod/src/app/[locale]/(protected)/dev/juil-test-urls/page.tsx
byeongcheolryu d38b1242d7 feat: fetchWrapper 마이그레이션 및 토큰 리프레시 캐싱 구현
- 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>
2025-12-30 17:00:18 +09:00

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;