feat: React 18 프론트엔드 추가 및 프로젝트 구조 정리

- wtm-frontend → wtm-frontend-vue 이름 변경
- wtm-frontend-react 추가 (React 18 + PrimeReact + Zustand)
  - 동일한 모듈 구조 및 API 연동 (Vue 버전과 기능 동일)
  - Vue:5173 / React:5174 포트 분리
- 개발자 가이드에 React 프론트엔드 안내 추가
- .gitignore: Claude/OMC, 문서 생성 스크립트, package-lock 제외
- 불필요 파일 git 추적 제거 (.omc, generate_*.py, regenerate_*.py)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
이 Commit은 다음에 포함되어 있습니다:
2026-03-30 20:50:23 +09:00
부모 dd263a6e46
커밋 cda5f9591e
212개의 변경된 파일3633개의 추가작업 그리고 5244개의 파일을 삭제

파일 보기

@@ -0,0 +1,69 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { Tag } from 'primereact/tag';
import BaseCrudTable from '@/core/components/BaseCrudTable';
import BasePageHeader from '@/core/components/BasePageHeader';
import ProjectFormDialog from '../components/ProjectFormDialog';
import { projectService } from '../project.service';
import { PROJECT_STATUS } from '@/core/constants/app.constants';
import type { Project } from '../project.types';
export default function ProjectListView() {
const [loading, setLoading] = useState(false);
const [projects, setProjects] = useState<Project[]>([]);
const [dialogVisible, setDialogVisible] = useState(false);
const [editProject, setEditProject] = useState<Project | null>(null);
const navigate = useNavigate();
function load() {
setLoading(true);
projectService.getAll()
.then(({ data }) => setProjects((data as { items?: Project[] }).items ?? data as Project[]))
.catch(() => setProjects([]))
.finally(() => setLoading(false));
}
useEffect(() => { load(); }, []);
function openCreate() {
setEditProject(null);
setDialogVisible(true);
}
function openEdit(p: Project) {
setEditProject(p);
setDialogVisible(true);
}
async function onSave(data: Partial<Project>) {
if (editProject) {
await projectService.update(editProject.id, data);
} else {
await projectService.create(data);
}
setDialogVisible(false);
load();
}
return (
<div>
<BasePageHeader title="프로젝트 목록" actions={<Button label="프로젝트 생성" icon="pi pi-plus" size="small" onClick={openCreate} />} />
<BaseCrudTable value={projects} loading={loading} globalFilterFields={['code', 'name']}
onRowSelect={(row) => navigate(`/projects/${row.id}`)}>
<Column field="code" header="코드" sortable />
<Column field="name" header="프로젝트명" sortable />
<Column field="type" header="유형" sortable />
<Column field="status" header="상태" body={(row) => {
const s = PROJECT_STATUS[row.status];
return <Tag value={s?.label ?? row.status} severity={(s?.severity ?? 'secondary') as 'success' | 'warning' | 'secondary'} />;
}} sortable />
<Column field="managerName" header="PM" />
<Column header="" body={(row) => <Button icon="pi pi-pencil" text size="small" onClick={() => openEdit(row)} />} style={{ width: '4rem' }} />
</BaseCrudTable>
<ProjectFormDialog visible={dialogVisible} onHide={() => setDialogVisible(false)} project={editProject} onSave={onSave} />
</div>
);
}