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:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user