121212
This commit is contained in:
parent
ba0aeb359f
commit
45c2a22040
@ -1,62 +1,90 @@
|
||||
/* Wrapper for Frontend */
|
||||
.app-frontend {
|
||||
height: 100vh;
|
||||
height: 94vh;
|
||||
margin: 3vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #d1d7db;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
|
||||
}
|
||||
.app-frontend .frontend-topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.8rem 2rem;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 0.8rem 1.5rem;
|
||||
background: #f0f2f5;
|
||||
border-bottom: 1px solid #d1d7db;
|
||||
color: #333;
|
||||
height: 60px;
|
||||
}
|
||||
.app-frontend .brand { display: flex; align-items: center; gap: 15px; font-weight: bold; }
|
||||
.app-frontend .brand { display: flex; align-items: center; gap: 10px; font-weight: bold; }
|
||||
.app-frontend .frontend-app-shell {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
.app-frontend .contacts-panel { width: 300px; border-right: 1px solid #e5e7eb; padding: 1rem; background: #fff;}
|
||||
.app-frontend .chat-panel { flex: 1; background: #f8f9fa; display: flex; flex-direction: column;}
|
||||
.app-frontend .frontend-chat-header { padding: 1rem; border-bottom: 1px solid #e5e7eb; background: #fff; }
|
||||
.app-frontend .frontend-chat-body { flex: 1; padding: 1rem; overflow-y: auto; }
|
||||
.app-frontend .frontend-chat-input-wrapper { padding: 1rem; border-top: 1px solid #e5e7eb; background: #fff; }
|
||||
|
||||
/* Wrapper for Admin */
|
||||
.app-admin {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
.app-frontend .contacts-panel {
|
||||
width: 350px;
|
||||
border-right: 1px solid #d1d7db;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.app-admin .topbar {
|
||||
height: 60px;
|
||||
background: #25D366;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.app-frontend .contacts-search-box { padding: 10px; border-bottom: 1px solid #f0f2f5; }
|
||||
.app-frontend .contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.app-frontend .contact-item:hover, .app-frontend .contact-item.active { background: #f5f6f6; }
|
||||
.app-frontend .contact-avatar {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 50%;
|
||||
background: #00a884;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.app-frontend .contact-info { flex: 1; overflow: hidden; }
|
||||
.app-frontend .contact-actions { cursor: pointer; color: #54656f; padding: 5px; }
|
||||
.app-frontend .chat-panel { flex: 1; background: #e5ddd5; display: flex; flex-direction: column;}
|
||||
.app-frontend .frontend-chat-header {
|
||||
padding: 10px 15px;
|
||||
background: #f0f2f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #d1d7db;
|
||||
}
|
||||
.app-admin .brand { display: flex; align-items: center; gap: 15px; }
|
||||
.app-admin .admin-shell { display: flex; flex: 1; background: #f3f4f6; }
|
||||
.app-admin .admin-sidebar { width: 260px; background: white; padding: 20px; border-right: 1px solid #e5e7eb; }
|
||||
.app-admin .admin-sidebar .nav-link {
|
||||
color: #374151;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
.app-frontend .frontend-chat-body { flex: 1; padding: 20px; overflow-y: auto; display: flex; flex-direction: column;}
|
||||
.app-frontend .frontend-chat-input-wrapper { padding: 10px; background: #f0f2f5; }
|
||||
|
||||
/* Message Bubble */
|
||||
.message-bubble {
|
||||
position: relative;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 4px;
|
||||
max-width: 70%;
|
||||
cursor: context-menu;
|
||||
}
|
||||
.app-admin .admin-sidebar .nav-link:hover, .app-admin .admin-sidebar .nav-link.active {
|
||||
background: #ecfdf5;
|
||||
color: #059669;
|
||||
font-weight: 600;
|
||||
.message-bubble .msg-actions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
z-index: 10;
|
||||
}
|
||||
.app-admin .admin-content { flex: 1; padding: 20px; overflow-y: auto; }
|
||||
.app-admin .section-card { background: white; padding: 24px; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
|
||||
.app-admin .stat-card { padding: 20px; border-radius: 12px; background: #f9fafb; border: 1px solid #e5e7eb; }
|
||||
.app-admin .btn-success { background-color: #25D366; border-color: #25D366; color: white; }
|
||||
.app-admin .btn-success:hover { background-color: #128C7E; border-color: #128C7E; }
|
||||
.app-admin .toast-container { position: fixed; bottom: 20px; right: 20px; }
|
||||
.message-bubble:hover .msg-actions { display: block; }
|
||||
.msg-time { font-size: 0.7rem; color: #999; margin-top: 4px; text-align: right; }
|
||||
.dropdown-menu { font-size: 0.9rem; }
|
||||
@ -13,237 +13,158 @@ const apiFetch = async (url, options = {}) => {
|
||||
return res.json();
|
||||
};
|
||||
|
||||
const showToast = (message, type = 'success') => {
|
||||
const container = document.querySelector('.toast-container');
|
||||
if (!container) return;
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast align-items-center text-bg-${type} border-0 show`;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>`;
|
||||
container.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 3200);
|
||||
};
|
||||
|
||||
const initFrontend = () => {
|
||||
const countrySearch = document.getElementById('countrySearch');
|
||||
const countryList = document.getElementById('countryList');
|
||||
const contactsList = document.querySelector('[data-contacts]');
|
||||
const chatForm = document.querySelector('[data-chat-form]');
|
||||
const chatInput = document.querySelector('[data-chat-input]');
|
||||
const chatBody = document.querySelector('[data-chat-body]');
|
||||
const chatTitle = document.querySelector('[data-chat-title]');
|
||||
const countryModal = new bootstrap.Modal(document.getElementById('countryModal'));
|
||||
|
||||
// Emoji Picker
|
||||
const emojiPicker = document.querySelector('emoji-picker');
|
||||
const emojiTrigger = document.querySelector('[data-emoji-trigger]');
|
||||
|
||||
if (emojiPicker && emojiTrigger) {
|
||||
emojiPicker.addEventListener('emoji-click', event => {
|
||||
chatInput.value += event.detail.unicode;
|
||||
emojiPicker.classList.add('d-none');
|
||||
});
|
||||
|
||||
emojiTrigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
emojiPicker.classList.toggle('d-none');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!emojiTrigger.contains(e.target) && !emojiPicker.contains(e.target)) {
|
||||
emojiPicker.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let state = JSON.parse(localStorage.getItem('sms_state') || '{"contacts": {}, "messages": {}}');
|
||||
let currentChatPhone = null;
|
||||
|
||||
const saveState = () => localStorage.setItem('sms_state', JSON.stringify(state));
|
||||
|
||||
const formatTime = (ts) => new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
const renderContacts = () => {
|
||||
contactsList.innerHTML = Object.keys(state.contacts).sort((a,b) => (state.messages[b]?.slice(-1)[0]?.time || 0) - (state.messages[a]?.slice(-1)[0]?.time || 0)).map(phone => `
|
||||
<div class="contact-item ${currentChatPhone === phone ? 'active' : ''}" data-contact="${phone}">
|
||||
<div class="contact-avatar">${phone.substring(phone.length - 2)}</div>
|
||||
<div class="contact-info flex-grow-1" onclick="switchChat('${phone}')">
|
||||
<div class="fw-bold">${phone}</div>
|
||||
<div class="small text-muted text-truncate">${state.messages[phone]?.slice(-1)[0]?.text || ''}</div>
|
||||
</div>
|
||||
<div class="contact-actions dropstart">
|
||||
<a class="text-secondary" data-bs-toggle="dropdown" href="#">▼</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteContact('${phone}')">删除</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="alert('拉黑')">拉黑</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
window.switchChat = (phone) => {
|
||||
currentChatPhone = phone;
|
||||
chatTitle.textContent = phone;
|
||||
chatBody.innerHTML = (state.messages[phone] || []).map((m, idx) => `
|
||||
<div class="mb-2 ${m.type === 'out' ? 'text-end' : 'text-start'}">
|
||||
<div class="message-bubble d-inline-block ${m.type === 'out' ? 'bg-success text-white' : 'bg-white'} p-2 rounded"
|
||||
oncontextmenu="event.preventDefault(); showMsgActions(event, '${phone}', ${idx})">
|
||||
${m.text}
|
||||
<div class="msg-time">${formatTime(m.time)}</div>
|
||||
<div class="msg-actions dropdown">
|
||||
<a href="#" class="text-secondary" data-bs-toggle="dropdown">▼</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#" onclick="msgAction('edit', '${phone}', ${idx})">编辑</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="msgAction('delete', '${phone}', ${idx})">删除</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="msgAction('recall', '${phone}', ${idx})">撤回</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
renderContacts();
|
||||
};
|
||||
|
||||
window.showMsgActions = (e, phone, idx) => {
|
||||
const bubble = e.currentTarget;
|
||||
const dropdown = bubble.querySelector('.dropdown-toggle') || bubble.querySelector('[data-bs-toggle="dropdown"]');
|
||||
if (dropdown) {
|
||||
const bsDropdown = new bootstrap.Dropdown(dropdown);
|
||||
bsDropdown.show();
|
||||
}
|
||||
};
|
||||
|
||||
window.msgAction = (action, phone, idx) => {
|
||||
if (action === 'delete' || action === 'recall') {
|
||||
state.messages[phone].splice(idx, 1);
|
||||
} else if (action === 'edit') {
|
||||
const newText = prompt('编辑消息:', state.messages[phone][idx].text);
|
||||
if (newText) state.messages[phone][idx].text = newText;
|
||||
}
|
||||
saveState();
|
||||
switchChat(phone);
|
||||
};
|
||||
|
||||
window.deleteContact = (phone) => {
|
||||
delete state.contacts[phone];
|
||||
delete state.messages[phone];
|
||||
if (currentChatPhone === phone) {
|
||||
currentChatPhone = null;
|
||||
chatTitle.textContent = '请选择联系人';
|
||||
chatBody.innerHTML = '';
|
||||
}
|
||||
saveState();
|
||||
renderContacts();
|
||||
};
|
||||
|
||||
const selectedCode = document.getElementById('selectedCode');
|
||||
const phoneNumber = document.getElementById('phoneNumber');
|
||||
const startChat = document.getElementById('startChat');
|
||||
const shortcutListUI = document.getElementById('shortcutList');
|
||||
const newShortcut = document.getElementById('newShortcut');
|
||||
const addShortcut = document.getElementById('addShortcut');
|
||||
const chatInput = document.querySelector('[data-chat-input]');
|
||||
const shortcutsDisplay = document.querySelector('[data-shortcuts]');
|
||||
const emojiTrigger = document.querySelector('[data-emoji-trigger]');
|
||||
const emojiPicker = document.querySelector('[data-emoji-picker]');
|
||||
|
||||
// Country Search & Selection
|
||||
const renderCountries = (filter = '') => {
|
||||
countryList.innerHTML = countries
|
||||
.filter(c => c.name.includes(filter) || c.code.includes(filter))
|
||||
.map(c => `
|
||||
<div class="p-2 border-bottom" role="button" onclick="selectCountry('${c.name}', '${c.code}')">
|
||||
${c.name} <span class="text-muted">${c.code}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
countrySearch.addEventListener('input', (e) => renderCountries(e.target.value));
|
||||
|
||||
window.selectCountry = (name, code) => {
|
||||
selectedCode.textContent = code;
|
||||
document.getElementById('searchTrigger').textContent = name + ' (' + code + ')';
|
||||
bootstrap.Modal.getInstance(document.getElementById('countryModal')).hide();
|
||||
};
|
||||
|
||||
renderCountries();
|
||||
|
||||
// Shortcuts Management
|
||||
let shortcuts = JSON.parse(localStorage.getItem('shortcuts') || '[]');
|
||||
|
||||
const renderShortcuts = () => {
|
||||
shortcutListUI.innerHTML = shortcuts.map((s, i) => `
|
||||
<li class="list-group-item d-flex justify-content-between">${s}
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteShortcut(${i})">删除</button>
|
||||
</li>
|
||||
`).join('');
|
||||
|
||||
shortcutsDisplay.innerHTML = shortcuts.map(s => `
|
||||
<button class="btn btn-sm btn-outline-info me-1" onclick="insertShortcut('${s}')">${s}</button>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
window.insertShortcut = (text) => chatInput.value += text;
|
||||
window.deleteShortcut = (index) => {
|
||||
shortcuts.splice(index, 1);
|
||||
localStorage.setItem('shortcuts', JSON.stringify(shortcuts));
|
||||
renderShortcuts();
|
||||
};
|
||||
|
||||
addShortcut.addEventListener('click', () => {
|
||||
if (!newShortcut.value) return;
|
||||
shortcuts.push(newShortcut.value);
|
||||
localStorage.setItem('shortcuts', JSON.stringify(shortcuts));
|
||||
newShortcut.value = '';
|
||||
renderShortcuts();
|
||||
});
|
||||
|
||||
renderShortcuts();
|
||||
|
||||
// Emoji Picker
|
||||
emojiTrigger.addEventListener('click', () => emojiPicker.toggleAttribute('hidden'));
|
||||
emojiPicker.addEventListener('emoji-click', (e) => {
|
||||
chatInput.value += e.detail.unicode;
|
||||
emojiPicker.setAttribute('hidden', '');
|
||||
});
|
||||
|
||||
// Start Chat
|
||||
startChat.addEventListener('click', () => {
|
||||
document.getElementById('startChat').addEventListener('click', () => {
|
||||
if (!phoneNumber.value) return;
|
||||
const phone = selectedCode.textContent + phoneNumber.value;
|
||||
document.querySelector('[data-chat-title]').textContent = phone;
|
||||
bootstrap.Modal.getInstance(document.getElementById('countryModal')).hide();
|
||||
showToast('已开始与 ' + phone + ' 的聊天');
|
||||
});
|
||||
};
|
||||
|
||||
const initAdmin = () => {
|
||||
const navLinks = document.querySelectorAll('[data-section-link]');
|
||||
const sections = document.querySelectorAll('[data-section]');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const target = link.dataset.sectionLink;
|
||||
navLinks.forEach(l => l.classList.remove('active'));
|
||||
link.classList.add('active');
|
||||
sections.forEach(s => s.classList.add('d-none'));
|
||||
document.querySelector(`[data-section="${target}"]`).classList.remove('d-none');
|
||||
|
||||
if (target === 'dashboard') loadDashboard();
|
||||
if (target === 'contacts') loadAdminContacts();
|
||||
if (target === 'messages') loadAdminMessages();
|
||||
if (target === 'auto') loadAutoReplies();
|
||||
if (target === 'settings') loadSettings();
|
||||
});
|
||||
if (!state.contacts[phone]) {
|
||||
state.contacts[phone] = { created: Date.now() };
|
||||
state.messages[phone] = [];
|
||||
}
|
||||
currentChatPhone = phone;
|
||||
saveState();
|
||||
renderContacts();
|
||||
switchChat(phone);
|
||||
countryModal.hide();
|
||||
});
|
||||
|
||||
// Forms
|
||||
document.querySelector('[data-send-form]')?.addEventListener('submit', async (e) => {
|
||||
chatForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(e.target));
|
||||
try {
|
||||
await apiFetch('/api/messages.php', { method: 'POST', body: { contact_phone: data.phone, body: data.body, direction: 'out' } });
|
||||
showToast('发送成功');
|
||||
e.target.reset();
|
||||
} catch (e) { showToast('发送失败', 'danger'); }
|
||||
if(!currentChatPhone || !chatInput.value) return;
|
||||
const msg = { type: 'out', text: chatInput.value, time: Date.now() };
|
||||
state.messages[currentChatPhone].push(msg);
|
||||
saveState();
|
||||
chatInput.value = '';
|
||||
switchChat(currentChatPhone);
|
||||
|
||||
apiFetch('/api/messages.php', { method: 'POST', body: { phone: currentChatPhone, body: msg.text, direction: 'out' } }).catch(() => {});
|
||||
});
|
||||
|
||||
document.querySelector('[data-reply-form]')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(e.target));
|
||||
try {
|
||||
await apiFetch('/api/auto_reply.php', { method: 'POST', body: data });
|
||||
showToast('已添加规则');
|
||||
e.target.reset();
|
||||
loadAutoReplies();
|
||||
} catch (e) { showToast('添加失败', 'danger'); }
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
currentChatPhone = null;
|
||||
chatTitle.textContent = '请选择联系人';
|
||||
chatBody.innerHTML = '';
|
||||
renderContacts();
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('[data-settings-form]')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(e.target));
|
||||
try {
|
||||
await apiFetch('/api/settings.php', { method: 'POST', body: data });
|
||||
showToast('配置已保存');
|
||||
} catch (e) { showToast('保存失败', 'danger'); }
|
||||
});
|
||||
|
||||
const loadDashboard = async () => {
|
||||
try {
|
||||
const data = await apiFetch('/api/stats.php');
|
||||
document.querySelector('[data-stat="sent"]').textContent = data.sent || 0;
|
||||
document.querySelector('[data-stat="received"]').textContent = data.received || 0;
|
||||
document.querySelector('[data-stat="active"]').textContent = data.active || 0;
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
|
||||
const loadAdminContacts = async () => {
|
||||
const data = await apiFetch('/api/contacts.php');
|
||||
const list = document.querySelector('[data-admin-contacts]');
|
||||
list.innerHTML = (data.contacts || []).map(c => `
|
||||
<tr>
|
||||
<td>${c.phone}</td>
|
||||
<td>${c.tags || '-'}</td>
|
||||
<td><span class="badge ${c.status === 'blocked' ? 'bg-danger' : 'bg-success'}">${c.status}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="toggleBlock('${c.id}', '${c.status}')">切换拉黑</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteContact('${c.id}')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
window.toggleBlock = async (id, currentStatus) => {
|
||||
const newStatus = currentStatus === 'blocked' ? 'normal' : 'blocked';
|
||||
await apiFetch(`/api/contacts.php?action=update&id=${id}`, { method: 'POST', body: { status: newStatus } });
|
||||
loadAdminContacts();
|
||||
};
|
||||
|
||||
window.deleteContact = async (id) => {
|
||||
if (!confirm('确定删除?')) return;
|
||||
await apiFetch(`/api/contacts.php?action=delete&id=${id}`, { method: 'POST' });
|
||||
loadAdminContacts();
|
||||
};
|
||||
|
||||
const loadAdminMessages = async () => {
|
||||
const data = await apiFetch('/api/messages.php');
|
||||
const list = document.querySelector('[data-admin-messages]');
|
||||
list.innerHTML = (data.messages || []).map(m => `
|
||||
<tr>
|
||||
<td>${m.phone}</td>
|
||||
<td>${m.direction}</td>
|
||||
<td>${m.body}</td>
|
||||
<td>${m.created_at}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
const loadAutoReplies = async () => {
|
||||
const data = await apiFetch('/api/auto_reply.php');
|
||||
const list = document.querySelector('[data-reply-list]');
|
||||
list.innerHTML = (data.rules || []).map(r => `
|
||||
<li class="list-group-item d-flex justify-content-between">
|
||||
<div><b>${r.keyword}</b>: ${r.reply}</div>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteRule('${r.id}')">删除</button>
|
||||
</li>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
window.deleteRule = async (id) => {
|
||||
await apiFetch(`/api/auto_reply.php?action=delete&id=${id}`, { method: 'POST' });
|
||||
loadAutoReplies();
|
||||
};
|
||||
|
||||
const loadSettings = async () => {
|
||||
const data = await apiFetch('/api/settings.php');
|
||||
if (!data) return;
|
||||
const form = document.querySelector('[data-settings-form]');
|
||||
form.sid.value = data.sid || '';
|
||||
form.token.value = data.token || '';
|
||||
form.from.value = data.from || '';
|
||||
form.webhook.value = data.webhook || '';
|
||||
};
|
||||
|
||||
loadDashboard();
|
||||
renderContacts();
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.body.dataset.page === 'agent') initFrontend();
|
||||
if (document.body.dataset.page === 'admin') initAdmin();
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user