- 일일순회점검 시스템 신규 구현 - DB 테이블: patrol_checklist_items, daily_patrol_sessions, patrol_check_records, workplace_items, item_types - API: /api/patrol/* 엔드포인트 - 프론트엔드: 지도 기반 작업장 점검 UI - 설비 관리 기능 개선 - 구매 관련 필드 추가 (구매일, 가격, 공급업체 등) - 설비 코드 자동 생성 (TKP-XXX 형식) - 작업장 관리 개선 - 레이아웃 이미지 업로드 기능 - 마커 위치 저장 기능 - 부서 관리 기능 추가 - 사이드바 네비게이션 카테고리 재구성 - 이미지 401 오류 수정 (정적 파일 경로 처리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
440 lines
15 KiB
Markdown
440 lines
15 KiB
Markdown
# TK-FB-Project 통합 개발 가이드
|
|
|
|
이 문서는 프로젝트의 실행, 규칙, 테스트, 호환성 등 모든 개발 관련 사항을 통합한 가이드입니다.
|
|
|
|
---
|
|
|
|
## 🏗 프로젝트 개요 및 아키텍처
|
|
**생산팀 내부 포털 개발 및 유지보수**
|
|
|
|
### 기술 스택
|
|
- **Backend**: Node.js, Express.js (Port: 20005)
|
|
- **Frontend**: Vanilla HTML/CSS/JS (Port: 20000)
|
|
- **Database**: MariaDB (Port: 20306), phpMyAdmin (Port: 20080)
|
|
- **Infra**: Docker Compose (Synology NAS, Mac Mini)
|
|
- **Bridge**: FastAPI (Port: 20010, Python 3.11+) - *2025.07 도입*
|
|
|
|
### 아키텍처 모식도
|
|
```
|
|
브라우저 → FastAPI (8000) → Express (3005) → MariaDB
|
|
↓
|
|
정적 파일 서빙
|
|
```
|
|
*Note: 현재 개발 환경 포트는 위 기술 스택 섹션 참조*
|
|
|
|
---
|
|
|
|
## 🚀 실행 가이드
|
|
|
|
### 필수: 환경 변수 설정
|
|
`.env.example`을 `.env`로 복사하고 설정하세요. **절대 커밋 금지!**
|
|
|
|
### Docker 실행
|
|
```bash
|
|
./start.sh # 간편 실행 (권장)
|
|
./stop.sh # 중지
|
|
docker-compose up -d # 수동 실행
|
|
```
|
|
|
|
---
|
|
|
|
## 📏 코딩 컨벤션
|
|
|
|
### 네이밍
|
|
| 대상 | 스타일 | 예시 |
|
|
|---|---|---|
|
|
| JS 변수/함수 | camelCase | `calculateTotal` |
|
|
| JS 클래스 | PascalCase | `UserReport` |
|
|
| 파일명 | kebab-case | `work-report.js` |
|
|
| DB 테이블/컬럼 | snake_case | `user_accounts` |
|
|
| API URL | plural, kebab | `/api/work-reports` |
|
|
|
|
### 코드 품질
|
|
- **파일 분리**: 750줄 초과 시 리팩토링 고려 (Controller/Service/Model 분리 필수).
|
|
- **Early Return**: 중첩 조건문 지양.
|
|
- **주석**: JSDoc 활용 권장.
|
|
|
|
---
|
|
|
|
## 🎨 UI/UX 디자인 가이드
|
|
|
|
### 디자인 원칙
|
|
- **모던하고 깔끔한 디자인**: 이모지 사용 지양, 아이콘 또는 심플한 텍스트 사용
|
|
- **일관성**: 모든 페이지에서 동일한 디자인 시스템 적용
|
|
- **컴포넌트 재사용**: navbar, footer 등은 컴포넌트로 관리
|
|
|
|
### 이모지 사용 금지
|
|
❌ **금지**:
|
|
```html
|
|
<!-- 나쁜 예 -->
|
|
<button>📊 대시보드</button>
|
|
<h1>🔧 작업 관리</h1>
|
|
```
|
|
|
|
✅ **권장**:
|
|
```html
|
|
<!-- 좋은 예 - 아이콘 라이브러리 사용 또는 심플한 텍스트 -->
|
|
<button class="btn-primary">
|
|
<i class="icon-dashboard"></i>
|
|
대시보드
|
|
</button>
|
|
<h1>작업 관리</h1>
|
|
```
|
|
|
|
### 색상 가이드
|
|
- **Primary**: 하늘색 계열 (`#0ea5e9`, `#38bdf8`, `#7dd3fc`)
|
|
- **기본 배경**: 흰색/밝은 회색 계열
|
|
- **텍스트**: `#1f2937` (dark gray)
|
|
- **보조 텍스트**: `#6b7280` (medium gray)
|
|
|
|
### 컴포넌트 구조
|
|
- **네비게이션**: `web-ui/components/navbar.html` 참조
|
|
- **일관된 헤더**: 모든 페이지에서 `<div id="navbar-container"></div>` 사용
|
|
- **CSS 로딩 순서**: `design-system.css` → 페이지별 CSS
|
|
|
|
### 페이지 구조 (2026-02-03 현행)
|
|
```
|
|
web-ui/pages/
|
|
├── dashboard.html # 메인 대시보드 (작업장 현황 지도 포함)
|
|
├── work/ # 작업 관리 (현장 입력/생산)
|
|
│ ├── tbm.html # TBM(Tool Box Meeting) 관리
|
|
│ ├── report-create.html # 작업보고서 작성
|
|
│ ├── report-view.html # 작업보고서 조회
|
|
│ ├── nonconformity.html # 부적합 현황
|
|
│ └── analysis.html # 작업 분석
|
|
├── [일간작업장 점검] # 일간작업장 점검 (사이드바 카테고리)
|
|
│ ├── attendance/checkin.html # 출근 체크
|
|
│ └── attendance/work-status.html # 근무 현황 (휴가/연장근무)
|
|
├── safety/ # 안전 관리 페이지
|
|
│ ├── report.html # 신고 (공통)
|
|
│ ├── report-status.html # 안전신고 현황
|
|
│ ├── issue-detail.html # 이슈 상세
|
|
│ ├── visit-request.html # 출입 신청
|
|
│ ├── management.html # 안전 관리 (출입 승인)
|
|
│ └── checklist-manage.html # 안전 체크리스트 관리
|
|
├── attendance/ # 근태 관리 페이지
|
|
│ ├── monthly.html # 월별 출퇴근 현황
|
|
│ ├── vacation-request.html # 휴가 신청
|
|
│ ├── vacation-management.html # 휴가 관리 (통합)
|
|
│ ├── vacation-approval.html # 휴가 승인 관리
|
|
│ ├── vacation-input.html # 휴가 직접 입력
|
|
│ ├── vacation-allocation.html # 휴가 발생 입력
|
|
│ └── annual-overview.html # 연간 연차 현황
|
|
├── admin/ # 시스템 관리 페이지
|
|
│ ├── accounts.html # 계정 관리
|
|
│ ├── page-access.html # 페이지 접근 권한 관리
|
|
│ ├── workers.html # 작업자 관리
|
|
│ ├── projects.html # 프로젝트 관리
|
|
│ ├── tasks.html # 작업 관리
|
|
│ ├── workplaces.html # 작업장 관리 (지도 구역 설정)
|
|
│ ├── equipments.html # 설비 관리
|
|
│ ├── codes.html # 코드 관리
|
|
│ ├── issue-categories.html # 신고 카테고리 관리
|
|
│ └── attendance-report.html # 출퇴근-작업보고서 대조
|
|
├── profile/ # 사용자 프로필
|
|
│ ├── info.html # 내 정보
|
|
│ └── password.html # 비밀번호 변경
|
|
└── .archived-*/ # 미사용 페이지 보관
|
|
```
|
|
|
|
**폴더 분류 기준** (2026-02-03 변경):
|
|
- `work/`: 현장 입력/생산 활동 (TBM, 작업보고서, 부적합)
|
|
- `[일간작업장 점검]`: 일일 작업장 점검 관련 (사이드바 카테고리)
|
|
- `safety/`: 안전 관리/분석 (신고, 출입)
|
|
- `attendance/`: 근태/휴가 관리
|
|
- `admin/`: 시스템 관리 (관리자 전용)
|
|
- `profile/`: 개인 설정 페이지
|
|
|
|
**네이밍 규칙**:
|
|
- 메인 페이지: 단일 명사 (`dashboard.html`)
|
|
- 관리 페이지: 복수형 명사 (`projects.html`, `workers.html`)
|
|
- 기능 페이지: 동사-명사 또는 명사 (`report-create.html`, `daily.html`)
|
|
- 폴더명: 단수형, 소문자 (`work/`, `safety/`, `attendance/`, `admin/`, `profile/`)
|
|
|
|
**네비게이션 구조**:
|
|
- 1차: `dashboard.html` (메인 진입점, 작업장 현황 지도)
|
|
- 2차: 사이드 메뉴 또는 빠른 작업 카드를 통한 각 기능 페이지 이동
|
|
- 모든 페이지: navbar를 통해 profile, 로그아웃 가능
|
|
|
|
### 대기 중인 DB 마이그레이션
|
|
페이지 구조 변경에 따른 DB 마이그레이션이 필요합니다:
|
|
```bash
|
|
cd /Users/hyungiahn/Documents/code/TK-FB-Project/api.hyungi.net
|
|
npx knex migrate:latest
|
|
```
|
|
- 마이그레이션 파일: `db/migrations/20260202200000_reorganize_pages.js`
|
|
- 내용: pages 테이블 경로 업데이트, role_default_pages 테이블 생성
|
|
|
|
### 표준 컴포넌트 (2026-01-20 업데이트)
|
|
|
|
#### 네비게이션 헤더
|
|
모든 페이지는 표준 navbar 컴포넌트를 사용합니다:
|
|
|
|
```html
|
|
<!-- HTML에 컨테이너 추가 -->
|
|
<div id="navbar-container"></div>
|
|
|
|
<!-- 스크립트로 로드 -->
|
|
<script src="/js/load-navbar.js"></script>
|
|
```
|
|
|
|
**특징**:
|
|
- 자동으로 사용자 정보 표시 (이름, 역할)
|
|
- 프로필 메뉴 (내 프로필, 비밀번호 변경, 로그아웃)
|
|
- 관리자 전용 메뉴 자동 표시/숨김
|
|
- 현재 시각 실시간 표시
|
|
- 대시보드 버튼
|
|
|
|
#### CSS 변수 시스템
|
|
모든 스타일은 `design-system.css`의 CSS 변수를 사용합니다:
|
|
|
|
```css
|
|
/* 색상 - 하늘색 계열 primary */
|
|
var(--primary-500) /* 기본 하늘색: #0ea5e9 */
|
|
var(--primary-400) /* 밝은 하늘색: #38bdf8 */
|
|
var(--header-gradient) /* 헤더 그라디언트 */
|
|
|
|
/* 간격 */
|
|
var(--space-2) /* 8px */
|
|
var(--space-4) /* 16px */
|
|
var(--space-6) /* 24px */
|
|
|
|
/* 타이포그래피 */
|
|
var(--text-sm) /* 14px */
|
|
var(--text-base) /* 16px */
|
|
var(--font-medium) /* 500 */
|
|
|
|
/* 기타 */
|
|
var(--radius-md) /* 8px 둥근 모서리 */
|
|
var(--shadow-md) /* 그림자 */
|
|
var(--transition-fast) /* 150ms */
|
|
```
|
|
|
|
**금지**: 하드코딩된 색상 값 사용 (`#0ea5e9` 대신 `var(--primary-500)` 사용)
|
|
|
|
#### 페이지 레이아웃 템플릿
|
|
`web-ui/templates/` 디렉토리에 4가지 표준 템플릿 제공:
|
|
|
|
1. **dashboard-layout.html**: 메인 대시보드, 통계 페이지
|
|
2. **work-layout.html**: 작업 관련 페이지 (보고서, 분석)
|
|
3. **admin-layout.html**: 관리자 페이지 (테이블, CRUD)
|
|
4. **simple-layout.html**: 프로필, 설정 등 단순 페이지
|
|
|
|
새 페이지 생성 시:
|
|
```bash
|
|
# 템플릿 복사
|
|
cp web-ui/templates/work-layout.html web-ui/pages/work/new-page.html
|
|
|
|
# 내용 수정
|
|
# - <title> 변경
|
|
# - 페이지별 CSS/JS 추가
|
|
# - 콘텐츠 작성
|
|
```
|
|
|
|
상세한 사용법은 `web-ui/templates/README.md` 참조.
|
|
|
|
---
|
|
|
|
## 🔐 페이지 접근 권한 관리
|
|
|
|
### 권한 체크 방식
|
|
1. **관리자 전용 페이지**: `admin-only` 클래스 사용
|
|
2. **페이지별 권한 체크**: `pages` 테이블 기반 권한 확인
|
|
3. **클라이언트 측**: `auth-check.js`에서 자동 권한 검증
|
|
|
|
### 페이지 등록 (pages 테이블)
|
|
새 페이지 생성 시 반드시 `pages` 테이블에 등록:
|
|
|
|
```sql
|
|
-- 마이그레이션 예시
|
|
INSERT INTO pages (page_name, page_url, page_category, description, display_order, is_active)
|
|
VALUES
|
|
('출입 신청', '/pages/work/visit-request.html', 'work', '작업장 출입 및 안전교육 신청', 150, 1),
|
|
('안전관리', '/pages/admin/safety-management.html', 'admin', '출입 신청 승인 및 안전교육 관리', 210, 1);
|
|
```
|
|
|
|
### 페이지 권한 할당
|
|
- **Admin**: 모든 페이지 자동 접근 가능
|
|
- **일반 사용자**: `page_access` 테이블에 명시적 권한 부여 필요
|
|
|
|
```sql
|
|
-- 특정 사용자에게 페이지 권한 부여
|
|
INSERT INTO page_access (user_id, page_id, granted_by, granted_at)
|
|
VALUES (123, 45, 1, NOW());
|
|
```
|
|
|
|
### HTML 페이지 설정
|
|
```html
|
|
<!-- 관리자 전용 페이지 -->
|
|
<a href="/pages/admin/safety-management.html" class="quick-action-card admin-only">
|
|
<div class="action-content">
|
|
<h3>안전관리</h3>
|
|
</div>
|
|
</a>
|
|
|
|
<!-- 모든 사용자 접근 가능 -->
|
|
<a href="/pages/work/visit-request.html" class="quick-action-card">
|
|
<div class="action-content">
|
|
<h3>출입 신청</h3>
|
|
</div>
|
|
</a>
|
|
```
|
|
|
|
### API 라우트 보호
|
|
```javascript
|
|
const { verifyToken } = require('../middlewares/authMiddleware');
|
|
|
|
// 모든 라우트에 인증 필요
|
|
router.use(verifyToken);
|
|
|
|
// 관리자 전용 라우트
|
|
router.put('/requests/:id/approve', (req, res) => {
|
|
// verifyToken에서 req.user 제공
|
|
// 필요시 추가 권한 체크
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 📡 API 개발 가이드
|
|
- **RESTful**: 명사형 리소스 사용 (`POST /users` O, `/createUser` X).
|
|
- **응답 포맷**:
|
|
```json
|
|
{ "success": true, "data": {...}, "message": "..." }
|
|
```
|
|
- **계층 구조**:
|
|
- `Controller`: 요청/응답 처리, 유효성 검사.
|
|
- `Service`: 비즈니스 로직, 트랜잭션 관리.
|
|
- `Model`: DB 쿼리 실행.
|
|
|
|
### MySQL 8.0 호환성 주의사항 (중요)
|
|
Synology NAS(MySQL 8.0)의 `Strict Mode`로 인해 `db.execute()` 사용 시 `Incorrect arguments` 에러가 발생할 수 있습니다.
|
|
- **해결책**: `db.query()` 사용 권장 (특히 복잡한 JOIN/Subquery).
|
|
- **가이드**: `models/WorkAnalysis.js` 등의 `getRecentWork` 참조.
|
|
|
|
---
|
|
|
|
## 🕐 시간대(Timezone) 처리 가이드
|
|
|
|
### 핵심 원칙
|
|
이 프로젝트는 **한국 시간(KST, UTC+9)** 기준으로 운영됩니다.
|
|
|
|
### 문제 상황
|
|
서버가 UTC 시간대로 설정된 경우, `NOW()`, `CURRENT_TIMESTAMP`, `new Date()`를 사용하면 **한국 시간과 9시간 차이**가 발생합니다.
|
|
|
|
```
|
|
예시: 한국 시간 2026-02-03 08:00 AM에 신고 등록
|
|
- UTC 시간: 2026-02-02 11:00 PM
|
|
- DB 저장: 2026-02-02 (잘못된 날짜!)
|
|
```
|
|
|
|
### 해결 방법
|
|
|
|
#### 백엔드 (Node.js)
|
|
**공용 유틸리티 사용** (`utils/dateUtils.js`):
|
|
|
|
```javascript
|
|
const { getKoreaDatetime, getKoreaDateString } = require('../utils/dateUtils');
|
|
|
|
// 비즈니스 날짜 저장 시
|
|
const reportDate = getKoreaDatetime(); // '2026-02-03 08:00:00'
|
|
await db.query('INSERT INTO reports (report_date, ...) VALUES (?, ...)', [reportDate, ...]);
|
|
|
|
// 날짜만 필요한 경우
|
|
const today = getKoreaDateString(); // '2026-02-03'
|
|
```
|
|
|
|
**제공되는 함수들**:
|
|
| 함수 | 반환값 | 용도 |
|
|
|------|--------|------|
|
|
| `getKoreaDatetime()` | `'2026-02-03 08:00:00'` | DB DATETIME 저장 |
|
|
| `getKoreaDateString()` | `'2026-02-03'` | DB DATE 저장 |
|
|
| `getKoreaTimeString()` | `'08:00:00'` | DB TIME 저장 |
|
|
| `getKoreaYear()` | `2026` | 연도 |
|
|
| `getKoreaMonth()` | `2` | 월 (1-12) |
|
|
| `toKoreaDatetime(date)` | `'2026-02-03 08:00:00'` | Date 객체 변환 |
|
|
|
|
#### 프론트엔드 (JavaScript)
|
|
```javascript
|
|
// 로컬 시간대 기준 날짜 문자열
|
|
function getLocalDateString() {
|
|
const now = new Date();
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
// ❌ 잘못된 방법 (UTC 변환됨)
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
// ✅ 올바른 방법 (로컬 시간)
|
|
const today = getLocalDateString();
|
|
```
|
|
|
|
### 컬럼별 적용 기준
|
|
|
|
| 컬럼 유형 | 처리 방법 | 비고 |
|
|
|-----------|-----------|------|
|
|
| `created_at`, `updated_at` | `NOW()` 또는 `CURRENT_TIMESTAMP` 사용 가능 | 감사용 메타데이터, UTC로 저장해도 무방 |
|
|
| `report_date`, `session_date` | **반드시 `getKoreaDatetime()` 사용** | 비즈니스 날짜, 사용자에게 표시됨 |
|
|
| `visit_date`, `attendance_date` | **반드시 한국 시간 기준** | 필터링/조회에 사용됨 |
|
|
| API 응답 `timestamp` | `new Date().toISOString()` 사용 가능 | 디버깅용 |
|
|
|
|
### 마이그레이션 주의사항
|
|
새 테이블 생성 시 비즈니스 날짜 컬럼은 **default 값을 사용하지 말고** 애플리케이션에서 명시적으로 설정:
|
|
|
|
```javascript
|
|
// ❌ 잘못된 방법
|
|
table.datetime('report_date').defaultTo(knex.fn.now());
|
|
|
|
// ✅ 올바른 방법
|
|
table.datetime('report_date').notNullable(); // default 없음
|
|
// 애플리케이션에서 getKoreaDatetime()으로 값 설정
|
|
```
|
|
|
|
### 기존 데이터 보정 (필요시)
|
|
UTC로 잘못 저장된 데이터를 한국 시간으로 보정:
|
|
```sql
|
|
-- 주의: 백업 후 실행
|
|
UPDATE work_issue_reports
|
|
SET report_date = DATE_ADD(report_date, INTERVAL 9 HOUR)
|
|
WHERE report_date < '2026-02-03';
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 테스트 가이드 (Jest)
|
|
|
|
### 중요도
|
|
1. **Service Layer** ⭐ (최우선: 비즈니스 로직 검증)
|
|
2. Controller Layer (요청 처리 검증)
|
|
3. Integration (E2E)
|
|
|
|
### 실행
|
|
```bash
|
|
npm test # 전체 실행
|
|
npm run test:watch # 변경 감지
|
|
npm run test:coverage # 커버리지 측정
|
|
```
|
|
|
|
### 작성 패턴 (Service 예시)
|
|
```javascript
|
|
// Service는 DB Model을 Mocking하여 테스트
|
|
const service = require('../service');
|
|
jest.mock('../model');
|
|
|
|
it('should create report', async () => {
|
|
Model.create.mockResolvedValue(123); // Mock DB response
|
|
const result = await service.createReport(...);
|
|
expect(result).toEqual(...);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 Git 관리
|
|
- **커밋 메시지**: `type: subject` (예: `feat: 작업보고서 API 추가`)
|
|
- **Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
|
- **브랜치**: `main`(배포), `develop`(통합), `feature/*`(기능)
|