forma-sbora-dannyh-formirova/index.html

426 lines
20 KiB
HTML
Raw 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.

<!DOCTYPE html>
<html lang="ru" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>График дежурств</title>
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
<link rel="stylesheet" href="design-system/kt-ai-components.css">
<link rel="stylesheet" href="design-system/kt-ai-page.css">
<style>
a.kt-ai-btn { text-decoration: none; }
.kt-ai-app { min-height: 100vh; display: flex; flex-direction: column; }
.kt-ai-main { flex: 1; padding: var(--kt-ai-space-8) 0; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--kt-ai-space-6); }
.status-bar { display: flex; gap: var(--kt-ai-space-3); flex-wrap: wrap; margin-bottom: var(--kt-ai-space-6); }
.table-responsive { overflow-x: auto; }
.view-toggle { display: flex; gap: var(--kt-ai-space-2); margin-bottom: var(--kt-ai-space-4); }
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.calendar-day { min-height: 80px; border: 1px solid var(--kt-ai-border); border-radius: var(--kt-ai-radius-md); padding: var(--kt-ai-space-2); }
.calendar-day.header { font-weight: var(--kt-ai-weight-semibold); text-align: center; }
.calendar-day.empty { background: var(--kt-ai-bg-soft); }
.duty-badge { font-size: var(--kt-ai-text-xs); padding: 2px 6px; border-radius: var(--kt-ai-radius-md); margin-top: 4px; display: block; }
.filter-active { background: var(--kt-ai-primary); color: white; }
</style>
</head>
<body class="kt-ai-app">
<header class="kt-ai-pagehead">
<div class="kt-ai-wrap kt-ai-pagehead-in">
<div style="display:flex;align-items:center;gap:var(--kt-ai-space-3)">
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
<strong>График дежурств</strong>
</div>
<span class="kt-ai-spacer"></span>
<button class="kt-ai-btn" data-variant="ghost" onclick="toggleView()">Переключить вид</button>
<button class="kt-ai-btn" data-variant="primary" onclick="showTab('form')">Новая заявка</button>
</div>
</header>
<main class="kt-ai-main">
<div class="kt-ai-wrap">
<div class="kt-ai-kpi-strip" style="margin-bottom: var(--kt-ai-space-8)">
<div class="kt-ai-kpi">
<div class="value" id="kpi-total">0</div>
<div class="label">Всего заявок</div>
<div class="hint">За все время</div>
</div>
<div class="kt-ai-kpi">
<div class="value" id="kpi-pending" style="color: var(--kt-ai-chart-orange)">0</div>
<div class="label">На рассмотрении</div>
<div class="hint">Ожидают подтверждения</div>
</div>
<div class="kt-ai-kpi">
<div class="value" id="kpi-approved" style="color: var(--kt-ai-chart-green)">0</div>
<div class="label">Утверждено</div>
<div class="hint">Включено в график</div>
</div>
<div class="kt-ai-kpi">
<div class="value" id="kpi-rejected" style="color: var(--kt-ai-chart-red)">0</div>
<div class="label">Отклонено</div>
<div class="hint">Требуют внимания</div>
</div>
</div>
<div id="tab-form">
<div class="kt-ai-card" style="padding: var(--kt-ai-space-8); margin-bottom: var(--kt-ai-space-8)">
<h2 style="margin-bottom: var(--kt-ai-space-6)">Заявка на дежурство</h2>
<form id="dutyForm" onsubmit="submitForm(event)">
<div class="form-grid">
<div class="kt-ai-field">
<label>ФИО *</label>
<input type="text" class="kt-ai-input" id="fio" placeholder="Иванов Иван Иванович" required>
</div>
<div class="kt-ai-field">
<label>Должность *</label>
<input type="text" class="kt-ai-input" id="position" placeholder="Инженер технической поддержки" required>
</div>
<div class="kt-ai-field">
<label>Подразделение</label>
<input type="text" class="kt-ai-input" id="department" placeholder="Департамент ИТ">
</div>
<div class="kt-ai-field">
<label>Контактный телефон</label>
<input type="tel" class="kt-ai-input" id="phone" placeholder="+7 (___) ___-__-__">
</div>
<div class="kt-ai-field">
<label>Дата начала дежурства *</label>
<input type="date" class="kt-ai-input" id="dateStart" required>
</div>
<div class="kt-ai-field">
<label>Дата окончания дежурства *</label>
<input type="date" class="kt-ai-input" id="dateEnd" required>
</div>
<div class="kt-ai-field">
<label>Тип дежурства *</label>
<select class="kt-ai-select" id="dutyType" required>
<option value="">Выберите тип</option>
<option value="weekday">Будний день</option>
<option value="weekend">Выходной день</option>
<option value="holiday">Праздничный день</option>
<option value="night">Ночное дежурство</option>
<option value="full">Круглосуточное</option>
</select>
</div>
<div class="kt-ai-field">
<label>Приоритет</label>
<select class="kt-ai-select" id="priority">
<option value="normal">Обычный</option>
<option value="high">Высокий</option>
<option value="urgent">Срочный</option>
</select>
</div>
</div>
<div class="kt-ai-field" style="margin-top: var(--kt-ai-space-6)">
<label>Комментарий (пожелания по графику)</label>
<textarea class="kt-ai-textarea" id="comment" rows="3" placeholder="Например: предпочтительно утренние смены"></textarea>
</div>
<div style="display: flex; gap: var(--kt-ai-space-4); margin-top: var(--kt-ai-space-8)">
<button type="submit" class="kt-ai-btn" data-variant="primary" data-size="lg">Сохранить заявку</button>
<button type="button" class="kt-ai-btn" data-variant="ghost" data-size="lg" onclick="clearForm()">Очистить</button>
</div>
</form>
</div>
</div>
<div id="tab-list" style="display: none;">
<div class="kt-ai-card" style="padding: var(--kt-ai-space-6)">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--kt-ai-space-4); flex-wrap: wrap; gap: var(--kt-ai-space-4)">
<h2 style="margin: 0">Заявки</h2>
<input type="text" class="kt-ai-input" placeholder="Поиск по ФИО..." oninput="filterTable(this.value)" style="max-width: 280px">
</div>
<div class="status-bar">
<button class="kt-ai-chip filter-active" onclick="filterStatus('all', this)">Все</button>
<button class="kt-ai-chip" onclick="filterStatus('pending', this)">На рассмотрении</button>
<button class="kt-ai-chip" onclick="filterStatus('approved', this)">Утверждено</button>
<button class="kt-ai-chip" onclick="filterStatus('rejected', this)">Отклонено</button>
</div>
<div class="table-responsive">
<table class="kt-ai-table">
<thead>
<tr>
<th>ФИО</th>
<th>Должность</th>
<th>Даты</th>
<th>Тип</th>
<th>Статус</th>
<th>Дата подачи</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="applicationsTable"></tbody>
</table>
</div>
</div>
</div>
<div id="tab-calendar" style="display: none;">
<div class="kt-ai-card" style="padding: var(--kt-ai-space-6)">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--kt-ai-space-6)">
<h2 style="margin: 0">Календарь дежурств</h2>
<div style="display: flex; gap: var(--kt-ai-space-2)">
<button class="kt-ai-btn" onclick="prevMonth()">←</button>
<span id="currentMonth" style="font-weight: var(--kt-ai-weight-semibold); min-width: 180px; text-align: center;"></span>
<button class="kt-ai-btn" onclick="nextMonth()">→</button>
</div>
</div>
<div class="calendar-grid" id="calendarGrid"></div>
</div>
</div>
</div>
</main>
<footer class="kt-ai-wrap kt-ai-pagefoot">
<span>© 2026 Казахтелеком</span>
<span class="kt-ai-spacer"></span>
<span>Система учёта дежурств</span>
</footer>
<script>
let applications = [];
let currentFilter = 'all';
let currentDate = new Date();
const DEMO_DATA = [
{ id: 1, fio: 'Иванов Иван Иванович', position: 'Инженер технической поддержки', department: 'Департамент ИТ', phone: '+7 (777) 123-45-67', dateStart: '2026-09-15', dateEnd: '2026-09-15', dutyType: 'weekday', priority: 'normal', comment: '', status: 'approved', submittedAt: '2026-09-01' },
{ id: 2, fio: 'Петров Пётр Петрович', position: 'Системный администратор', department: 'Отдел инфраструктуры', phone: '+7 (777) 234-56-78', dateStart: '2026-09-20', dateEnd: '2026-09-21', dutyType: 'weekend', priority: 'high', comment: 'Можно заменить', status: 'pending', submittedAt: '2026-09-03' },
{ id: 3, fio: 'Сидорова Анна Михайловна', position: 'Специалист кол-центра', department: 'Департамент сервиса', phone: '+7 (777) 345-67-89', dateStart: '2026-09-01', dateEnd: '2026-09-01', dutyType: 'holiday', priority: 'urgent', comment: '', status: 'approved', submittedAt: '2026-08-25' },
{ id: 4, fio: 'Касымов Ержан Болатович', position: 'Ведущий инженер', department: 'Служба безопасности', phone: '+7 (777) 456-78-90', dateStart: '2026-09-10', dateEnd: '2026-09-12', dutyType: 'night', priority: 'normal', comment: 'Предпочитаю ночь', status: 'rejected', submittedAt: '2026-09-05' },
{ id: 5, fio: 'Алиева Гульнара Талгатовна', position: 'Менеджер проектов', department: 'Департамент развития', phone: '+7 (777) 567-89-01', dateStart: '2026-09-25', dateEnd: '2026-09-25', dutyType: 'full', priority: 'high', comment: '', status: 'pending', submittedAt: '2026-09-08' }
];
function init() {
const stored = localStorage.getItem('dutyApplications');
applications = stored ? JSON.parse(stored) : [...DEMO_DATA];
updateKPI();
renderTable();
renderCalendar();
updateMonthLabel();
}
function saveApplications() {
localStorage.setItem('dutyApplications', JSON.stringify(applications));
updateKPI();
renderTable();
renderCalendar();
}
function updateKPI() {
document.getElementById('kpi-total').textContent = applications.length;
document.getElementById('kpi-pending').textContent = applications.filter(a => a.status === 'pending').length;
document.getElementById('kpi-approved').textContent = applications.filter(a => a.status === 'approved').length;
document.getElementById('kpi-rejected').textContent = applications.filter(a => a.status === 'rejected').length;
}
function submitForm(e) {
e.preventDefault();
const newApp = {
id: Date.now(),
fio: document.getElementById('fio').value,
position: document.getElementById('position').value,
department: document.getElementById('department').value,
phone: document.getElementById('phone').value,
dateStart: document.getElementById('dateStart').value,
dateEnd: document.getElementById('dateEnd').value,
dutyType: document.getElementById('dutyType').value,
priority: document.getElementById('priority').value,
comment: document.getElementById('comment').value,
status: 'pending',
submittedAt: new Date().toISOString().split('T')[0]
};
applications.push(newApp);
saveApplications();
clearForm();
showTab('list');
showNotification('Заявка успешно сохранена!', 'success');
}
function clearForm() {
document.getElementById('dutyForm').reset();
}
function renderTable(filterText = '') {
const tbody = document.getElementById('applicationsTable');
let filtered = applications;
if (currentFilter !== 'all') {
filtered = filtered.filter(a => a.status === currentFilter);
}
if (filterText) {
const lower = filterText.toLowerCase();
filtered = filtered.filter(a => a.fio.toLowerCase().includes(lower));
}
tbody.innerHTML = filtered.map(app => {
const statusConfig = getStatusConfig(app.status);
const typeLabel = getTypeLabel(app.dutyType);
return `
<tr>
<td style="font-weight: var(--kt-ai-weight-medium)">${app.fio}</td>
<td>${app.position}</td>
<td>${formatDateRange(app.dateStart, app.dateEnd)}</td>
<td>${typeLabel}</td>
<td><span class="kt-ai-chip" style="background: ${statusConfig.bg}; color: ${statusConfig.color}">${statusConfig.label}</span></td>
<td>${app.submittedAt}</td>
<td>
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="editApp(${app.id})">✏️</button>
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="deleteApp(${app.id})">🗑️</button>
</td>
</tr>
`;
}).join('');
}
function getStatusConfig(status) {
const configs = {
pending: { label: 'На рассмотрении', bg: 'var(--kt-ai-chart-orange)', color: 'white' },
approved: { label: 'Утверждено', bg: 'var(--kt-ai-chart-green)', color: 'white' },
rejected: { label: 'Отклонено', bg: 'var(--kt-ai-chart-red)', color: 'white' }
};
return configs[status] || configs.pending;
}
function getTypeLabel(type) {
const labels = {
weekday: 'Будний день',
weekend: 'Выходной',
holiday: 'Праздничный',
night: 'Ночное',
full: 'Круглосуточное'
};
return labels[type] || type;
}
function formatDateRange(start, end) {
const s = new Date(start).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' });
const e = new Date(end).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: '2-digit' });
return s === e ? s : `${s} — ${e}`;
}
function filterStatus(status, btn) {
currentFilter = status;
document.querySelectorAll('.status-bar .kt-ai-chip').forEach(b => b.classList.remove('filter-active'));
btn.classList.add('filter-active');
renderTable();
}
function filterTable(value) {
renderTable(value);
}
function editApp(id) {
const app = applications.find(a => a.id === id);
if (!app) return;
document.getElementById('fio').value = app.fio;
document.getElementById('position').value = app.position;
document.getElementById('department').value = app.department;
document.getElementById('phone').value = app.phone;
document.getElementById('dateStart').value = app.dateStart;
document.getElementById('dateEnd').value = app.dateEnd;
document.getElementById('dutyType').value = app.dutyType;
document.getElementById('priority').value = app.priority;
document.getElementById('comment').value = app.comment;
showTab('form');
}
function deleteApp(id) {
if (confirm('Удалить эту заявку?')) {
applications = applications.filter(a => a.id !== id);
saveApplications();
showNotification('Заявка удалена', 'info');
}
}
function showTab(tab) {
document.getElementById('tab-form').style.display = tab === 'form' ? 'block' : 'none';
document.getElementById('tab-list').style.display = tab === 'list' ? 'block' : 'none';
document.getElementById('tab-calendar').style.display = tab === 'calendar' ? 'block' : 'none';
}
function toggleView() {
const current = document.getElementById('tab-list').style.display === 'block' ? 'list' :
document.getElementById('tab-calendar').style.display === 'block' ? 'calendar' : 'form';
const next = current === 'list' ? 'calendar' : current === 'calendar' ? 'form' : 'list';
showTab(next);
}
function renderCalendar() {
const grid = document.getElementById('calendarGrid');
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const startDay = firstDay.getDay() || 7;
const days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
grid.innerHTML = days.map(d => `<div class="calendar-day header">${d}</div>`).join('');
for (let i = 1; i < startDay; i++) {
grid.innerHTML += '<div class="calendar-day empty"></div>';
}
const monthApps = applications.filter(a => {
const start = new Date(a.dateStart);
const end = new Date(a.dateEnd);
return start.getMonth() === month && start.getFullYear() === year ||
end.getMonth() === month && end.getFullYear() === year ||
(start < firstDay && end > lastDay);
});
for (let day = 1; day <= lastDay.getDate(); day++) {
const date = new Date(year, month, day);
const dateStr = date.toISOString().split('T')[0];
const dayApps = monthApps.filter(a => date >= new Date(a.dateStart) && date <= new Date(a.dateEnd));
grid.innerHTML += `
<div class="calendar-day">
<div style="font-weight: var(--kt-ai-weight-semibold); margin-bottom: 4px">${day}</div>
${dayApps.map(a => `
<span class="duty-badge" style="background: ${getStatusConfig(a.status).bg}; color: ${getStatusConfig(a.status).color}" title="${a.fio}">
${a.fio.split(' ').slice(0, 2).join(' ').slice(0, 15)}
</span>
`).join('')}
</div>
`;
}
}
function updateMonthLabel() {
const months = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'];
document.getElementById('currentMonth').textContent = `${months[currentDate.getMonth()]} ${currentDate.getFullYear()}`;
}
function prevMonth() {
currentDate.setMonth(currentDate.getMonth() - 1);
updateMonthLabel();
renderCalendar();
}
function nextMonth() {
currentDate.setMonth(currentDate.getMonth() + 1);
updateMonthLabel();
renderCalendar();
}
function showNotification(message, type) {
const colors = { success: 'var(--kt-ai-chart-green)', info: 'var(--kt-ai-chart-blue)', error: 'var(--kt-ai-chart-red)' };
const notif = document.createElement('div');
notif.style.cssText = `position: fixed; bottom: var(--kt-ai-space-6); right: var(--kt-ai-space-6); padding: var(--kt-ai-space-4) var(--kt-ai-space-6); background: ${colors[type] || colors.info}; color: white; border-radius: var(--kt-ai-radius-xl); font-weight: var(--kt-ai-weight-medium); z-index: 1000; animation: slideIn 0.3s ease`;
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(() => notif.remove(), 3000);
}
init();
</script>
</body>
</html>