google-dashboard/index.html
2026-06-10 11:00:37 +00:00

290 lines
10 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">
<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>