fix: 출근체크/근무현황 페이지 버그 수정
- workers API 기본 limit 10 → 100 변경 (작업자 누락 문제 해결) - 작업자 필터 조건 수정 (status='active' + employment_status 체크) - 근태 기록 저장 시 컬럼명 불일치 수정 (attendance_type_id) - 근무현황 페이지에 저장 상태 표시 추가 (✓저장됨) - 디버그 로그 제거 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,12 +26,7 @@ exports.createWorker = asyncHandler(async (req, res) => {
|
|||||||
|
|
||||||
logger.info('작업자 생성 요청', { name: workerData.worker_name, create_account: createAccount });
|
logger.info('작업자 생성 요청', { name: workerData.worker_name, create_account: createAccount });
|
||||||
|
|
||||||
const lastID = await new Promise((resolve, reject) => {
|
const lastID = await workerModel.create(workerData);
|
||||||
workerModel.create(workerData, (err, id) => {
|
|
||||||
if (err) reject(new DatabaseError('작업자 생성 중 오류가 발생했습니다'));
|
|
||||||
else resolve(id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 계정 생성 요청이 있으면 users 테이블에 계정 생성
|
// 계정 생성 요청이 있으면 users 테이블에 계정 생성
|
||||||
if (createAccount && workerData.worker_name) {
|
if (createAccount && workerData.worker_name) {
|
||||||
@@ -73,7 +68,7 @@ exports.createWorker = asyncHandler(async (req, res) => {
|
|||||||
* 전체 작업자 조회 (캐싱 및 페이지네이션 적용)
|
* 전체 작업자 조회 (캐싱 및 페이지네이션 적용)
|
||||||
*/
|
*/
|
||||||
exports.getAllWorkers = asyncHandler(async (req, res) => {
|
exports.getAllWorkers = asyncHandler(async (req, res) => {
|
||||||
const { page = 1, limit = 10, search = '', status = '', department_id = null } = req.query;
|
const { page = 1, limit = 100, search = '', status = '', department_id = null } = req.query;
|
||||||
|
|
||||||
const cacheKey = cache.createKey('workers', 'list', page, limit, search, status, department_id);
|
const cacheKey = cache.createKey('workers', 'list', page, limit, search, status, department_id);
|
||||||
|
|
||||||
@@ -114,12 +109,7 @@ exports.getWorkerById = asyncHandler(async (req, res) => {
|
|||||||
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = await new Promise((resolve, reject) => {
|
const row = await workerModel.getById(id);
|
||||||
workerModel.getById(id, (err, data) => {
|
|
||||||
if (err) reject(new DatabaseError('작업자 조회 중 오류가 발생했습니다'));
|
|
||||||
else resolve(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!row) {
|
if (!row) {
|
||||||
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
||||||
@@ -153,27 +143,14 @@ exports.updateWorker = asyncHandler(async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 먼저 현재 작업자 정보 조회 (계정 여부 확인용)
|
// 먼저 현재 작업자 정보 조회 (계정 여부 확인용)
|
||||||
const currentWorker = await new Promise((resolve, reject) => {
|
const currentWorker = await workerModel.getById(id);
|
||||||
workerModel.getById(id, (err, data) => {
|
|
||||||
if (err) reject(new DatabaseError('작업자 조회 중 오류가 발생했습니다'));
|
|
||||||
else resolve(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!currentWorker) {
|
if (!currentWorker) {
|
||||||
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 작업자 정보 업데이트
|
// 작업자 정보 업데이트
|
||||||
const changes = await new Promise((resolve, reject) => {
|
const changes = await workerModel.update(workerData);
|
||||||
workerModel.update(workerData, (err, affected) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('❌ workerModel.update 에러:', err);
|
|
||||||
reject(new DatabaseError(`작업자 수정 중 오류가 발생했습니다: ${err.message}`));
|
|
||||||
}
|
|
||||||
else resolve(affected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 계정 생성/해제 처리
|
// 계정 생성/해제 처리
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
@@ -281,12 +258,7 @@ exports.removeWorker = asyncHandler(async (req, res) => {
|
|||||||
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
throw new ValidationError('유효하지 않은 작업자 ID입니다');
|
||||||
}
|
}
|
||||||
|
|
||||||
const changes = await new Promise((resolve, reject) => {
|
const changes = await workerModel.remove(id);
|
||||||
workerModel.remove(id, (err, affected) => {
|
|
||||||
if (err) reject(new DatabaseError('작업자 삭제 중 오류가 발생했습니다'));
|
|
||||||
else resolve(affected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (changes === 0) {
|
if (changes === 0) {
|
||||||
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
throw new NotFoundError('작업자를 찾을 수 없습니다');
|
||||||
|
|||||||
@@ -201,13 +201,15 @@ class AttendanceModel {
|
|||||||
const {
|
const {
|
||||||
record_date,
|
record_date,
|
||||||
worker_id,
|
worker_id,
|
||||||
total_work_hours,
|
total_work_hours = 8,
|
||||||
work_attendance_type_id,
|
work_attendance_type_id = 1,
|
||||||
vacation_type_id,
|
vacation_type_id = null,
|
||||||
is_overtime_approved,
|
is_overtime_approved = false,
|
||||||
created_by
|
created_by = 1
|
||||||
} = recordData;
|
} = recordData;
|
||||||
|
|
||||||
|
const attendance_type_id = work_attendance_type_id;
|
||||||
|
|
||||||
// 기존 기록 확인
|
// 기존 기록 확인
|
||||||
const [existing] = await db.execute(
|
const [existing] = await db.execute(
|
||||||
'SELECT id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?',
|
'SELECT id FROM daily_attendance_records WHERE worker_id = ? AND record_date = ?',
|
||||||
@@ -217,17 +219,17 @@ class AttendanceModel {
|
|||||||
if (existing.length > 0) {
|
if (existing.length > 0) {
|
||||||
// 업데이트
|
// 업데이트
|
||||||
const [result] = await db.execute(`
|
const [result] = await db.execute(`
|
||||||
UPDATE daily_attendance_records
|
UPDATE daily_attendance_records
|
||||||
SET
|
SET
|
||||||
total_work_hours = ?,
|
total_work_hours = ?,
|
||||||
work_attendance_type_id = ?,
|
attendance_type_id = ?,
|
||||||
vacation_type_id = ?,
|
vacation_type_id = ?,
|
||||||
is_overtime_approved = ?,
|
is_overtime_approved = ?,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`, [
|
`, [
|
||||||
total_work_hours,
|
total_work_hours,
|
||||||
work_attendance_type_id,
|
attendance_type_id,
|
||||||
vacation_type_id,
|
vacation_type_id,
|
||||||
is_overtime_approved,
|
is_overtime_approved,
|
||||||
existing[0].id
|
existing[0].id
|
||||||
@@ -238,14 +240,14 @@ class AttendanceModel {
|
|||||||
// 생성
|
// 생성
|
||||||
const [result] = await db.execute(`
|
const [result] = await db.execute(`
|
||||||
INSERT INTO daily_attendance_records (
|
INSERT INTO daily_attendance_records (
|
||||||
record_date, worker_id, total_work_hours, work_attendance_type_id,
|
record_date, worker_id, total_work_hours, attendance_type_id,
|
||||||
vacation_type_id, is_overtime_approved, created_by
|
vacation_type_id, is_overtime_approved, created_by
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
`, [
|
`, [
|
||||||
record_date,
|
record_date,
|
||||||
worker_id,
|
worker_id,
|
||||||
total_work_hours,
|
total_work_hours,
|
||||||
work_attendance_type_id,
|
attendance_type_id,
|
||||||
vacation_type_id,
|
vacation_type_id,
|
||||||
is_overtime_approved,
|
is_overtime_approved,
|
||||||
created_by
|
created_by
|
||||||
|
|||||||
@@ -95,12 +95,9 @@ const upsertAttendanceRecordService = async (recordData) => {
|
|||||||
record_date,
|
record_date,
|
||||||
worker_id,
|
worker_id,
|
||||||
total_work_hours,
|
total_work_hours,
|
||||||
attendance_type_id,
|
work_attendance_type_id: attendance_type_id,
|
||||||
vacation_type_id,
|
vacation_type_id,
|
||||||
is_vacation_processed,
|
is_overtime_approved: overtime_approved,
|
||||||
overtime_approved,
|
|
||||||
status,
|
|
||||||
notes,
|
|
||||||
created_by
|
created_by
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ window.VacationCommon = {
|
|||||||
*/
|
*/
|
||||||
async function loadWorkers() {
|
async function loadWorkers() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/workers');
|
const response = await axios.get('/workers?limit=100');
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
window.VacationCommon.workers = response.data.data.filter(w => w.employment_status === 'employed');
|
window.VacationCommon.workers = response.data.data.filter(w => w.employment_status === 'employed');
|
||||||
return window.VacationCommon.workers;
|
return window.VacationCommon.workers;
|
||||||
|
|||||||
@@ -264,7 +264,7 @@
|
|||||||
|
|
||||||
async function loadWorkers() {
|
async function loadWorkers() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/workers');
|
const response = await axios.get('/workers?limit=100');
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
workers = response.data.data.filter(w => w.employment_status === 'employed');
|
workers = response.data.data.filter(w => w.employment_status === 'employed');
|
||||||
|
|
||||||
|
|||||||
@@ -251,12 +251,13 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const [workersRes, checkinRes, recordsRes] = await Promise.all([
|
const [workersRes, checkinRes, recordsRes] = await Promise.all([
|
||||||
axios.get('/workers'),
|
axios.get('/workers?limit=100'),
|
||||||
axios.get(`/attendance/checkin-list?date=${selectedDate}`).catch(() => ({ data: { data: [] } })),
|
axios.get(`/attendance/checkin-list?date=${selectedDate}`).catch(() => ({ data: { data: [] } })),
|
||||||
axios.get(`/attendance/daily-records?date=${selectedDate}`).catch(() => ({ data: { data: [] } }))
|
axios.get(`/attendance/daily-records?date=${selectedDate}`).catch(() => ({ data: { data: [] } }))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
workers = (workersRes.data.data || []).filter(w => w.employment_status === 'employed');
|
const allWorkers = workersRes.data.data || [];
|
||||||
|
workers = allWorkers.filter(w => w.status === 'active' && (!w.employment_status || w.employment_status === 'employed'));
|
||||||
const checkinList = checkinRes.data.data || [];
|
const checkinList = checkinRes.data.data || [];
|
||||||
const records = recordsRes.data.data || [];
|
const records = recordsRes.data.data || [];
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@
|
|||||||
|
|
||||||
async function loadWorkers() {
|
async function loadWorkers() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/workers');
|
const response = await axios.get('/workers?limit=100');
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
workers = response.data.data.filter(w => w.employment_status === 'employed');
|
workers = response.data.data.filter(w => w.employment_status === 'employed');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@
|
|||||||
|
|
||||||
async function loadWorkers() {
|
async function loadWorkers() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/workers');
|
const response = await axios.get('/workers?limit=100');
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
workers = response.data.data.filter(w => w.employment_status === 'employed');
|
workers = response.data.data.filter(w => w.employment_status === 'employed');
|
||||||
|
|
||||||
|
|||||||
@@ -367,11 +367,11 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const [workersRes, recordsRes] = await Promise.all([
|
const [workersRes, recordsRes] = await Promise.all([
|
||||||
axios.get('/workers'),
|
axios.get('/workers?limit=100'),
|
||||||
axios.get(`/attendance/daily-records?date=${selectedDate}`).catch(() => ({ data: { data: [] } }))
|
axios.get(`/attendance/daily-records?date=${selectedDate}`).catch(() => ({ data: { data: [] } }))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
workers = (workersRes.data.data || []).filter(w => w.employment_status === 'employed');
|
workers = (workersRes.data.data || []).filter(w => w.status === 'active' && (!w.employment_status || w.employment_status === 'employed'));
|
||||||
const records = recordsRes.data.data || [];
|
const records = recordsRes.data.data || [];
|
||||||
|
|
||||||
// 출근 체크 데이터가 있는지 확인
|
// 출근 체크 데이터가 있는지 확인
|
||||||
@@ -415,7 +415,8 @@
|
|||||||
isPresent: record.is_present === 1,
|
isPresent: record.is_present === 1,
|
||||||
type: type,
|
type: type,
|
||||||
hours: attendanceTypes.find(t => t.value === type)?.hours || 8,
|
hours: attendanceTypes.find(t => t.value === type)?.hours || 8,
|
||||||
overtimeHours: overtimeHours
|
overtimeHours: overtimeHours,
|
||||||
|
isSaved: record.attendance_type_id != null || record.total_work_hours > 0
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 데이터 없으면 기본값 (출근, 정시근무)
|
// 데이터 없으면 기본값 (출근, 정시근무)
|
||||||
@@ -423,7 +424,8 @@
|
|||||||
isPresent: true,
|
isPresent: true,
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
hours: 8,
|
hours: 8,
|
||||||
overtimeHours: 0
|
overtimeHours: 0,
|
||||||
|
isSaved: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -451,11 +453,12 @@
|
|||||||
const showOvertimeInput = s.type === 'overtime';
|
const showOvertimeInput = s.type === 'overtime';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr class="${isAbsent ? 'absent' : ''}">
|
<tr class="${isAbsent ? 'absent' : ''}" style="${s.isSaved ? 'background:#f0fdf4;' : ''}">
|
||||||
<td>
|
<td>
|
||||||
<div class="worker-cell">
|
<div class="worker-cell">
|
||||||
<span class="worker-dot ${s.isPresent ? 'present' : 'absent'}"></span>
|
<span class="worker-dot ${s.isPresent ? 'present' : 'absent'}"></span>
|
||||||
<span>${w.worker_name}</span>
|
<span>${w.worker_name}</span>
|
||||||
|
${s.isSaved ? '<span style="margin-left:0.5rem;font-size:0.7rem;color:#10b981;">✓저장됨</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>${s.isPresent ? '<span style="color:#10b981">출근</span>' : '<span style="color:#ef4444">결근</span>'}</td>
|
<td>${s.isPresent ? '<span style="color:#10b981">출근</span>' : '<span style="color:#ef4444">결근</span>'}</td>
|
||||||
@@ -636,6 +639,13 @@
|
|||||||
// 성공 - 오버레이 표시
|
// 성공 - 오버레이 표시
|
||||||
showSaveOverlay(ok);
|
showSaveOverlay(ok);
|
||||||
isAlreadySaved = true;
|
isAlreadySaved = true;
|
||||||
|
// 모든 작업자 저장됨 표시
|
||||||
|
workers.forEach(w => {
|
||||||
|
if (workStatus[w.worker_id]) {
|
||||||
|
workStatus[w.worker_id].isSaved = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
render();
|
||||||
updateSaveStatus();
|
updateSaveStatus();
|
||||||
} else if (ok > 0) {
|
} else if (ok > 0) {
|
||||||
showToast(`${ok}명 성공, ${fail}명 실패`, 'error');
|
showToast(`${ok}명 성공, ${fail}명 실패`, 'error');
|
||||||
|
|||||||
@@ -1,210 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>일일순회점검 | (주)테크니컬코리아</title>
|
|
||||||
<link rel="stylesheet" href="/css/design-system.css">
|
|
||||||
<link rel="stylesheet" href="/css/admin-pages.css?v=8">
|
|
||||||
<link rel="stylesheet" href="/css/daily-patrol.css?v=1">
|
|
||||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
|
||||||
<script src="/js/api-base.js"></script>
|
|
||||||
<script src="/js/app-init.js?v=2" defer></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- 네비게이션 바 -->
|
|
||||||
<div id="navbar-container"></div>
|
|
||||||
|
|
||||||
<!-- 메인 레이아웃 -->
|
|
||||||
<div class="page-container">
|
|
||||||
<main class="main-content">
|
|
||||||
<div class="dashboard-main">
|
|
||||||
<!-- 페이지 헤더 -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="page-title-section">
|
|
||||||
<h1 class="page-title">일일순회점검</h1>
|
|
||||||
<p class="page-description">작업장을 순회하며 안전 및 정리정돈 상태를 점검합니다</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 점검 세션 선택 영역 -->
|
|
||||||
<div class="patrol-session-selector">
|
|
||||||
<div class="patrol-date-time">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="patrolDate">점검 일자</label>
|
|
||||||
<input type="date" id="patrolDate" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>점검 시간대</label>
|
|
||||||
<div class="patrol-time-buttons">
|
|
||||||
<button type="button" class="patrol-time-btn active" data-time="morning">오전</button>
|
|
||||||
<button type="button" class="patrol-time-btn" data-time="afternoon">오후</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="categorySelect">공장 선택</label>
|
|
||||||
<select id="categorySelect" class="form-control">
|
|
||||||
<option value="">공장 선택...</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-primary" id="startPatrolBtn" onclick="startPatrol()">
|
|
||||||
순회점검 시작
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- 오늘 점검 현황 요약 -->
|
|
||||||
<div id="todayStatusSummary" class="today-status-summary">
|
|
||||||
<!-- JS에서 렌더링 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 점검 진행 영역 (세션 시작 후 표시) -->
|
|
||||||
<div id="patrolArea" class="patrol-area" style="display: none;">
|
|
||||||
<!-- 세션 정보 -->
|
|
||||||
<div id="sessionInfo" class="session-info-bar">
|
|
||||||
<!-- JS에서 렌더링 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 지도 및 체크리스트 영역 -->
|
|
||||||
<div class="patrol-content">
|
|
||||||
<!-- 작업장 지도 (좌측) -->
|
|
||||||
<div class="patrol-map-section">
|
|
||||||
<div class="map-header">
|
|
||||||
<h3>작업장 지도</h3>
|
|
||||||
<div class="map-legend">
|
|
||||||
<span class="legend-item completed"><span class="dot"></span> 점검완료</span>
|
|
||||||
<span class="legend-item in-progress"><span class="dot"></span> 점검중</span>
|
|
||||||
<span class="legend-item pending"><span class="dot"></span> 미점검</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="patrolMapContainer" class="patrol-map-container">
|
|
||||||
<!-- 지도 이미지 및 작업장 마커 -->
|
|
||||||
</div>
|
|
||||||
<!-- 작업장 목록 (지도 대신 사용 가능) -->
|
|
||||||
<div id="workplaceListContainer" class="workplace-list-container">
|
|
||||||
<!-- 작업장 목록 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 체크리스트 영역 (우측) -->
|
|
||||||
<div class="patrol-checklist-section">
|
|
||||||
<div id="checklistHeader" class="checklist-header">
|
|
||||||
<h3>체크리스트</h3>
|
|
||||||
<p class="checklist-subtitle">작업장을 선택하면 체크리스트가 표시됩니다</p>
|
|
||||||
</div>
|
|
||||||
<div id="checklistContent" class="checklist-content">
|
|
||||||
<!-- 체크리스트 항목들 -->
|
|
||||||
<div class="checklist-placeholder">
|
|
||||||
<p>좌측 지도에서 점검할 작업장을 선택해주세요</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="checklistActions" class="checklist-actions" style="display: none;">
|
|
||||||
<button type="button" class="btn btn-secondary" onclick="saveChecklistDraft()">임시저장</button>
|
|
||||||
<button type="button" class="btn btn-primary" onclick="saveChecklist()">저장 후 다음</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 물품 현황 영역 -->
|
|
||||||
<div id="itemsSection" class="items-section" style="display: none;">
|
|
||||||
<div class="items-header">
|
|
||||||
<h3><span id="selectedWorkplaceName">작업장</span> 물품 현황</h3>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline" onclick="toggleItemEditMode()">
|
|
||||||
<span id="itemEditModeText">편집모드</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="itemsMapContainer" class="items-map-container">
|
|
||||||
<!-- 작업장 상세 지도 및 물품 마커 -->
|
|
||||||
</div>
|
|
||||||
<div id="itemsLegend" class="items-legend">
|
|
||||||
<!-- 물품 유형 범례 -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 순회점검 완료 버튼 -->
|
|
||||||
<div class="patrol-complete-section">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="patrolNotes">특이사항</label>
|
|
||||||
<textarea id="patrolNotes" class="form-control" rows="2" placeholder="순회 중 발견한 특이사항을 기록하세요..."></textarea>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-success btn-lg" onclick="completePatrol()">
|
|
||||||
순회점검 완료
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 물품 추가/수정 모달 -->
|
|
||||||
<div id="itemModal" class="modal-overlay" style="display: none;">
|
|
||||||
<div class="modal-container" style="max-width: 500px;">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 id="itemModalTitle">물품 추가</h2>
|
|
||||||
<button class="btn-close" onclick="closeItemModal()">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form id="itemForm">
|
|
||||||
<input type="hidden" id="itemId">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="itemType">물품 유형 *</label>
|
|
||||||
<select id="itemType" class="form-control" required>
|
|
||||||
<!-- JS에서 옵션 추가 -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="itemName">물품명/설명</label>
|
|
||||||
<input type="text" id="itemName" class="form-control" placeholder="예: A사 용기 10개">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="itemQuantity">수량</label>
|
|
||||||
<input type="number" id="itemQuantity" class="form-control" value="1" min="1">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" onclick="closeItemModal()">취소</button>
|
|
||||||
<button type="button" class="btn btn-danger" id="deleteItemBtn" onclick="deleteItem()" style="display: none;">삭제</button>
|
|
||||||
<button type="button" class="btn btn-primary" onclick="saveItem()">저장</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
||||||
<script type="module">
|
|
||||||
import '/js/api-config.js?v=3';
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
const checkApiConfig = setInterval(() => {
|
|
||||||
if (window.API_BASE_URL) {
|
|
||||||
clearInterval(checkApiConfig);
|
|
||||||
axios.defaults.baseURL = window.API_BASE_URL;
|
|
||||||
const token = localStorage.getItem('token');
|
|
||||||
if (token) {
|
|
||||||
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
axios.interceptors.request.use(
|
|
||||||
config => {
|
|
||||||
const token = localStorage.getItem('token');
|
|
||||||
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
error => Promise.reject(error)
|
|
||||||
);
|
|
||||||
axios.interceptors.response.use(
|
|
||||||
response => response,
|
|
||||||
error => {
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
|
||||||
window.location.href = '/pages/login.html';
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<script src="/js/daily-patrol.js?v=1"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user