392 lines
18 KiB
HTML
392 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Дашборд GLPI — Заявки Service Desk</title>
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||
<style>
|
||
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--success:#10B981;--warning:#F59E0B;--danger:#EF4444}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100)}
|
||
.header{background:var(--ink);color:var(--white);padding:24px 0}
|
||
.header h1{font-size:28px;font-weight:700;margin-bottom:8px}
|
||
.header p{color:#9aa3b2;font-size:14px}
|
||
.container{max-width:1400px;margin:0 auto;padding:24px}
|
||
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px}
|
||
.metric{background:var(--white);border-radius:12px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08)}
|
||
.metric-label{font-size:13px;color:var(--gray-500);margin-bottom:8px}
|
||
.metric-value{font-size:32px;font-weight:700;color:var(--ink)}
|
||
.metric-sub{font-size:12px;color:var(--gray-500);margin-top:4px}
|
||
.metric.success .metric-value{color:var(--success)}
|
||
.metric.warning .metric-value{color:var(--warning)}
|
||
.metric.danger .metric-value{color:var(--danger)}
|
||
.charts{display:grid;grid-template-columns:repeat(auto-fit,minmax(400px,1fr));gap:24px;margin-bottom:24px}
|
||
.chart-card{background:var(--white);border-radius:12px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08)}
|
||
.chart-card h3{font-size:16px;font-weight:600;margin-bottom:16px}
|
||
.chart-container{position:relative;height:300px}
|
||
.filters{background:var(--white);border-radius:12px;padding:16px;margin-bottom:24px;box-shadow:0 1px 3px rgba(0,0,0,0.08)}
|
||
.filters-row{display:flex;gap:16px;flex-wrap:wrap;align-items:center}
|
||
.filter-group{display:flex;flex-direction:column;gap:4px}
|
||
.filter-group label{font-size:12px;color:var(--gray-500)}
|
||
.filter-group select,.filter-group input{padding:8px 12px;border:1px solid var(--gray-100);border-radius:6px;font-size:14px;background:var(--white)}
|
||
.top-lists{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:24px}
|
||
.top-card{background:var(--white);border-radius:12px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08)}
|
||
.top-card h3{font-size:16px;font-weight:600;margin-bottom:16px}
|
||
.top-item{display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--gray-100)}
|
||
.top-item:last-child{border-bottom:none}
|
||
.top-name{font-size:13px;flex:1}
|
||
.top-val{font-size:13px;font-weight:600;color:var(--cyan);background:var(--ink);padding:2px 8px;border-radius:4px}
|
||
.table-card{background:var(--white);border-radius:12px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08)}
|
||
.table-card h3{font-size:16px;font-weight:600;margin-bottom:16px}
|
||
.table-wrap{overflow-x:auto}
|
||
table{width:100%;border-collapse:collapse;font-size:13px}
|
||
th{background:var(--gray-100);padding:10px;text-align:left;font-weight:600;font-size:12px}
|
||
td{padding:10px;border-bottom:1px solid var(--gray-100)}
|
||
tr:hover{background:var(--cyan-50)}
|
||
.status{padding:4px 8px;border-radius:4px;font-size:11px;font-weight:600;display:inline-block}
|
||
.status-closed{background:var(--success);color:var(--white)}
|
||
.status-assigned{background:var(--warning);color:var(--ink)}
|
||
.status-planned{background:#3B82F6;color:var(--white)}
|
||
.status-waiting{background:var(--gray-500);color:var(--white)}
|
||
.badge{padding:2px 6px;border-radius:3px;font-size:11px;background:var(--gray-100)}
|
||
漏斗{display:flex;gap:8px;margin-bottom:24px}
|
||
.funnel-step{flex:1;background:var(--white);border-radius:12px;padding:16px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,0.08);position:relative}
|
||
.funnel-step:not(:last-child)::after{content:"→";position:absolute;right:-12px;top:50%;transform:translateY(-50%);font-size:20px;color:var(--gray-500)}
|
||
.funnel-num{font-size:24px;font-weight:700;color:var(--cyan)}
|
||
.funnel-label{font-size:12px;color:var(--gray-500);margin-top:4px}
|
||
@media(max-width:768px){.charts{grid-template-columns:1fr}.top-lists{grid-template-columns:1fr}.漏斗{flex-wrap:wrap}.funnel-step{min-width:140px}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<section class="header">
|
||
<div class="container">
|
||
<h1>📊 Дашборд заявок Service Desk</h1>
|
||
<p>Аналитика по системе GLPI | Данные от 20.05.2026</p>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="container">
|
||
|
||
<div class="filters">
|
||
<div class="filters-row">
|
||
<div class="filter-group">
|
||
<label>Период</label>
|
||
<select id="periodFilter">
|
||
<option value="all">Весь период</option>
|
||
<option value="7">Последние 7 дней</option>
|
||
<option value="30">Последние 30 дней</option>
|
||
<option value="year">2026 год</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Статус</label>
|
||
<select id="statusFilter">
|
||
<option value="all">Все статусы</option>
|
||
<option value="closed">Закрыто</option>
|
||
<option value="assigned">Назначена</option>
|
||
<option value="planned">Запланирована</option>
|
||
<option value="waiting">В ожидании</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Департамент</label>
|
||
<select id="deptFilter">
|
||
<option value="all">Все</option>
|
||
<option value="dit">ДИТ</option>
|
||
<option value="duis">ДУИС</option>
|
||
<option value="doc">Документооборот</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-group">
|
||
<label>Поиск</label>
|
||
<input type="text" id="searchInput" placeholder="Поиск по заявкам...">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metrics">
|
||
<div class="metric">
|
||
<div class="metric-label">ВСЕГО ЗАЯВОК</div>
|
||
<div class="metric-value">992</div>
|
||
<div class="metric-sub">за период</div>
|
||
</div>
|
||
<div class="metric success">
|
||
<div class="metric-label">ЗАКРЫТО</div>
|
||
<div class="metric-value">990</div>
|
||
<div class="metric-sub">99.8% успешных</div>
|
||
</div>
|
||
<div class="metric warning">
|
||
<div class="metric-label">В РАБОТЕ</div>
|
||
<div class="metric-value">3</div>
|
||
<div class="metric-sub">актуальные</div>
|
||
</div>
|
||
<div class="metric">
|
||
<div class="metric-label">SLA: РЕАКЦИЯ</div>
|
||
<div class="metric-value">63.4</div>
|
||
<div class="metric-sub">мин среднее</div>
|
||
</div>
|
||
<div class="metric danger">
|
||
<div class="metric-label">SLA: ЗАКРЫТИЕ</div>
|
||
<div class="metric-value">251.3</div>
|
||
<div class="metric-sub">мин среднее</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="漏斗">
|
||
<div class="funnel-step">
|
||
<div class="funnel-num">4334</div>
|
||
<div class="funnel-label">Новый</div>
|
||
</div>
|
||
<div class="funnel-step">
|
||
<div class="funnel-num">4373</div>
|
||
<div class="funnel-label">Назначена</div>
|
||
</div>
|
||
<div class="funnel-step">
|
||
<div class="funnel-num">481</div>
|
||
<div class="funnel-label">В ожидании</div>
|
||
</div>
|
||
<div class="funnel-step">
|
||
<div class="funnel-num">59</div>
|
||
<div class="funnel-label">Запланирована</div>
|
||
</div>
|
||
<div class="funnel-step">
|
||
<div class="funnel-num" style="color:var(--success)">4416</div>
|
||
<div class="funnel-label">Закрыто</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts">
|
||
<div class="chart-card">
|
||
<h3>📈 Динамика заявок по месяцам</h3>
|
||
<div class="chart-container">
|
||
<canvas id="trendChart"></canvas>
|
||
</div>
|
||
</div>
|
||
<div class="chart-card">
|
||
<h3>🎯 Распределение по статусам</h3>
|
||
<div class="chart-container">
|
||
<canvas id="statusChart"></canvas>
|
||
</div>
|
||
</div>
|
||
<div class="chart-card">
|
||
<h3>👥 ТОП-10 исполнителей</h3>
|
||
<div class="chart-container">
|
||
<canvas id="executorsChart"></canvas>
|
||
</div>
|
||
</div>
|
||
<div class="chart-card">
|
||
<h3>🏢 Заявки по департаментам</h3>
|
||
<div class="chart-container">
|
||
<canvas id="deptChart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="top-lists">
|
||
<div class="top-card">
|
||
<h3>🏆 ТОП исполнителей</h3>
|
||
<div id="executorsList"></div>
|
||
</div>
|
||
<div class="top-card">
|
||
<h3>📢 ТОП заявителей</h3>
|
||
<div id="requestersList"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-card">
|
||
<h3>📋 Последние заявки</h3>
|
||
<div class="table-wrap">
|
||
<table id="ticketsTable">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Заголовок</th>
|
||
<th>Организация</th>
|
||
<th>Дата</th>
|
||
<th>Статус</th>
|
||
<th>Инициатор</th>
|
||
<th>Исполнитель</th>
|
||
<th>Канал</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
const DATA = {
|
||
tickets: [
|
||
{id:68217,title:"Заявки на доработку",org:"SK SAP",date:"20.05.2026",status:"Назначена",initiator:"Арын Ерсаин",executor:"Салимгожинов Тамерлан",channel:"-"},
|
||
{id:68203,title:"Запрос на доступ",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Абраев Жумабек",executor:"Карипбаев Бауыржан",channel:"E-Mail"},
|
||
{id:68202,title:"Запрос на доступ",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Абраев Жумабек",executor:"Карипбаев Бауыржан",channel:"E-Mail"},
|
||
{id:68201,title:"Запрос на доступ",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Абраев Жумабек",executor:"Карипбаев Бауыржан",channel:"E-Mail"},
|
||
{id:68190,title:"Заявки на обслуживание",org:"SK SAP",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Берденова Асыл",channel:"-"},
|
||
{id:68188,title:"Заявки на обслуживание",org:"SK SAP",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Берденова Асыл",channel:"-"},
|
||
{id:68186,title:"Заявки на обслуживание",org:"SK SAP",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Берденова Асыл",channel:"-"},
|
||
{id:68185,title:"Заявки на обслуживание",org:"SK SAP",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Берденова Асыл",channel:"-"},
|
||
{id:68184,title:"Заявки на обслуживание",org:"SK SAP",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Берденова Асыл",channel:"-"},
|
||
{id:68183,title:"Заявки на обслуживание",org:"SK SAP",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Берденова Асыл",channel:"-"},
|
||
{id:68179,title:"Запрос на обслуживание",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Амангалиев Азамат",channel:"Мессенджеры"},
|
||
{id:68172,title:"Запрос на доступ",org:"SK SIP телефония",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Жолдасбек Ердос",channel:"-"},
|
||
{id:68171,title:"Запрос на доступ",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Мукашев Данияр",executor:"Сулейменова Дина",channel:"E-Mail"},
|
||
{id:68170,title:"Запрос на доступ",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Прназаров Алибек",executor:"Сулейменова Дина",channel:"E-Mail"},
|
||
{id:68169,title:"Запрос на доступ",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Прназаров Алибек",executor:"Сулейменова Дина",channel:"E-Mail"},
|
||
{id:68165,title:"Заявки на доступ",org:"Service Desk",date:"19.05.2026",status:"Закрыто",initiator:"Арын Ерсаин",executor:"Жусупов Галым",channel:"-"}
|
||
],
|
||
executors: [
|
||
{name:"Жусупов Галым",count:150},
|
||
{name:"Арын Ерсаин",count:2891},
|
||
{name:"Магперов Куаныш",count:1881},
|
||
{name:"Абраев Жумабек",count:1000},
|
||
{name:"Адилхан Максат",count:580},
|
||
{name:"Мукашев Данияр",count:470},
|
||
{name:"Неталина Асем",count:350},
|
||
{name:"Бузаубаев Парасат",count:140},
|
||
{name:"Рахимжанова Меруерт",count:140},
|
||
{name:"Мухтар Дидар",count:120}
|
||
],
|
||
requesters: [
|
||
{name:"Арын Ерсаин",count:2571},
|
||
{name:"Магперов Куаныш",count:1521},
|
||
{name:"Абраев Жумабек",count:1060},
|
||
{name:"Адилхан Максат",count:730},
|
||
{name:"Мукашев Данияр",count:420},
|
||
{name:"Неталина Асем",count:290},
|
||
{name:"Бузаубаев Парасат",count:110},
|
||
{name:"Мухтар Дидар",count:90},
|
||
{name:"Табултаев Адиль",count:80},
|
||
{name:"Ибраев Максат",count:60}
|
||
],
|
||
months: [
|
||
{m:"Янв",v:183},{m:"Фев",v:390},{m:"Мар",v:445},
|
||
{m:"Апр",v:510},{m:"Май",v:342},{m:"Июн",v:399},
|
||
{m:"Июл",v:474},{m:"Авг",v:440},{m:"Сен",v:427},
|
||
{m:"Окт",v:438},{m:"Ноя",v:419},{m:"Дек",v:444}
|
||
],
|
||
depts: [
|
||
{name:"ДИТ",v:149},{name:"ДУИС",v:12},{name:"Документооборот",v:7},
|
||
{name:"Управление рисками",v:2},{name:"Управление персоналом",v:1}
|
||
]
|
||
};
|
||
|
||
const statusDist = {closed:990,assigned:2,planned:1,waiting:0};
|
||
|
||
function getStatusClass(s){
|
||
if(s==="Закрыто") return "status-closed";
|
||
if(s==="Назначена") return "status-assigned";
|
||
if(s==="Запланирована") return "status-planned";
|
||
return "status-waiting";
|
||
}
|
||
|
||
function renderTable(data){
|
||
const tbody=document.querySelector("#ticketsTable tbody");
|
||
tbody.innerHTML=data.map(t=>
|
||
`<tr>
|
||
<td><span class="badge">#${t.id}</span></td>
|
||
<td>${t.title}</td>
|
||
<td>${t.org}</td>
|
||
<td>${t.date}</td>
|
||
<td><span class="status ${getStatusClass(t.status)}">${t.status}</span></td>
|
||
<td>${t.initiator}</td>
|
||
<td>${t.executor}</td>
|
||
<td>${t.channel}</td>
|
||
</tr>`
|
||
).join("");
|
||
}
|
||
|
||
function renderTopList(data,containerId){
|
||
document.getElementById(containerId).innerHTML=data.map(i=>
|
||
`<div class="top-item">
|
||
<div class="top-name">${i.name}</div>
|
||
<div class="top-val">${i.count}</div>
|
||
</div>`
|
||
).join("");
|
||
}
|
||
|
||
new Chart(document.getElementById("trendChart"),{
|
||
type:"line",
|
||
data:{
|
||
labels:DATA.months.map(m=>m.m),
|
||
datasets:[{
|
||
label:"Заявки",
|
||
data:DATA.months.map(m=>m.v),
|
||
borderColor:"#00E5FF",
|
||
backgroundColor:"rgba(0,229,255,0.1)",
|
||
fill:true,
|
||
tension:0.4
|
||
}]
|
||
},
|
||
options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{y:{beginAtZero:false}}}
|
||
});
|
||
|
||
new Chart(document.getElementById("statusChart"),{
|
||
type:"doughnut",
|
||
data:{
|
||
labels:["Закрыто","Назначена","Запланирована","В ожидании"],
|
||
datasets:[{
|
||
data:[statusDist.closed,statusDist.assigned,59,statusDist.waiting],
|
||
backgroundColor:["#10B981","#F59E0B","#3B82F6","#6B7280"]
|
||
}]
|
||
},
|
||
options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:"right"}}}
|
||
});
|
||
|
||
new Chart(document.getElementById("executorsChart"),{
|
||
type:"bar",
|
||
data:{
|
||
labels:DATA.executors.slice(0,10).map(e=>e.name.split(" ")[0]+" "+e.name.split(" ")[1].charAt(0)+"."),
|
||
datasets:[{
|
||
label:"Заявок",
|
||
data:DATA.executors.slice(0,10).map(e=>e.count),
|
||
backgroundColor:"#00E5FF"
|
||
}]
|
||
},
|
||
options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{y:{beginAtZero:true},x:{ticks:{maxRotation:45,minRotation:45}}}
|
||
});
|
||
|
||
new Chart(document.getElementById("deptChart"),{
|
||
type:"bar",
|
||
data:{
|
||
labels:DATA.depts.map(d=>d.name),
|
||
datasets:[{
|
||
label:"Заявок",
|
||
data:DATA.depts.map(d=>d.v),
|
||
backgroundColor:"#3B82F6"
|
||
}]
|
||
},
|
||
options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},indexAxis:"y"}
|
||
});
|
||
|
||
renderTable(DATA.tickets);
|
||
renderTopList(DATA.executors,"executorsList");
|
||
renderTopList(DATA.requesters,"requestersList");
|
||
|
||
document.getElementById("searchInput").addEventListener("input",function(e){
|
||
const q=e.target.value.toLowerCase();
|
||
const filtered=DATA.tickets.filter(t=>
|
||
t.title.toLowerCase().includes(q)||t.initiator.toLowerCase().includes(q)||t.executor.toLowerCase().includes(q)
|
||
);
|
||
renderTable(filtered);
|
||
});
|
||
|
||
document.getElementById("statusFilter").addEventListener("change",function(e){
|
||
const s=e.target.value;
|
||
let filtered=DATA.tickets;
|
||
if(s!=="all"){
|
||
if(s==="closed") filtered=DATA.tickets.filter(t=>t.status==="Закрыто");
|
||
if(s==="assigned") filtered=DATA.tickets.filter(t=>t.status==="Назначена");
|
||
if(s==="planned") filtered=DATA.tickets.filter(t=>t.status==="Запланирована");
|
||
if(s==="waiting") filtered=DATA.tickets.filter(t=>t.status==="В ожидании");
|
||
}
|
||
renderTable(filtered);
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|