일일 스크럼 테이블 JavaScript 코드 정리 (index.blade.php로 이동)
This commit is contained in:
@@ -137,234 +137,4 @@ class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition" title="삭제">
|
||||
<div class="px-6 py-4 border-t border-gray-200">
|
||||
{{ $logs->withQueryString()->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<script>
|
||||
// 카드 아코디언 기능
|
||||
let cardOpenAccordionId = null;
|
||||
|
||||
function toggleCardAccordion(logId, event) {
|
||||
// 클릭한 요소가 버튼이면 무시
|
||||
if (event.target.closest('button') || event.target.closest('a')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const card = document.querySelector(`.log-card[data-log-id="${logId}"]`);
|
||||
const accordion = card.querySelector('.card-accordion');
|
||||
const chevron = card.querySelector('.accordion-chevron');
|
||||
|
||||
// 같은 카드를 다시 클릭하면 닫기
|
||||
if (cardOpenAccordionId === logId) {
|
||||
accordion.classList.add('hidden');
|
||||
chevron.classList.remove('rotate-90');
|
||||
card.classList.remove('ring-2', 'ring-blue-500');
|
||||
cardOpenAccordionId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 다른 열린 아코디언 닫기
|
||||
if (cardOpenAccordionId !== null) {
|
||||
const prevCard = document.querySelector(`.log-card[data-log-id="${cardOpenAccordionId}"]`);
|
||||
if (prevCard) {
|
||||
prevCard.querySelector('.card-accordion')?.classList.add('hidden');
|
||||
prevCard.querySelector('.accordion-chevron')?.classList.remove('rotate-90');
|
||||
prevCard.classList.remove('ring-2', 'ring-blue-500');
|
||||
}
|
||||
}
|
||||
|
||||
// 현재 아코디언 열기
|
||||
accordion.classList.remove('hidden');
|
||||
chevron.classList.add('rotate-90');
|
||||
card.classList.add('ring-2', 'ring-blue-500');
|
||||
cardOpenAccordionId = logId;
|
||||
|
||||
// 데이터 로드
|
||||
loadCardAccordionContent(logId);
|
||||
}
|
||||
|
||||
function loadCardAccordionContent(logId) {
|
||||
const contentDiv = document.getElementById(`card-accordion-content-${logId}`);
|
||||
contentDiv.innerHTML = '<div class="text-center py-4 text-gray-500">로딩 중...</div>';
|
||||
|
||||
fetch(`/api/admin/daily-logs/${logId}`, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
renderCardAccordionContent(logId, data.data);
|
||||
} else {
|
||||
contentDiv.innerHTML = '<div class="text-center py-4 text-red-500">데이터 로드 실패</div>';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
contentDiv.innerHTML = '<div class="text-center py-4 text-red-500">데이터 로드 실패</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function renderCardAccordionContent(logId, log) {
|
||||
const contentDiv = document.getElementById(`card-accordion-content-${logId}`);
|
||||
|
||||
const statusColors = {
|
||||
'todo': 'bg-gray-100 text-gray-700',
|
||||
'in_progress': 'bg-yellow-100 text-yellow-700',
|
||||
'done': 'bg-green-100 text-green-700'
|
||||
};
|
||||
const statusLabels = {
|
||||
'todo': '예정',
|
||||
'in_progress': '진행중',
|
||||
'done': '완료'
|
||||
};
|
||||
|
||||
// HTML 이스케이프 헬퍼
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
function nl2br(text) {
|
||||
if (!text) return '';
|
||||
return escapeHtml(text).replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
let entriesHtml = '';
|
||||
if (log.entries && log.entries.length > 0) {
|
||||
// 담당자별로 그룹핑
|
||||
const grouped = {};
|
||||
log.entries.forEach(entry => {
|
||||
const name = entry.assignee_name || '미지정';
|
||||
if (!grouped[name]) {
|
||||
grouped[name] = [];
|
||||
}
|
||||
grouped[name].push(entry);
|
||||
});
|
||||
|
||||
// 담당자별 카드 생성
|
||||
entriesHtml = Object.entries(grouped).map(([assigneeName, entries]) => `
|
||||
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||
<!-- 담당자 헤더 -->
|
||||
<div class="px-3 py-2 bg-gray-100 border-b border-gray-200 flex items-center justify-between">
|
||||
<span class="text-sm font-semibold text-gray-900">${escapeHtml(assigneeName)}</span>
|
||||
<span class="text-xs text-gray-500">${entries.length}건</span>
|
||||
</div>
|
||||
<!-- 항목 목록 -->
|
||||
<div class="divide-y divide-gray-100">
|
||||
${entries.map(entry => `
|
||||
<div class="p-3 hover:bg-gray-50" data-entry-id="${entry.id}">
|
||||
<div class="flex items-start gap-2">
|
||||
<span class="px-1.5 py-0.5 text-[10px] rounded shrink-0 ${statusColors[entry.status]}">${statusLabels[entry.status]}</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm text-gray-700">${nl2br(entry.content)}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-0.5 shrink-0">
|
||||
${entry.status !== 'todo' ? `
|
||||
<button onclick="updateCardEntryStatus(${logId}, ${entry.id}, 'todo')" class="p-1 text-gray-400 hover:bg-gray-100 rounded" title="예정">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" stroke-width="2"/></svg>
|
||||
</button>` : ''}
|
||||
${entry.status !== 'in_progress' ? `
|
||||
<button onclick="updateCardEntryStatus(${logId}, ${entry.id}, 'in_progress')" class="p-1 text-yellow-500 hover:bg-yellow-50 rounded" title="진행중">
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/></svg>
|
||||
</button>` : ''}
|
||||
${entry.status !== 'done' ? `
|
||||
<button onclick="updateCardEntryStatus(${logId}, ${entry.id}, 'done')" class="p-1 text-green-500 hover:bg-green-50 rounded" title="완료">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
</button>` : ''}
|
||||
<button onclick="deleteCardEntry(${logId}, ${entry.id})" class="p-1 text-red-400 hover:bg-red-50 rounded" title="삭제">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
entriesHtml = '<div class="text-center py-4 text-gray-400">등록된 항목이 없습니다.</div>';
|
||||
}
|
||||
|
||||
// 요약 섹션 (전체 내용)
|
||||
const summaryHtml = log.summary ? `
|
||||
<div class="mb-4 p-3 bg-white rounded-lg border border-gray-200">
|
||||
<div class="text-xs font-medium text-gray-500 mb-1">요약</div>
|
||||
<div class="text-sm text-gray-700">${nl2br(log.summary)}</div>
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
contentDiv.innerHTML = `
|
||||
<div class="space-y-3">
|
||||
${summaryHtml}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
${entriesHtml}
|
||||
</div>
|
||||
<div class="pt-3 border-t border-gray-200 flex justify-between items-center">
|
||||
<button onclick="openQuickAddCardEntry(${logId})" class="text-sm text-blue-600 hover:text-blue-800 font-medium">
|
||||
+ 항목 추가
|
||||
</button>
|
||||
<button onclick="editLog(${logId})" class="text-sm text-indigo-600 hover:text-indigo-800 font-medium">
|
||||
전체 수정
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function updateCardEntryStatus(logId, entryId, status) {
|
||||
fetch(`/api/admin/daily-logs/entries/${entryId}/status`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
||||
},
|
||||
body: JSON.stringify({ status })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
loadCardAccordionContent(logId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCardEntry(logId, entryId) {
|
||||
showConfirm('이 항목을 삭제하시겠습니까?', () => {
|
||||
fetch(`/api/admin/daily-logs/entries/${entryId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
loadCardAccordionContent(logId);
|
||||
}
|
||||
});
|
||||
}, { title: '항목 삭제', icon: 'warning' });
|
||||
}
|
||||
|
||||
function openQuickAddCardEntry(logId) {
|
||||
// 부모 페이지의 openQuickAddModal 함수 사용 (prompt 대신 모달에서 담당자 입력)
|
||||
if (typeof openQuickAddModal === 'function') {
|
||||
openQuickAddModal(logId);
|
||||
} else {
|
||||
showToast('모달을 열 수 없습니다. 페이지를 새로고침해주세요.', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
// 주간 타임라인에서 카드로 스크롤
|
||||
function scrollToCard(logId) {
|
||||
const card = document.querySelector(`.log-card[data-log-id="${logId}"]`);
|
||||
if (card) {
|
||||
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
setTimeout(() => {
|
||||
const fakeEvent = { target: card };
|
||||
toggleCardAccordion(logId, fakeEvent);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endif
|
||||
Reference in New Issue
Block a user