/* tkpurchase-schedule.js - Schedule management */
let schedulePage = 1;
const scheduleLimit = 20;
let companySearchTimer = null;
let projectList = [];
async function loadProjects() {
try {
const r = await api('/projects/active');
projectList = r.data || [];
} catch(e) {
console.warn('Load projects error:', e);
projectList = [];
}
}
function populateProjectDropdown(selectId, selectedId) {
const select = document.getElementById(selectId);
select.innerHTML = '';
projectList.forEach(p => {
const label = p.job_no ? `[${p.job_no}] ${p.project_name}` : p.project_name;
select.innerHTML += ``;
});
}
function formatDateRange(startDate, endDate) {
const s = formatDate(startDate);
const e = formatDate(endDate);
if (s === e) return s;
// 같은 연도이면 월/일만 표시
const sd = new Date(startDate);
const ed = new Date(endDate);
if (sd.getFullYear() === ed.getFullYear()) {
return `${sd.getMonth()+1}/${sd.getDate()} ~ ${ed.getMonth()+1}/${ed.getDate()}`;
}
return `${s} ~ ${e}`;
}
async function loadSchedules() {
const company = document.getElementById('filterCompany').value.trim();
const dateFrom = document.getElementById('filterDateFrom').value;
const dateTo = document.getElementById('filterDateTo').value;
const status = document.getElementById('filterStatus').value;
let query = `?page=${schedulePage}&limit=${scheduleLimit}`;
if (company) query += '&company=' + encodeURIComponent(company);
if (dateFrom) query += '&date_from=' + dateFrom;
if (dateTo) query += '&date_to=' + dateTo;
if (status) query += '&status=' + status;
try {
const r = await api('/schedules' + query);
renderScheduleTable(r.data || [], r.total || 0);
} catch(e) {
console.warn('Schedule load error:', e);
document.getElementById('scheduleTableBody').innerHTML = '
| 로딩 실패 |
';
}
}
function renderScheduleTable(list, total) {
const tbody = document.getElementById('scheduleTableBody');
if (!list.length) {
tbody.innerHTML = '| 일정이 없습니다 |
';
document.getElementById('schedulePagination').innerHTML = '';
return;
}
const statusMap = {
requested: ['badge-orange', '신청'],
scheduled: ['badge-amber', '예정'],
in_progress: ['badge-green', '진행중'],
completed: ['badge-blue', '완료'],
cancelled: ['badge-gray', '취소'],
rejected: ['badge-red', '반려']
};
tbody.innerHTML = list.map(s => {
const [cls, label] = statusMap[s.status] || ['badge-gray', s.status];
const canEdit = s.status === 'scheduled';
const isRequest = s.status === 'requested';
const projectLabel = s.project_name ? (s.job_no ? `[${s.job_no}] ${s.project_name}` : s.project_name) : '';
return `
| ${escapeHtml(s.company_name || '')} |
${formatDateRange(s.start_date, s.end_date)} |
${escapeHtml(projectLabel)} |
${escapeHtml(s.work_description || '')} |
${escapeHtml(s.workplace_name || '')} |
${s.expected_workers || 0}명 |
${label} |
${isRequest ? `` : ''}
${(s.status === 'in_progress' || s.status === 'completed') ? `` : ''}
${canEdit ? `
` : ''}
|
`;
}).join('');
// Pagination
const totalPages = Math.ceil(total / scheduleLimit);
renderSchedulePagination(totalPages);
}
function renderSchedulePagination(totalPages) {
const container = document.getElementById('schedulePagination');
if (totalPages <= 1) { container.innerHTML = ''; return; }
let html = '';
if (schedulePage > 1) {
html += ``;
}
for (let i = 1; i <= totalPages; i++) {
if (i === schedulePage) {
html += ``;
} else if (Math.abs(i - schedulePage) <= 2 || i === 1 || i === totalPages) {
html += ``;
} else if (Math.abs(i - schedulePage) === 3) {
html += '...';
}
}
if (schedulePage < totalPages) {
html += ``;
}
container.innerHTML = html;
}
function goToSchedulePage(p) {
schedulePage = p;
loadSchedules();
}
/* ===== Company Autocomplete ===== */
function setupCompanyAutocomplete(inputId, dropdownId, hiddenId) {
const input = document.getElementById(inputId);
const dropdown = document.getElementById(dropdownId);
const hidden = document.getElementById(hiddenId);
input.addEventListener('input', function() {
hidden.value = '';
clearTimeout(companySearchTimer);
const q = this.value.trim();
if (q.length < 1) { dropdown.classList.add('hidden'); return; }
companySearchTimer = setTimeout(async () => {
try {
const r = await api('/partners/search?q=' + encodeURIComponent(q));
const list = r.data || [];
if (!list.length) {
dropdown.innerHTML = '결과 없음
';
} else {
dropdown.innerHTML = list.map(c =>
`${escapeHtml(c.company_name)}
`
).join('');
}
dropdown.classList.remove('hidden');
} catch(e) {
dropdown.classList.add('hidden');
}
}, 300);
});
input.addEventListener('blur', function() {
setTimeout(() => dropdown.classList.add('hidden'), 200);
});
}
function selectCompany(inputId, hiddenId, dropdownId, id, name) {
document.getElementById(inputId).value = name;
document.getElementById(hiddenId).value = id;
document.getElementById(dropdownId).classList.add('hidden');
}
/* ===== Add Schedule ===== */
async function openAddSchedule() {
document.getElementById('addScheduleForm').reset();
document.getElementById('addCompanyId').value = '';
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const tomorrowStr = tomorrow.toISOString().substring(0, 10);
document.getElementById('addStartDate').value = tomorrowStr;
document.getElementById('addEndDate').value = tomorrowStr;
await loadProjects();
populateProjectDropdown('addProject', '');
document.getElementById('addScheduleModal').classList.remove('hidden');
}
function closeAddSchedule() {
document.getElementById('addScheduleModal').classList.add('hidden');
}
async function submitAddSchedule(e) {
e.preventDefault();
const companyId = document.getElementById('addCompanyId').value;
if (!companyId) { showToast('업체를 선택하세요', 'error'); return; }
const startDate = document.getElementById('addStartDate').value;
const endDate = document.getElementById('addEndDate').value;
if (endDate < startDate) { showToast('종료일은 시작일 이후여야 합니다', 'error'); return; }
const projectId = document.getElementById('addProject').value;
const body = {
company_id: parseInt(companyId),
start_date: startDate,
end_date: endDate,
project_id: projectId ? parseInt(projectId) : null,
work_description: document.getElementById('addWorkDescription').value.trim(),
workplace_name: document.getElementById('addWorkplaceName').value.trim(),
expected_workers: parseInt(document.getElementById('addExpectedWorkers').value) || 0,
notes: document.getElementById('addNotes').value.trim()
};
try {
await api('/schedules', { method: 'POST', body: JSON.stringify(body) });
showToast('일정이 등록되었습니다');
closeAddSchedule();
loadSchedules();
} catch(e) {
showToast(e.message || '등록 실패', 'error');
}
}
/* ===== Edit Schedule ===== */
let scheduleCache = {};
async function openEditSchedule(id) {
try {
const r = await api('/schedules/' + id);
const s = r.data || r;
scheduleCache[id] = s;
document.getElementById('editScheduleId').value = id;
document.getElementById('editCompanySearch').value = s.company_name || '';
document.getElementById('editCompanyId').value = s.company_id || '';
document.getElementById('editStartDate').value = formatDate(s.start_date);
document.getElementById('editEndDate').value = formatDate(s.end_date);
document.getElementById('editWorkDescription').value = s.work_description || '';
document.getElementById('editWorkplaceName').value = s.workplace_name || '';
document.getElementById('editExpectedWorkers').value = s.expected_workers || 0;
document.getElementById('editNotes').value = s.notes || '';
await loadProjects();
populateProjectDropdown('editProject', s.project_id || '');
document.getElementById('editScheduleModal').classList.remove('hidden');
} catch(e) {
showToast('일정 정보를 불러올 수 없습니다', 'error');
}
}
function closeEditSchedule() {
document.getElementById('editScheduleModal').classList.add('hidden');
}
async function submitEditSchedule(e) {
e.preventDefault();
const id = document.getElementById('editScheduleId').value;
const companyId = document.getElementById('editCompanyId').value;
if (!companyId) { showToast('업체를 선택하세요', 'error'); return; }
const startDate = document.getElementById('editStartDate').value;
const endDate = document.getElementById('editEndDate').value;
if (endDate < startDate) { showToast('종료일은 시작일 이후여야 합니다', 'error'); return; }
const projectId = document.getElementById('editProject').value;
const body = {
company_id: parseInt(companyId),
start_date: startDate,
end_date: endDate,
project_id: projectId ? parseInt(projectId) : null,
work_description: document.getElementById('editWorkDescription').value.trim(),
workplace_name: document.getElementById('editWorkplaceName').value.trim(),
expected_workers: parseInt(document.getElementById('editExpectedWorkers').value) || 0,
notes: document.getElementById('editNotes').value.trim()
};
try {
await api('/schedules/' + id, { method: 'PUT', body: JSON.stringify(body) });
showToast('일정이 수정되었습니다');
closeEditSchedule();
loadSchedules();
} catch(e) {
showToast(e.message || '수정 실패', 'error');
}
}
/* ===== Delete Schedule ===== */
async function deleteSchedule(id) {
if (!confirm('이 일정을 삭제하시겠습니까?')) return;
try {
await api('/schedules/' + id, { method: 'DELETE' });
showToast('일정이 삭제되었습니다');
loadSchedules();
} catch(e) {
showToast(e.message || '삭제 실패', 'error');
}
}
/* ===== Checkin Management ===== */
async function viewCheckins(scheduleId) {
document.getElementById('checkinModal').classList.remove('hidden');
const body = document.getElementById('checkinModalBody');
body.innerHTML = '로딩 중...
';
try {
const r = await api('/checkins/schedule/' + scheduleId);
const list = r.data || [];
if (!list.length) {
body.innerHTML = '체크인 기록이 없습니다
';
return;
}
body.innerHTML = list.map(c => {
let names = '';
if (c.worker_names) {
try {
const parsed = JSON.parse(c.worker_names);
names = Array.isArray(parsed) ? parsed.join(', ') : parsed;
} catch { names = c.worker_names; }
}
const checkinTime = c.check_in_time ? new Date(c.check_in_time).toLocaleString('ko-KR', { month:'numeric', day:'numeric', hour:'2-digit', minute:'2-digit' }) : '-';
const checkoutTime = c.check_out_time ? new Date(c.check_out_time).toLocaleString('ko-KR', { hour:'2-digit', minute:'2-digit' }) : '작업중';
return `
${escapeHtml(c.company_name || '')} ${checkinTime} ~ ${checkoutTime}
인원: ${c.actual_worker_count || 0}명
작업자: ${escapeHtml(names) || '-'}
`;
}).join('');
} catch(e) {
body.innerHTML = '로딩 실패
';
}
}
function closeCheckinModal() {
document.getElementById('checkinModal').classList.add('hidden');
}
async function deleteCheckin(checkinId, scheduleId) {
if (!confirm('이 체크인을 삭제하시겠습니까?\n관련 업무현황도 함께 삭제됩니다.')) return;
try {
await api('/checkins/' + checkinId, { method: 'DELETE' });
showToast('체크인이 삭제되었습니다');
viewCheckins(scheduleId);
loadSchedules();
} catch(e) {
showToast(e.message || '삭제 실패', 'error');
}
}
async function editCheckinWorkers(checkinId) {
const el = document.getElementById('checkinNames_' + checkinId);
const current = el.textContent.replace('작업자: ', '').trim();
const input = prompt('작업자 명단 (콤마로 구분)', current === '-' ? '' : current);
if (input === null) return;
const names = input.split(/[,,]\s*/).map(n => n.trim()).filter(Boolean);
try {
await api('/checkins/' + checkinId, {
method: 'PUT',
body: JSON.stringify({ worker_names: names.length ? names : null })
});
el.textContent = '작업자: ' + (names.length ? names.join(', ') : '-');
showToast('작업자 명단이 수정되었습니다');
} catch(e) {
showToast(e.message || '수정 실패', 'error');
}
}
/* ===== Approve/Reject Request ===== */
async function openApproveModal(id) {
try {
const r = await api('/schedules/' + id);
const s = r.data || r;
scheduleCache[id] = s;
document.getElementById('approveScheduleId').value = id;
document.getElementById('approveInfo').innerHTML = `
업체: ${escapeHtml(s.company_name || '')}
작업일: ${formatDate(s.start_date)}
예상 인원: ${s.expected_workers || 0}명
작업 내용: ${escapeHtml(s.work_description || '-')}
작업장: ${escapeHtml(s.workplace_name || '-')}
`;
document.getElementById('approveWorkplace').value = s.workplace_name || '';
document.getElementById('approveStartDate').value = formatDate(s.start_date);
document.getElementById('approveEndDate').value = formatDate(s.end_date);
await loadProjects();
populateProjectDropdown('approveProject', '');
document.getElementById('approveModal').classList.remove('hidden');
} catch(e) {
showToast('신청 정보를 불러올 수 없습니다', 'error');
}
}
function closeApproveModal() {
document.getElementById('approveModal').classList.add('hidden');
}
async function doApprove() {
const id = document.getElementById('approveScheduleId').value;
if (!confirm('이 작업 신청을 승인하시겠습니까?')) return;
const projectId = document.getElementById('approveProject').value;
const body = {
project_id: projectId ? parseInt(projectId) : null,
workplace_name: document.getElementById('approveWorkplace').value.trim(),
start_date: document.getElementById('approveStartDate').value,
end_date: document.getElementById('approveEndDate').value
};
try {
await api('/schedules/' + id + '/approve', { method: 'PUT', body: JSON.stringify(body) });
showToast('승인 완료');
closeApproveModal();
loadSchedules();
} catch(e) {
showToast(e.message || '승인 실패', 'error');
}
}
async function doReject() {
const id = document.getElementById('approveScheduleId').value;
if (!confirm('이 작업 신청을 반려하시겠습니까?')) return;
try {
await api('/schedules/' + id + '/reject', { method: 'PUT' });
showToast('반려 완료');
closeApproveModal();
loadSchedules();
} catch(e) {
showToast(e.message || '반려 실패', 'error');
}
}
/* ===== Init ===== */
function initSchedulePage() {
if (!initAuth()) return;
// Set default date range
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
document.getElementById('filterDateFrom').value = firstDay.toISOString().substring(0, 10);
document.getElementById('filterDateTo').value = lastDay.toISOString().substring(0, 10);
// Setup autocomplete for both modals
setupCompanyAutocomplete('addCompanySearch', 'addCompanyDropdown', 'addCompanyId');
setupCompanyAutocomplete('editCompanySearch', 'editCompanyDropdown', 'editCompanyId');
loadSchedules();
}