Files
tk-factory-services/tkpurchase/web/static/js/tkpurchase-dashboard.js
Hyungi Ahn b5b0fa1728 feat: 작업일정 기간 기반 + 프로젝트 연결
- partner_schedules: work_date → start_date/end_date 기간 기반으로 변경
- project_id 컬럼 추가 (projects 테이블 연결, 선택사항)
- 프로젝트 조회 API 추가 (GET /projects/active)
- 일정 조회 시 기간 겹침 조건으로 필터링
- 체크인 시 기간 내 검증 추가
- 프론트엔드: 시작일/종료일 입력 + 프로젝트 선택 드롭다운
- 마이그레이션 SQL 포함 (scripts/migration-schedule-daterange.sql)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 08:18:53 +09:00

83 lines
4.4 KiB
JavaScript

/* tkpurchase-dashboard.js - Dashboard logic */
async function loadDashboardStats() {
try {
const [dlStats, schedules, reports] = await Promise.all([
api('/day-labor/stats'),
api('/schedules?date_from=' + todayStr() + '&date_to=' + todayStr()),
api('/work-reports?confirmed=false&page=1&limit=5')
]);
// Update stat cards
const pending = (dlStats.data || []).find(s => s.status === 'pending');
document.getElementById('statPending').textContent = pending ? pending.cnt : 0;
document.getElementById('statSchedules').textContent = (schedules.data || []).length;
document.getElementById('statUnconfirmed').textContent = (reports.data || []).length;
} catch(e) { console.warn('Dashboard stats error:', e); }
// Load active checkins count separately
try {
const checkins = await api('/checkins?status=checked_in&page=1&limit=1');
document.getElementById('statCheckins').textContent = checkins.total || 0;
} catch(e) { console.warn('Checkins stat error:', e); }
}
async function loadRecentDayLabor() {
try {
const r = await api('/day-labor?page=1&limit=5');
const list = r.data || [];
const c = document.getElementById('recentDayLabor');
if (!list.length) { c.innerHTML = '<p class="text-gray-400 text-center py-4 text-sm">신청 내역이 없습니다</p>'; return; }
const statusMap = { pending: ['bg-amber-50 text-amber-600', '대기'], approved: ['bg-emerald-50 text-emerald-600', '승인'], rejected: ['bg-red-50 text-red-600', '거절'], completed: ['bg-gray-100 text-gray-500', '완료'] };
c.innerHTML = list.map(d => {
const [cls, label] = statusMap[d.status] || ['bg-gray-100 text-gray-500', d.status];
return `<div class="p-3 bg-gray-50 rounded-lg">
<div class="flex items-center justify-between">
<span class="text-sm font-medium">${formatDate(d.work_date)}</span>
<span class="px-2 py-0.5 rounded text-xs ${cls}">${label}</span>
</div>
<div class="text-xs text-gray-500 mt-1">${escapeHtml(d.requester_name || '')} · ${d.worker_count}명 · ${escapeHtml(d.workplace_name || '')}</div>
${d.work_description ? `<div class="text-xs text-gray-400 mt-0.5 truncate">${escapeHtml(d.work_description)}</div>` : ''}
</div>`;
}).join('');
} catch(e) { console.warn(e); }
}
async function loadTodaySchedules() {
try {
const today = todayStr();
const r = await api('/schedules?date_from=' + today + '&date_to=' + today);
const list = r.data || [];
const c = document.getElementById('todaySchedules');
if (!list.length) { c.innerHTML = '<p class="text-gray-400 text-center py-4 text-sm">오늘 일정이 없습니다</p>'; return; }
const statusMap = { scheduled: ['badge-amber', '예정'], in_progress: ['badge-green', '진행중'], completed: ['badge-blue', '완료'], cancelled: ['badge-gray', '취소'] };
c.innerHTML = list.map(s => {
const [cls, label] = statusMap[s.status] || ['badge-gray', s.status];
const dateDisplay = formatDate(s.start_date) === formatDate(s.end_date) ? formatDate(s.start_date) : formatDate(s.start_date) + ' ~ ' + formatDate(s.end_date);
return `<div class="p-3 bg-gray-50 rounded-lg">
<div class="flex items-center justify-between">
<span class="text-sm font-medium">${escapeHtml(s.company_name || '')}</span>
<span class="badge ${cls}">${label}</span>
</div>
<div class="text-xs text-gray-500 mt-1">${dateDisplay} · ${escapeHtml(s.workplace_name || '')} · ${s.expected_workers || 0}명</div>
${s.work_description ? `<div class="text-xs text-gray-400 mt-0.5 truncate">${escapeHtml(s.work_description)}</div>` : ''}
</div>`;
}).join('');
} catch(e) { console.warn(e); }
}
function todayStr() { return new Date().toISOString().substring(0, 10); }
function initDashboard() {
if (!initAuth()) return;
// If partner account, redirect to portal
const token = getToken();
const decoded = decodeToken(token);
if (decoded && decoded.partner_company_id) {
location.href = '/partner-portal.html';
return;
}
loadDashboardStats();
loadRecentDayLabor();
loadTodaySchedules();
}