Fix: Worker/Project status update and filtering issues
- Added cache invalidation for Workers and Projects - Implemented server-side status filtering for Workers - Fixed worker update query bug (removed non-existent join_date column) - Updated daily-work-report UI to fetch only active workers
This commit is contained in:
14
DEV_LOG.md
14
DEV_LOG.md
@@ -27,6 +27,20 @@
|
|||||||
4. **테스트 계정 생성**
|
4. **테스트 계정 생성**
|
||||||
- `tester` / `000000` 관리자(Leader) 계정 생성.
|
- `tester` / `000000` 관리자(Leader) 계정 생성.
|
||||||
|
|
||||||
|
### 🛠️ 작업자 및 프로젝트 관리 기능 개선 (2026-01-06)
|
||||||
|
**개요**: 작업자/프로젝트의 비활성화(퇴사/종료) 처리가 즉시 반영되지 않는 문제 및 로직 오류 수정.
|
||||||
|
|
||||||
|
1. **캐시 무효화 및 필터링 적용 (Cache & Filtering)**
|
||||||
|
- **문제**: 작업자/프로젝트 상태 변경 후에도 캐시가 남아있어 드롭다운 목록에서 사라지지 않음.
|
||||||
|
- **해결**:
|
||||||
|
- `WorkerController`, `ProjectController`: 생성/수정/삭제 시 `request` 단위의 캐시 즉시 무효화 로직 추가.
|
||||||
|
- `WorkerController`: 목록 조회 시 `status` 파라미터 지원 추가.
|
||||||
|
- `daily-work-report.js`: 작업보고서 작성 시 `active` 상태인 작업자만 필터링하여 조회하도록 수정.
|
||||||
|
|
||||||
|
2. **작업자 비활성화 오류 수정 (Bug Fix)**
|
||||||
|
- **원인**: `workerModel.update` 쿼리에 DB에 존재하지 않는 `join_date` 컬럼을 업데이트하려는 시도가 있어 SQL 에러 발생.
|
||||||
|
- **해결**: `workerModel.js`에서 잘못된 컬럼(`join_date`) 참조 제거. (올바른 컬럼 `hire_date`는 유지)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛡보안 및 검토 리포트 (History)
|
## 🛡보안 및 검토 리포트 (History)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const projectModel = require('../models/projectModel');
|
|||||||
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors');
|
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors');
|
||||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
|
const cache = require('../utils/cache');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 프로젝트 생성
|
* 프로젝트 생성
|
||||||
@@ -27,6 +28,9 @@ exports.createProject = asyncHandler(async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 프로젝트 캐시 무효화
|
||||||
|
await cache.invalidateCache.project();
|
||||||
|
|
||||||
logger.info('프로젝트 생성 성공', { project_id: id });
|
logger.info('프로젝트 생성 성공', { project_id: id });
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
@@ -123,6 +127,9 @@ exports.updateProject = asyncHandler(async (req, res) => {
|
|||||||
throw new NotFoundError('프로젝트를 찾을 수 없습니다');
|
throw new NotFoundError('프로젝트를 찾을 수 없습니다');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 프로젝트 캐시 무효화
|
||||||
|
await cache.invalidateCache.project();
|
||||||
|
|
||||||
logger.info('프로젝트 수정 성공', { project_id: id });
|
logger.info('프로젝트 수정 성공', { project_id: id });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -153,6 +160,9 @@ exports.removeProject = asyncHandler(async (req, res) => {
|
|||||||
throw new NotFoundError('프로젝트를 찾을 수 없습니다');
|
throw new NotFoundError('프로젝트를 찾을 수 없습니다');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 프로젝트 캐시 무효화
|
||||||
|
await cache.invalidateCache.project();
|
||||||
|
|
||||||
logger.info('프로젝트 삭제 성공', { project_id: id });
|
logger.info('프로젝트 삭제 성공', { project_id: id });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ exports.createWorker = asyncHandler(async (req, res) => {
|
|||||||
* 전체 작업자 조회 (캐싱 및 페이지네이션 적용)
|
* 전체 작업자 조회 (캐싱 및 페이지네이션 적용)
|
||||||
*/
|
*/
|
||||||
exports.getAllWorkers = asyncHandler(async (req, res) => {
|
exports.getAllWorkers = asyncHandler(async (req, res) => {
|
||||||
const { page = 1, limit = 10, search = '' } = req.query;
|
const { page = 1, limit = 10, search = '', status = '' } = req.query;
|
||||||
|
|
||||||
const cacheKey = cache.createKey('workers', 'list', page, limit, search);
|
const cacheKey = cache.createKey('workers', 'list', page, limit, search, status);
|
||||||
|
|
||||||
// 캐시에서 조회
|
// 캐시에서 조회
|
||||||
const cachedData = await cache.get(cacheKey);
|
const cachedData = await cache.get(cacheKey);
|
||||||
@@ -62,7 +62,7 @@ exports.getAllWorkers = asyncHandler(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 최적화된 쿼리 사용
|
// 최적화된 쿼리 사용
|
||||||
const result = await optimizedQueries.getWorkersPaged(page, limit, search);
|
const result = await optimizedQueries.getWorkersPaged(page, limit, search, status);
|
||||||
|
|
||||||
// 캐시에 저장 (5분)
|
// 캐시에 저장 (5분)
|
||||||
await cache.set(cacheKey, result, cache.TTL.MEDIUM);
|
await cache.set(cacheKey, result, cache.TTL.MEDIUM);
|
||||||
@@ -127,6 +127,10 @@ exports.updateWorker = asyncHandler(async (req, res) => {
|
|||||||
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 작업자 관련 캐시 무효화
|
||||||
|
logger.info('작업자 수정 후 캐시 무효화', { worker_id: id });
|
||||||
|
await cache.invalidateCache.worker();
|
||||||
|
|
||||||
logger.info('작업자 수정 성공', { worker_id: id });
|
logger.info('작업자 수정 성공', { worker_id: id });
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ const update = async (worker, callback) => {
|
|||||||
worker_id,
|
worker_id,
|
||||||
worker_name,
|
worker_name,
|
||||||
job_type,
|
job_type,
|
||||||
join_date,
|
|
||||||
status,
|
status,
|
||||||
phone_number,
|
phone_number,
|
||||||
email,
|
email,
|
||||||
@@ -92,7 +91,6 @@ const update = async (worker, callback) => {
|
|||||||
`UPDATE workers
|
`UPDATE workers
|
||||||
SET worker_name = ?,
|
SET worker_name = ?,
|
||||||
job_type = ?,
|
job_type = ?,
|
||||||
join_date = ?,
|
|
||||||
status = ?,
|
status = ?,
|
||||||
phone_number = ?,
|
phone_number = ?,
|
||||||
email = ?,
|
email = ?,
|
||||||
@@ -103,7 +101,6 @@ const update = async (worker, callback) => {
|
|||||||
[
|
[
|
||||||
worker_name,
|
worker_name,
|
||||||
job_type,
|
job_type,
|
||||||
formatDate(join_date),
|
|
||||||
status,
|
status,
|
||||||
phone_number,
|
phone_number,
|
||||||
email,
|
email,
|
||||||
@@ -116,7 +113,7 @@ const update = async (worker, callback) => {
|
|||||||
|
|
||||||
callback(null, result.affectedRows);
|
callback(null, result.affectedRows);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
callback(new Error(err.message || String(err)));
|
callback(new Error(err.message || String(err)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,12 +121,12 @@ const update = async (worker, callback) => {
|
|||||||
const remove = async (worker_id, callback) => {
|
const remove = async (worker_id, callback) => {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
const conn = await db.getConnection();
|
const conn = await db.getConnection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await conn.beginTransaction();
|
await conn.beginTransaction();
|
||||||
|
|
||||||
console.log(`🗑️ 작업자 삭제 시작: worker_id=${worker_id}`);
|
console.log(`🗑️ 작업자 삭제 시작: worker_id=${worker_id}`);
|
||||||
|
|
||||||
// 안전한 삭제: 각 테이블을 개별적으로 처리하고 오류가 발생해도 계속 진행
|
// 안전한 삭제: 각 테이블을 개별적으로 처리하고 오류가 발생해도 계속 진행
|
||||||
const tables = [
|
const tables = [
|
||||||
{ name: 'users', query: 'UPDATE users SET worker_id = NULL WHERE worker_id = ?', action: '업데이트' },
|
{ name: 'users', query: 'UPDATE users SET worker_id = NULL WHERE worker_id = ?', action: '업데이트' },
|
||||||
@@ -142,7 +139,7 @@ const remove = async (worker_id, callback) => {
|
|||||||
{ name: 'monthly_worker_status', query: 'DELETE FROM monthly_worker_status WHERE worker_id = ?', action: '삭제' },
|
{ name: 'monthly_worker_status', query: 'DELETE FROM monthly_worker_status WHERE worker_id = ?', action: '삭제' },
|
||||||
{ name: 'worker_groups', query: 'DELETE FROM worker_groups WHERE worker_id = ?', action: '삭제' }
|
{ name: 'worker_groups', query: 'DELETE FROM worker_groups WHERE worker_id = ?', action: '삭제' }
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
try {
|
try {
|
||||||
const [result] = await conn.query(table.query, [worker_id]);
|
const [result] = await conn.query(table.query, [worker_id]);
|
||||||
@@ -153,17 +150,17 @@ const remove = async (worker_id, callback) => {
|
|||||||
console.log(`⚠️ ${table.name} 테이블 ${table.action} 실패 (무시): ${tableError.message}`);
|
console.log(`⚠️ ${table.name} 테이블 ${table.action} 실패 (무시): ${tableError.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 마지막으로 작업자 삭제
|
// 마지막으로 작업자 삭제
|
||||||
const [result] = await conn.query(
|
const [result] = await conn.query(
|
||||||
`DELETE FROM workers WHERE worker_id = ?`,
|
`DELETE FROM workers WHERE worker_id = ?`,
|
||||||
[worker_id]
|
[worker_id]
|
||||||
);
|
);
|
||||||
console.log(`✅ 작업자 삭제 완료: ${result.affectedRows}건`);
|
console.log(`✅ 작업자 삭제 완료: ${result.affectedRows}건`);
|
||||||
|
|
||||||
await conn.commit();
|
await conn.commit();
|
||||||
callback(null, result.affectedRows);
|
callback(null, result.affectedRows);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await conn.rollback();
|
await conn.rollback();
|
||||||
console.error(`❌ 작업자 삭제 오류 (worker_id: ${worker_id}):`, err);
|
console.error(`❌ 작업자 삭제 오류 (worker_id: ${worker_id}):`, err);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const paginate = (page = 1, limit = 10) => {
|
|||||||
const pageNum = Math.max(1, parseInt(page));
|
const pageNum = Math.max(1, parseInt(page));
|
||||||
const limitNum = Math.min(100, Math.max(1, parseInt(limit))); // 최대 100개 제한
|
const limitNum = Math.min(100, Math.max(1, parseInt(limit))); // 최대 100개 제한
|
||||||
const offset = (pageNum - 1) * limitNum;
|
const offset = (pageNum - 1) * limitNum;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
limit: limitNum,
|
limit: limitNum,
|
||||||
offset,
|
offset,
|
||||||
@@ -23,21 +23,21 @@ const paginate = (page = 1, limit = 10) => {
|
|||||||
const executePagedQuery = async (baseQuery, countQuery, params = [], options = {}) => {
|
const executePagedQuery = async (baseQuery, countQuery, params = [], options = {}) => {
|
||||||
const { page = 1, limit = 10, orderBy = 'id', orderDirection = 'DESC' } = options;
|
const { page = 1, limit = 10, orderBy = 'id', orderDirection = 'DESC' } = options;
|
||||||
const { limit: limitNum, offset, page: pageNum } = paginate(page, limit);
|
const { limit: limitNum, offset, page: pageNum } = paginate(page, limit);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
|
|
||||||
// 전체 개수 조회
|
// 전체 개수 조회
|
||||||
const [countResult] = await db.execute(countQuery, params);
|
const [countResult] = await db.execute(countQuery, params);
|
||||||
const totalCount = countResult[0]?.total || 0;
|
const totalCount = countResult[0]?.total || 0;
|
||||||
|
|
||||||
// 데이터 조회 (ORDER BY와 LIMIT 추가)
|
// 데이터 조회 (ORDER BY와 LIMIT 추가)
|
||||||
const pagedQuery = `${baseQuery} ORDER BY ${orderBy} ${orderDirection} LIMIT ${limitNum} OFFSET ${offset}`;
|
const pagedQuery = `${baseQuery} ORDER BY ${orderBy} ${orderDirection} LIMIT ${limitNum} OFFSET ${offset}`;
|
||||||
const [rows] = await db.execute(pagedQuery, params);
|
const [rows] = await db.execute(pagedQuery, params);
|
||||||
|
|
||||||
// 페이지네이션 메타데이터 계산
|
// 페이지네이션 메타데이터 계산
|
||||||
const totalPages = Math.ceil(totalCount / limitNum);
|
const totalPages = Math.ceil(totalCount / limitNum);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: rows,
|
data: rows,
|
||||||
pagination: {
|
pagination: {
|
||||||
@@ -49,7 +49,7 @@ const executePagedQuery = async (baseQuery, countQuery, params = [], options = {
|
|||||||
hasPrevPage: pageNum > 1
|
hasPrevPage: pageNum > 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`페이지네이션 쿼리 실행 오류: ${error.message}`);
|
throw new Error(`페이지네이션 쿼리 실행 오류: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -61,20 +61,20 @@ const executePagedQuery = async (baseQuery, countQuery, params = [], options = {
|
|||||||
const suggestIndexes = async (tableName) => {
|
const suggestIndexes = async (tableName) => {
|
||||||
try {
|
try {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
|
|
||||||
// 현재 인덱스 조회
|
// 현재 인덱스 조회
|
||||||
const [indexes] = await db.execute(`SHOW INDEX FROM ${tableName}`);
|
const [indexes] = await db.execute(`SHOW INDEX FROM ${tableName}`);
|
||||||
|
|
||||||
// 테이블 구조 조회
|
// 테이블 구조 조회
|
||||||
const [columns] = await db.execute(`DESCRIBE ${tableName}`);
|
const [columns] = await db.execute(`DESCRIBE ${tableName}`);
|
||||||
|
|
||||||
const suggestions = [];
|
const suggestions = [];
|
||||||
|
|
||||||
// 외래키 컬럼에 인덱스 제안
|
// 외래키 컬럼에 인덱스 제안
|
||||||
const foreignKeyColumns = columns.filter(col =>
|
const foreignKeyColumns = columns.filter(col =>
|
||||||
col.Field.endsWith('_id') && !indexes.some(idx => idx.Column_name === col.Field)
|
col.Field.endsWith('_id') && !indexes.some(idx => idx.Column_name === col.Field)
|
||||||
);
|
);
|
||||||
|
|
||||||
foreignKeyColumns.forEach(col => {
|
foreignKeyColumns.forEach(col => {
|
||||||
suggestions.push({
|
suggestions.push({
|
||||||
type: 'INDEX',
|
type: 'INDEX',
|
||||||
@@ -83,13 +83,13 @@ const suggestIndexes = async (tableName) => {
|
|||||||
sql: `CREATE INDEX idx_${tableName}_${col.Field} ON ${tableName}(${col.Field});`
|
sql: `CREATE INDEX idx_${tableName}_${col.Field} ON ${tableName}(${col.Field});`
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 날짜 컬럼에 인덱스 제안
|
// 날짜 컬럼에 인덱스 제안
|
||||||
const dateColumns = columns.filter(col =>
|
const dateColumns = columns.filter(col =>
|
||||||
(col.Type.includes('date') || col.Type.includes('timestamp')) &&
|
(col.Type.includes('date') || col.Type.includes('timestamp')) &&
|
||||||
!indexes.some(idx => idx.Column_name === col.Field)
|
!indexes.some(idx => idx.Column_name === col.Field)
|
||||||
);
|
);
|
||||||
|
|
||||||
dateColumns.forEach(col => {
|
dateColumns.forEach(col => {
|
||||||
suggestions.push({
|
suggestions.push({
|
||||||
type: 'INDEX',
|
type: 'INDEX',
|
||||||
@@ -98,7 +98,7 @@ const suggestIndexes = async (tableName) => {
|
|||||||
sql: `CREATE INDEX idx_${tableName}_${col.Field} ON ${tableName}(${col.Field});`
|
sql: `CREATE INDEX idx_${tableName}_${col.Field} ON ${tableName}(${col.Field});`
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tableName,
|
tableName,
|
||||||
currentIndexes: indexes.map(idx => ({
|
currentIndexes: indexes.map(idx => ({
|
||||||
@@ -108,7 +108,7 @@ const suggestIndexes = async (tableName) => {
|
|||||||
})),
|
})),
|
||||||
suggestions
|
suggestions
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`인덱스 분석 오류: ${error.message}`);
|
throw new Error(`인덱스 분석 오류: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -120,23 +120,23 @@ const suggestIndexes = async (tableName) => {
|
|||||||
const analyzeQuery = async (query, params = []) => {
|
const analyzeQuery = async (query, params = []) => {
|
||||||
try {
|
try {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
|
|
||||||
// EXPLAIN 실행
|
// EXPLAIN 실행
|
||||||
const explainQuery = `EXPLAIN ${query}`;
|
const explainQuery = `EXPLAIN ${query}`;
|
||||||
const [explainResult] = await db.execute(explainQuery, params);
|
const [explainResult] = await db.execute(explainQuery, params);
|
||||||
|
|
||||||
// 쿼리 실행 시간 측정
|
// 쿼리 실행 시간 측정
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
await db.execute(query, params);
|
await db.execute(query, params);
|
||||||
const executionTime = Date.now() - startTime;
|
const executionTime = Date.now() - startTime;
|
||||||
|
|
||||||
// 성능 분석
|
// 성능 분석
|
||||||
const analysis = {
|
const analysis = {
|
||||||
executionTime,
|
executionTime,
|
||||||
explainResult,
|
explainResult,
|
||||||
recommendations: []
|
recommendations: []
|
||||||
};
|
};
|
||||||
|
|
||||||
// 성능 권장사항 생성
|
// 성능 권장사항 생성
|
||||||
explainResult.forEach(row => {
|
explainResult.forEach(row => {
|
||||||
if (row.type === 'ALL') {
|
if (row.type === 'ALL') {
|
||||||
@@ -146,7 +146,7 @@ const analyzeQuery = async (query, params = []) => {
|
|||||||
suggestion: '적절한 인덱스 추가 권장'
|
suggestion: '적절한 인덱스 추가 권장'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.rows > 1000) {
|
if (row.rows > 1000) {
|
||||||
analysis.recommendations.push({
|
analysis.recommendations.push({
|
||||||
type: 'WARNING',
|
type: 'WARNING',
|
||||||
@@ -154,7 +154,7 @@ const analyzeQuery = async (query, params = []) => {
|
|||||||
suggestion: 'WHERE 조건 최적화 또는 인덱스 추가 권장'
|
suggestion: 'WHERE 조건 최적화 또는 인덱스 추가 권장'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.Extra && row.Extra.includes('Using filesort')) {
|
if (row.Extra && row.Extra.includes('Using filesort')) {
|
||||||
analysis.recommendations.push({
|
analysis.recommendations.push({
|
||||||
type: 'INFO',
|
type: 'INFO',
|
||||||
@@ -163,9 +163,9 @@ const analyzeQuery = async (query, params = []) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return analysis;
|
return analysis;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`쿼리 분석 오류: ${error.message}`);
|
throw new Error(`쿼리 분석 오류: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -178,39 +178,39 @@ const batchInsert = async (tableName, data, batchSize = 100) => {
|
|||||||
if (!Array.isArray(data) || data.length === 0) {
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
throw new Error('삽입할 데이터가 없습니다.');
|
throw new Error('삽입할 데이터가 없습니다.');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
const connection = await db.getConnection();
|
const connection = await db.getConnection();
|
||||||
|
|
||||||
await connection.beginTransaction();
|
await connection.beginTransaction();
|
||||||
|
|
||||||
const columns = Object.keys(data[0]);
|
const columns = Object.keys(data[0]);
|
||||||
const placeholders = columns.map(() => '?').join(', ');
|
const placeholders = columns.map(() => '?').join(', ');
|
||||||
const insertQuery = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;
|
const insertQuery = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;
|
||||||
|
|
||||||
let insertedCount = 0;
|
let insertedCount = 0;
|
||||||
|
|
||||||
// 배치 단위로 처리
|
// 배치 단위로 처리
|
||||||
for (let i = 0; i < data.length; i += batchSize) {
|
for (let i = 0; i < data.length; i += batchSize) {
|
||||||
const batch = data.slice(i, i + batchSize);
|
const batch = data.slice(i, i + batchSize);
|
||||||
|
|
||||||
for (const row of batch) {
|
for (const row of batch) {
|
||||||
const values = columns.map(col => row[col]);
|
const values = columns.map(col => row[col]);
|
||||||
await connection.execute(insertQuery, values);
|
await connection.execute(insertQuery, values);
|
||||||
insertedCount++;
|
insertedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await connection.commit();
|
await connection.commit();
|
||||||
connection.release();
|
connection.release();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
insertedCount,
|
insertedCount,
|
||||||
batchSize,
|
batchSize,
|
||||||
totalBatches: Math.ceil(data.length / batchSize)
|
totalBatches: Math.ceil(data.length / batchSize)
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`배치 삽입 오류: ${error.message}`);
|
throw new Error(`배치 삽입 오류: ${error.message}`);
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ const generateCacheKey = (query, params = [], prefix = 'query') => {
|
|||||||
.createHash('md5')
|
.createHash('md5')
|
||||||
.update(query + paramString)
|
.update(query + paramString)
|
||||||
.digest('hex');
|
.digest('hex');
|
||||||
|
|
||||||
return `${prefix}:${queryHash}`;
|
return `${prefix}:${queryHash}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -234,30 +234,43 @@ const generateCacheKey = (query, params = [], prefix = 'query') => {
|
|||||||
*/
|
*/
|
||||||
const optimizedQueries = {
|
const optimizedQueries = {
|
||||||
// 작업자 목록 (페이지네이션)
|
// 작업자 목록 (페이지네이션)
|
||||||
getWorkersPaged: async (page = 1, limit = 10, search = '') => {
|
getWorkersPaged: async (page = 1, limit = 10, search = '', status = '') => {
|
||||||
let baseQuery = `
|
let baseQuery = `
|
||||||
SELECT w.*, COUNT(dwr.id) as report_count
|
SELECT w.*, COUNT(dwr.id) as report_count
|
||||||
FROM workers w
|
FROM workers w
|
||||||
LEFT JOIN daily_work_reports dwr ON w.worker_id = dwr.worker_id
|
LEFT JOIN daily_work_reports dwr ON w.worker_id = dwr.worker_id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let countQuery = 'SELECT COUNT(*) as total FROM workers w';
|
let countQuery = 'SELECT COUNT(*) as total FROM workers w';
|
||||||
let params = [];
|
let params = [];
|
||||||
|
let conditions = [];
|
||||||
|
|
||||||
|
// 검색 조건
|
||||||
if (search) {
|
if (search) {
|
||||||
const searchCondition = ' WHERE w.worker_name LIKE ? OR w.position LIKE ?';
|
conditions.push('(w.worker_name LIKE ? OR w.position LIKE ?)');
|
||||||
baseQuery += searchCondition + ' GROUP BY w.worker_id';
|
params.push(`%${search}%`, `%${search}%`);
|
||||||
countQuery += searchCondition;
|
|
||||||
params = [`%${search}%`, `%${search}%`];
|
|
||||||
} else {
|
|
||||||
baseQuery += ' GROUP BY w.worker_id';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 상태 조건
|
||||||
|
if (status) {
|
||||||
|
conditions.push('w.status = ?');
|
||||||
|
params.push(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 조건 조합
|
||||||
|
if (conditions.length > 0) {
|
||||||
|
const whereClause = ' WHERE ' + conditions.join(' AND ');
|
||||||
|
baseQuery += whereClause;
|
||||||
|
countQuery += whereClause;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseQuery += ' GROUP BY w.worker_id';
|
||||||
|
|
||||||
return executePagedQuery(baseQuery, countQuery, params, {
|
return executePagedQuery(baseQuery, countQuery, params, {
|
||||||
page, limit, orderBy: 'w.worker_id', orderDirection: 'DESC'
|
page, limit, orderBy: 'w.worker_id', orderDirection: 'DESC'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 프로젝트 목록 (페이지네이션)
|
// 프로젝트 목록 (페이지네이션)
|
||||||
getProjectsPaged: async (page = 1, limit = 10, status = '') => {
|
getProjectsPaged: async (page = 1, limit = 10, status = '') => {
|
||||||
let baseQuery = `
|
let baseQuery = `
|
||||||
@@ -266,10 +279,10 @@ const optimizedQueries = {
|
|||||||
FROM projects p
|
FROM projects p
|
||||||
LEFT JOIN daily_work_reports dwr ON p.project_id = dwr.project_id
|
LEFT JOIN daily_work_reports dwr ON p.project_id = dwr.project_id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let countQuery = 'SELECT COUNT(*) as total FROM projects p';
|
let countQuery = 'SELECT COUNT(*) as total FROM projects p';
|
||||||
let params = [];
|
let params = [];
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
const statusCondition = ' WHERE p.status = ?';
|
const statusCondition = ' WHERE p.status = ?';
|
||||||
baseQuery += statusCondition + ' GROUP BY p.project_id';
|
baseQuery += statusCondition + ' GROUP BY p.project_id';
|
||||||
@@ -278,12 +291,12 @@ const optimizedQueries = {
|
|||||||
} else {
|
} else {
|
||||||
baseQuery += ' GROUP BY p.project_id';
|
baseQuery += ' GROUP BY p.project_id';
|
||||||
}
|
}
|
||||||
|
|
||||||
return executePagedQuery(baseQuery, countQuery, params, {
|
return executePagedQuery(baseQuery, countQuery, params, {
|
||||||
page, limit, orderBy: 'p.project_id', orderDirection: 'DESC'
|
page, limit, orderBy: 'p.project_id', orderDirection: 'DESC'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 일일 작업 보고서 (날짜 범위, 페이지네이션)
|
// 일일 작업 보고서 (날짜 범위, 페이지네이션)
|
||||||
getDailyWorkReportsPaged: async (startDate, endDate, page = 1, limit = 10) => {
|
getDailyWorkReportsPaged: async (startDate, endDate, page = 1, limit = 10) => {
|
||||||
const baseQuery = `
|
const baseQuery = `
|
||||||
@@ -299,13 +312,13 @@ const optimizedQueries = {
|
|||||||
LEFT JOIN users u ON dwr.created_by = u.user_id
|
LEFT JOIN users u ON dwr.created_by = u.user_id
|
||||||
WHERE dwr.report_date BETWEEN ? AND ?
|
WHERE dwr.report_date BETWEEN ? AND ?
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const countQuery = `
|
const countQuery = `
|
||||||
SELECT COUNT(*) as total
|
SELECT COUNT(*) as total
|
||||||
FROM daily_work_reports dwr
|
FROM daily_work_reports dwr
|
||||||
WHERE dwr.report_date BETWEEN ? AND ?
|
WHERE dwr.report_date BETWEEN ? AND ?
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return executePagedQuery(baseQuery, countQuery, [startDate, endDate], {
|
return executePagedQuery(baseQuery, countQuery, [startDate, endDate], {
|
||||||
page, limit, orderBy: 'dwr.report_date', orderDirection: 'DESC'
|
page, limit, orderBy: 'dwr.report_date', orderDirection: 'DESC'
|
||||||
});
|
});
|
||||||
@@ -318,11 +331,11 @@ const optimizedQueries = {
|
|||||||
const getPerformanceStats = async () => {
|
const getPerformanceStats = async () => {
|
||||||
try {
|
try {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
|
|
||||||
// 연결 상태 조회
|
// 연결 상태 조회
|
||||||
const [connections] = await db.execute('SHOW STATUS LIKE "Threads_connected"');
|
const [connections] = await db.execute('SHOW STATUS LIKE "Threads_connected"');
|
||||||
const [maxConnections] = await db.execute('SHOW VARIABLES LIKE "max_connections"');
|
const [maxConnections] = await db.execute('SHOW VARIABLES LIKE "max_connections"');
|
||||||
|
|
||||||
// 쿼리 캐시 상태 (MySQL 8.0 이전 버전)
|
// 쿼리 캐시 상태 (MySQL 8.0 이전 버전)
|
||||||
let queryCacheStats = null;
|
let queryCacheStats = null;
|
||||||
try {
|
try {
|
||||||
@@ -331,10 +344,10 @@ const getPerformanceStats = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// MySQL 8.0+에서는 쿼리 캐시가 제거됨
|
// MySQL 8.0+에서는 쿼리 캐시가 제거됨
|
||||||
}
|
}
|
||||||
|
|
||||||
// 슬로우 쿼리 로그 상태
|
// 슬로우 쿼리 로그 상태
|
||||||
const [slowQueries] = await db.execute('SHOW STATUS LIKE "Slow_queries"');
|
const [slowQueries] = await db.execute('SHOW STATUS LIKE "Slow_queries"');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connections: {
|
connections: {
|
||||||
current: parseInt(connections[0]?.Value || 0),
|
current: parseInt(connections[0]?.Value || 0),
|
||||||
@@ -344,7 +357,7 @@ const getPerformanceStats = async () => {
|
|||||||
slowQueries: parseInt(slowQueries[0]?.Value || 0),
|
slowQueries: parseInt(slowQueries[0]?.Value || 0),
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`성능 통계 조회 오류: ${error.message}`);
|
throw new Error(`성능 통계 조회 오류: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function getCurrentUser() {
|
|||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
|
|
||||||
const payloadBase64 = token.split('.')[1];
|
const payloadBase64 = token.split('.')[1];
|
||||||
if (payloadBase64) {
|
if (payloadBase64) {
|
||||||
const payload = JSON.parse(atob(payloadBase64));
|
const payload = JSON.parse(atob(payloadBase64));
|
||||||
@@ -40,7 +40,7 @@ function getCurrentUser() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('토큰에서 사용자 정보 추출 실패:', error);
|
console.log('토큰에서 사용자 정보 추출 실패:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userInfo = localStorage.getItem('user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser');
|
const userInfo = localStorage.getItem('user') || localStorage.getItem('userInfo') || localStorage.getItem('currentUser');
|
||||||
if (userInfo) {
|
if (userInfo) {
|
||||||
@@ -51,7 +51,7 @@ function getCurrentUser() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('localStorage에서 사용자 정보 가져오기 실패:', error);
|
console.log('localStorage에서 사용자 정보 가져오기 실패:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ function getCurrentUser() {
|
|||||||
function showMessage(message, type = 'info') {
|
function showMessage(message, type = 'info') {
|
||||||
const container = document.getElementById('message-container');
|
const container = document.getElementById('message-container');
|
||||||
container.innerHTML = `<div class="message ${type}">${message}</div>`;
|
container.innerHTML = `<div class="message ${type}">${message}</div>`;
|
||||||
|
|
||||||
if (type === 'success') {
|
if (type === 'success') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hideMessage();
|
hideMessage();
|
||||||
@@ -76,7 +76,7 @@ function showSaveResultModal(type, title, message, details = null) {
|
|||||||
const modal = document.getElementById('saveResultModal');
|
const modal = document.getElementById('saveResultModal');
|
||||||
const titleElement = document.getElementById('resultModalTitle');
|
const titleElement = document.getElementById('resultModalTitle');
|
||||||
const contentElement = document.getElementById('resultModalContent');
|
const contentElement = document.getElementById('resultModalContent');
|
||||||
|
|
||||||
// 아이콘 설정
|
// 아이콘 설정
|
||||||
let icon = '';
|
let icon = '';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -92,14 +92,14 @@ function showSaveResultModal(type, title, message, details = null) {
|
|||||||
default:
|
default:
|
||||||
icon = 'ℹ️';
|
icon = 'ℹ️';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 모달 내용 구성
|
// 모달 내용 구성
|
||||||
let content = `
|
let content = `
|
||||||
<div class="result-icon ${type}">${icon}</div>
|
<div class="result-icon ${type}">${icon}</div>
|
||||||
<h3 class="result-title ${type}">${title}</h3>
|
<h3 class="result-title ${type}">${title}</h3>
|
||||||
<p class="result-message">${message}</p>
|
<p class="result-message">${message}</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// 상세 정보가 있으면 추가
|
// 상세 정보가 있으면 추가
|
||||||
if (details && details.length > 0) {
|
if (details && details.length > 0) {
|
||||||
content += `
|
content += `
|
||||||
@@ -111,20 +111,20 @@ function showSaveResultModal(type, title, message, details = null) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
titleElement.textContent = '저장 결과';
|
titleElement.textContent = '저장 결과';
|
||||||
contentElement.innerHTML = content;
|
contentElement.innerHTML = content;
|
||||||
modal.style.display = 'flex';
|
modal.style.display = 'flex';
|
||||||
|
|
||||||
// ESC 키로 닫기
|
// ESC 키로 닫기
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function (e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
closeSaveResultModal();
|
closeSaveResultModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 배경 클릭으로 닫기
|
// 배경 클릭으로 닫기
|
||||||
modal.addEventListener('click', function(e) {
|
modal.addEventListener('click', function (e) {
|
||||||
if (e.target === modal) {
|
if (e.target === modal) {
|
||||||
closeSaveResultModal();
|
closeSaveResultModal();
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ function showSaveResultModal(type, title, message, details = null) {
|
|||||||
function closeSaveResultModal() {
|
function closeSaveResultModal() {
|
||||||
const modal = document.getElementById('saveResultModal');
|
const modal = document.getElementById('saveResultModal');
|
||||||
modal.style.display = 'none';
|
modal.style.display = 'none';
|
||||||
|
|
||||||
// 이벤트 리스너 제거
|
// 이벤트 리스너 제거
|
||||||
document.removeEventListener('keydown', closeSaveResultModal);
|
document.removeEventListener('keydown', closeSaveResultModal);
|
||||||
}
|
}
|
||||||
@@ -155,10 +155,10 @@ function goToStep(stepNumber) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 진행 단계 표시 업데이트
|
// 진행 단계 표시 업데이트
|
||||||
updateProgressSteps(stepNumber);
|
updateProgressSteps(stepNumber);
|
||||||
|
|
||||||
currentStep = stepNumber;
|
currentStep = stepNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ function updateProgressSteps(currentStepNumber) {
|
|||||||
const progressStep = document.getElementById(`progressStep${i}`);
|
const progressStep = document.getElementById(`progressStep${i}`);
|
||||||
if (progressStep) {
|
if (progressStep) {
|
||||||
progressStep.classList.remove('active', 'completed');
|
progressStep.classList.remove('active', 'completed');
|
||||||
|
|
||||||
if (i < currentStepNumber) {
|
if (i < currentStepNumber) {
|
||||||
progressStep.classList.add('completed');
|
progressStep.classList.add('completed');
|
||||||
} else if (i === currentStepNumber) {
|
} else if (i === currentStepNumber) {
|
||||||
@@ -189,14 +189,14 @@ async function loadData() {
|
|||||||
await loadWorkTypes();
|
await loadWorkTypes();
|
||||||
await loadWorkStatusTypes();
|
await loadWorkStatusTypes();
|
||||||
await loadErrorTypes();
|
await loadErrorTypes();
|
||||||
|
|
||||||
console.log('로드된 작업자 수:', workers.length);
|
console.log('로드된 작업자 수:', workers.length);
|
||||||
console.log('로드된 프로젝트 수:', projects.length);
|
console.log('로드된 프로젝트 수:', projects.length);
|
||||||
console.log('작업 유형 수:', workTypes.length);
|
console.log('작업 유형 수:', workTypes.length);
|
||||||
|
|
||||||
populateWorkerGrid();
|
populateWorkerGrid();
|
||||||
hideMessage();
|
hideMessage();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('데이터 로드 실패:', error);
|
console.error('데이터 로드 실패:', error);
|
||||||
showMessage('데이터 로드 중 오류가 발생했습니다: ' + error.message, 'error');
|
showMessage('데이터 로드 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||||
@@ -206,7 +206,8 @@ async function loadData() {
|
|||||||
async function loadWorkers() {
|
async function loadWorkers() {
|
||||||
try {
|
try {
|
||||||
console.log('Workers API 호출 중... (통합 API 사용)');
|
console.log('Workers API 호출 중... (통합 API 사용)');
|
||||||
const data = await window.apiCall(`${window.API}/workers`);
|
// 활성 작업자 1000명까지 조회 (서버 사이드 필터링 적용)
|
||||||
|
const data = await window.apiCall(`${window.API}/workers?status=active&limit=1000`);
|
||||||
const allWorkers = Array.isArray(data) ? data : (data.data || data.workers || []);
|
const allWorkers = Array.isArray(data) ? data : (data.data || data.workers || []);
|
||||||
|
|
||||||
// 활성화된 작업자만 필터링
|
// 활성화된 작업자만 필터링
|
||||||
@@ -245,9 +246,9 @@ async function loadWorkTypes() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ 작업 유형 API 사용 불가, 기본값 사용');
|
console.log('⚠️ 작업 유형 API 사용 불가, 기본값 사용');
|
||||||
workTypes = [
|
workTypes = [
|
||||||
{id: 1, name: 'Base'},
|
{ id: 1, name: 'Base' },
|
||||||
{id: 2, name: 'Vessel'},
|
{ id: 2, name: 'Vessel' },
|
||||||
{id: 3, name: 'Piping'}
|
{ id: 3, name: 'Piping' }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,8 +265,8 @@ async function loadWorkStatusTypes() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ 업무 상태 유형 API 사용 불가, 기본값 사용');
|
console.log('⚠️ 업무 상태 유형 API 사용 불가, 기본값 사용');
|
||||||
workStatusTypes = [
|
workStatusTypes = [
|
||||||
{id: 1, name: '정규'},
|
{ id: 1, name: '정규' },
|
||||||
{id: 2, name: '에러'}
|
{ id: 2, name: '에러' }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,10 +283,10 @@ async function loadErrorTypes() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('⚠️ 에러 유형 API 사용 불가, 기본값 사용');
|
console.log('⚠️ 에러 유형 API 사용 불가, 기본값 사용');
|
||||||
errorTypes = [
|
errorTypes = [
|
||||||
{id: 1, name: '설계미스'},
|
{ id: 1, name: '설계미스' },
|
||||||
{id: 2, name: '외주작업 불량'},
|
{ id: 2, name: '외주작업 불량' },
|
||||||
{id: 3, name: '입고지연'},
|
{ id: 3, name: '입고지연' },
|
||||||
{id: 4, name: '작업 불량'}
|
{ id: 4, name: '작업 불량' }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,18 +295,18 @@ async function loadErrorTypes() {
|
|||||||
function populateWorkerGrid() {
|
function populateWorkerGrid() {
|
||||||
const grid = document.getElementById('workerGrid');
|
const grid = document.getElementById('workerGrid');
|
||||||
grid.innerHTML = '';
|
grid.innerHTML = '';
|
||||||
|
|
||||||
workers.forEach(worker => {
|
workers.forEach(worker => {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.type = 'button';
|
btn.type = 'button';
|
||||||
btn.className = 'worker-card';
|
btn.className = 'worker-card';
|
||||||
btn.textContent = worker.worker_name;
|
btn.textContent = worker.worker_name;
|
||||||
btn.dataset.id = worker.worker_id;
|
btn.dataset.id = worker.worker_id;
|
||||||
|
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
toggleWorkerSelection(worker.worker_id, btn);
|
toggleWorkerSelection(worker.worker_id, btn);
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.appendChild(btn);
|
grid.appendChild(btn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -319,7 +320,7 @@ function toggleWorkerSelection(workerId, btnElement) {
|
|||||||
selectedWorkers.add(workerId);
|
selectedWorkers.add(workerId);
|
||||||
btnElement.classList.add('selected');
|
btnElement.classList.add('selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextBtn = document.getElementById('nextStep2');
|
const nextBtn = document.getElementById('nextStep2');
|
||||||
nextBtn.disabled = selectedWorkers.size === 0;
|
nextBtn.disabled = selectedWorkers.size === 0;
|
||||||
}
|
}
|
||||||
@@ -331,12 +332,12 @@ function addWorkEntry() {
|
|||||||
console.log('🔧 컨테이너:', container);
|
console.log('🔧 컨테이너:', container);
|
||||||
workEntryCounter++;
|
workEntryCounter++;
|
||||||
console.log('🔧 작업 항목 카운터:', workEntryCounter);
|
console.log('🔧 작업 항목 카운터:', workEntryCounter);
|
||||||
|
|
||||||
const entryDiv = document.createElement('div');
|
const entryDiv = document.createElement('div');
|
||||||
entryDiv.className = 'work-entry';
|
entryDiv.className = 'work-entry';
|
||||||
entryDiv.dataset.id = workEntryCounter;
|
entryDiv.dataset.id = workEntryCounter;
|
||||||
console.log('🔧 생성된 작업 항목 div:', entryDiv);
|
console.log('🔧 생성된 작업 항목 div:', entryDiv);
|
||||||
|
|
||||||
entryDiv.innerHTML = `
|
entryDiv.innerHTML = `
|
||||||
<div class="work-entry-header">
|
<div class="work-entry-header">
|
||||||
<div class="work-entry-title">작업 항목 #${workEntryCounter}</div>
|
<div class="work-entry-title">작업 항목 #${workEntryCounter}</div>
|
||||||
@@ -414,12 +415,12 @@ function addWorkEntry() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
container.appendChild(entryDiv);
|
container.appendChild(entryDiv);
|
||||||
console.log('🔧 작업 항목이 컨테이너에 추가됨');
|
console.log('🔧 작업 항목이 컨테이너에 추가됨');
|
||||||
console.log('🔧 현재 컨테이너 내용:', container.innerHTML.length, '문자');
|
console.log('🔧 현재 컨테이너 내용:', container.innerHTML.length, '문자');
|
||||||
console.log('🔧 현재 .work-entry 개수:', container.querySelectorAll('.work-entry').length);
|
console.log('🔧 현재 .work-entry 개수:', container.querySelectorAll('.work-entry').length);
|
||||||
|
|
||||||
setupWorkEntryEvents(entryDiv);
|
setupWorkEntryEvents(entryDiv);
|
||||||
console.log('🔧 이벤트 설정 완료');
|
console.log('🔧 이벤트 설정 완료');
|
||||||
}
|
}
|
||||||
@@ -430,7 +431,7 @@ function setupWorkEntryEvents(entryDiv) {
|
|||||||
const workStatusSelect = entryDiv.querySelector('.work-status-select');
|
const workStatusSelect = entryDiv.querySelector('.work-status-select');
|
||||||
const errorTypeSection = entryDiv.querySelector('.error-type-section');
|
const errorTypeSection = entryDiv.querySelector('.error-type-section');
|
||||||
const errorTypeSelect = entryDiv.querySelector('.error-type-select');
|
const errorTypeSelect = entryDiv.querySelector('.error-type-select');
|
||||||
|
|
||||||
// 시간 입력 이벤트
|
// 시간 입력 이벤트
|
||||||
timeInput.addEventListener('input', updateTotalHours);
|
timeInput.addEventListener('input', updateTotalHours);
|
||||||
|
|
||||||
@@ -440,7 +441,7 @@ function setupWorkEntryEvents(entryDiv) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
timeInput.value = btn.dataset.hours;
|
timeInput.value = btn.dataset.hours;
|
||||||
updateTotalHours();
|
updateTotalHours();
|
||||||
|
|
||||||
// 버튼 클릭 효과
|
// 버튼 클릭 효과
|
||||||
btn.style.transform = 'scale(0.95)';
|
btn.style.transform = 'scale(0.95)';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -452,11 +453,11 @@ function setupWorkEntryEvents(entryDiv) {
|
|||||||
// 업무 상태 변경 시 에러 유형 섹션 토글
|
// 업무 상태 변경 시 에러 유형 섹션 토글
|
||||||
workStatusSelect.addEventListener('change', (e) => {
|
workStatusSelect.addEventListener('change', (e) => {
|
||||||
const isError = e.target.value === '2'; // 에러 상태 ID가 2라고 가정
|
const isError = e.target.value === '2'; // 에러 상태 ID가 2라고 가정
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
errorTypeSection.classList.add('visible');
|
errorTypeSection.classList.add('visible');
|
||||||
errorTypeSelect.required = true;
|
errorTypeSelect.required = true;
|
||||||
|
|
||||||
// 에러 상태일 때 시각적 피드백
|
// 에러 상태일 때 시각적 피드백
|
||||||
errorTypeSection.style.animation = 'slideDown 0.4s ease-out';
|
errorTypeSection.style.animation = 'slideDown 0.4s ease-out';
|
||||||
} else {
|
} else {
|
||||||
@@ -465,7 +466,7 @@ function setupWorkEntryEvents(entryDiv) {
|
|||||||
errorTypeSelect.value = '';
|
errorTypeSelect.value = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 폼 필드 포커스 효과
|
// 폼 필드 포커스 효과
|
||||||
entryDiv.querySelectorAll('.form-field-group').forEach(group => {
|
entryDiv.querySelectorAll('.form-field-group').forEach(group => {
|
||||||
const input = group.querySelector('select, input');
|
const input = group.querySelector('select, input');
|
||||||
@@ -473,7 +474,7 @@ function setupWorkEntryEvents(entryDiv) {
|
|||||||
input.addEventListener('focus', () => {
|
input.addEventListener('focus', () => {
|
||||||
group.classList.add('focused');
|
group.classList.add('focused');
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('blur', () => {
|
input.addEventListener('blur', () => {
|
||||||
group.classList.remove('focused');
|
group.classList.remove('focused');
|
||||||
});
|
});
|
||||||
@@ -499,15 +500,15 @@ function removeWorkEntry(id) {
|
|||||||
function updateTotalHours() {
|
function updateTotalHours() {
|
||||||
const timeInputs = document.querySelectorAll('.time-input');
|
const timeInputs = document.querySelectorAll('.time-input');
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
|
||||||
timeInputs.forEach(input => {
|
timeInputs.forEach(input => {
|
||||||
const value = parseFloat(input.value) || 0;
|
const value = parseFloat(input.value) || 0;
|
||||||
total += value;
|
total += value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const display = document.getElementById('totalHoursDisplay');
|
const display = document.getElementById('totalHoursDisplay');
|
||||||
display.textContent = `총 작업시간: ${total}시간`;
|
display.textContent = `총 작업시간: ${total}시간`;
|
||||||
|
|
||||||
if (total > 24) {
|
if (total > 24) {
|
||||||
display.style.background = 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)';
|
display.style.background = 'linear-gradient(135deg, #e74c3c 0%, #c0392b 100%)';
|
||||||
display.textContent += ' ⚠️ 24시간 초과';
|
display.textContent += ' ⚠️ 24시간 초과';
|
||||||
@@ -532,7 +533,7 @@ async function saveWorkReport() {
|
|||||||
const entries = document.querySelectorAll('.work-entry');
|
const entries = document.querySelectorAll('.work-entry');
|
||||||
console.log('🔍 찾은 작업 항목들:', entries);
|
console.log('🔍 찾은 작업 항목들:', entries);
|
||||||
console.log('🔍 작업 항목 개수:', entries.length);
|
console.log('🔍 작업 항목 개수:', entries.length);
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
showSaveResultModal(
|
showSaveResultModal(
|
||||||
'error',
|
'error',
|
||||||
@@ -544,16 +545,16 @@ async function saveWorkReport() {
|
|||||||
|
|
||||||
const newWorkEntries = [];
|
const newWorkEntries = [];
|
||||||
console.log('🔍 작업 항목 수집 시작...');
|
console.log('🔍 작업 항목 수집 시작...');
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
console.log('🔍 작업 항목 처리 중:', entry);
|
console.log('🔍 작업 항목 처리 중:', entry);
|
||||||
|
|
||||||
const projectSelect = entry.querySelector('.project-select');
|
const projectSelect = entry.querySelector('.project-select');
|
||||||
const workTypeSelect = entry.querySelector('.work-type-select');
|
const workTypeSelect = entry.querySelector('.work-type-select');
|
||||||
const workStatusSelect = entry.querySelector('.work-status-select');
|
const workStatusSelect = entry.querySelector('.work-status-select');
|
||||||
const errorTypeSelect = entry.querySelector('.error-type-select');
|
const errorTypeSelect = entry.querySelector('.error-type-select');
|
||||||
const timeInput = entry.querySelector('.time-input');
|
const timeInput = entry.querySelector('.time-input');
|
||||||
|
|
||||||
console.log('🔍 선택된 요소들:', {
|
console.log('🔍 선택된 요소들:', {
|
||||||
projectSelect,
|
projectSelect,
|
||||||
workTypeSelect,
|
workTypeSelect,
|
||||||
@@ -561,13 +562,13 @@ async function saveWorkReport() {
|
|||||||
errorTypeSelect,
|
errorTypeSelect,
|
||||||
timeInput
|
timeInput
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectId = projectSelect?.value;
|
const projectId = projectSelect?.value;
|
||||||
const workTypeId = workTypeSelect?.value;
|
const workTypeId = workTypeSelect?.value;
|
||||||
const workStatusId = workStatusSelect?.value;
|
const workStatusId = workStatusSelect?.value;
|
||||||
const errorTypeId = errorTypeSelect?.value;
|
const errorTypeId = errorTypeSelect?.value;
|
||||||
const workHours = timeInput?.value;
|
const workHours = timeInput?.value;
|
||||||
|
|
||||||
console.log('🔍 수집된 값들:', {
|
console.log('🔍 수집된 값들:', {
|
||||||
projectId,
|
projectId,
|
||||||
workTypeId,
|
workTypeId,
|
||||||
@@ -575,7 +576,7 @@ async function saveWorkReport() {
|
|||||||
errorTypeId,
|
errorTypeId,
|
||||||
workHours
|
workHours
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!projectId || !workTypeId || !workStatusId || !workHours) {
|
if (!projectId || !workTypeId || !workStatusId || !workHours) {
|
||||||
showSaveResultModal(
|
showSaveResultModal(
|
||||||
'error',
|
'error',
|
||||||
@@ -593,7 +594,7 @@ async function saveWorkReport() {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workEntry = {
|
const workEntry = {
|
||||||
project_id: parseInt(projectId),
|
project_id: parseInt(projectId),
|
||||||
work_type_id: parseInt(workTypeId),
|
work_type_id: parseInt(workTypeId),
|
||||||
@@ -601,18 +602,18 @@ async function saveWorkReport() {
|
|||||||
error_type_id: errorTypeId ? parseInt(errorTypeId) : null,
|
error_type_id: errorTypeId ? parseInt(errorTypeId) : null,
|
||||||
work_hours: parseFloat(workHours)
|
work_hours: parseFloat(workHours)
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('🔍 생성된 작업 항목:', workEntry);
|
console.log('🔍 생성된 작업 항목:', workEntry);
|
||||||
console.log('🔍 작업 항목 상세:', {
|
console.log('🔍 작업 항목 상세:', {
|
||||||
project_id: workEntry.project_id,
|
project_id: workEntry.project_id,
|
||||||
work_type_id: workEntry.work_type_id,
|
work_type_id: workEntry.work_type_id,
|
||||||
work_status_id: workEntry.work_status_id,
|
work_status_id: workEntry.work_status_id,
|
||||||
error_type_id: workEntry.error_type_id,
|
error_type_id: workEntry.error_type_id,
|
||||||
work_hours: workEntry.work_hours
|
work_hours: workEntry.work_hours
|
||||||
});
|
});
|
||||||
newWorkEntries.push(workEntry);
|
newWorkEntries.push(workEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔍 최종 수집된 작업 항목들:', newWorkEntries);
|
console.log('🔍 최종 수집된 작업 항목들:', newWorkEntries);
|
||||||
console.log('🔍 총 작업 항목 개수:', newWorkEntries.length);
|
console.log('🔍 총 작업 항목 개수:', newWorkEntries.length);
|
||||||
|
|
||||||
@@ -628,7 +629,7 @@ async function saveWorkReport() {
|
|||||||
|
|
||||||
for (const workerId of selectedWorkers) {
|
for (const workerId of selectedWorkers) {
|
||||||
const workerName = workers.find(w => w.worker_id == workerId)?.worker_name || '알 수 없음';
|
const workerName = workers.find(w => w.worker_id == workerId)?.worker_name || '알 수 없음';
|
||||||
|
|
||||||
// 서버가 기대하는 work_entries 배열 형태로 전송
|
// 서버가 기대하는 work_entries 배열 형태로 전송
|
||||||
const requestData = {
|
const requestData = {
|
||||||
report_date: reportDate,
|
report_date: reportDate,
|
||||||
@@ -656,11 +657,11 @@ async function saveWorkReport() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 저장 실패:', error);
|
console.error('❌ 저장 실패:', error);
|
||||||
totalFailed++;
|
totalFailed++;
|
||||||
|
|
||||||
failureDetails.push(`${workerName}: ${error.message}`);
|
failureDetails.push(`${workerName}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 결과 모달 표시
|
// 결과 모달 표시
|
||||||
if (totalSaved > 0 && totalFailed === 0) {
|
if (totalSaved > 0 && totalFailed === 0) {
|
||||||
showSaveResultModal(
|
showSaveResultModal(
|
||||||
@@ -683,14 +684,14 @@ async function saveWorkReport() {
|
|||||||
failureDetails
|
failureDetails
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalSaved > 0) {
|
if (totalSaved > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
refreshTodayWorkers();
|
refreshTodayWorkers();
|
||||||
resetForm();
|
resetForm();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('저장 오류:', error);
|
console.error('저장 오류:', error);
|
||||||
showSaveResultModal(
|
showSaveResultModal(
|
||||||
@@ -709,18 +710,18 @@ async function saveWorkReport() {
|
|||||||
// 폼 초기화
|
// 폼 초기화
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
goToStep(1);
|
goToStep(1);
|
||||||
|
|
||||||
selectedWorkers.clear();
|
selectedWorkers.clear();
|
||||||
document.querySelectorAll('.worker-card.selected').forEach(btn => {
|
document.querySelectorAll('.worker-card.selected').forEach(btn => {
|
||||||
btn.classList.remove('selected');
|
btn.classList.remove('selected');
|
||||||
});
|
});
|
||||||
|
|
||||||
const container = document.getElementById('workEntriesList');
|
const container = document.getElementById('workEntriesList');
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
workEntryCounter = 0;
|
workEntryCounter = 0;
|
||||||
updateTotalHours();
|
updateTotalHours();
|
||||||
|
|
||||||
document.getElementById('nextStep2').disabled = true;
|
document.getElementById('nextStep2').disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,19 +729,19 @@ function resetForm() {
|
|||||||
async function loadTodayWorkers() {
|
async function loadTodayWorkers() {
|
||||||
const section = document.getElementById('dailyWorkersSection');
|
const section = document.getElementById('dailyWorkersSection');
|
||||||
const content = document.getElementById('dailyWorkersContent');
|
const content = document.getElementById('dailyWorkersContent');
|
||||||
|
|
||||||
if (!section || !content) {
|
if (!section || !content) {
|
||||||
console.log('당일 현황 섹션이 HTML에 없습니다.');
|
console.log('당일 현황 섹션이 HTML에 없습니다.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const today = getKoreaToday();
|
const today = getKoreaToday();
|
||||||
const currentUser = getCurrentUser();
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
content.innerHTML = '<div class="loading-spinner">📊 내가 입력한 오늘의 작업 현황을 불러오는 중... (통합 API)</div>';
|
content.innerHTML = '<div class="loading-spinner">📊 내가 입력한 오늘의 작업 현황을 불러오는 중... (통합 API)</div>';
|
||||||
section.style.display = 'block';
|
section.style.display = 'block';
|
||||||
|
|
||||||
// 본인이 입력한 데이터만 조회 (통합 API 사용)
|
// 본인이 입력한 데이터만 조회 (통합 API 사용)
|
||||||
let queryParams = `date=${today}`;
|
let queryParams = `date=${today}`;
|
||||||
if (currentUser?.user_id) {
|
if (currentUser?.user_id) {
|
||||||
@@ -748,21 +749,21 @@ async function loadTodayWorkers() {
|
|||||||
} else if (currentUser?.id) {
|
} else if (currentUser?.id) {
|
||||||
queryParams += `&created_by=${currentUser.id}`;
|
queryParams += `&created_by=${currentUser.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🔒 본인 입력분만 조회 (통합 API): ${API}/daily-work-reports?${queryParams}`);
|
console.log(`🔒 본인 입력분만 조회 (통합 API): ${API}/daily-work-reports?${queryParams}`);
|
||||||
|
|
||||||
const rawData = await window.apiCall(`${window.API}/daily-work-reports?${queryParams}`);
|
const rawData = await window.apiCall(`${window.API}/daily-work-reports?${queryParams}`);
|
||||||
console.log('📊 당일 작업 데이터 (통합 API):', rawData);
|
console.log('📊 당일 작업 데이터 (통합 API):', rawData);
|
||||||
|
|
||||||
let data = [];
|
let data = [];
|
||||||
if (Array.isArray(rawData)) {
|
if (Array.isArray(rawData)) {
|
||||||
data = rawData;
|
data = rawData;
|
||||||
} else if (rawData?.data) {
|
} else if (rawData?.data) {
|
||||||
data = rawData.data;
|
data = rawData.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayMyDailyWorkers(data, today);
|
displayMyDailyWorkers(data, today);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('당일 작업자 로드 오류:', error);
|
console.error('당일 작업자 로드 오류:', error);
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
@@ -777,7 +778,7 @@ async function loadTodayWorkers() {
|
|||||||
// 본인 입력 작업자 현황 표시 (수정/삭제 기능 포함)
|
// 본인 입력 작업자 현황 표시 (수정/삭제 기능 포함)
|
||||||
function displayMyDailyWorkers(data, date) {
|
function displayMyDailyWorkers(data, date) {
|
||||||
const content = document.getElementById('dailyWorkersContent');
|
const content = document.getElementById('dailyWorkersContent');
|
||||||
|
|
||||||
if (!Array.isArray(data) || data.length === 0) {
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
<div class="no-data-message">
|
<div class="no-data-message">
|
||||||
@@ -787,7 +788,7 @@ function displayMyDailyWorkers(data, date) {
|
|||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 작업자별로 데이터 그룹화
|
// 작업자별로 데이터 그룹화
|
||||||
const workerGroups = {};
|
const workerGroups = {};
|
||||||
data.forEach(work => {
|
data.forEach(work => {
|
||||||
@@ -797,10 +798,10 @@ function displayMyDailyWorkers(data, date) {
|
|||||||
}
|
}
|
||||||
workerGroups[workerName].push(work);
|
workerGroups[workerName].push(work);
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalWorkers = Object.keys(workerGroups).length;
|
const totalWorkers = Object.keys(workerGroups).length;
|
||||||
const totalWorks = data.length;
|
const totalWorks = data.length;
|
||||||
|
|
||||||
const headerHtml = `
|
const headerHtml = `
|
||||||
<div class="daily-workers-header">
|
<div class="daily-workers-header">
|
||||||
<h4>📊 내가 입력한 오늘(${date}) 작업 현황 - 총 ${totalWorkers}명, ${totalWorks}개 작업</h4>
|
<h4>📊 내가 입력한 오늘(${date}) 작업 현황 - 총 ${totalWorkers}명, ${totalWorks}개 작업</h4>
|
||||||
@@ -809,12 +810,12 @@ function displayMyDailyWorkers(data, date) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const workersHtml = Object.entries(workerGroups).map(([workerName, works]) => {
|
const workersHtml = Object.entries(workerGroups).map(([workerName, works]) => {
|
||||||
const totalHours = works.reduce((sum, work) => {
|
const totalHours = works.reduce((sum, work) => {
|
||||||
return sum + parseFloat(work.work_hours || 0);
|
return sum + parseFloat(work.work_hours || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// 개별 작업 항목들 (수정/삭제 버튼 포함)
|
// 개별 작업 항목들 (수정/삭제 버튼 포함)
|
||||||
const individualWorksHtml = works.map((work) => {
|
const individualWorksHtml = works.map((work) => {
|
||||||
const projectName = work.project_name || '미지정';
|
const projectName = work.project_name || '미지정';
|
||||||
@@ -823,7 +824,7 @@ function displayMyDailyWorkers(data, date) {
|
|||||||
const workHours = work.work_hours || 0;
|
const workHours = work.work_hours || 0;
|
||||||
const errorTypeName = work.error_type_name || null;
|
const errorTypeName = work.error_type_name || null;
|
||||||
const workId = work.id;
|
const workId = work.id;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="individual-work-item">
|
<div class="individual-work-item">
|
||||||
<div class="work-details-grid">
|
<div class="work-details-grid">
|
||||||
@@ -861,7 +862,7 @@ function displayMyDailyWorkers(data, date) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="worker-status-item">
|
<div class="worker-status-item">
|
||||||
<div class="worker-header">
|
<div class="worker-header">
|
||||||
@@ -874,7 +875,7 @@ function displayMyDailyWorkers(data, date) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
content.innerHTML = headerHtml + '<div class="worker-status-grid">' + workersHtml + '</div>';
|
content.innerHTML = headerHtml + '<div class="worker-status-grid">' + workersHtml + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -882,17 +883,17 @@ function displayMyDailyWorkers(data, date) {
|
|||||||
async function editWorkItem(workId) {
|
async function editWorkItem(workId) {
|
||||||
try {
|
try {
|
||||||
console.log('수정할 작업 ID:', workId);
|
console.log('수정할 작업 ID:', workId);
|
||||||
|
|
||||||
// 1. 기존 데이터 조회 (통합 API 사용)
|
// 1. 기존 데이터 조회 (통합 API 사용)
|
||||||
showMessage('작업 정보를 불러오는 중... (통합 API)', 'loading');
|
showMessage('작업 정보를 불러오는 중... (통합 API)', 'loading');
|
||||||
|
|
||||||
const workData = await window.apiCall(`${window.API}/daily-work-reports/${workId}`);
|
const workData = await window.apiCall(`${window.API}/daily-work-reports/${workId}`);
|
||||||
console.log('수정할 작업 데이터 (통합 API):', workData);
|
console.log('수정할 작업 데이터 (통합 API):', workData);
|
||||||
|
|
||||||
// 2. 수정 모달 표시
|
// 2. 수정 모달 표시
|
||||||
showEditModal(workData);
|
showEditModal(workData);
|
||||||
hideMessage();
|
hideMessage();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('작업 정보 조회 오류:', error);
|
console.error('작업 정보 조회 오류:', error);
|
||||||
showMessage('작업 정보를 불러올 수 없습니다: ' + error.message, 'error');
|
showMessage('작업 정보를 불러올 수 없습니다: ' + error.message, 'error');
|
||||||
@@ -902,7 +903,7 @@ async function editWorkItem(workId) {
|
|||||||
// 수정 모달 표시
|
// 수정 모달 표시
|
||||||
function showEditModal(workData) {
|
function showEditModal(workData) {
|
||||||
editingWorkId = workData.id;
|
editingWorkId = workData.id;
|
||||||
|
|
||||||
const modalHtml = `
|
const modalHtml = `
|
||||||
<div class="edit-modal" id="editModal">
|
<div class="edit-modal" id="editModal">
|
||||||
<div class="edit-modal-content">
|
<div class="edit-modal-content">
|
||||||
@@ -973,9 +974,9 @@ function showEditModal(workData) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||||
|
|
||||||
// 업무 상태 변경 이벤트
|
// 업무 상태 변경 이벤트
|
||||||
document.getElementById('editWorkStatus').addEventListener('change', (e) => {
|
document.getElementById('editWorkStatus').addEventListener('change', (e) => {
|
||||||
const errorTypeGroup = document.getElementById('editErrorTypeGroup');
|
const errorTypeGroup = document.getElementById('editErrorTypeGroup');
|
||||||
@@ -1004,17 +1005,17 @@ async function saveEditedWork() {
|
|||||||
const workStatusId = document.getElementById('editWorkStatus').value;
|
const workStatusId = document.getElementById('editWorkStatus').value;
|
||||||
const errorTypeId = document.getElementById('editErrorType').value;
|
const errorTypeId = document.getElementById('editErrorType').value;
|
||||||
const workHours = document.getElementById('editWorkHours').value;
|
const workHours = document.getElementById('editWorkHours').value;
|
||||||
|
|
||||||
if (!projectId || !workTypeId || !workStatusId || !workHours) {
|
if (!projectId || !workTypeId || !workStatusId || !workHours) {
|
||||||
showMessage('모든 필수 항목을 입력해주세요.', 'error');
|
showMessage('모든 필수 항목을 입력해주세요.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workStatusId === '2' && !errorTypeId) {
|
if (workStatusId === '2' && !errorTypeId) {
|
||||||
showMessage('에러 상태인 경우 에러 유형을 선택해주세요.', 'error');
|
showMessage('에러 상태인 경우 에러 유형을 선택해주세요.', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
project_id: parseInt(projectId),
|
project_id: parseInt(projectId),
|
||||||
work_type_id: parseInt(workTypeId),
|
work_type_id: parseInt(workTypeId),
|
||||||
@@ -1022,20 +1023,20 @@ async function saveEditedWork() {
|
|||||||
error_type_id: errorTypeId ? parseInt(errorTypeId) : null,
|
error_type_id: errorTypeId ? parseInt(errorTypeId) : null,
|
||||||
work_hours: parseFloat(workHours)
|
work_hours: parseFloat(workHours)
|
||||||
};
|
};
|
||||||
|
|
||||||
showMessage('작업을 수정하는 중... (통합 API)', 'loading');
|
showMessage('작업을 수정하는 중... (통합 API)', 'loading');
|
||||||
|
|
||||||
const result = await window.apiCall(`${window.API}/daily-work-reports/${editingWorkId}`, {
|
const result = await window.apiCall(`${window.API}/daily-work-reports/${editingWorkId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(updateData)
|
body: JSON.stringify(updateData)
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ 수정 성공 (통합 API):', result);
|
console.log('✅ 수정 성공 (통합 API):', result);
|
||||||
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success');
|
||||||
|
|
||||||
closeEditModal();
|
closeEditModal();
|
||||||
refreshTodayWorkers();
|
refreshTodayWorkers();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 수정 실패:', error);
|
console.error('❌ 수정 실패:', error);
|
||||||
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||||
@@ -1047,23 +1048,23 @@ async function deleteWorkItem(workId) {
|
|||||||
if (!confirm('정말로 이 작업을 삭제하시겠습니까?\n삭제된 작업은 복구할 수 없습니다.')) {
|
if (!confirm('정말로 이 작업을 삭제하시겠습니까?\n삭제된 작업은 복구할 수 없습니다.')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('삭제할 작업 ID:', workId);
|
console.log('삭제할 작업 ID:', workId);
|
||||||
|
|
||||||
showMessage('작업을 삭제하는 중... (통합 API)', 'loading');
|
showMessage('작업을 삭제하는 중... (통합 API)', 'loading');
|
||||||
|
|
||||||
// 개별 항목 삭제 API 호출 (본인 작성분만 삭제 가능) - 통합 API 사용
|
// 개별 항목 삭제 API 호출 (본인 작성분만 삭제 가능) - 통합 API 사용
|
||||||
const result = await window.apiCall(`${window.API}/daily-work-reports/my-entry/${workId}`, {
|
const result = await window.apiCall(`${window.API}/daily-work-reports/my-entry/${workId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ 삭제 성공 (통합 API):', result);
|
console.log('✅ 삭제 성공 (통합 API):', result);
|
||||||
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success');
|
||||||
|
|
||||||
// 화면 새로고침
|
// 화면 새로고침
|
||||||
refreshTodayWorkers();
|
refreshTodayWorkers();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 삭제 실패:', error);
|
console.error('❌ 삭제 실패:', error);
|
||||||
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error');
|
||||||
@@ -1117,9 +1118,9 @@ async function init() {
|
|||||||
await loadData();
|
await loadData();
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
loadTodayWorkers();
|
loadTodayWorkers();
|
||||||
|
|
||||||
console.log('✅ 시스템 초기화 완료 (통합 API 설정 적용)');
|
console.log('✅ 시스템 초기화 완료 (통합 API 설정 적용)');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('초기화 오류:', error);
|
console.error('초기화 오류:', error);
|
||||||
showMessage('초기화 중 오류가 발생했습니다.', 'error');
|
showMessage('초기화 중 오류가 발생했습니다.', 'error');
|
||||||
|
|||||||
Reference in New Issue
Block a user