Модалка отправки отклика: кнопка в колонке действий, генерация с прелоадером, отправка по готовности текста

This commit is contained in:
apuc committed 2026-09-16 13:26:46 +03:00
1 parent e4cd06a23d
commit 2d781737f2
1 file changed
+106
+106
View File
@@ -86,6 +86,12 @@
max-height: 92vh; overflow: auto; }
.modal-box img { max-width: 100%; border: 1px solid var(--border);
border-radius: 8px; display: block; }
.spinner { width: 18px; height: 18px; border: 2px solid var(--border);
border-top-color: var(--accent); border-radius: 50%;
animation: spin .8s linear infinite; flex-shrink: 0; }
@keyframes spin { to { transform: rotate(360deg); } }
.loading { display: flex; align-items: center; gap: 10px;
color: var(--muted); margin-bottom: 8px; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 5px; }
@@ -240,6 +246,28 @@
</section>
</main>
<!-- ============ МОДАЛКА: ОТПРАВКА ОТКЛИКА ============ -->
<div id="send-modal" class="modal hidden" onclick="if(event.target===this) closeSendModal()">
<div class="modal-box">
<div class="toolbar">
<h2 style="margin:0">Отправить отклик</h2>
<div class="spacer"></div>
<button class="secondary" onclick="closeSendModal()">Закрыть</button>
</div>
<div id="send-vacancy-title" class="muted" style="margin-bottom:8px"></div>
<div id="send-loading" class="loading">
<div class="spinner"></div>
<span>Текст генерируется...</span>
</div>
<textarea id="send-letter" style="min-height:140px" disabled
placeholder="Сопроводительное письмо появится здесь..."></textarea>
<div class="toolbar" style="margin-top:12px">
<button id="btn-send-letter" onclick="sendFromModal()" disabled>Отправить отклик</button>
<span id="send-modal-note" class="muted"></span>
</div>
</div>
</div>
<!-- ============ МОДАЛКА: ПРОВЕРКА ОТКЛИКА ============ -->
<div id="shot-modal" class="modal hidden" onclick="if(event.target===this) closeShot()">
<div class="modal-box">
@@ -264,6 +292,7 @@ let vacancies = [];
let generatedTexts = {};
let currentShotId = null;
let sentTexts = new Set();
let sendModalId = null;
// ---------- Вкладки ----------
function showTab(name) {
@@ -511,6 +540,7 @@ function renderVacancies() {
<td>${exp}</td>
<td>${status}</td>
<td class="actions-cell">
<button class="secondary" onclick="openSendModal('${v.id}')">Отправить отклик</button>
<button class="secondary" onclick="openVacancy('${v.id}')">Открыть</button>
<button class="secondary" onclick="markDone('${v.id}')">Отправил</button>
</td>
@@ -589,6 +619,82 @@ function escapeHtml(s) {
}
// ---------- Открытие, проверка и отправка ----------
async function openSendModal(id) {
if (!currentAccount) { alert('Сначала выберите аккаунт'); return; }
const v = vacancies.find(x => x.id === id);
if (!v) return;
sendModalId = id;
document.getElementById('send-vacancy-title').textContent = `${v.title} — ${v.company || ''}`;
document.getElementById('send-letter').value = '';
document.getElementById('send-letter').disabled = true;
document.getElementById('btn-send-letter').disabled = true;
document.getElementById('send-modal-note').textContent = '';
document.getElementById('send-modal').classList.remove('hidden');
const cached = generatedTexts[id];
if (cached) {
// Текст уже сгенерирован ранее — показываем сразу
document.getElementById('send-letter').value = cached;
document.getElementById('send-letter').disabled = false;
document.getElementById('btn-send-letter').disabled = false;
document.getElementById('send-loading').classList.add('hidden');
return;
}
// Генерируем текст (прелоадер виден)
document.getElementById('send-loading').classList.remove('hidden');
try {
const data = await api(`/api/accounts/${currentAccount}/generate`, {
method: 'POST', body: JSON.stringify({ ids: [id] }),
});
document.getElementById('send-loading').classList.add('hidden');
if (data.texts && data.texts[id]) {
generatedTexts[id] = data.texts[id];
document.getElementById('send-letter').value = data.texts[id];
document.getElementById('send-letter').disabled = false;
document.getElementById('btn-send-letter').disabled = false;
renderTexts();
} else if (data.errors && data.errors[id]) {
document.getElementById('send-modal-note').textContent = 'Ошибка: ' + data.errors[id];
}
} catch (e) {
document.getElementById('send-loading').classList.add('hidden');
document.getElementById('send-modal-note').textContent = 'Ошибка: ' + e.message;
}
}
function closeSendModal() {
document.getElementById('send-modal').classList.add('hidden');
}
async function sendFromModal() {
if (!currentAccount || !sendModalId) return;
const message = document.getElementById('send-letter').value;
const btn = document.getElementById('btn-send-letter');
const note = document.getElementById('send-modal-note');
btn.disabled = true; note.textContent = 'Отправляю...';
try {
const data = await api(`/api/accounts/${currentAccount}/send-now`, {
method: 'POST', body: JSON.stringify({ id: sendModalId, message }),
});
if (data.screenshot) {
showShotModal(sendModalId, data.screenshot, data.note || 'Отклик отправлен', true);
sentTexts.add(sendModalId);
const v = vacancies.find(x => x.id === sendModalId);
if (v) v.responded = true;
renderVacancies();
renderTexts();
closeSendModal();
} else {
note.textContent = data.note || 'Не удалось отправить отклик';
btn.disabled = false;
}
} catch (e) {
note.textContent = 'Ошибка: ' + e.message;
btn.disabled = false;
}
}
function showShotModal(id, screenshot, note, sent) {
currentShotId = id;
document.getElementById('shot-img').src = screenshot + '?t=' + Date.now();