- API에 Cache-Control: no-store 미들웨어 추가 (304 캐시 문제 해결) - toLocalDate() 유틸 추가, 전체 8개 JS의 toISOString 타임존 버그 수정 - scheduleModel.findAll에 total COUNT 추가, 컨트롤러에서 total 반환 - HTML 캐시 버스팅 ?v=2026031601 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
186 lines
7.7 KiB
JavaScript
186 lines
7.7 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.company_name)}
|
|
</button>`;
|
|
}).join('');
|
|
}
|
|
|
|
function filterCompanyList() {
|
|
const q = document.getElementById('companyFilter').value.trim().toLowerCase();
|
|
const filtered = q ? allCompanies.filter(c => (c.company_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.company_name + ' - 계정 목록' : '계정 목록';
|
|
document.getElementById('addAccountBtn').classList.remove('hidden');
|
|
|
|
// Re-render company list to highlight selection
|
|
filterCompanyList();
|
|
|
|
// Load accounts
|
|
try {
|
|
const r = await api('/partner-accounts/company/' + id);
|
|
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 = toLocalDate(oneYear);
|
|
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 {
|
|
body.partner_company_id = selectedCompanyId;
|
|
await api('/partner-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('/partner-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('/partner-accounts/' + id, { method: 'DELETE' });
|
|
showToast('계정이 비활성화되었습니다');
|
|
selectCompanyForAccounts(selectedCompanyId);
|
|
} catch(e) {
|
|
showToast(e.message || '비활성화 실패', 'error');
|
|
}
|
|
}
|
|
|
|
/* ===== Init ===== */
|
|
function initAccountsPage() {
|
|
if (!initAuth()) return;
|
|
loadCompaniesForAccounts();
|
|
}
|