feat: v2.2.0 - 중복 카드 문제 해결 및 삭제 기능 개선

### 주요 변경사항

1. 작업 현황 모달 중복 카드 문제 근본 해결
   - monthlyStatusModel.getDailyWorkerStatus() 리팩토링
   - 집계 테이블 대신 daily_work_reports에서 직접 조회
   - GROUP BY로 작업자별 1개 카드 보장

2. 삭제 권한 강화
   - 작업보고서 삭제는 그룹장/시스템/관리자만 가능
   - 권한 없는 사용자는 403 에러 반환

3. 작업 입력 UI 개선
   - 작업 항목 삭제 버튼 스타일 개선 (이모지 + 빨간색)
   - 삭제 버튼 호버 효과 추가

4. 작업 현황 모달에 삭제 기능 추가
   - 관리자/그룹장만 삭제 버튼 표시
   - 작업자의 해당 날짜 전체 작업 삭제 가능

5. 시놀로지 배포 스크립트 추가
   - update.sh: DB 보존하면서 코드만 업데이트
   - 안전한 배포 절차 자동화
This commit is contained in:
Hyungi Ahn
2025-12-02 13:33:24 +09:00
parent a9bce9d20b
commit a2669e08c4
16 changed files with 420 additions and 64 deletions

View File

@@ -351,25 +351,33 @@
}
.remove-work-btn {
width: 40px;
height: 40px;
border-radius: var(--radius-full);
background: linear-gradient(135deg, var(--error-500), var(--error-600));
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: bold;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.4);
transition: all 0.2s ease;
transition: var(--transition-normal);
font-size: var(--text-lg);
box-shadow: var(--shadow-sm);
}
.remove-work-btn:hover {
background: linear-gradient(135deg, var(--error-600), var(--error-700));
transform: scale(1.1) rotate(90deg);
box-shadow: var(--shadow-md);
background: linear-gradient(135deg, #dc2626, #b91c1c);
transform: scale(1.15);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.5);
}
.remove-work-btn:active {
transform: scale(0.95);
}
.work-entry-grid {

View File

@@ -664,6 +664,32 @@
transform: scale(1.05);
}
/* 작업 삭제 버튼 (관리자/그룹장용) */
.btn-delete-worker-work {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
border: none;
padding: 0.4rem 0.6rem;
border-radius: 0.5rem;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s ease;
margin-right: 0.5rem;
}
.btn-delete-worker-work:hover {
background: linear-gradient(135deg, #dc2626, #b91c1c);
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
/* 작업자 액션 버튼 컨테이너 */
.worker-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* 캘린더 페이지 컨테이너 */
.calendar-page-container {
max-width: 1000px;

View File

@@ -334,8 +334,8 @@ function addWorkEntry() {
entryDiv.innerHTML = `
<div class="work-entry-header">
<div class="work-entry-title">작업 항목 #${workEntryCounter}</div>
<button type="button" class="remove-work-btn" onclick="removeWorkEntry(${workEntryCounter})">
<i class="fas fa-times"></i>
<button type="button" class="remove-work-btn" onclick="removeWorkEntry(${workEntryCounter})" title="이 작업 삭제">
</button>
</div>

View File

@@ -591,6 +591,17 @@ async function renderModalDataFromSummary(workers, summary) {
// 작업자 이름의 첫 글자 추출
const initial = worker.workerName ? worker.workerName.charAt(0) : '?';
// 관리자/그룹장 권한 확인
const currentUser = JSON.parse(localStorage.getItem('user') || '{}');
const isAdmin = ['admin', 'system', 'group_leader'].includes(currentUser.access_level || currentUser.role);
// 삭제 버튼 (관리자/그룹장만 표시, 작업이 있는 경우에만)
const deleteBtn = isAdmin && worker.totalWorkCount > 0 ? `
<button class="btn-delete-worker-work" onclick="event.stopPropagation(); deleteWorkerDayWork(${worker.workerId}, '${currentModalDate}', '${worker.workerName}')" title="이 작업자의 해당 날짜 작업 전체 삭제">
🗑️
</button>
` : '';
return `
<div class="worker-card ${statusClass}" onclick="openWorkerModal(${worker.workerId}, '${currentModalDate}')">
<div class="worker-avatar">
@@ -618,6 +629,7 @@ async function renderModalDataFromSummary(workers, summary) {
</div>
</div>
<div class="worker-actions">
${deleteBtn}
<button class="btn-work-entry" onclick="event.stopPropagation(); openWorkerModal(${worker.workerId}, '${currentModalDate}')" title="작업입력">
작업입력
</button>
@@ -813,6 +825,40 @@ function showToast(message, type = 'info') {
}, 3000);
}
// 작업자의 해당 날짜 작업 전체 삭제 (관리자/그룹장용)
async function deleteWorkerDayWork(workerId, date, workerName) {
// 확인 대화상자
const confirmed = confirm(
`⚠️ 정말로 삭제하시겠습니까?\n\n` +
`작업자: ${workerName}\n` +
`날짜: ${date}\n\n` +
`이 작업자의 해당 날짜 모든 작업이 삭제됩니다.\n` +
`삭제된 작업은 복구할 수 없습니다.`
);
if (!confirmed) return;
try {
showToast('작업을 삭제하는 중...', 'info');
// 날짜+작업자별 전체 삭제 API 호출
const result = await window.apiCall(`/daily-work-reports/date/${date}/worker/${workerId}`, 'DELETE');
console.log('✅ 작업 삭제 성공:', result);
showToast(`${workerName}${date} 작업이 삭제되었습니다.`, 'success');
// 모달 데이터 새로고침
await openDailyWorkModal(currentModalDate);
// 캘린더도 새로고침
await loadCalendarData();
} catch (error) {
console.error('❌ 작업 삭제 실패:', error);
showToast(`작업 삭제 실패: ${error.message}`, 'error');
}
}
// 작업자 개별 작업 모달 열기
async function openWorkerModal(workerId, date) {
try {