- DB: 유니크 제약 제거, report_seq 컬럼, work_report_workers 테이블 - API: 트랜잭션 기반 다건 생성/수정, 작업자 CRUD, 요약/엑셀 엔드포인트 - 협력업체 포탈: 다건 보고 UI, 작업자+시간 입력(자동완성), 수정 기능 - 업무현황 페이지: 보고순번/작업자 상세 표시 - 종합 페이지(NEW): 업체별/프로젝트별 취합, 엑셀 추출 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
442 lines
21 KiB
JavaScript
442 lines
21 KiB
JavaScript
/* tkpurchase-partner-portal.js - Partner portal logic */
|
|
|
|
let portalSchedules = [];
|
|
let portalCheckins = {};
|
|
let partnerCompanyId = null;
|
|
let companyWorkersCache = null; // 작업자 목록 캐시
|
|
let editingReportId = null; // 수정 모드일 때 보고 ID
|
|
|
|
async function loadMySchedules() {
|
|
try {
|
|
const r = await api('/schedules/my');
|
|
portalSchedules = r.data || [];
|
|
} catch(e) {
|
|
console.warn('Load schedules error:', e);
|
|
portalSchedules = [];
|
|
}
|
|
}
|
|
|
|
async function loadMyCheckins() {
|
|
try {
|
|
const r = await api('/checkins/my');
|
|
const list = r.data || [];
|
|
portalCheckins = {};
|
|
list.forEach(c => {
|
|
if (c.schedule_id) portalCheckins[c.schedule_id] = c;
|
|
});
|
|
} catch(e) {
|
|
console.warn('Load checkins error:', e);
|
|
portalCheckins = {};
|
|
}
|
|
}
|
|
|
|
async function loadCompanyWorkers() {
|
|
if (companyWorkersCache) return companyWorkersCache;
|
|
try {
|
|
const r = await api('/partners/' + partnerCompanyId + '/workers');
|
|
companyWorkersCache = (r.data || []).filter(w => w.is_active !== 0);
|
|
return companyWorkersCache;
|
|
} catch(e) {
|
|
console.warn('Load workers error:', e);
|
|
companyWorkersCache = [];
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async function renderScheduleCards() {
|
|
await Promise.all([loadMySchedules(), loadMyCheckins()]);
|
|
|
|
const container = document.getElementById('scheduleCards');
|
|
const noMsg = document.getElementById('noScheduleMessage');
|
|
|
|
if (!portalSchedules.length) {
|
|
container.innerHTML = '';
|
|
noMsg.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
noMsg.classList.add('hidden');
|
|
|
|
container.innerHTML = portalSchedules.map(s => {
|
|
const checkin = portalCheckins[s.id];
|
|
const isCheckedIn = checkin && !checkin.check_out_time;
|
|
const isCheckedOut = checkin && checkin.check_out_time;
|
|
const reportCount = checkin ? (parseInt(checkin.work_report_count) || 0) : 0;
|
|
|
|
// Step indicators
|
|
const step1Class = checkin ? 'text-emerald-600' : 'text-gray-400';
|
|
const step2Class = isCheckedIn || isCheckedOut ? 'text-emerald-600' : 'text-gray-400';
|
|
const step3Class = isCheckedOut ? 'text-emerald-600' : 'text-gray-400';
|
|
|
|
return `<div class="bg-white rounded-xl shadow-sm overflow-hidden">
|
|
<!-- 일정 정보 -->
|
|
<div class="p-5 border-b">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h3 class="text-base font-semibold text-gray-800">${escapeHtml(s.workplace_name || '작업장 미지정')}</h3>
|
|
<span class="text-xs text-gray-500">${formatDate(s.start_date) === formatDate(s.end_date) ? formatDate(s.start_date) : formatDate(s.start_date) + ' ~ ' + formatDate(s.end_date)}</span>
|
|
</div>
|
|
${s.work_description ? `<p class="text-sm text-gray-600 mb-2">${escapeHtml(s.work_description)}</p>` : ''}
|
|
<div class="flex gap-4 text-xs text-gray-500">
|
|
<span><i class="fas fa-users mr-1"></i>예상 ${s.expected_workers || 0}명</span>
|
|
${s.notes ? `<span><i class="fas fa-sticky-note mr-1"></i>${escapeHtml(s.notes)}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3-step 진행 표시 -->
|
|
<div class="px-5 py-3 bg-gray-50 border-b">
|
|
<div class="flex items-center justify-between text-xs">
|
|
<div class="flex items-center gap-1 ${step1Class}">
|
|
<i class="fas ${checkin ? 'fa-check-circle' : 'fa-circle'}"></i>
|
|
<span>1. 작업 시작</span>
|
|
</div>
|
|
<div class="flex-1 border-t border-gray-300 mx-2"></div>
|
|
<div class="flex items-center gap-1 ${step2Class}">
|
|
<i class="fas ${(isCheckedIn || isCheckedOut) ? 'fa-check-circle' : 'fa-circle'}"></i>
|
|
<span>2. 업무현황${reportCount > 0 ? ' (' + reportCount + '건)' : ''}</span>
|
|
</div>
|
|
<div class="flex-1 border-t border-gray-300 mx-2"></div>
|
|
<div class="flex items-center gap-1 ${step3Class}">
|
|
<i class="fas ${isCheckedOut ? 'fa-check-circle' : 'fa-circle'}"></i>
|
|
<span>3. 작업 종료</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 1: 작업 시작 (체크인) -->
|
|
<div class="p-5 ${checkin ? 'bg-gray-50' : ''}">
|
|
${!checkin ? `
|
|
<div id="checkinForm_${s.id}">
|
|
<h4 class="text-sm font-semibold text-gray-700 mb-3"><i class="fas fa-play-circle text-emerald-500 mr-1"></i>작업 시작</h4>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">실투입 인원 <span class="text-red-400">*</span></label>
|
|
<input type="number" id="checkinWorkers_${s.id}" min="1" value="${s.expected_workers || 1}" class="input-field w-full px-3 py-2 rounded-lg text-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">작업자 명단</label>
|
|
<input type="text" id="checkinNames_${s.id}" class="input-field w-full px-3 py-2 rounded-lg text-sm" placeholder="홍길동, 김철수">
|
|
</div>
|
|
</div>
|
|
<button onclick="doCheckIn(${s.id})" class="px-4 py-2 bg-emerald-600 text-white rounded-lg text-sm hover:bg-emerald-700">
|
|
<i class="fas fa-play mr-1"></i>작업 시작
|
|
</button>
|
|
</div>
|
|
` : `
|
|
<div class="text-sm text-emerald-600 mb-1">
|
|
<i class="fas fa-check-circle mr-1"></i>체크인 완료 (${formatTime(checkin.check_in_time)})
|
|
· ${checkin.actual_worker_count || 0}명
|
|
</div>
|
|
`}
|
|
</div>
|
|
|
|
<!-- Step 2: 업무현황 (체크인 후 표시) -->
|
|
${isCheckedIn ? `
|
|
<div class="p-5 border-t">
|
|
<h4 class="text-sm font-semibold text-gray-700 mb-3"><i class="fas fa-clipboard-list text-blue-500 mr-1"></i>업무현황</h4>
|
|
<!-- 제출된 보고 목록 -->
|
|
<div id="reportsList_${checkin.id}" class="mb-3"></div>
|
|
<!-- 추가/수정 폼 토글 버튼 -->
|
|
<div id="reportFormToggle_${checkin.id}">
|
|
<button onclick="showReportForm(${checkin.id}, ${s.id})" class="px-4 py-2 bg-blue-50 text-blue-600 rounded-lg text-sm hover:bg-blue-100 border border-blue-200">
|
|
<i class="fas fa-plus mr-1"></i>업무현황 추가
|
|
</button>
|
|
</div>
|
|
<!-- 입력 폼 (숨김) -->
|
|
<div id="reportForm_${checkin.id}" class="hidden mt-3"></div>
|
|
</div>
|
|
|
|
<!-- Step 3: 작업 종료 -->
|
|
<div class="p-5 border-t">
|
|
<button onclick="doCheckOut(${checkin.id})" class="w-full px-4 py-3 bg-gray-800 text-white rounded-lg text-sm hover:bg-gray-900 font-medium">
|
|
<i class="fas fa-stop-circle mr-1"></i>작업 종료 (체크아웃)
|
|
</button>
|
|
<p class="text-xs text-gray-400 text-center mt-2">업무현황을 먼저 저장한 후 작업을 종료하세요.</p>
|
|
</div>
|
|
` : ''}
|
|
|
|
${isCheckedOut ? `
|
|
<div class="p-5 border-t bg-gray-50">
|
|
<div class="text-sm text-blue-600">
|
|
<i class="fas fa-check-double mr-1"></i>작업 종료 완료 (${formatTime(checkin.check_out_time)})
|
|
</div>
|
|
${reportCount > 0 ? '<div class="text-xs text-emerald-600 mt-1"><i class="fas fa-clipboard-check mr-1"></i>업무현황 ' + reportCount + '건 제출 완료</div>' : ''}
|
|
</div>
|
|
` : ''}
|
|
</div>`;
|
|
}).join('');
|
|
|
|
// 체크인된 카드의 보고 목록 로드
|
|
for (const s of portalSchedules) {
|
|
const checkin = portalCheckins[s.id];
|
|
if (checkin && !checkin.check_out_time) {
|
|
loadReportsList(checkin.id, s.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function loadReportsList(checkinId, scheduleId) {
|
|
const container = document.getElementById('reportsList_' + checkinId);
|
|
if (!container) return;
|
|
|
|
try {
|
|
const r = await api('/work-reports?checkin_id=' + checkinId + '&limit=50');
|
|
const reports = (r.data || []).filter(rr => rr.checkin_id === checkinId);
|
|
renderReportsList(checkinId, scheduleId, reports);
|
|
} catch(e) {
|
|
container.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
function renderReportsList(checkinId, scheduleId, reports) {
|
|
const container = document.getElementById('reportsList_' + checkinId);
|
|
if (!container) return;
|
|
|
|
if (!reports.length) {
|
|
container.innerHTML = '<p class="text-xs text-gray-400 mb-2">아직 등록된 업무현황이 없습니다.</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = reports.map(r => {
|
|
const workerCount = r.workers ? r.workers.length : 0;
|
|
const totalHours = r.workers ? r.workers.reduce((sum, w) => sum + Number(w.hours_worked || 0), 0) : 0;
|
|
const isConfirmed = !!r.confirmed_by;
|
|
return `<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg mb-2 text-sm">
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="font-medium text-gray-700">보고 #${r.report_seq || 1}</span>
|
|
${isConfirmed ? '<span class="text-xs text-emerald-600"><i class="fas fa-check-circle"></i> 확인완료</span>' : '<span class="text-xs text-amber-500">미확인</span>'}
|
|
</div>
|
|
<div class="text-xs text-gray-500 truncate">${escapeHtml((r.work_content || '').substring(0, 50))}${(r.work_content || '').length > 50 ? '...' : ''}</div>
|
|
<div class="text-xs text-gray-400 mt-1">${workerCount}명 · ${totalHours}h · 진행률 ${r.progress_rate || 0}%</div>
|
|
</div>
|
|
${!isConfirmed ? `<button onclick="openEditReport(${r.id}, ${checkinId}, ${scheduleId})" class="ml-2 px-3 py-1 text-xs bg-white border border-gray-300 text-gray-600 rounded hover:bg-gray-100 flex-shrink-0">
|
|
<i class="fas fa-edit mr-1"></i>수정
|
|
</button>` : ''}
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
async function showReportForm(checkinId, scheduleId, editReport) {
|
|
editingReportId = editReport ? editReport.id : null;
|
|
const formContainer = document.getElementById('reportForm_' + checkinId);
|
|
const toggleBtn = document.getElementById('reportFormToggle_' + checkinId);
|
|
if (!formContainer) return;
|
|
|
|
// 작업자 목록 로드
|
|
const workers = await loadCompanyWorkers();
|
|
const datalistHtml = workers.map(w => `<option value="${escapeHtml(w.worker_name)}">`).join('');
|
|
|
|
// 첫 보고 여부 판단
|
|
const checkin = Object.values(portalCheckins).find(c => c.id === checkinId);
|
|
const isFirstReport = !editReport && checkin && parseInt(checkin.work_report_count) === 0;
|
|
|
|
// 기본 작업자 행
|
|
let existingWorkers = [];
|
|
if (editReport && editReport.workers) {
|
|
existingWorkers = editReport.workers;
|
|
} else if (isFirstReport && currentUser) {
|
|
existingWorkers = [{ worker_name: currentUser.name || '', hours_worked: 8.0 }];
|
|
}
|
|
|
|
formContainer.innerHTML = `
|
|
<div class="space-y-3 border border-blue-200 rounded-lg p-4 bg-blue-50/30">
|
|
<div class="flex items-center justify-between mb-1">
|
|
<h5 class="text-sm font-semibold text-gray-700">${editReport ? '보고 #' + (editReport.report_seq || 1) + ' 수정' : '새 업무현황'}</h5>
|
|
<button onclick="hideReportForm(${checkinId})" class="text-gray-400 hover:text-gray-600 text-xs"><i class="fas fa-times"></i> 취소</button>
|
|
</div>
|
|
<datalist id="workerDatalist_${checkinId}">${datalistHtml}</datalist>
|
|
<!-- 작업자 목록 -->
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">작업자 및 투입시간</label>
|
|
<div id="workerRows_${checkinId}" class="space-y-2">
|
|
${existingWorkers.length > 0 ? existingWorkers.map((w, i) => workerRowHtml(checkinId, i, w)).join('') : workerRowHtml(checkinId, 0, null)}
|
|
</div>
|
|
<button onclick="addWorkerRow(${checkinId})" class="mt-2 text-xs text-blue-600 hover:text-blue-800"><i class="fas fa-plus mr-1"></i>작업자 추가</button>
|
|
</div>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">실투입 인원</label>
|
|
<input type="number" id="reportWorkers_${checkinId}" min="0" value="${editReport ? (editReport.actual_workers || 0) : (checkin ? checkin.actual_worker_count || 0 : 0)}" class="input-field w-full px-3 py-2 rounded-lg text-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">진행률 (%)</label>
|
|
<input type="number" id="reportProgress_${checkinId}" min="0" max="100" value="${editReport ? (editReport.progress_rate || 0) : 0}" class="input-field w-full px-3 py-2 rounded-lg text-sm">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">작업내용 <span class="text-red-400">*</span></label>
|
|
<textarea id="reportContent_${checkinId}" rows="3" class="input-field w-full px-3 py-2 rounded-lg text-sm" placeholder="오늘 수행한 작업 내용">${editReport ? escapeHtml(editReport.work_content || '') : ''}</textarea>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">이슈사항</label>
|
|
<textarea id="reportIssues_${checkinId}" rows="2" class="input-field w-full px-3 py-2 rounded-lg text-sm" placeholder="문제점이나 특이사항">${editReport ? escapeHtml(editReport.issues || '') : ''}</textarea>
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs font-medium text-gray-600 mb-1">향후 계획</label>
|
|
<textarea id="reportNextPlan_${checkinId}" rows="2" class="input-field w-full px-3 py-2 rounded-lg text-sm" placeholder="다음 작업 계획">${editReport ? escapeHtml(editReport.next_plan || '') : ''}</textarea>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button onclick="submitWorkReport(${checkinId}, ${scheduleId})" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
|
|
<i class="fas fa-save mr-1"></i>${editReport ? '수정 저장' : '업무현황 저장'}
|
|
</button>
|
|
<button onclick="hideReportForm(${checkinId})" class="px-4 py-2 bg-gray-100 text-gray-600 rounded-lg text-sm hover:bg-gray-200">취소</button>
|
|
</div>
|
|
</div>`;
|
|
|
|
formContainer.classList.remove('hidden');
|
|
if (toggleBtn) toggleBtn.classList.add('hidden');
|
|
}
|
|
|
|
let workerRowCounter = 100;
|
|
|
|
function workerRowHtml(checkinId, idx, worker) {
|
|
const rowId = 'wr_' + checkinId + '_' + (workerRowCounter++);
|
|
const name = worker ? (worker.worker_name || '') : '';
|
|
const hours = worker ? (worker.hours_worked ?? 8.0) : 8.0;
|
|
const pwId = worker ? (worker.partner_worker_id || '') : '';
|
|
return `<div id="${rowId}" class="flex items-center gap-2 worker-row">
|
|
<input type="text" list="workerDatalist_${checkinId}" value="${escapeHtml(name)}" placeholder="작업자명" class="worker-name input-field flex-1 px-3 py-1.5 rounded-lg text-sm" data-pw-id="${pwId}">
|
|
<input type="number" value="${hours}" step="0.5" min="0" max="24" class="worker-hours input-field w-20 px-2 py-1.5 rounded-lg text-sm text-center" placeholder="시간">
|
|
<span class="text-xs text-gray-400">h</span>
|
|
<button onclick="removeWorkerRow('${rowId}', ${checkinId})" class="text-gray-400 hover:text-red-500 text-sm"><i class="fas fa-times"></i></button>
|
|
</div>`;
|
|
}
|
|
|
|
function addWorkerRow(checkinId) {
|
|
const container = document.getElementById('workerRows_' + checkinId);
|
|
if (!container) return;
|
|
container.insertAdjacentHTML('beforeend', workerRowHtml(checkinId, 0, null));
|
|
}
|
|
|
|
function removeWorkerRow(rowId, checkinId) {
|
|
const row = document.getElementById(rowId);
|
|
if (!row) return;
|
|
const container = document.getElementById('workerRows_' + checkinId);
|
|
// 최소 1행 유지
|
|
if (container && container.querySelectorAll('.worker-row').length <= 1) return;
|
|
row.remove();
|
|
}
|
|
|
|
function collectWorkers(checkinId) {
|
|
const container = document.getElementById('workerRows_' + checkinId);
|
|
if (!container) return [];
|
|
const rows = container.querySelectorAll('.worker-row');
|
|
const workers = [];
|
|
rows.forEach(row => {
|
|
const nameInput = row.querySelector('.worker-name');
|
|
const hoursInput = row.querySelector('.worker-hours');
|
|
const name = nameInput ? nameInput.value.trim() : '';
|
|
if (!name) return;
|
|
// partner_worker_id 매칭
|
|
let pwId = nameInput.dataset.pwId ? parseInt(nameInput.dataset.pwId) : null;
|
|
if (companyWorkersCache) {
|
|
const match = companyWorkersCache.find(w => w.worker_name === name);
|
|
if (match) pwId = match.id;
|
|
}
|
|
workers.push({
|
|
partner_worker_id: pwId || null,
|
|
worker_name: name,
|
|
hours_worked: parseFloat(hoursInput ? hoursInput.value : 8) || 8.0
|
|
});
|
|
});
|
|
return workers;
|
|
}
|
|
|
|
function hideReportForm(checkinId) {
|
|
editingReportId = null;
|
|
const formContainer = document.getElementById('reportForm_' + checkinId);
|
|
const toggleBtn = document.getElementById('reportFormToggle_' + checkinId);
|
|
if (formContainer) formContainer.classList.add('hidden');
|
|
if (toggleBtn) toggleBtn.classList.remove('hidden');
|
|
}
|
|
|
|
async function submitWorkReport(checkinId, scheduleId) {
|
|
const workContent = document.getElementById('reportContent_' + checkinId).value.trim();
|
|
if (!workContent) { showToast('작업내용을 입력하세요', 'error'); return; }
|
|
|
|
const workers = collectWorkers(checkinId);
|
|
|
|
const body = {
|
|
checkin_id: checkinId,
|
|
schedule_id: scheduleId,
|
|
report_date: new Date().toISOString().substring(0, 10),
|
|
actual_workers: parseInt(document.getElementById('reportWorkers_' + checkinId).value) || 0,
|
|
work_content: workContent,
|
|
progress_rate: parseInt(document.getElementById('reportProgress_' + checkinId).value) || 0,
|
|
issues: document.getElementById('reportIssues_' + checkinId).value.trim() || null,
|
|
next_plan: document.getElementById('reportNextPlan_' + checkinId).value.trim() || null,
|
|
workers: workers
|
|
};
|
|
|
|
try {
|
|
if (editingReportId) {
|
|
await api('/work-reports/' + editingReportId, { method: 'PUT', body: JSON.stringify(body) });
|
|
showToast('업무현황이 수정되었습니다');
|
|
} else {
|
|
await api('/work-reports', { method: 'POST', body: JSON.stringify(body) });
|
|
showToast('업무현황이 저장되었습니다');
|
|
}
|
|
editingReportId = null;
|
|
renderScheduleCards();
|
|
} catch(e) {
|
|
showToast(e.message || '저장 실패', 'error');
|
|
}
|
|
}
|
|
|
|
async function openEditReport(reportId, checkinId, scheduleId) {
|
|
try {
|
|
const r = await api('/work-reports/' + reportId);
|
|
const report = r.data || r;
|
|
showReportForm(checkinId, scheduleId, report);
|
|
} catch(e) {
|
|
showToast('보고 정보를 불러올 수 없습니다', 'error');
|
|
}
|
|
}
|
|
|
|
async function doCheckIn(scheduleId) {
|
|
const workerCount = parseInt(document.getElementById('checkinWorkers_' + scheduleId).value) || 1;
|
|
const workerNames = document.getElementById('checkinNames_' + scheduleId).value.trim();
|
|
|
|
const body = {
|
|
schedule_id: scheduleId,
|
|
actual_worker_count: workerCount,
|
|
worker_names: workerNames || null
|
|
};
|
|
|
|
try {
|
|
await api('/checkins', { method: 'POST', body: JSON.stringify(body) });
|
|
showToast('체크인 완료');
|
|
renderScheduleCards();
|
|
} catch(e) {
|
|
showToast(e.message || '체크인 실패', 'error');
|
|
}
|
|
}
|
|
|
|
async function doCheckOut(checkinId) {
|
|
if (!confirm('작업을 종료하시겠습니까? 업무현황을 먼저 저장했는지 확인하세요.')) return;
|
|
try {
|
|
await api('/checkins/' + checkinId + '/checkout', { method: 'PUT' });
|
|
showToast('작업 종료 (체크아웃) 완료');
|
|
renderScheduleCards();
|
|
} catch(e) {
|
|
showToast(e.message || '체크아웃 실패', 'error');
|
|
}
|
|
}
|
|
|
|
function initPartnerPortal() {
|
|
if (!initAuth()) return;
|
|
|
|
// Check if partner account
|
|
const token = getToken();
|
|
const decoded = decodeToken(token);
|
|
if (!decoded || !decoded.partner_company_id) {
|
|
location.href = '/';
|
|
return;
|
|
}
|
|
|
|
partnerCompanyId = decoded.partner_company_id;
|
|
document.getElementById('welcomeCompanyName').textContent = decoded.partner_company_name || decoded.name || '협력업체';
|
|
|
|
renderScheduleCards();
|
|
}
|