EENE Dashboard upload to Gitea

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EENE Dashboard
2026-06-17 16:59:34 +09:00
parent cf72281c6d
commit b3f2da203b
138 changed files with 13013 additions and 1929 deletions

View File

@@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import type { Priority, TaskStatus } from '@prisma/client';
import { getHrSeedPath, getUploadDir } from '../src/lib/projectPaths';
export interface HrProject {
idx?: number;
@@ -26,6 +27,21 @@ export interface HrProject {
subPhases?: { name: string; status?: string; text?: string }[];
timelineItems?: { startDate?: string; endDate?: string; desc?: string }[];
showOnDashboard?: boolean;
owners?: string[];
}
export interface HrTeamEntry {
name: string;
photo?: string;
}
export interface ParsedHrTeamMember {
name: string;
rank: string | null;
role: string | null;
cell: string | null;
photoUrl: string | null;
sortOrder: number;
}
export interface MappedTask {
@@ -59,6 +75,8 @@ const SECTION_MAP: Record<string, string> = {
: '인사관리',
: '학습성장',
: '운영관리',
: '운영관리',
: '운영관리',
};
const STATUS_MAP: Record<string, TaskStatus> = {
@@ -75,7 +93,7 @@ const PHASE_PROGRESS: Record<string, number> = {
};
export function defaultHrDataPath(): string {
return path.resolve(__dirname, '../../../HR_Dashboard/data.json');
return getHrSeedPath();
}
export function loadHrProjects(filePath = defaultHrDataPath()): HrProject[] {
@@ -84,6 +102,72 @@ export function loadHrProjects(filePath = defaultHrDataPath()): HrProject[] {
return (data.PROJECTS ?? []).filter((p) => p.showOnDashboard !== false);
}
export function loadHrTeam(filePath = defaultHrDataPath()): HrTeamEntry[] {
const raw = fs.readFileSync(filePath, 'utf-8');
const data = JSON.parse(raw) as { TEAM?: HrTeamEntry[] };
return data.TEAM ?? [];
}
/** "조태희 수석(팀장)" → name / rank / role */
export function parseHrTeamLabel(label: string, sortOrder: number): ParsedHrTeamMember {
const s = label.trim();
const withRole = s.match(/^(.+?)\s+(.+?)\((.+)\)$/);
if (withRole) {
const role = withRole[3].trim();
return {
name: withRole[1].trim(),
rank: withRole[2].trim(),
role,
cell: role === '팀장' ? null : 'HR',
photoUrl: null,
sortOrder,
};
}
const plain = s.match(/^(.+?)\s+(.+)$/);
if (plain) {
return {
name: plain[1].trim(),
rank: plain[2].trim(),
role: null,
cell: 'HR',
photoUrl: null,
sortOrder,
};
}
return { name: s, rank: null, role: null, cell: 'HR', photoUrl: null, sortOrder };
}
export function mapHrTeamMembers(filePath = defaultHrDataPath()): ParsedHrTeamMember[] {
return loadHrTeam(filePath).map((entry, i) => {
const parsed = parseHrTeamLabel(entry.name, i);
const photo = resolveTeamPhotoPath(entry.photo?.trim() || null);
return {
...parsed,
photoUrl: photo,
};
});
}
/** seed용 — 파일이 프로젝트 uploads/ 에 실제 있을 때만 경로 사용 */
export function resolveTeamPhotoPath(photo: string | null): string | null {
if (!photo?.trim()) return null;
const trimmed = photo.trim();
if (/^https?:\/\//i.test(trimmed) || trimmed.startsWith('data:')) return null;
const uploadDir = getUploadDir();
const relative = trimmed.replace(/^\//, '').replace(/^uploads\//, '');
const abs = path.join(uploadDir, relative);
if (fs.existsSync(abs)) {
return trimmed.startsWith('/') ? trimmed : `/uploads/${relative}`;
}
return null;
}
/** PM·담당자 문자열 → team_members.name 매칭용 */
export function normalizePersonName(value: string): string {
return value.trim().replace(/\s+/g, '');
}
function parseDate(value?: string): Date | null {
if (!value?.trim()) return null;
const d = new Date(value);
@@ -94,6 +178,14 @@ function mapSection(category: string): string {
return SECTION_MAP[category] ?? category;
}
function mapBoardSection(p: HrProject): string {
const name = p.name.trim();
if (/회사생활|C\.E\.L|조직문화|복리후생|문화\s*진단|직원\s*소통/i.test(name)) {
return '조직문화';
}
return mapSection(p.category);
}
function mapStatus(status?: string, isRoutine = false): TaskStatus {
if (!status?.trim()) return isRoutine ? 'IN_PROGRESS' : 'TODO';
return STATUS_MAP[status] ?? 'IN_PROGRESS';
@@ -186,8 +278,8 @@ export function mapHrProjectToTask(p: HrProject, quarter = '2026-Q2'): MappedTas
status: mapStatus(p.status, isRoutine),
priority: mapPriority(p.priority),
quarter,
category: mapSection(p.category),
section: mapSection(p.category),
category: mapBoardSection(p),
section: mapBoardSection(p),
taskType,
progress: mapProgress(p.progress),
issueNote: pickIssueNote(p),
@@ -204,5 +296,7 @@ export function mapHrProjectToTask(p: HrProject, quarter = '2026-Q2'): MappedTas
}
export function mapAllHrProjects(filePath?: string, quarter = '2026-Q2'): MappedTask[] {
return loadHrProjects(filePath).map((p) => mapHrProjectToTask(p, quarter));
return loadHrProjects(filePath)
.filter((p) => p.priority !== '상시')
.map((p) => mapHrProjectToTask(p, quarter));
}