264 lines
9.8 KiB
JavaScript
264 lines
9.8 KiB
JavaScript
const STATUSES = ['Новая', 'В работе', 'На согласовании', 'Готово'];
|
||
const DEMO_TASKS = [
|
||
{ id: 1, task: 'Подготовить отчёт по итогам квартала', person: 'Айжан Сәрсен', date: '10.08.2026', priority: 'Высокий', status: 'В работе' },
|
||
{ id: 2, task: 'Согласовать бюджет на следующий месяц', person: 'Ерлан Нурбек', date: '12.08.2026', priority: 'Критичный', status: 'Новая' },
|
||
{ id: 3, task: 'Провести обучение сотрудников', person: 'Динара Қуат', date: '14.08.2026', priority: 'Средний', status: 'В работе' },
|
||
{ id: 4, task: 'Обновить документацию проекта', person: 'Тимур Аскаров', date: '08.08.2026', priority: 'Низкий', status: 'На согласовании' },
|
||
{ id: 5, task: 'Настроить автоматическую рассылку', person: 'Айжан Сәрсен', date: '05.08.2026', priority: 'Средний', status: 'Новая' },
|
||
{ id: 6, task: 'Подготовить презентацию для руководства', person: 'Ерлан Нурбек', date: '18.08.2026', priority: 'Высокий', status: 'Готово' },
|
||
{ id: 7, task: 'Проверить договоры с контрагентами', person: 'Динара Қуат', date: '22.08.2026', priority: 'Средний', status: 'Готово' }
|
||
];
|
||
|
||
let tasks = [];
|
||
let log = [];
|
||
let filterStatus = null;
|
||
|
||
function init() {
|
||
const saved = localStorage.getItem('tasks');
|
||
if (saved) {
|
||
tasks = JSON.parse(saved);
|
||
} else {
|
||
tasks = [...DEMO_TASKS];
|
||
saveTasks();
|
||
}
|
||
const savedLog = localStorage.getItem('taskLog');
|
||
if (savedLog) {
|
||
log = JSON.parse(savedLog);
|
||
} else {
|
||
log = [{ when: new Date().toLocaleString('ru-RU'), what: 'Инициализация демо-данных', who: 'Система' }];
|
||
saveLog();
|
||
}
|
||
render();
|
||
}
|
||
|
||
function saveTasks() {
|
||
localStorage.setItem('tasks', JSON.stringify(tasks));
|
||
}
|
||
|
||
function saveLog() {
|
||
localStorage.setItem('taskLog', JSON.stringify(log));
|
||
}
|
||
|
||
function addLogEntry(what) {
|
||
log.unshift({
|
||
when: new Date().toLocaleString('ru-RU'),
|
||
what: what,
|
||
who: 'Пользователь'
|
||
});
|
||
if (log.length > 50) log.pop();
|
||
saveLog();
|
||
}
|
||
|
||
function parseDate(dateStr) {
|
||
const parts = dateStr.split('.');
|
||
return new Date(parts[2], parts[1] - 1, parts[0]);
|
||
}
|
||
|
||
function isOverdue(task) {
|
||
if (task.status === 'Готово') return false;
|
||
const taskDate = parseDate(task.date);
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
return taskDate < today;
|
||
}
|
||
|
||
function getStatusClass(status) {
|
||
switch (status) {
|
||
case 'Новая': return 'status-new';
|
||
case 'В работе': return 'status-progress';
|
||
case 'На согласовании': return 'status-review';
|
||
case 'Готово': return 'status-done';
|
||
default: return '';
|
||
}
|
||
}
|
||
|
||
function getPriorityClass(priority) {
|
||
switch (priority) {
|
||
case 'Низкий': return 'priority-low';
|
||
case 'Средний': return 'priority-medium';
|
||
case 'Высокий': return 'priority-high';
|
||
case 'Критичный': return 'priority-critical';
|
||
default: return '';
|
||
}
|
||
}
|
||
|
||
function updateStats() {
|
||
const stats = { total: tasks.length, 'Новая': 0, 'В работе': 0, 'На согласовании': 0, 'Готово': 0 };
|
||
tasks.forEach(t => stats[t.status]++);
|
||
document.getElementById('stat-total').textContent = stats.total;
|
||
document.getElementById('stat-new').textContent = stats['Новая'];
|
||
document.getElementById('stat-progress').textContent = stats['В работе'];
|
||
document.getElementById('stat-review').textContent = stats['На согласовании'];
|
||
document.getElementById('stat-done').textContent = stats['Готово'];
|
||
}
|
||
|
||
function renderChips() {
|
||
const container = document.getElementById('chips');
|
||
container.innerHTML = STATUSES.map(s =>
|
||
`<button class="chip ${filterStatus === s ? 'active' : ''}" data-status="${s}">${s}</button>`
|
||
).join('') + `<button class="chip ${filterStatus === null ? 'active' : ''}" data-status="">Все</button>`;
|
||
container.querySelectorAll('.chip').forEach(chip => {
|
||
chip.addEventListener('click', () => {
|
||
filterStatus = chip.dataset.status || null;
|
||
renderTasks();
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderTasks() {
|
||
const search = document.getElementById('search').value.toLowerCase();
|
||
let filtered = tasks.filter(t => {
|
||
const matchesSearch =
|
||
t.task.toLowerCase().includes(search) ||
|
||
t.person.toLowerCase().includes(search) ||
|
||
t.date.toLowerCase().includes(search) ||
|
||
t.priority.toLowerCase().includes(search) ||
|
||
t.status.toLowerCase().includes(search);
|
||
const matchesStatus = filterStatus === null || t.status === filterStatus;
|
||
return matchesSearch && matchesStatus;
|
||
});
|
||
|
||
const tbody = document.getElementById('tasks-body');
|
||
if (filtered.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="6" class="empty-state">Нет задач для отображения</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = filtered.map(t => {
|
||
const overdue = isOverdue(t);
|
||
return `
|
||
<tr class="${overdue ? 'overdue' : ''}">
|
||
<td>${t.task}</td>
|
||
<td>${t.person}</td>
|
||
<td>${t.date}</td>
|
||
<td><span class="${getPriorityClass(t.priority)}">${t.priority}</span></td>
|
||
<td><span class="status-badge ${getStatusClass(t.status)}">${t.status}</span></td>
|
||
<td class="actions">
|
||
<button class="btn btn-secondary btn-sm" onclick="editTask(${t.id})">✏️ Редактировать</button>
|
||
<button class="btn btn-danger btn-sm" onclick="deleteTask(${t.id})">🗑️ Удалить</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function renderLog() {
|
||
const container = document.getElementById('log');
|
||
container.innerHTML = log.map(entry =>
|
||
`<div class="log-entry"><strong>${entry.what}</strong> — ${entry.who} <span>(${entry.when})</span></div>`
|
||
).join('');
|
||
}
|
||
|
||
function render() {
|
||
updateStats();
|
||
renderChips();
|
||
renderTasks();
|
||
renderLog();
|
||
}
|
||
|
||
function openModal(editId = null) {
|
||
const modal = document.getElementById('modal');
|
||
const form = document.getElementById('task-form');
|
||
form.reset();
|
||
document.getElementById('edit-id').value = '';
|
||
document.getElementById('modal-title').textContent = 'Новая задача';
|
||
|
||
if (editId !== null) {
|
||
const task = tasks.find(t => t.id === editId);
|
||
if (task) {
|
||
document.getElementById('edit-id').value = task.id;
|
||
document.getElementById('f-task').value = task.task;
|
||
document.getElementById('f-person').value = task.person;
|
||
document.getElementById('f-date').value = task.date;
|
||
document.getElementById('f-priority').value = task.priority;
|
||
document.getElementById('f-status').value = task.status;
|
||
document.getElementById('modal-title').textContent = 'Редактирование задачи';
|
||
}
|
||
}
|
||
modal.showModal();
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modal').close();
|
||
}
|
||
|
||
function saveTask(e) {
|
||
e.preventDefault();
|
||
const editId = document.getElementById('edit-id').value;
|
||
const taskData = {
|
||
task: document.getElementById('f-task').value,
|
||
person: document.getElementById('f-person').value,
|
||
date: document.getElementById('f-date').value,
|
||
priority: document.getElementById('f-priority').value,
|
||
status: document.getElementById('f-status').value
|
||
};
|
||
|
||
if (editId) {
|
||
const id = parseInt(editId);
|
||
const index = tasks.findIndex(t => t.id === id);
|
||
if (index !== -1) {
|
||
tasks[index] = { ...tasks[index], ...taskData };
|
||
addLogEntry(`Изменена задача: "${taskData.task}"`);
|
||
}
|
||
} else {
|
||
const newId = Math.max(0, ...tasks.map(t => t.id)) + 1;
|
||
tasks.push({ id: newId, ...taskData });
|
||
addLogEntry(`Добавлена задача: "${taskData.task}"`);
|
||
}
|
||
saveTasks();
|
||
closeModal();
|
||
render();
|
||
}
|
||
|
||
window.editTask = function(id) {
|
||
openModal(id);
|
||
};
|
||
|
||
window.deleteTask = function(id) {
|
||
const task = tasks.find(t => t.id === id);
|
||
if (!task) return;
|
||
if (confirm(`Удалить задачу "${task.task}"?`)) {
|
||
tasks = tasks.filter(t => t.id !== id);
|
||
addLogEntry(`Удалена задача: "${task.task}"`);
|
||
saveTasks();
|
||
render();
|
||
}
|
||
};
|
||
|
||
function exportCSV() {
|
||
const headers = ['Задача', 'Исполнитель', 'Срок', 'Приоритет', 'Статус'];
|
||
const rows = tasks.map(t => [t.task, t.person, t.date, t.priority, t.status]
|
||
.map(v => '"' + String(v ?? '').replace(/"/g, '""') + '"')
|
||
.join(';')
|
||
);
|
||
const csv = '\uFEFF' + [headers.join(';'), ...rows].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 = 'zadachi_otdela.csv';
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
addLogEntry('Выгружен список задач в CSV');
|
||
saveLog();
|
||
}
|
||
|
||
function resetDemo() {
|
||
if (confirm('Сбросить все данные к демо-версии? Все изменения будут потеряны.')) {
|
||
tasks = [...DEMO_TASKS];
|
||
log = [{ when: new Date().toLocaleString('ru-RU'), what: 'Сброшено к демо-данным', who: 'Пользователь' }];
|
||
saveTasks();
|
||
saveLog();
|
||
render();
|
||
}
|
||
}
|
||
|
||
document.getElementById('btn-add').addEventListener('click', () => openModal());
|
||
document.getElementById('btn-cancel').addEventListener('click', closeModal);
|
||
document.getElementById('task-form').addEventListener('submit', saveTask);
|
||
document.getElementById('search').addEventListener('input', renderTasks);
|
||
document.getElementById('btn-export').addEventListener('click', exportCSV);
|
||
document.getElementById('btn-reset').addEventListener('click', resetDemo);
|
||
|
||
init();
|