chore: 팀 워크플로우 초기화 + Prettier + 타입 에러 수정

- /init-project로 팀 표준 워크플로우 적용 (CLAUDE.md, settings.json hooks, pre-commit)
- Prettier + eslint-config-prettier 설치 및 ESLint 연동
- format/format:check npm 스크립트 추가
- vite-env.d.ts 추가 (import.meta.env 타입 정의)
- pre-commit 차단 해제: GearDetection/BaseChart 타입 캐스팅

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
htlee 2026-04-07 08:44:28 +09:00
부모 7a9ece5f0f
커밋 1eccbd7a72
11개의 변경된 파일236개의 추가작업 그리고 3개의 파일을 삭제

파일 보기

@ -46,5 +46,42 @@
"Read(./**/.env.*)", "Read(./**/.env.*)",
"Read(./**/secrets/**)" "Read(./**/secrets/**)"
] ]
},
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "bash .claude/scripts/on-post-compact.sh",
"timeout": 10
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/scripts/on-pre-compact.sh",
"timeout": 30
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/scripts/on-commit.sh",
"timeout": 15
}
]
}
]
} }
} }

54
.githooks/pre-commit Executable file
파일 보기

@ -0,0 +1,54 @@
#!/bin/bash
#==============================================================================
# pre-commit hook (React TypeScript)
# TypeScript 컴파일 + 린트 검증 — 실패 시 커밋 차단
#==============================================================================
echo "pre-commit: TypeScript 타입 체크 중..."
# npm 확인
if ! command -v npx &>/dev/null; then
echo "경고: npx가 설치되지 않았습니다. 검증을 건너뜁니다."
exit 0
fi
# node_modules 확인
if [ ! -d "node_modules" ]; then
echo "경고: node_modules가 없습니다. 'npm install' 실행 후 다시 시도하세요."
exit 1
fi
# TypeScript 타입 체크
npx tsc --noEmit --pretty 2>&1
TSC_RESULT=$?
if [ $TSC_RESULT -ne 0 ]; then
echo ""
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ TypeScript 타입 에러! 커밋이 차단되었습니다. ║"
echo "║ 타입 에러를 수정한 후 다시 커밋해주세요. ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo ""
exit 1
fi
echo "pre-commit: 타입 체크 성공"
# ESLint 검증 (설정 파일이 있는 경우만)
if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f ".eslintrc.cjs" ] || [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ]; then
echo "pre-commit: ESLint 검증 중..."
npx eslint src/ --ext .ts,.tsx --quiet 2>&1
LINT_RESULT=$?
if [ $LINT_RESULT -ne 0 ]; then
echo ""
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ ESLint 에러! 커밋이 차단되었습니다. ║"
echo "║ 'npm run lint -- --fix'로 자동 수정을 시도해보세요. ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo ""
exit 1
fi
echo "pre-commit: ESLint 통과"
fi

7
.prettierignore Normal file
파일 보기

@ -0,0 +1,7 @@
dist/
build/
node_modules/
coverage/
*.min.js
*.min.css
package-lock.json

10
.prettierrc Normal file
파일 보기

@ -0,0 +1,10 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}

74
CLAUDE.md Normal file
파일 보기

@ -0,0 +1,74 @@
# KCG AI Monitoring
해양경찰청 AI 기반 불법어선 탐지 및 단속 지원 플랫폼
## 기술 스택
- **프레임워크**: React 19 + TypeScript 5.9
- **빌드**: Vite 8
- **스타일**: Tailwind CSS 4 + CVA (class-variance-authority)
- **지도**: MapLibre GL 5 + deck.gl 9
- **차트**: ECharts 6
- **상태관리**: Zustand 5
- **다국어**: i18next (ko/en, 10개 네임스페이스)
- **라우팅**: React Router 7
- **린트**: ESLint 10 (flat config)
## 명령어
```bash
npm run dev # 개발 서버 (Vite)
npm run build # 프로덕션 빌드
npm run lint # ESLint 검사
npm run lint:fix # ESLint 자동 수정
npm run format # Prettier 포맷팅
npm run format:check # 포맷팅 검사
```
## 디렉토리 구조
```
src/
├── app/ # 라우터, 인증, 레이아웃
├── features/ # 13개 도메인 모듈 (31+ 페이지)
│ ├── admin/ # 관리자
│ ├── ai-operations/ # AI 작전
│ ├── auth/ # 인증
│ ├── dashboard/ # 대시보드
│ ├── detection/ # 탐지
│ ├── enforcement/ # 단속
│ ├── field-ops/ # 현장작전
│ ├── monitoring/ # 모니터링
│ ├── patrol/ # 순찰
│ ├── risk-assessment/# 위험평가
│ ├── statistics/ # 통계
│ ├── surveillance/ # 감시
│ └── vessel/ # 선박
├── lib/ # 공유 라이브러리
│ ├── charts/ # ECharts 래퍼 + 프리셋
│ ├── i18n/ # i18next 설정 + 로케일
│ ├── map/ # MapLibre + deck.gl 통합
│ └── theme/ # 디자인 토큰 + CVA 변형
├── data/mock/ # 7개 목 데이터 모듈
├── stores/ # Zustand 스토어 (8개)
├── services/ # API 서비스 샘플
├── shared/ # 공유 UI 컴포넌트
└── styles/ # CSS (Dark/Light 테마)
```
## Path Alias
| Alias | 경로 |
|-------|------|
| `@/` | `src/` |
| `@lib/` | `src/lib/` |
| `@shared/` | `src/shared/` |
| `@features/` | `src/features/` |
| `@data/` | `src/data/` |
| `@stores/` | `src/stores/` |
## 팀 컨벤션
- 팀 규칙은 `.claude/rules/` 참조
- 커밋: Conventional Commits (한국어), `.githooks/commit-msg`로 검증
- Git Hooks: `.githooks/` (core.hooksPath 설정됨)

