Files
tk-factory-services/tkpurchase/web/static/js/tkpurchase-accounts.js
Hyungi Ahn b800792152 feat: 구매/안전 시스템 전면 개편 — tkpurchase 개편 + tksafety 신규 + 권한 보강
Phase 1: tkuser 협력업체 CRUD 이관 (읽기전용 → 전체 CRUD)
Phase 2: tkpurchase 개편 — 일용공 신청/확정, 작업일정, 업무현황, 계정관리, 협력업체 포털
Phase 3: tksafety 신규 시스템 — 방문관리 + 안전교육 신고
Phase 4: SSO 인증 보강 (partner_company_id JWT, 만료일 체크), 권한 테이블 기반 접근 제어

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:42:59 +09:00

185 lines
7.8 KiB
JavaScript

/* tkpurchase-accounts.js - Partner account management */
let allCompanies = [];
let selectedCompanyId = null;
async function loadCompaniesForAccounts() {
try {
const r = await api('/partners?limit=200');
allCompanies = r.data || [];
renderCompanyList(allCompanies);
} catch(e) {
console.warn('Load companies error:', e);
document.getElementById('companyList').innerHTML = '<p class="text-red-400 text-center text-sm py-4">로딩 실패</p>';
}
}
function renderCompanyList(list) {
const container = document.getElementById('companyList');
if (!list.length) {
container.innerHTML = '<p class="text-gray-400 text-center text-sm py-4">등록된 업체가 없습니다</p>';
return;
}
container.innerHTML = list.map(c => {
const active = c.id === selectedCompanyId;
return `<button onclick="selectCompanyForAccounts(${c.id})" class="w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${active ? 'bg-emerald-50 text-emerald-700 font-medium' : 'text-gray-700 hover:bg-gray-50'}">
<i class="fas fa-building mr-2 ${active ? 'text-emerald-500' : 'text-gray-400'}"></i>${escapeHtml(c.name)}
</button>`;
}).join('');
}
function filterCompanyList() {
const q = document.getElementById('companyFilter').value.trim().toLowerCase();
const filtered = q ? allCompanies.filter(c => (c.name || '').toLowerCase().includes(q)) : allCompanies;
renderCompanyList(filtered);
}
async function selectCompanyForAccounts(id) {
selectedCompanyId = id;
const company = allCompanies.find(c => c.id === id);
document.getElementById('selectedCompanyName').textContent = company ? company.name + ' - 계정 목록' : '계정 목록';
document.getElementById('addAccountBtn').classList.remove('hidden');
// Re-render company list to highlight selection
filterCompanyList();
// Load accounts
try {
const r = await api('/partners/' + id + '/accounts');
renderAccountList(r.data || []);
} catch(e) {
console.warn('Load accounts error:', e);
document.getElementById('accountList').innerHTML = '<p class="text-red-400 text-center py-4 text-sm">계정 로딩 실패</p>';
}
}
function renderAccountList(list) {
const container = document.getElementById('accountList');
if (!list.length) {
container.innerHTML = '<p class="text-gray-400 text-center py-8 text-sm">등록된 계정이 없습니다</p>';
return;
}
container.innerHTML = `<div class="space-y-3">${list.map(a => {
const isExpired = a.account_expires_at && new Date(a.account_expires_at) < new Date();
const statusBadge = !a.is_active
? '<span class="badge badge-gray">비활성</span>'
: isExpired
? '<span class="badge badge-red">만료</span>'
: '<span class="badge badge-green">활성</span>';
return `<div class="border rounded-lg p-4 ${!a.is_active ? 'bg-gray-50 opacity-60' : ''}">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-emerald-100 rounded-full flex items-center justify-center text-emerald-700 font-semibold">
${(a.name || a.username || '?').charAt(0).toUpperCase()}
</div>
<div>
<div class="text-sm font-medium text-gray-800">${escapeHtml(a.name || '')}</div>
<div class="text-xs text-gray-500">${escapeHtml(a.username || '')}</div>
</div>
</div>
<div class="flex items-center gap-2">
${statusBadge}
<button onclick="openEditAccount(${a.id}, '${escapeHtml(a.name || '')}', '${a.account_expires_at ? formatDate(a.account_expires_at) : ''}')" class="text-blue-600 hover:text-blue-800 text-xs p-1" title="수정">
<i class="fas fa-edit"></i>
</button>
${a.is_active ? `<button onclick="deactivateAccount(${a.id})" class="text-red-500 hover:text-red-700 text-xs p-1" title="비활성화">
<i class="fas fa-user-slash"></i>
</button>` : ''}
</div>
</div>
<div class="mt-2 flex gap-4 text-xs text-gray-500">
<span><i class="fas fa-calendar mr-1"></i>만료: ${a.account_expires_at ? formatDate(a.account_expires_at) : '무기한'}</span>
${a.last_login_at ? `<span><i class="fas fa-sign-in-alt mr-1"></i>최근 로그인: ${formatDateTime(a.last_login_at)}</span>` : ''}
</div>
</div>`;
}).join('')}</div>`;
}
/* ===== Add Account ===== */
function openAddAccount() {
if (!selectedCompanyId) { showToast('업체를 먼저 선택하세요', 'error'); return; }
document.getElementById('addAccountForm').reset();
// Default expiration: 1 year from now
const oneYear = new Date();
oneYear.setFullYear(oneYear.getFullYear() + 1);
document.getElementById('addExpiresAt').value = oneYear.toISOString().substring(0, 10);
document.getElementById('addAccountModal').classList.remove('hidden');
}
function closeAddAccount() {
document.getElementById('addAccountModal').classList.add('hidden');
}
async function submitAddAccount(e) {
e.preventDefault();
const body = {
username: document.getElementById('addUsername').value.trim(),
password: document.getElementById('addPassword').value,
name: document.getElementById('addName').value.trim(),
account_expires_at: document.getElementById('addExpiresAt').value || null
};
if (!body.username || !body.password || !body.name) {
showToast('필수 항목을 입력하세요', 'error');
return;
}
try {
await api('/partners/' + selectedCompanyId + '/accounts', { method: 'POST', body: JSON.stringify(body) });
showToast('계정이 추가되었습니다');
closeAddAccount();
selectCompanyForAccounts(selectedCompanyId);
} catch(e) {
showToast(e.message || '계정 추가 실패', 'error');
}
}
/* ===== Edit Account ===== */
function openEditAccount(id, name, expiresAt) {
document.getElementById('editAccountId').value = id;
document.getElementById('editName').value = name;
document.getElementById('editExpiresAt').value = expiresAt;
document.getElementById('editAccountModal').classList.remove('hidden');
}
function closeEditAccount() {
document.getElementById('editAccountModal').classList.add('hidden');
}
async function submitEditAccount(e) {
e.preventDefault();
const id = document.getElementById('editAccountId').value;
const body = {
name: document.getElementById('editName').value.trim(),
account_expires_at: document.getElementById('editExpiresAt').value || null
};
try {
await api('/partners/' + selectedCompanyId + '/accounts/' + id, { method: 'PUT', body: JSON.stringify(body) });
showToast('계정이 수정되었습니다');
closeEditAccount();
selectCompanyForAccounts(selectedCompanyId);
} catch(e) {
showToast(e.message || '수정 실패', 'error');
}
}
/* ===== Deactivate Account ===== */
async function deactivateAccount(id) {
if (!confirm('이 계정을 비활성화하시겠습니까?')) return;
try {
await api('/partners/' + selectedCompanyId + '/accounts/' + id + '/deactivate', { method: 'PUT' });
showToast('계정이 비활성화되었습니다');
selectCompanyForAccounts(selectedCompanyId);
} catch(e) {
showToast(e.message || '비활성화 실패', 'error');
}
}
/* ===== Init ===== */
function initAccountsPage() {
if (!initAuth()) return;
loadCompaniesForAccounts();
}