const STORAGE_KEY = 'uchet_komandirovok_data';
const CURRENT_USER = 'Ответственный за учёт';
const DEMO_DATA = {
items: [
{
id: 1,
employee: 'Айдос Нурланов',
city: 'Астана',
dateStart: '2026-08-12',
dateEnd: '2026-08-15',
purpose: 'Участие в международной конференции по цифровизации',
amount: 185000,
status: 'согласовано'
},
{
id: 2,
employee: 'Гульназ Сейдахметова',
city: 'Алматы',
dateStart: '2026-09-03',
dateEnd: '2026-09-05',
purpose: 'Переговоры с партнёрами по проекту',
amount: 95000,
status: 'на согласовании'
},
{
id: 3,
employee: 'Ержан Тлеубергенов',
city: 'Шымкент',
dateStart: '2026-09-18',
dateEnd: '2026-09-20',
purpose: 'Обучение сотрудников филиала',
amount: 72000,
status: 'на согласовании'
},
{
id: 4,
employee: 'Алия Касымова',
city: 'Караганда',
dateStart: '2026-10-05',
dateEnd: '2026-10-07',
purpose: 'Аудит регионального офиса',
amount: 68000,
status: 'согласовано'
},
{
id: 5,
employee: 'Айдос Нурланов',
city: 'Актобе',
dateStart: '2026-10-12',
dateEnd: '2026-10-14',
purpose: 'Презентация нового продукта',
amount: 89000,
status: 'отклонено'
},
{
id: 6,
employee: 'Гульназ Сейдахметова',
city: 'Астана',
dateStart: '2026-11-02',
dateEnd: '2026-11-06',
purpose: 'Участие в выставке EXPO',
amount: 145000,
status: 'согласовано'
},
{
id: 7,
employee: 'Ержан Тлеубергенов',
city: 'Алматы',
dateStart: '2026-11-15',
dateEnd: '2026-11-17',
purpose: 'Встреча с инвесторами',
amount: 112000,
status: 'на согласовании'
}
],
log: [
{ when: '2026-08-01 10:23', what: 'Создана запись: Айдос Нурланов — Астана', who: 'Ответственный за учёт' },
{ when: '2026-08-01 14:15', what: 'Статус изменён на «согласовано»: Айдос Нурланов', who: 'Ответственный за учёт' },
{ when: '2026-08-02 09:45', what: 'Создана запись: Гульназ Сейдахметова — Алматы', who: 'Ответственный за учёт' },
{ when: '2026-08-02 11:30', what: 'Создана запись: Ержан Тлеубергенов — Шымкент', who: 'Ответственный за учёт' },
{ when: '2026-08-03 16:20', what: 'Создана запись: Алия Касымова — Караганда', who: 'Ответственный за учёт' },
{ when: '2026-08-04 10:00', what: 'Статус изменён на «согласовано»: Алия Касымова', who: 'Ответственный за учёт' },
{ when: '2026-08-05 13:45', what: 'Создана запись: Айдос Нурланов — Актобе', who: 'Ответственный за учёт' },
{ when: '2026-08-05 15:30', what: 'Статус изменён на «отклонено»: Айдос Нурланов', who: 'Ответственный за учёт' },
{ when: '2026-08-06 09:15', what: 'Создана запись: Гульназ Сейдахметова — Астана', who: 'Ответственный за учёт' },
{ when: '2026-08-07 11:00', what: 'Создана запись: Ержан Тлеубергенов — Алматы', who: 'Ответственный за учёт' }
]
};
function loadData() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
return JSON.parse(stored);
}
} catch (e) {
console.error('Ошибка загрузки данных:', e);
}
return JSON.parse(JSON.stringify(DEMO_DATA));
}
function saveData(data) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}
function formatDate(dateStr) {
const [year, month, day] = dateStr.split('-');
return `${day}.${month}.${year}`;
}
function formatDateTime() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
function getStatusClass(status) {
if (status === 'согласовано') return 'status-approved';
if (status === 'отклонено') return 'status-rejected';
return 'status-pending';
}
function getStatusLabel(status) {
if (status === 'согласовано') return '✓ Согласовано';
if (status === 'отклонено') return '✗ Отклонено';
return '⏳ На согласовании';
}
let data = loadData();
let currentFilter = '';
let searchQuery = '';
function renderStats() {
const total = data.items.length;
const pending = data.items.filter(i => i.status === 'на согласовании').length;
const approved = data.items.filter(i => i.status === 'согласовано').length;
const rejected = data.items.filter(i => i.status === 'отклонено').length;
document.getElementById('stat-total').textContent = total;
document.getElementById('stat-pending').textContent = pending;
document.getElementById('stat-approved').textContent = approved;
document.getElementById('stat-rejected').textContent = rejected;
}
function renderTable() {
const tbody = document.getElementById('table-body');
let filtered = data.items;
if (currentFilter) {
filtered = filtered.filter(i => i.status === currentFilter);
}
if (searchQuery) {
const q = searchQuery.toLowerCase();
filtered = filtered.filter(i =>
i.employee.toLowerCase().includes(q) ||
i.city.toLowerCase().includes(q) ||
i.purpose.toLowerCase().includes(q)
);
}
if (filtered.length === 0) {
tbody.innerHTML = `
Нет записей для отображения
|
`;
return;
}
tbody.innerHTML = filtered.map(item => `
| ${item.employee} |
${item.city} |
${formatDate(item.dateStart)} – ${formatDate(item.dateEnd)} |
${item.purpose} |
${item.amount.toLocaleString('ru-RU')} |
${getStatusLabel(item.status)} |
|
`).join('');
}
function renderLog() {
const tbody = document.getElementById('log-body');
if (data.log.length === 0) {
tbody.innerHTML = '| Журнал пуст |
';
return;
}
const sortedLog = [...data.log].reverse();
tbody.innerHTML = sortedLog.map(entry => `
| ${entry.when} |
${entry.what} |
${entry.who} |
`).join('');
}
function openModal(modalId) {
document.getElementById(modalId).classList.add('active');
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
function openForm(id = null) {
const modal = document.getElementById('modal-form');
const title = document.getElementById('modal-title');
const form = document.getElementById('form');
form.reset();
document.getElementById('form-id').value = '';
if (id) {
const item = data.items.find(i => i.id === id);
if (item) {
title.textContent = 'Редактировать командировку';
document.getElementById('form-id').value = item.id;
document.getElementById('form-employee').value = item.employee;
document.getElementById('form-city').value = item.city;
document.getElementById('form-date-start').value = item.dateStart;
document.getElementById('form-date-end').value = item.dateEnd;
document.getElementById('form-purpose').value = item.purpose;
document.getElementById('form-amount').value = item.amount;
document.getElementById('form-status').value = item.status;
}
} else {
title.textContent = 'Добавить командировку';
}
openModal('modal-form');
}
function editItem(id) {
openForm(id);
}
function deleteItem(id) {
const item = data.items.find(i => i.id === id);
if (!item) return;
if (confirm(`Вы уверены, что хотите удалить запись?\n\nСотрудник: ${item.employee}\nГород: ${item.city}\nДаты: ${formatDate(item.dateStart)} – ${formatDate(item.dateEnd)}`)) {
const logEntry = {
when: formatDateTime(),
what: `Удалена запись: ${item.employee} — ${item.city}`,
who: CURRENT_USER
};
data.items = data.items.filter(i => i.id !== id);
data.log.push(logEntry);
saveData(data);
renderStats();
renderTable();
renderLog();
}
}
function addItem(item) {
const logEntry = {
when: formatDateTime(),
what: `Создана запись: ${item.employee} — ${item.city}`,
who: CURRENT_USER
};
data.items.push(item);
data.log.push(logEntry);
saveData(data);
renderStats();
renderTable();
renderLog();
}
function updateItem(id, updatedItem) {
const oldItem = data.items.find(i => i.id === id);
if (!oldItem) return;
const changes = [];
if (oldItem.employee !== updatedItem.employee) changes.push(`сотрудник: ${oldItem.employee} → ${updatedItem.employee}`);
if (oldItem.city !== updatedItem.city) changes.push(`город: ${oldItem.city} → ${updatedItem.city}`);
if (oldItem.status !== updatedItem.status) changes.push(`статус: ${oldItem.status} → ${updatedItem.status}`);
const logEntry = {
when: formatDateTime(),
what: `Изменена запись: ${oldItem.employee} — ${item.city}` + (changes.length > 0 ? ` (${changes.join(', ')})` : ''),
who: CURRENT_USER
};
const index = data.items.findIndex(i => i.id === id);
data.items[index] = { ...updatedItem, id };
data.log.push(logEntry);
saveData(data);
renderStats();
renderTable();
renderLog();
}
function exportCsv() {
const headers = ['Сотрудник', 'Город', 'Дата начала', 'Дата окончания', 'Цель', 'Сумма (тенге)', 'Статус'];
const rows = data.items.map(item => [
item.employee,
item.city,
formatDate(item.dateStart),
formatDate(item.dateEnd),
item.purpose,
item.amount.toString(),
item.status
]);
const csvContent = '\uFEFF' + [
headers.join(';'),
...rows.map(row => row.map(cell => '"' + String(cell).replace(/"/g, '""') + '"').join(';'))
].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'komandirovki_' + new Date().toISOString().slice(0, 10) + '.csv';
link.click();
URL.revokeObjectURL(link.href);
}
document.addEventListener('DOMContentLoaded', () => {
renderStats();
renderTable();
renderLog();
document.getElementById('btn-add').addEventListener('click', () => openForm());
document.getElementById('close-form').addEventListener('click', () => closeModal('modal-form'));
document.getElementById('cancel-form').addEventListener('click', () => closeModal('modal-form'));
document.getElementById('modal-form').addEventListener('click', (e) => {
if (e.target.id === 'modal-form') closeModal('modal-form');
});
document.getElementById('btn-log').addEventListener('click', () => {
renderLog();
openModal('modal-log');
});
document.getElementById('close-log').addEventListener('click', () => closeModal('modal-log'));
document.getElementById('modal-log').addEventListener('click', (e) => {
if (e.target.id === 'modal-log') closeModal('modal-log');
});
document.getElementById('btn-export').addEventListener('click', exportCsv);
document.getElementById('search').addEventListener('input', (e) => {
searchQuery = e.target.value;
renderTable();
});
document.querySelectorAll('.chip').forEach(chip => {
chip.addEventListener('click', () => {
document.querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
chip.classList.add('active');
currentFilter = chip.dataset.status;
renderTable();
});
});
document.getElementById('form').addEventListener('submit', (e) => {
e.preventDefault();
const id = document.getElementById('form-id').value;
const item = {
employee: document.getElementById('form-employee').value,
city: document.getElementById('form-city').value,
dateStart: document.getElementById('form-date-start').value,
dateEnd: document.getElementById('form-date-end').value,
purpose: document.getElementById('form-purpose').value,
amount: parseInt(document.getElementById('form-amount').value, 10),
status: document.getElementById('form-status').value
};
if (id) {
updateItem(parseInt(id, 10), item);
} else {
item.id = Date.now();
addItem(item);
}
closeModal('modal-form');
});
});