- 포털 스케줄 카드에 프로젝트명·job_no 초록 배지 표시 - 이력 카드에 프로젝트명·job_no 초록 배지 표시 - checkinModel.findHistoryByCompany에 LEFT JOIN projects 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
140 lines
6.2 KiB
JavaScript
140 lines
6.2 KiB
JavaScript
/* tkpurchase-partner-history.js - Partner work history */
|
|
|
|
let historyPage = 1;
|
|
const historyLimit = 20;
|
|
|
|
function initPartnerHistory() {
|
|
if (!initAuth()) return;
|
|
|
|
const token = getToken();
|
|
const decoded = decodeToken(token);
|
|
if (!decoded || !decoded.partner_company_id) {
|
|
location.href = '/';
|
|
return;
|
|
}
|
|
|
|
// 기본 날짜: 최근 30일
|
|
const today = new Date();
|
|
const thirtyDaysAgo = new Date(today);
|
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
document.getElementById('filterDateTo').value = toLocalDate(today);
|
|
document.getElementById('filterDateFrom').value = toLocalDate(thirtyDaysAgo);
|
|
|
|
loadHistory();
|
|
}
|
|
|
|
async function loadHistory(page) {
|
|
historyPage = page || 1;
|
|
const dateFrom = document.getElementById('filterDateFrom').value;
|
|
const dateTo = document.getElementById('filterDateTo').value;
|
|
|
|
const params = new URLSearchParams();
|
|
if (dateFrom) params.set('date_from', dateFrom);
|
|
if (dateTo) params.set('date_to', dateTo);
|
|
params.set('page', historyPage);
|
|
params.set('limit', historyLimit);
|
|
|
|
const container = document.getElementById('historyList');
|
|
container.innerHTML = '<p class="text-gray-400 text-center py-8 text-sm">로딩 중...</p>';
|
|
|
|
try {
|
|
const r = await api('/checkins/my-history?' + params.toString());
|
|
const checkins = r.data || [];
|
|
const total = r.total || 0;
|
|
renderHistoryList(checkins);
|
|
renderPagination(total);
|
|
} catch(e) {
|
|
container.innerHTML = '<p class="text-red-400 text-center py-8 text-sm">데이터를 불러올 수 없습니다.</p>';
|
|
}
|
|
}
|
|
|
|
function renderHistoryList(checkins) {
|
|
const container = document.getElementById('historyList');
|
|
|
|
if (!checkins.length) {
|
|
container.innerHTML = `<div class="bg-white rounded-xl shadow-sm p-8 text-center">
|
|
<i class="fas fa-inbox text-gray-300 text-3xl mb-3"></i>
|
|
<p class="text-gray-500 text-sm">조회 기간에 작업 이력이 없습니다.</p>
|
|
</div>`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = checkins.map(c => {
|
|
const checkinDate = formatDate(c.check_in_time);
|
|
const checkinTime = formatTime(c.check_in_time);
|
|
const checkoutTime = c.check_out_time ? formatTime(c.check_out_time) : null;
|
|
const reports = c.reports || [];
|
|
|
|
// 상태 배지
|
|
let statusHtml = '';
|
|
if (!c.check_out_time) {
|
|
statusHtml = '<span class="text-xs px-2 py-0.5 rounded-full bg-amber-100 text-amber-700">진행중</span>';
|
|
} else {
|
|
statusHtml = '<span class="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-700">완료</span>';
|
|
}
|
|
|
|
// 보고 정보
|
|
let reportHtml = '';
|
|
if (reports.length > 0) {
|
|
reportHtml = reports.map(r => {
|
|
const rWorkers = r.workers || [];
|
|
const totalHours = rWorkers.reduce((sum, w) => sum + Number(w.hours_worked || 0), 0);
|
|
const isConfirmed = !!r.confirmed_by;
|
|
const isRejected = !!r.rejected_by;
|
|
const rStatus = isConfirmed
|
|
? '<span class="text-xs text-emerald-600"><i class="fas fa-check-circle"></i> 확인완료</span>'
|
|
: isRejected
|
|
? '<span class="text-xs text-red-600"><i class="fas fa-times-circle"></i> 반려</span>'
|
|
: '<span class="text-xs text-amber-500">미확인</span>';
|
|
|
|
const workersDetail = rWorkers.length > 0
|
|
? `<div class="mt-1 text-xs text-gray-500">${rWorkers.map(w => escapeHtml(w.worker_name) + ' ' + w.hours_worked + 'h').join(', ')}</div>`
|
|
: '';
|
|
|
|
return `<div class="p-2 bg-gray-50 rounded text-sm">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-gray-700">${escapeHtml((r.work_content || '').substring(0, 60))}${(r.work_content || '').length > 60 ? '...' : ''}</span>
|
|
${rStatus}
|
|
</div>
|
|
<div class="text-xs text-gray-400 mt-1">${rWorkers.length}명 · ${totalHours}h</div>
|
|
${workersDetail}
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
return `<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
|
<div class="p-4">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-sm font-semibold text-gray-800">${checkinDate}</span>
|
|
${statusHtml}
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
${c.project_name ? `<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700 text-xs font-medium"><i class="fas fa-project-diagram text-[10px]"></i>${escapeHtml(c.project_name)}${c.job_no ? ' · ' + escapeHtml(c.job_no) : ''}</span>` : ''}
|
|
<span class="text-xs text-gray-500">${escapeHtml(c.workplace_name || '')}</span>
|
|
</div>
|
|
</div>
|
|
${c.work_description ? `<p class="text-sm text-gray-600 mb-2">${escapeHtml(c.work_description)}</p>` : ''}
|
|
<div class="flex gap-4 text-xs text-gray-500 mb-2">
|
|
<span><i class="fas fa-clock mr-1"></i>${checkinTime}${checkoutTime ? ' ~ ' + checkoutTime : ' ~'}</span>
|
|
<span><i class="fas fa-users mr-1"></i>${c.actual_worker_count || 0}명</span>
|
|
</div>
|
|
${reportHtml ? `<div class="space-y-2 mt-3 border-t pt-3">${reportHtml}</div>` : ''}
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function renderPagination(total) {
|
|
const container = document.getElementById('historyPagination');
|
|
const totalPages = Math.ceil(total / historyLimit);
|
|
if (totalPages <= 1) { container.innerHTML = ''; return; }
|
|
|
|
let html = '';
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
const active = i === historyPage;
|
|
html += `<button onclick="loadHistory(${i})" class="px-3 py-1 rounded text-sm ${active ? 'bg-emerald-600 text-white' : 'bg-white text-gray-600 border hover:bg-gray-50'}">${i}</button>`;
|
|
}
|
|
container.innerHTML = html;
|
|
}
|