파일 보기

@ -3,6 +3,7 @@ import globals from 'globals';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
import reactHooks from 'eslint-plugin-react-hooks'; import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh'; import reactRefresh from 'eslint-plugin-react-refresh';
import eslintConfigPrettier from 'eslint-config-prettier';
export default tseslint.config( export default tseslint.config(
{ ignores: ['dist/**', 'node_modules/**'] }, { ignores: ['dist/**', 'node_modules/**'] },
@ -27,4 +28,5 @@ export default tseslint.config(
'prefer-const': 'warn', 'prefer-const': 'warn',
}, },
}, },
eslintConfigPrettier,
); );

34
package-lock.json generated
파일 보기

@ -28,9 +28,11 @@
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
"eslint": "^10.2.0", "eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0", "globals": "^17.4.0",
"prettier": "^3.8.1",
"tailwindcss": "^4.2.2", "tailwindcss": "^4.2.2",
"typescript": "5.9", "typescript": "5.9",
"typescript-eslint": "^8.58.0", "typescript-eslint": "^8.58.0",
@ -3103,6 +3105,22 @@
} }
} }
}, },
"node_modules/eslint-config-prettier": {
"version": "10.1.8",
"resolved": "https://nexus.gc-si.dev/repository/npm-public/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"funding": {
"url": "https://opencollective.com/eslint-config-prettier"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-react-hooks": { "node_modules/eslint-plugin-react-hooks": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
@ -4373,6 +4391,22 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "3.8.1",
"resolved": "https://nexus.gc-si.dev/repository/npm-public/prettier/-/prettier-3.8.1.tgz",
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

파일 보기

@ -7,7 +7,9 @@
"build": "vite build", "build": "vite build",
"dev": "vite", "dev": "vite",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix" "lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\""
}, },
"dependencies": { "dependencies": {
"@deck.gl/mapbox": "^9.2.11", "@deck.gl/mapbox": "^9.2.11",
@ -30,9 +32,11 @@
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.1",
"eslint": "^10.2.0", "eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0", "globals": "^17.4.0",
"prettier": "^3.8.1",
"tailwindcss": "^4.2.2", "tailwindcss": "^4.2.2",
"typescript": "5.9", "typescript": "5.9",
"typescript-eslint": "^8.58.0", "typescript-eslint": "^8.58.0",

파일 보기

@ -47,7 +47,7 @@ export function GearDetection() {
useEffect(() => { if (!loaded) load(); }, [loaded, load]); useEffect(() => { if (!loaded) load(); }, [loaded, load]);
// GearRecord from the store matches the local Gear shape exactly // GearRecord from the store matches the local Gear shape exactly
const DATA: Gear[] = items; const DATA: Gear[] = items as unknown as Gear[];
const mapRef = useRef<MapHandle>(null); const mapRef = useRef<MapHandle>(null);

파일 보기

@ -47,7 +47,7 @@ export function BaseChart({
if (!containerRef.current) return; if (!containerRef.current) return;
const chart = echarts.init(containerRef.current, 'kcg-dark'); const chart = echarts.init(containerRef.current, 'kcg-dark');
chartRef.current = chart; chartRef.current = chart as unknown as ECharts;
chart.setOption(option, notMerge); chart.setOption(option, notMerge);
if (onEvents) { if (onEvents) {

11
src/vite-env.d.ts vendored Normal file
파일 보기

@ -0,0 +1,11 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL?: string;
readonly VITE_PREDICTION_URL?: string;
readonly VITE_USE_MOCK?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}