mirror of
https://github.com/wasrusgen/zashita-brandbook.git
synced 2026-06-03 15:44:47 +00:00
feat: reminder system - date picker, overdue check on login, refuse flow
This commit is contained in:
parent
a8fcc20745
commit
8595a0ca16
193
mockup.html
193
mockup.html
@ -5891,6 +5891,9 @@ function initReturnChat() {
|
|||||||
if (!msgs) return;
|
if (!msgs) return;
|
||||||
msgs.innerHTML = '';
|
msgs.innerHTML = '';
|
||||||
|
|
||||||
|
// Проверяем просроченные напоминания — показываем ПОСЛЕ стандартного приветствия
|
||||||
|
if (typeof _checkRemindersOnStart === 'function') _checkRemindersOnStart();
|
||||||
|
|
||||||
// ── Собираем персональные данные ──
|
// ── Собираем персональные данные ──
|
||||||
var lastOrder = JSON.parse(localStorage.getItem('zashita_last_order') || 'null');
|
var lastOrder = JSON.parse(localStorage.getItem('zashita_last_order') || 'null');
|
||||||
var credits = parseInt(localStorage.getItem('zashita_credits') || '0');
|
var credits = parseInt(localStorage.getItem('zashita_credits') || '0');
|
||||||
@ -8120,6 +8123,12 @@ function _handleMissingDoc(contractType, docId, variant, intent) {
|
|||||||
refused: 'Контрагент отказывается дать документ "' + doc.label + '" клиенту. Объясни права клиента и предложи официальный запрос + судебное истребование.'
|
refused: 'Контрагент отказывается дать документ "' + doc.label + '" клиенту. Объясни права клиента и предложи официальный запрос + судебное истребование.'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Для варианта "позже" — сразу показываем выбор даты напоминания
|
||||||
|
if (variant === 'later') {
|
||||||
|
setTimeout(function(){ _showReminderPicker(doc, contractType, intent); }, 300);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
_rcAddTyping();
|
_rcAddTyping();
|
||||||
_elenaApi(promptMap[variant] || promptMap.never, intent, function(apiReply, apiActions){
|
_elenaApi(promptMap[variant] || promptMap.never, intent, function(apiReply, apiActions){
|
||||||
@ -8135,6 +8144,190 @@ function _handleMissingDoc(contractType, docId, variant, intent) {
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── НАПОМИНАНИЯ ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
var _REMINDERS_KEY = 'zashita_reminders';
|
||||||
|
|
||||||
|
function _saveReminder(docLabel, docId, contractType, remindAt) {
|
||||||
|
try {
|
||||||
|
var reminders = JSON.parse(localStorage.getItem(_REMINDERS_KEY) || '[]');
|
||||||
|
// Удаляем старый если был
|
||||||
|
reminders = reminders.filter(function(r){ return !(r.docId === docId && r.contractType === contractType); });
|
||||||
|
reminders.push({
|
||||||
|
id: Date.now(), docId: docId, contractType: contractType,
|
||||||
|
docLabel: docLabel, remindAt: remindAt, done: false,
|
||||||
|
createdAt: new Date().toISOString()
|
||||||
|
});
|
||||||
|
localStorage.setItem(_REMINDERS_KEY, JSON.stringify(reminders));
|
||||||
|
} catch(e){}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getOverdueReminders() {
|
||||||
|
try {
|
||||||
|
var reminders = JSON.parse(localStorage.getItem(_REMINDERS_KEY) || '[]');
|
||||||
|
var now = Date.now();
|
||||||
|
return reminders.filter(function(r){ return !r.done && new Date(r.remindAt).getTime() <= now; });
|
||||||
|
} catch(e){ return []; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function _showReminderPicker(doc, contractType, intent) {
|
||||||
|
var msgs = document.getElementById('rchat-msgs');
|
||||||
|
if (!msgs) return;
|
||||||
|
|
||||||
|
var now = new Date();
|
||||||
|
var dates = [
|
||||||
|
{ label: 'Сегодня вечером', val: _dateOffset(0, 18) },
|
||||||
|
{ label: 'Завтра утром', val: _dateOffset(1, 9) },
|
||||||
|
{ label: 'Через 3 дня', val: _dateOffset(3, 9) },
|
||||||
|
{ label: 'Через неделю', val: _dateOffset(7, 9) },
|
||||||
|
];
|
||||||
|
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.className = 'hc-msg hc-elena';
|
||||||
|
div.innerHTML =
|
||||||
|
'<img class="hc-av" src="logos/elena-photo.jpg">' +
|
||||||
|
'<div class="hc-bubble">' +
|
||||||
|
'<div style="font-size:13px;margin-bottom:10px">' +
|
||||||
|
'Хорошо! Когда напомнить загрузить <b>' + doc.label + '</b>?' +
|
||||||
|
'</div>' +
|
||||||
|
'<div style="display:flex;flex-direction:column;gap:6px">' +
|
||||||
|
dates.map(function(d){
|
||||||
|
return '<button style="text-align:left;padding:8px 12px;border:1.5px solid var(--line);' +
|
||||||
|
'border-radius:8px;background:#fafafa;cursor:pointer;font-size:12px;font-family:inherit" ' +
|
||||||
|
'onclick="_setReminder(\'' + doc.id + '\',\'' + doc.label.replace(/'/g,"\\'") + '\',\'' +
|
||||||
|
contractType + '\',\'' + d.val + '\',\'' + intent + '\')">' +
|
||||||
|
'🔔 ' + d.label + ' <span style="color:var(--mut);font-size:11px">(' + _formatDate(d.val) + ')</span>' +
|
||||||
|
'</button>';
|
||||||
|
}).join('') +
|
||||||
|
'<div style="display:flex;gap:8px;align-items:center;margin-top:4px">' +
|
||||||
|
'<input type="date" id="reminder-custom-date" style="border:1.5px solid var(--line);border-radius:8px;' +
|
||||||
|
'padding:7px 10px;font-size:12px;font-family:inherit;flex:1" min="' + now.toISOString().slice(0,10) + '">' +
|
||||||
|
'<button class="btn btn-o" style="padding:7px 12px;font-size:12px" ' +
|
||||||
|
'onclick="var d=document.getElementById(\'reminder-custom-date\').value;if(d)_setReminder(\'' +
|
||||||
|
doc.id + '\',\'' + doc.label.replace(/'/g,"\\'") + '\',\'' + contractType + '\',d+\'T09:00\',\'' + intent + '\')">' +
|
||||||
|
'Выбрать</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
msgs.appendChild(div);
|
||||||
|
msgs.scrollTop = msgs.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _dateOffset(days, hour) {
|
||||||
|
var d = new Date();
|
||||||
|
d.setDate(d.getDate() + days);
|
||||||
|
d.setHours(hour, 0, 0, 0);
|
||||||
|
return d.toISOString().slice(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _formatDate(isoStr) {
|
||||||
|
var d = new Date(isoStr);
|
||||||
|
return d.toLocaleDateString('ru-RU', {day:'numeric', month:'short', hour:'2-digit', minute:'2-digit'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _setReminder(docId, docLabel, contractType, remindAt, intent) {
|
||||||
|
// Убираем picker
|
||||||
|
var msgs = document.getElementById('rchat-msgs');
|
||||||
|
var last = msgs ? msgs.lastElementChild : null;
|
||||||
|
if (last && last.className.includes('hc-elena')) last.remove();
|
||||||
|
|
||||||
|
_saveReminder(docLabel, docId, contractType, remindAt);
|
||||||
|
_addCaseNote('promise', docLabel + ' — напоминание установлено на ' + _formatDate(remindAt),
|
||||||
|
{ docId: docId, contractType: contractType, remindAt: remindAt });
|
||||||
|
|
||||||
|
// Пузырь клиента
|
||||||
|
if (msgs) {
|
||||||
|
var uDiv = document.createElement('div');
|
||||||
|
uDiv.className = 'hc-msg hc-user';
|
||||||
|
uDiv.innerHTML = '<div class="hc-bubble">🔔 Напомни ' + _formatDate(remindAt) + '</div>';
|
||||||
|
msgs.appendChild(uDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ответ Елены
|
||||||
|
setTimeout(function(){
|
||||||
|
_rcAddTyping();
|
||||||
|
var prompt = 'Клиент установил напоминание загрузить документ "' + docLabel +
|
||||||
|
'" на ' + _formatDate(remindAt) + '. Зафиксируй и скажи что будет видно при следующем входе. Продолжаем работу.';
|
||||||
|
_elenaApi(prompt, intent, function(apiReply) {
|
||||||
|
_rcRemoveTyping();
|
||||||
|
var reply = apiReply || 'Зафиксировала. ' + _formatDate(remindAt) + ' напомню. Продолжаем.';
|
||||||
|
_rcAddBubble(reply, false);
|
||||||
|
_rcShowControls();
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка при старте — есть ли просроченные напоминания
|
||||||
|
function _checkRemindersOnStart() {
|
||||||
|
var overdue = _getOverdueReminders();
|
||||||
|
if (!overdue.length) return;
|
||||||
|
|
||||||
|
// Показываем через 1 сек после initReturnChat
|
||||||
|
setTimeout(function(){
|
||||||
|
var msgs = document.getElementById('rchat-msgs');
|
||||||
|
if (!msgs) return;
|
||||||
|
overdue.forEach(function(r) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.className = 'hc-msg hc-elena';
|
||||||
|
div.innerHTML =
|
||||||
|
'<img class="hc-av" src="logos/elena-photo.jpg">' +
|
||||||
|
'<div class="hc-bubble">' +
|
||||||
|
'<div style="font-weight:700;margin-bottom:8px">🔔 Напоминание</div>' +
|
||||||
|
'<div style="font-size:13px;margin-bottom:10px">' +
|
||||||
|
'Вы обещали загрузить <b>' + r.docLabel + '</b>. Удалось найти?' +
|
||||||
|
'</div>' +
|
||||||
|
'<div style="display:flex;gap:8px;flex-wrap:wrap">' +
|
||||||
|
'<button class="btn btn-p" style="padding:7px 14px;font-size:12px" ' +
|
||||||
|
'onclick="_reminderDone(\'' + r.id + '\',true)">✅ Загрузил — отмечаю</button>' +
|
||||||
|
'<button class="btn btn-o" style="padding:7px 14px;font-size:12px" ' +
|
||||||
|
'onclick="_setReminder(\'' + r.docId + '\',\'' + r.docLabel.replace(/'/g,"\\'") + '\',\'' + r.contractType + '\',\'' + _dateOffset(1,9) + '\',\'question\')">' +
|
||||||
|
'🔔 Перенести на завтра</button>' +
|
||||||
|
'<button class="svc-btn-detail" style="font-size:12px" ' +
|
||||||
|
'onclick="_reminderDone(\'' + r.id + '\',false)">❌ Не буду — зафиксировать отказ</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
msgs.appendChild(div);
|
||||||
|
msgs.scrollTop = msgs.scrollHeight;
|
||||||
|
});
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _reminderDone(reminderId, uploaded) {
|
||||||
|
// Убираем пузырь
|
||||||
|
var msgs = document.getElementById('rchat-msgs');
|
||||||
|
var last = msgs ? msgs.lastElementChild : null;
|
||||||
|
if (last) last.remove();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var reminders = JSON.parse(localStorage.getItem(_REMINDERS_KEY) || '[]');
|
||||||
|
var r = reminders.find(function(x){ return x.id == reminderId; });
|
||||||
|
if (r) {
|
||||||
|
r.done = true;
|
||||||
|
r.outcome = uploaded ? 'uploaded' : 'refused';
|
||||||
|
localStorage.setItem(_REMINDERS_KEY, JSON.stringify(reminders));
|
||||||
|
|
||||||
|
if (uploaded) {
|
||||||
|
// Отмечаем документ в чеклисте как загруженный
|
||||||
|
try {
|
||||||
|
var saved = JSON.parse(localStorage.getItem('zashita_doccheck_' + r.contractType) || '{}');
|
||||||
|
saved[r.docId] = true;
|
||||||
|
localStorage.setItem('zashita_doccheck_' + r.contractType, JSON.stringify(saved));
|
||||||
|
} catch(e){}
|
||||||
|
_addCaseNote('decision', r.docLabel + ' — загружен (подтверждено)', { reminderId: reminderId });
|
||||||
|
if (msgs) { var ok = document.createElement('div'); ok.className='hc-msg hc-user'; ok.innerHTML='<div class="hc-bubble">✅ Загрузил</div>'; msgs.appendChild(ok); }
|
||||||
|
toast('✅ Зафиксировано — документ получен');
|
||||||
|
} else {
|
||||||
|
// Конвертируем обещание в отказ
|
||||||
|
_addCaseNote('missing_doc', r.docLabel + ' — клиент решил не загружать (зафиксирован отказ)', { final: true });
|
||||||
|
if (msgs) { var no = document.createElement('div'); no.className='hc-msg hc-user'; no.innerHTML='<div class="hc-bubble">❌ Не буду загружать</div>'; msgs.appendChild(no); }
|
||||||
|
toast('📝 Зафиксировано в карте дела как окончательный отказ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e){}
|
||||||
|
|
||||||
|
_rcShowControls();
|
||||||
|
}
|
||||||
|
|
||||||
function _showAuditGaps(contractType, intent) {
|
function _showAuditGaps(contractType, intent) {
|
||||||
var msgs = document.getElementById('rchat-msgs');
|
var msgs = document.getElementById('rchat-msgs');
|
||||||
if (!msgs) return;
|
if (!msgs) return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user