uchet-zayavok-na-podklyuchen/script.js

368 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Демо-данные: ~30 заявок как в брифе (30-50 в день, пик до 100)
const DEMO_DATA = [
{ id: 1, name: "Алиев Арман Берикович", phone: "+7 777 123 45 67", address: "г. Алматы, ул. Абая 10, кв. 5", tariff: "Домашний интернет 100 Мбит/с", manager: "Серик Ж.", status: "в работе", comment: "", date: "2026-08-27", isDuplicate: false },
{ id: 2, name: "Байкова Гульнара Талгатовна", phone: "+7 701 234 56 78", address: "г. Алматы, пр. Достык 45, офис 12", tariff: "ТВ + Интернет", manager: "Айгуль К.", status: "новая", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 3, name: "Васильченко Игорь Петрович", phone: "+7 778 345 67 89", address: "г. Алматы, ул. Толе би 88, кв. 42", tariff: "Домашний интернет 500 Мбит/с", manager: "", status: "новая", comment: "Клиент просит перезвонить после 18:00", date: "2026-08-28", isDuplicate: false },
{ id: 4, name: "Гребенюк Сергей Владимирович", phone: "+7 702 456 78 90", address: "г. Алматы, мкр. Аксай-2, д. 15", tariff: "Тройной пакет", manager: "Серик Ж.", status: "на проверке", comment: "", date: "2026-08-26", isDuplicate: false },
{ id: 5, name: "Дюсенова Айнур Маратовна", phone: "+7 777 567 89 01", address: "г. Алматы, ул. Сатпаева 22, кв. 18", tariff: "Домашний интернет 50 Мбит/с", manager: "Бауыржан Т.", status: "в работе", comment: "", date: "2026-08-27", isDuplicate: false },
{ id: 6, name: "Ержанов Максат", phone: "+7 701 678 90 12", address: "г. Алматы, ул. Рыскулова 100, кв. 55", tariff: "Бизнес тариф", manager: "", status: "спорная", comment: "Нужно уточнить юридический адрес", date: "2026-08-25", isDuplicate: false },
{ id: 7, name: "Жуманова Лейла Ахметовна", phone: "+7 778 789 01 23", address: "г. Алматы, пр. Абая 150, кв. 88", tariff: "Домашний интернет 100 Мбит/с", manager: "Айгуль К.", status: "завершена", comment: "", date: "2026-08-24", isDuplicate: false },
{ id: 8, name: "Зинченко Александр", phone: "+7 702 890 12 34", address: "г. Алматы, ул. Макатаева 35, офис 5", tariff: "ТВ + Интернет", manager: "Серик Ж.", status: "в работе", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 9, name: "Ибраева Сауле", phone: "+7 777 901 23 45", address: "г. Алматы, мкр. Коктем-3, д. 20, кв. 10", tariff: "Домашний интернет 500 Мбит/с", manager: "", status: "новая", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 10, name: "Ким Дмитрий Юрьевич", phone: "+7 701 012 34 56", address: "г. Алматы, ул. Бейбитшилик 12, кв. 30", tariff: "Домашний интернет 100 Мбит/с", manager: "Бауыржан Т.", status: "на проверке", comment: "Клиент на больничном", date: "2026-08-26", isDuplicate: false },
{ id: 11, name: "Лебедева Наталья", phone: "+7 778 123 45 67", address: "г. Алматы, пр. Райымбека 200, кв. 75", tariff: "Тройной пакет", manager: "", status: "новая", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 12, name: "Мамедов Руслан", phone: "+7 702 234 56 78", address: "г. Алматы, ул. Желтоксан 55, офис 8", tariff: "Бизнес тариф", manager: "Серик Ж.", status: "в работе", comment: "", date: "2026-08-27", isDuplicate: false },
{ id: 13, name: "Нургалиева Айжан", phone: "+7 777 345 67 89", address: "г. Алматы, мкр. Таугуль-3, д. 8А, кв. 22", tariff: "Домашний интернет 50 Мбит/с", manager: "Айгуль К.", status: "завершена", comment: "", date: "2026-08-23", isDuplicate: false },
{ id: 14, name: "Омаров Ерлан", phone: "+7 701 456 78 90", address: "г. Алматы, ул. Панфилова 90, кв. 15", tariff: "ТВ + Интернет", manager: "", status: "спорная", comment: "Неправильный адрес, уточняется", date: "2026-08-25", isDuplicate: false },
{ id: 15, name: "Петрова Елена Сергеевна", phone: "+7 778 567 89 01", address: "г. Алматы, пр. Абая 50, кв. 100", tariff: "Домашний интернет 500 Мбит/с", manager: "Бауыржан Т.", status: "в работе", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 16, name: "Рахимов Берик", phone: "+7 702 678 90 12", address: "г. Алматы, ул. Кабанбай батыра 70, кв. 45", tariff: "Домашний интернет 100 Мбит/с", manager: "", status: "новая", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 17, name: "Смирнова Ольга", phone: "+7 777 789 01 23", address: "г. Алматы, мкр. Орбита-2, д. 55, кв. 8", tariff: "Тройной пакет", manager: "Серик Ж.", status: "на проверке", comment: "", date: "2026-08-26", isDuplicate: false },
{ id: 18, name: "Турсунов Марат", phone: "+7 701 890 12 34", address: "г. Алматы, ул. Пушкина 25, офис 12", tariff: "Бизнес тариф", manager: "Айгуль К.", status: "завершена", comment: "", date: "2026-08-22", isDuplicate: false },
{ id: 19, name: "Уалиева Жанар", phone: "+7 778 901 23 45", address: "г. Алматы, пр. Достык 100, кв. 60", tariff: "Домашний интернет 50 Мбит/с", manager: "", status: "новая", comment: "Заявка с сайта", date: "2026-08-28", isDuplicate: false },
{ id: 20, name: "Федоров Виктор", phone: "+7 702 012 34 56", address: "г. Алматы, ул. Богенбай батыра 40, кв. 20", tariff: "ТВ + Интернет", manager: "Бауыржан Т.", status: "в работе", comment: "", date: "2026-08-27", isDuplicate: false },
{ id: 21, name: "Хасанова Мадина", phone: "+7 777 123 45 67", address: "г. Алматы, мкр. Нуркент, д. 12, кв. 35", tariff: "Домашний интернет 100 Мбит/с", manager: "", status: "спорная", comment: "Дубль? Тот же телефон что у Алиева А.", date: "2026-08-27", isDuplicate: true },
{ id: 22, name: "Иванов Петр", phone: "+7 701 333 22 11", address: "г. Алматы, ул. Тимирязева 80, кв. 50", tariff: "Домашний интернет 500 Мбит/с", manager: "Серик Ж.", status: "в работе", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 23, name: "Козлов Андрей", phone: "+7 778 444 33 22", address: "г. Алматы, пр. Абая 200, кв. 120", tariff: "Бизнес тариф", manager: "", status: "новая", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 24, name: "Алиев Арман Берикович", phone: "+7 777 123 45 67", address: "г. Алматы, ул. Абая 10, кв. 5", tariff: "Домашний интернет 100 Мбит/с", manager: "", status: "спорная", comment: "ВОЗМОЖНЫЙ ДУБЛЬ - заявка #1", date: "2026-08-28", isDuplicate: true },
{ id: 25, name: "Шарипова Гульмира", phone: "+7 702 555 44 33", address: "г. Алматы, ул. Сейфуллина 150, кв. 70", tariff: "Тройной пакет", manager: "Айгуль К.", status: "на проверке", comment: "", date: "2026-08-26", isDuplicate: false },
{ id: 26, name: "Яковлев Михаил", phone: "+7 777 666 55 44", address: "г. Алматы, мкр. Самал-2, д. 30, кв. 15", tariff: "Домашний интернет 50 Мбит/с", manager: "", status: "новая", comment: "Клиент из рекламы", date: "2026-08-28", isDuplicate: false },
{ id: 27, name: "Есенбаева Айгуль", phone: "+7 701 777 66 55", address: "г. Алматы, ул. Масанчи 85, кв. 40", tariff: "ТВ + Интернет", manager: "Бауыржан Т.", status: "завершена", comment: "", date: "2026-08-21", isDuplicate: false },
{ id: 28, name: "Морозов Игорь", phone: "+7 778 888 77 66", address: "г. Алматы, пр. Райымбека 300, офис 20", tariff: "Бизнес тариф", manager: "Серик Ж.", status: "в работе", comment: "", date: "2026-08-27", isDuplicate: false },
{ id: 29, name: "Кенжебаева Сауле", phone: "+7 702 999 88 77", address: "г. Алматы, мкр. Айнабулак, д. 5, кв. 25", tariff: "Домашний интернет 100 Мбит/с", manager: "", status: "новая", comment: "", date: "2026-08-28", isDuplicate: false },
{ id: 30, name: "Ткаченко Владимир", phone: "+7 777 000 99 88", address: "г. Алматы, ул. Курмангазы 60, кв. 90", tariff: "Домашний интернет 500 Мбит/с", manager: "Айгуль К.", status: "просрочка", comment: "Клиент не отвечает", date: "2026-08-20", isDuplicate: false }
];
let requests = [];
let currentFilter = 'all';
let duplicateCheckId = null;
// Инициализация
function init() {
const stored = localStorage.getItem('uchet_requests');
if (stored) {
requests = JSON.parse(stored);
} else {
requests = [...DEMO_DATA];
saveToStorage();
}
renderDashboard();
renderTable();
}
function saveToStorage() {
localStorage.setItem('uchet_requests', JSON.stringify(requests));
}
// Дашборд
function renderDashboard() {
const today = new Date().toISOString().slice(0, 10);
const counts = {
'новая': 0,
'в работе': 0,
'на проверке': 0,
'спорная': 0,
'завершена': 0,
'просрочка': 0,
'total': requests.length
};
requests.forEach(r => {
if (counts[r.status] !== undefined) counts[r.status]++;
// Просрочка: старше 3 дней и не завершена
if (r.status !== 'завершена' && r.status !== 'просрочка') {
const daysOld = daysBetween(r.date, today);
if (daysOld > 3) {
counts['просрочка']++;
}
}
});
document.getElementById('count-new').textContent = counts['новая'];
document.getElementById('count-work').textContent = counts['в работе'];
document.getElementById('count-duplicate').textContent = requests.filter(r => r.isDuplicate).length;
document.getElementById('count-overdue').textContent = counts['просрочка'];
document.getElementById('count-total').textContent = counts.total;
}
function daysBetween(date1, date2) {
const d1 = new Date(date1);
const d2 = new Date(date2);
return Math.round((d2 - d1) / (1000 * 60 * 60 * 24));
}
// Фильтры
function setFilter(status) {
currentFilter = status;
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.status === status);
});
renderTable();
}
// Таблица
function renderTable() {
const tbody = document.getElementById('table-body');
const search = document.getElementById('search').value.toLowerCase();
let filtered = requests.filter(r => {
const matchesSearch = r.name.toLowerCase().includes(search) ||
r.phone.toLowerCase().includes(search) ||
r.address.toLowerCase().includes(search) ||
(r.manager && r.manager.toLowerCase().includes(search));
const matchesStatus = currentFilter === 'all' || r.status === currentFilter;
return matchesSearch && matchesStatus;
});
// Сортировка: новые сверху
filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
if (filtered.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="8" class="empty-state">
<div class="empty-state-icon">📭</div>
<p>Нет заявок по выбранным фильтрам</p>
</td>
</tr>
`;
return;
}
tbody.innerHTML = filtered.map(r => `
<tr>
<td>${formatDate(r.date)}</td>
<td>
${escapeHtml(r.name)}
${r.isDuplicate ? '<span class="duplicate-flag">ДУБЛЬ</span>' : ''}
</td>
<td>${escapeHtml(r.phone)}</td>
<td>${escapeHtml(r.address)}</td>
<td>${escapeHtml(r.tariff)}</td>
<td>${r.manager || '—'}</td>
<td><span class="status-badge status-${r.status}">${r.status}</span></td>
<td class="actions">
<button class="btn btn-sm btn-secondary" onclick="editRequest(${r.id})">✏️</button>
${r.status === 'новая' ? `<button class="btn btn-sm btn-primary" onclick="takeToWork(${r.id})">В работу</button>` : ''}
${!r.isDuplicate && r.status !== 'завершена' ? `<button class="btn btn-sm btn-warning" onclick="checkDuplicate(${r.id})">Дубль?</button>` : ''}
</td>
</tr>
`).join('');
}
function formatDate(dateStr) {
const [y, m, d] = dateStr.split('-');
return `${d}.${m}.${y}`;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Модальное окно
function openModal() {
document.getElementById('modal-title').textContent = 'Новая заявка';
document.getElementById('request-form').reset();
document.getElementById('req-id').value = '';
document.getElementById('modal').classList.add('active');
}
function closeModal() {
document.getElementById('modal').classList.remove('active');
}
function editRequest(id) {
const r = requests.find(x => x.id === id);
if (!r) return;
document.getElementById('modal-title').textContent = 'Редактирование заявки';
document.getElementById('req-id').value = r.id;
document.getElementById('req-name').value = r.name;
document.getElementById('req-phone').value = r.phone;
document.getElementById('req-address').value = r.address;
document.getElementById('req-tariff').value = r.tariff;
document.getElementById('req-manager').value = r.manager || '';
document.getElementById('req-status').value = r.status;
document.getElementById('req-comment').value = r.comment || '';
document.getElementById('modal').classList.add('active');
}
function saveRequest(e) {
e.preventDefault();
const id = document.getElementById('req-id').value;
const data = {
name: document.getElementById('req-name').value.trim(),
phone: document.getElementById('req-phone').value.trim(),
address: document.getElementById('req-address').value.trim(),
tariff: document.getElementById('req-tariff').value,
manager: document.getElementById('req-manager').value.trim(),
status: document.getElementById('req-status').value,
comment: document.getElementById('req-comment').value.trim(),
date: new Date().toISOString().slice(0, 10)
};
if (id) {
const idx = requests.findIndex(r => r.id == id);
if (idx !== -1) {
requests[idx] = { ...requests[idx], ...data };
}
} else {
const newId = Math.max(...requests.map(r => r.id), 0) + 1;
requests.unshift({ id: newId, ...data, isDuplicate: false });
}
saveToStorage();
closeModal();
renderDashboard();
renderTable();
}
// Действия
function takeToWork(id) {
const r = requests.find(x => x.id === id);
if (r) {
r.status = 'в работе';
if (!r.manager) r.manager = prompt('Введите ФИО менеджера:');
saveToStorage();
renderDashboard();
renderTable();
}
}
function checkDuplicate(id) {
const r = requests.find(x => x.id === id);
if (!r) return;
// Ищем дубли по телефону или ФИО
const duplicates = requests.filter(x =>
x.id !== id &&
(x.phone === r.phone || normalizeName(x.name) === normalizeName(r.name))
);
if (duplicates.length > 0) {
duplicateCheckId = id;
const dup = duplicates[0];
document.getElementById('duplicate-content').innerHTML = `
<p>Найдено совпадение с заявкой #${dup.id}:</p>
<div class="duplicate-card">
<strong>${escapeHtml(dup.name)}</strong><br>
Тел: ${escapeHtml(dup.phone)}<br>
Адрес: ${escapeHtml(dup.address)}<br>
Статус: ${dup.status}<br>
Дата: ${formatDate(dup.date)}
</div>
<p>Пометить текущую заявку как дубль?</p>
`;
document.getElementById('duplicate-modal').classList.add('active');
} else {
alert('Дубли не найдены');
}
}
function normalizeName(name) {
return name.toLowerCase().replace(/[^а-яёa-z]/g, '').trim();
}
function confirmDuplicate() {
if (duplicateCheckId) {
const r = requests.find(x => x.id === duplicateCheckId);
if (r) {
r.isDuplicate = true;
r.status = 'спорная';
r.comment = (r.comment || '') + ' Помечена как дубль вручную.';
saveToStorage();
renderDashboard();
renderTable();
}
closeDuplicateModal();
}
}
function keepBoth() {
closeDuplicateModal();
}
function closeDuplicateModal() {
document.getElementById('duplicate-modal').classList.remove('active');
duplicateCheckId = null;
}
// Telegram сводка
function showTelegramSummary() {
const today = new Date().toISOString().slice(0, 10);
const counts = {
'новая': requests.filter(r => r.status === 'новая').length,
'в работе': requests.filter(r => r.status === 'в работе').length,
'на проверке': requests.filter(r => r.status === 'на проверке').length,
'спорная': requests.filter(r => r.status === 'спорная').length,
'завершена': requests.filter(r => r.status === 'завершена').length,
'дубли': requests.filter(r => r.isDuplicate).length
};
const managers = {};
requests.filter(r => r.manager && r.status !== 'завершена').forEach(r => {
managers[r.manager] = (managers[r.manager] || 0) + 1;
});
const text = `📊 *Сводка по заявкам на ${formatDate(today)}*
🔹 Новые: ${counts['новая']}
🔹 В работе: ${counts['в работе']}
🔹 На проверке: ${counts['на проверке']}
🔹 Спорные: ${counts['спорная']}
🔹 Дубли: ${counts['дубли']}
🔹 Завершено: ${counts['завершена']}
📈 *Всего в работе:* ${counts['новая'] + counts['в работе'] + counts['на проверке'] + counts['спорная']}
👥 *По менеджерам:*
${Object.entries(managers).map(([m, c]) => `${m}: ${c} заяв.`).join('\n') || '—'}
⚠️ *Просрочка:* ${requests.filter(r => r.status === 'просрочка').length} заявок старше 3 дней`;
document.getElementById('telegram-content').textContent = text;
document.getElementById('telegram-modal').classList.add('active');
}
function closeTelegramModal() {
document.getElementById('telegram-modal').classList.remove('active');
}
function copyTelegram() {
const text = document.getElementById('telegram-content').textContent;
navigator.clipboard.writeText(text).then(() => {
alert('Сводка скопирована в буфер обмена!');
});
}
// Экспорт в Excel (CSV с BOM)
function exportToExcel() {
const headers = ['ID', 'Дата', 'ФИО', 'Телефон', 'Адрес', 'Тариф', 'Менеджер', 'Статус', 'Комментарий', 'Дубль'];
const rows = requests.map(r => [
r.id,
r.date,
`"${r.name.replace(/"/g, '""')}"`,
`"${r.phone.replace(/"/g, '""')}"`,
`"${r.address.replace(/"/g, '""')}"`,
`"${r.tariff.replace(/"/g, '""')}"`,
`"${(r.manager || '').replace(/"/g, '""')}"`,
r.status,
`"${(r.comment || '').replace(/"/g, '""')}"`,
r.isDuplicate ? 'Да' : 'Нет'
]);
const csv = '\uFEFF' + [headers.join(';'), ...rows.map(r => r.join(';'))].join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `zayavki_${new Date().toISOString().slice(0, 10)}.csv`;
a.click();
URL.revokeObjectURL(url);
}
// Старт
init();