290 lines
10 KiB
HTML
290 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Dashboard — Google Sheets</title>
|
||
<style>
|
||
:root{
|
||
--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;
|
||
--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;
|
||
--gray-800:#1a1f2b;--gray-700:#252b38;
|
||
--red:#ff4757;--green:#2ed573;
|
||
}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{
|
||
font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
|
||
color:var(--ink);background:var(--gray-100);min-height:100vh;
|
||
}
|
||
|
||
/* Header */
|
||
.topbar{
|
||
background:var(--ink);color:var(--white);
|
||
padding:16px 24px;display:flex;align-items:center;
|
||
justify-content:space-between;flex-wrap:wrap;gap:12px;
|
||
position:sticky;top:0;z-index:100;
|
||
}
|
||
.topbar h1{font-size:22px;font-weight:700}
|
||
.topbar .controls{display:flex;align-items:center;gap:12px}
|
||
.badge{background:var(--green);color:var(--ink);font-size:13px;
|
||
font-weight:600;padding:4px 12px;border-radius:20px}
|
||
.badge.offline{background:var(--red);color:var(--white)}
|
||
.btn-refresh{
|
||
background:var(--cyan);color:var(--ink);
|
||
border:none;padding:8px 18px;border-radius:8px;
|
||
font-weight:700;font-size:14px;cursor:pointer;
|
||
transition:opacity .2s;
|
||
}
|
||
.btn-refresh:hover{opacity:.85}
|
||
.timer{font-size:13px;color:var(--gray-500)}
|
||
|
||
/* Tabs */
|
||
.tabs{
|
||
display:flex;background:var(--white);border-bottom:2px solid var(--gray-100);
|
||
overflow-x:auto;-webkit-overflow-scrolling:touch;
|
||
padding:0 24px;
|
||
}
|
||
.tab{
|
||
padding:14px 20px;font-size:15px;font-weight:600;
|
||
color:var(--gray-500);border:none;background:none;
|
||
border-bottom:3px solid transparent;cursor:pointer;
|
||
white-space:nowrap;transition:all .2s;
|
||
}
|
||
.tab:hover{color:var(--ink)}
|
||
.tab.active{color:var(--ink);border-bottom-color:var(--cyan)}
|
||
.tab .count{font-size:12px;background:var(--gray-100);color:var(--gray-500);
|
||
padding:2px 8px;border-radius:10px;margin-left:6px}
|
||
|
||
/* Content */
|
||
.content{padding:24px;max-width:1400px;margin:0 auto}
|
||
.panel{background:var(--white);border-radius:16px;overflow:hidden;
|
||
box-shadow:0 1px 3px rgba(0,0,0,.08)}
|
||
.panel-header{
|
||
display:flex;align-items:center;justify-content:space-between;
|
||
padding:16px 24px;border-bottom:1px solid var(--gray-100);
|
||
}
|
||
.panel-header h2{font-size:18px;font-weight:700}
|
||
.panel-header a{color:var(--cyan);font-size:14px;font-weight:600;
|
||
text-decoration:none}
|
||
.panel-header a:hover{text-decoration:underline}
|
||
.iframe-wrap{position:relative;width:100%;height:0;
|
||
padding-bottom:75vh;overflow:hidden}
|
||
.iframe-wrap iframe{
|
||
position:absolute;top:0;left:0;width:100%;height:100%;
|
||
border:none}
|
||
|
||
/* Grid (all view) */
|
||
.grid{
|
||
display:grid;grid-template-columns:repeat(3,1fr);
|
||
gap:20px;padding:24px
|
||
}
|
||
@media(max-width:1000px){.grid{grid-template-columns:repeat(2,1fr)}}
|
||
@media(max-width:640px){.grid{grid-template-columns:1fr}}
|
||
|
||
.card{
|
||
background:var(--white);border-radius:16px;overflow:hidden;
|
||
box-shadow:0 1px 3px rgba(0,0,0,.08);
|
||
transition:transform .2s,box-shadow .2s
|
||
}
|
||
.card:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.12)}
|
||
.card-header{
|
||
padding:14px 18px;border-bottom:1px solid var(--gray-100);
|
||
display:flex;align-items:center;justify-content:space-between
|
||
}
|
||
.card-header h3{font-size:15px;font-weight:700}
|
||
.card-header a{font-size:13px;color:var(--cyan);text-decoration:none;font-weight:600}
|
||
.card .mini-iframe{width:100%;height:320px;border:none}
|
||
|
||
/* Empty state */
|
||
.empty-state{
|
||
text-align:center;padding:80px 24px;color:var(--gray-500)
|
||
}
|
||
.empty-state .icon{font-size:48px;margin-bottom:16px}
|
||
.empty-state h3{font-size:22px;color:var(--ink);margin-bottom:8px}
|
||
.empty-state p{font-size:16px;max-width:500px;margin:0 auto 24px}
|
||
.empty-state .steps{
|
||
text-align:left;max-width:500px;margin:0 auto;
|
||
background:var(--gray-100);border-radius:12px;padding:20px 24px
|
||
}
|
||
.empty-state .steps li{margin-bottom:8px;font-size:14px}
|
||
|
||
/* Status bar */
|
||
.statusbar{
|
||
background:var(--white);padding:12px 24px;font-size:13px;
|
||
color:var(--gray-500);display:flex;align-items:center;
|
||
justify-content:space-between;flex-wrap:wrap;gap:8px;
|
||
border-top:1px solid var(--gray-100)
|
||
}
|
||
.dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:6px}
|
||
.dot.online{background:var(--green)}
|
||
.dot.error{background:var(--red)}
|
||
|
||
@media(max-width:640px){
|
||
.topbar h1{font-size:18px}
|
||
.tabs{padding:0 8px}
|
||
.tab{padding:12px 14px;font-size:14px}
|
||
.content{padding:12px}
|
||
.panel-header{padding:12px 16px}
|
||
.panel-header h2{font-size:16px}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="topbar">
|
||
<h1>📊 Google Sheets Dashboard</h1>
|
||
<div class="controls">
|
||
<span id="statusBadge" class="badge">
|
||
<span class="dot online" style="display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:4px;background:var(--green)"></span>
|
||
Обновлено
|
||
</span>
|
||
<button class="btn-refresh" onclick="refreshAll()" title="Обновить все таблицы">↻ Обновить</button>
|
||
<span class="timer" id="timerLabel">60 сек</span>
|
||
</div>
|
||
</header>
|
||
|
||
<nav class="tabs" id="tabBar"></nav>
|
||
|
||
<main class="content" id="mainContent"></main>
|
||
|
||
<footer class="statusbar">
|
||
<span id="lastRefresh">Последнее обновление: —</span>
|
||
<span>Автообновление: каждые 60 секунд</span>
|
||
</footer>
|
||
|
||
<script>
|
||
const sheets = [
|
||
{ id: "150YZTbzkbYosICYfrR6K_od_1pOm6u64lkotQXIZw78", gid: "684524029", label: "Таблица 1", emoji: "📋" },
|
||
{ id: "1U1u3ZOVQCNLqiYJyId-DSlhENr3ZgNz-7xmwxePp3I4", gid: "684524029", label: "Таблица 2", emoji: "📋" },
|
||
{ id: "1Ksb1KN-SEO74a2To0qEsWP17P6wpzv44V2S4tDPWjs4", gid: "1492525306", label: "Таблица 3", emoji: "📋" },
|
||
{ id: "1kRQdo-H-seMQ0JkoLQ_q9izQvWnQ3DVwLoS6qOdP0rI", gid: "684524029", label: "Таблица 4", emoji: "📋" },
|
||
{ id: "1APZKFlxOPmgY82Te2JisGr8xE7njWjhxShZU_jiIQxU", gid: "684524029", label: "Таблица 5", emoji: "📋" },
|
||
{ id: "1BkLoaLY3PvS80pw-nSTz_au4HwuOgvMVTo0tBBYjIIQ", gid: "885466660", label: "Таблица 6", emoji: "📋" },
|
||
];
|
||
|
||
const REFRESH_SEC = 60;
|
||
let activeTab = "all";
|
||
let countdown = REFRESH_SEC;
|
||
let sheetStatus = sheets.map(() => "pending");
|
||
|
||
function buildTabs() {
|
||
const bar = document.getElementById("tabBar");
|
||
bar.innerHTML = sheets.map((s, i) =>
|
||
`<button class="tab" data-idx="${i}" onclick="switchTab(${i})">${s.emoji} ${s.label}</button>`
|
||
).join("");
|
||
bar.insertAdjacentHTML("afterbegin",
|
||
`<button class="tab active" data-idx="all" onclick="switchTab('all')">📊 Все таблицы</button>`
|
||
);
|
||
}
|
||
|
||
function switchTab(idx) {
|
||
activeTab = idx;
|
||
document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
|
||
document.querySelector(`.tab[data-idx="${idx}"]`).classList.add("active");
|
||
renderContent();
|
||
}
|
||
|
||
function renderContent() {
|
||
const main = document.getElementById("mainContent");
|
||
if (activeTab === "all") {
|
||
main.innerHTML = `<div class="grid" id="grid"></div>`;
|
||
const grid = document.getElementById("grid");
|
||
sheets.forEach((s, i) => {
|
||
const card = document.createElement("div");
|
||
card.className = "card";
|
||
card.innerHTML = `
|
||
<div class="card-header">
|
||
<h3>${s.emoji} ${s.label}</h3>
|
||
<a href="https://docs.google.com/spreadsheets/d/${s.id}/edit?gid=${s.gid}" target="_blank" rel="noopener">Открыть ↗</a>
|
||
</div>
|
||
<iframe class="mini-iframe"
|
||
src="https://docs.google.com/spreadsheets/d/${s.id}/htmlembed?gid=${s.gid}&single=true&widget=true&headers=false"
|
||
onerror="markStatus(${i},'error')"
|
||
onload="markStatus(${i},'ok')"
|
||
sandbox="allow-scripts allow-same-origin allow-popups"
|
||
loading="lazy">
|
||
</iframe>`;
|
||
grid.appendChild(card);
|
||
});
|
||
} else {
|
||
const s = sheets[activeTab];
|
||
main.innerHTML = `
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<h2>${s.emoji} ${s.label}</h2>
|
||
<a href="https://docs.google.com/spreadsheets/d/${s.id}/edit?gid=${s.gid}" target="_blank" rel="noopener">Открыть в Google Sheets ↗</a>
|
||
</div>
|
||
<div class="iframe-wrap">
|
||
<iframe
|
||
src="https://docs.google.com/spreadsheets/d/${s.id}/htmlembed?gid=${s.gid}&single=true&widget=true&headers=false"
|
||
onerror="markStatus(${activeTab},'error')"
|
||
onload="markStatus(${activeTab},'ok')"
|
||
sandbox="allow-scripts allow-same-origin allow-popups"
|
||
loading="lazy">
|
||
</iframe>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
}
|
||
|
||
function markStatus(i, st) {
|
||
sheetStatus[i] = st;
|
||
updateBadge();
|
||
}
|
||
|
||
function updateBadge() {
|
||
const badge = document.getElementById("statusBadge");
|
||
const errors = sheetStatus.filter(s => s === "error").length;
|
||
const all = sheetStatus.every(s => s === "ok" || s === "pending");
|
||
if (errors > 0) {
|
||
badge.className = "badge offline";
|
||
badge.innerHTML = `<span class="dot error" style="display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:4px;background:var(--red)"></span>${errors} недоступны`;
|
||
} else {
|
||
badge.className = "badge";
|
||
badge.innerHTML = `<span class="dot online" style="display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:4px;background:var(--green)"></span>Онлайн`;
|
||
}
|
||
}
|
||
|
||
function refreshAll() {
|
||
const iframes = document.querySelectorAll("iframe");
|
||
iframes.forEach(f => {
|
||
const src = f.src;
|
||
f.src = "";
|
||
setTimeout(() => { f.src = src; }, 100);
|
||
});
|
||
countdown = REFRESH_SEC;
|
||
updateTimer();
|
||
document.getElementById("lastRefresh").textContent =
|
||
"Последнее обновление: " + new Date().toLocaleTimeString("ru-RU");
|
||
sheetStatus = sheets.map(() => "pending");
|
||
updateBadge();
|
||
}
|
||
|
||
function updateTimer() {
|
||
document.getElementById("timerLabel").textContent = countdown + " сек";
|
||
}
|
||
|
||
function startTimer() {
|
||
setInterval(() => {
|
||
countdown--;
|
||
updateTimer();
|
||
if (countdown <= 0) {
|
||
refreshAll();
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
function init() {
|
||
buildTabs();
|
||
renderContent();
|
||
document.getElementById("lastRefresh").textContent =
|
||
"Последнее обновление: " + new Date().toLocaleTimeString("ru-RU");
|
||
startTimer();
|
||
}
|
||
|
||
init();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|