report-generator/index.html

702 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">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Генератор отчётов и постеров</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<style>
:root {
--ink: #0F1218;
--cyan: #00A3FF;
--cyan-50: #EBF5FF;
--white: #FFFFFF;
--gray-500: #6B7280;
--gray-200: #E5E7EB;
--gray-100: #F3F4F6;
--gray-50: #F9FAFB;
--green: #10B981;
--red: #EF4444;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
color: var(--ink);
background: var(--gray-50);
}
/* ── Header ── */
.header {
background: var(--white);
border-bottom: 1px solid var(--gray-200);
padding: 16px 0;
position: sticky;
top: 0;
z-index: 100;
}
.header-inner {
max-width: 1140px;
margin: 0 auto;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.logo-area {
display: flex;
align-items: center;
gap: 12px;
}
.logo-area img {
height: 36px;
display: none;
}
.logo-area img.visible {
display: block;
}
.app-title {
font-size: 18px;
font-weight: 700;
}
/* ── Tabs ── */
.tabs {
display: flex;
gap: 4px;
background: var(--gray-100);
border-radius: 10px;
padding: 4px;
}
.tab-btn {
padding: 8px 20px;
border: none;
background: transparent;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
color: var(--gray-500);
transition: all 0.2s;
}
.tab-btn.active {
background: var(--white);
color: var(--ink);
box-shadow: 0 1px 3px rgba(0,0,0,.08);
}
/* ── Main Layout ── */
.main {
max-width: 1140px;
margin: 0 auto;
padding: 32px 24px;
display: grid;
grid-template-columns: 380px 1fr;
gap: 32px;
align-items: start;
}
@media (max-width: 860px) {
.main {
grid-template-columns: 1fr;
}
}
/* ── Sidebar (forms) ── */
.sidebar {
background: var(--white);
border-radius: 12px;
padding: 24px;
border: 1px solid var(--gray-200);
position: sticky;
top: 100px;
}
.sidebar h2 {
font-size: 20px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--gray-500);
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--gray-200);
border-radius: 8px;
font-size: 15px;
font-family: inherit;
transition: border 0.2s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--cyan);
box-shadow: 0 0 0 3px var(--cyan-50);
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.color-grid {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.color-swatch {
width: 32px;
height: 32px;
border-radius: 8px;
border: 2px solid transparent;
cursor: pointer;
transition: 0.2s;
}
.color-swatch.active {
border-color: var(--ink);
transform: scale(1.15);
}
.file-upload {
display: block;
width: 100%;
padding: 10px;
border: 2px dashed var(--gray-200);
border-radius: 8px;
text-align: center;
font-size: 14px;
color: var(--gray-500);
cursor: pointer;
transition: 0.2s;
}
.file-upload:hover {
border-color: var(--cyan);
background: var(--cyan-50);
}
.file-upload input {
display: none;
}
/* ── Preview ── */
.preview-area {
background: var(--white);
border-radius: 12px;
padding: 24px;
border: 1px solid var(--gray-200);
}
.preview-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
flex-wrap: wrap;
gap: 8px;
}
.preview-label {
font-size: 14px;
color: var(--gray-500);
font-weight: 600;
}
.download-btns {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.btn-dl {
padding: 8px 14px;
border: 1px solid var(--gray-200);
border-radius: 8px;
background: var(--white);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: 0.2s;
display: flex;
align-items: center;
gap: 6px;
}
.btn-dl:hover {
background: var(--gray-50);
border-color: var(--cyan);
}
/* ── Report/Poster Canvas ── */
.canvas-wrap {
background: var(--gray-200);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
min-height: 500px;
}
/* Report Slide */
.report-slide {
width: 100%;
max-width: 680px;
aspect-ratio: 16/9;
background: var(--white);
border-radius: 4px;
box-shadow: 0 2px 20px rgba(0,0,0,.08);
padding: 48px 56px;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.report-slide .rs-company {
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--gray-500);
margin-bottom: 8px;
}
.report-slide .rs-title {
font-size: 32px;
font-weight: 800;
line-height: 1.15;
margin-bottom: 4px;
}
.report-slide .rs-period {
font-size: 16px;
color: var(--gray-500);
margin-bottom: 32px;
}
.report-slide .rs-metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
flex: 1;
}
.report-slide .rs-metric {
background: var(--gray-50);
border-radius: 12px;
padding: 20px;
text-align: center;
}
.report-slide .rs-metric-value {
font-size: 28px;
font-weight: 800;
line-height: 1;
margin-bottom: 6px;
}
.report-slide .rs-metric-label {
font-size: 13px;
color: var(--gray-500);
font-weight: 500;
}
.report-slide .rs-footer {
margin-top: 24px;
font-size: 12px;
color: var(--gray-500);
border-top: 1px solid var(--gray-200);
padding-top: 12px;
}
.report-slide .rs-logo {
position: absolute;
top: 40px;
right: 56px;
max-height: 40px;
display: none;
}
.report-slide .rs-logo.visible {
display: block;
}
/* Poster */
.poster-canvas {
width: 100%;
max-width: 500px;
aspect-ratio: 3/4;
border-radius: 4px;
box-shadow: 0 2px 20px rgba(0,0,0,.08);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 48px 40px;
position: relative;
overflow: hidden;
color: var(--white);
}
.poster-canvas .ps-logo {
max-height: 64px;
margin-bottom: 24px;
display: none;
}
.poster-canvas .ps-logo.visible {
display: block;
}
.poster-canvas .ps-title {
font-size: 36px;
font-weight: 800;
line-height: 1.1;
margin-bottom: 12px;
}
.poster-canvas .ps-subtitle {
font-size: 18px;
font-weight: 500;
opacity: 0.85;
margin-bottom: 24px;
}
.poster-canvas .ps-info {
font-size: 14px;
opacity: 0.7;
max-width: 300px;
}
.poster-canvas .ps-company {
position: absolute;
bottom: 40px;
left: 0;
right: 0;
font-size: 12px;
opacity: 0.6;
text-transform: uppercase;
letter-spacing: 2px;
}
@media (max-width: 640px) {
.main { padding: 16px; }
.sidebar { position: static; }
.report-slide { padding: 28px 24px; }
.report-slide .rs-title { font-size: 24px; }
.report-slide .rs-metrics { grid-template-columns: repeat(2, 1fr); gap: 12px; }
.report-slide .rs-metric-value { font-size: 22px; }
.poster-canvas .ps-title { font-size: 26px; }
}
</style>
</head>
<body>
<!-- ═══ HEADER ═══ -->
<div class="header">
<div class="header-inner">
<div class="logo-area">
<img id="headerLogo" src="" alt="Logo" class="">
<span class="app-title">Report & Poster Builder</span>
</div>
<div class="tabs" id="modeTabs">
<button class="tab-btn active" data-mode="report">Отчёт</button>
<button class="tab-btn" data-mode="poster">Постер</button>
</div>
</div>
</div>
<!-- ═══ MAIN ═══ -->
<div class="main">
<!-- ── SIDEBAR: Report Form ── -->
<aside class="sidebar" id="reportForm">
<h2>Данные отчёта</h2>
<div class="form-group">
<label>Компания</label>
<input type="text" id="rCompany" placeholder="ООО «Твоя Компания»">
</div>
<div class="form-group">
<label>Период</label>
<select id="rPeriodType">
<option value="Ежемесячный">Ежемесячный</option>
<option value="Ежеквартальный">Ежеквартальный</option>
<option value="Годовой">Годовой</option>
</select>
</div>
<div class="form-group">
<label>Период (укажи)</label>
<input type="text" id="rPeriod" placeholder="Q2 2026">
</div>
<div class="form-group">
<label>Логотип (опционально)</label>
<label class="file-upload">
<input type="file" id="rLogo" accept="image/*">
Загрузить логотип
</label>
</div>
<div style="border-top:1px solid var(--gray-200); margin:16px 0; padding-top:16px; font-size:13px; font-weight:600; color:var(--gray-500);">Показатели</div>
<div class="form-group">
<label>Выручка</label>
<input type="text" id="m1v" placeholder="12 500 000 ₸">
</div>
<div class="form-group">
<label>План (%)</label>
<input type="text" id="m2v" placeholder="104%">
</div>
<div class="form-group">
<label>Прибыль</label>
<input type="text" id="m3v" placeholder="2 800 000 ₸">
</div>
<div class="form-group">
<label>Клиенты</label>
<input type="text" id="m4v" placeholder="340">
</div>
<div class="form-group">
<label>Средний чек</label>
<input type="text" id="m5v" placeholder="36 700 ₸">
</div>
<div class="form-group">
<label>NPS</label>
<input type="text" id="m6v" placeholder="78">
</div>
<div class="form-group">
<label>Примечания</label>
<textarea id="rNotes" placeholder="Краткий комментарий к отчёту..."></textarea>
</div>
</aside>
<!-- ── SIDEBAR: Poster Form (hidden) ── -->
<aside class="sidebar" id="posterForm" style="display:none;">
<h2>Дизайн постера</h2>
<div class="form-group">
<label>Заголовок</label>
<input type="text" id="pTitle" placeholder="Ключевое сообщение">
</div>
<div class="form-group">
<label>Подзаголовок</label>
<input type="text" id="pSubtitle" placeholder="Дополнительная информация">
</div>
<div class="form-group">
<label>Детали</label>
<textarea id="pInfo" placeholder="Дата, место, контакты..."></textarea>
</div>
<div class="form-group">
<label>Логотип</label>
<label class="file-upload">
<input type="file" id="pLogo" accept="image/*">
Загрузить логотип
</label>
</div>
<div class="form-group">
<label>Компания</label>
<input type="text" id="pCompany" placeholder="ООО «Твоя Компания»">
</div>
<div class="form-group">
<label>Цвет</label>
<div class="color-grid" id="colorPicker">
<span class="color-swatch active" style="background:#0F1218" data-color="#0F1218" data-text="#FFFFFF"></span>
<span class="color-swatch" style="background:#1E3A5F" data-color="#1E3A5F" data-text="#FFFFFF"></span>
<span class="color-swatch" style="background:#00A3FF" data-color="#00A3FF" data-text="#FFFFFF"></span>
<span class="color-swatch" style="background:#10B981" data-color="#10B981" data-text="#FFFFFF"></span>
<span class="color-swatch" style="background:#7C3AED" data-color="#7C3AED" data-text="#FFFFFF"></span>
<span class="color-swatch" style="background:#DC2626" data-color="#DC2626" data-text="#FFFFFF"></span>
<span class="color-swatch" style="background:#F59E0B" data-color="#F59E0B" data-text="#0F1218"></span>
<span class="color-swatch" style="background:#FFFFFF;border:2px solid #E5E7EB" data-color="#FFFFFF" data-text="#0F1218"></span>
</div>
</div>
</aside>
<!-- ── PREVIEW ── -->
<div class="preview-area">
<div class="preview-toolbar">
<span class="preview-label">Предпросмотр</span>
<div class="download-btns">
<button class="btn-dl" onclick="downloadAs('pdf')">PDF</button>
<button class="btn-dl" onclick="downloadAs('png')">PNG</button>
<button class="btn-dl" onclick="downloadAs('jpeg')">JPEG</button>
</div>
</div>
<div class="canvas-wrap" id="canvasWrap">
<!-- Report Slide -->
<div class="report-slide" id="reportSlide">
<img class="rs-logo" id="rsLogo" src="" alt="">
<div class="rs-company" id="rsCompany">ООО «Твоя Компания»</div>
<div class="rs-title" id="rsTitle">Ежемесячный отчёт</div>
<div class="rs-period" id="rsPeriod">Q2 2026</div>
<div class="rs-metrics">
<div class="rs-metric"><div class="rs-metric-value" id="rm1v"></div><div class="rs-metric-label">Выручка</div></div>
<div class="rs-metric"><div class="rs-metric-value" id="rm2v"></div><div class="rs-metric-label">План</div></div>
<div class="rs-metric"><div class="rs-metric-value" id="rm3v"></div><div class="rs-metric-label">Прибыль</div></div>
<div class="rs-metric"><div class="rs-metric-value" id="rm4v"></div><div class="rs-metric-label">Клиенты</div></div>
<div class="rs-metric"><div class="rs-metric-value" id="rm5v"></div><div class="rs-metric-label">Средний чек</div></div>
<div class="rs-metric"><div class="rs-metric-value" id="rm6v"></div><div class="rs-metric-label">NPS</div></div>
</div>
<div class="rs-footer" id="rsNotes"></div>
</div>
<!-- Poster Canvas (hidden) -->
<div class="poster-canvas" id="posterCanvas" style="display:none;">
<img class="ps-logo" id="psLogo" src="" alt="">
<div class="ps-title" id="psTitle"></div>
<div class="ps-subtitle" id="psSubtitle"></div>
<div class="ps-info" id="psInfo"></div>
<div class="ps-company" id="psCompany"></div>
</div>
</div>
</div>
</div>
<script>
// ═══ MODE SWITCHING ═══
document.getElementById('modeTabs').addEventListener('click', function(e) {
if (e.target.classList.contains('tab-btn')) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
const mode = e.target.dataset.mode;
if (mode === 'report') {
document.getElementById('reportForm').style.display = '';
document.getElementById('posterForm').style.display = 'none';
document.getElementById('reportSlide').style.display = '';
document.getElementById('posterCanvas').style.display = 'none';
document.querySelector('.app-title').textContent = 'Report & Poster Builder';
} else {
document.getElementById('reportForm').style.display = 'none';
document.getElementById('posterForm').style.display = '';
document.getElementById('reportSlide').style.display = 'none';
document.getElementById('posterCanvas').style.display = '';
document.querySelector('.app-title').textContent = 'Report & Poster Builder';
}
currentMode = mode;
}
});
let currentMode = 'report';
// ═══ COLOR PICKER ═══
let posterBg = '#0F1218';
let posterText = '#FFFFFF';
document.getElementById('colorPicker').addEventListener('click', function(e) {
const swatch = e.target.closest('.color-swatch');
if (!swatch) return;
document.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('active'));
swatch.classList.add('active');
posterBg = swatch.dataset.color;
posterText = swatch.dataset.text;
updatePoster();
});
// ═══ LIVE UPDATE: Report ═══
const reportFields = [
{ id: 'rCompany', target: 'rsCompany', type: 'text' },
{ id: 'rPeriod', target: 'rsPeriod', type: 'text' },
{ id: 'rNotes', target: 'rsNotes', type: 'text' },
{ id: 'm1v', target: 'rm1v', type: 'text' },
{ id: 'm2v', target: 'rm2v', type: 'text' },
{ id: 'm3v', target: 'rm3v', type: 'text' },
{ id: 'm4v', target: 'rm4v', type: 'text' },
{ id: 'm5v', target: 'rm5v', type: 'text' },
{ id: 'm6v', target: 'rm6v', type: 'text' },
];
document.getElementById('rPeriodType').addEventListener('change', function() {
document.getElementById('rsTitle').textContent = this.value + ' отчёт';
});
reportFields.forEach(f => {
const el = document.getElementById(f.id);
if (!el) return;
el.addEventListener('input', function() {
document.getElementById(f.target).textContent = this.value;
});
});
// ═══ LIVE UPDATE: Poster ═══
['pTitle','pSubtitle','pInfo','pCompany'].forEach(id => {
const el = document.getElementById(id);
if (!el) return;
el.addEventListener('input', function() {
updatePoster();
});
});
function updatePoster() {
document.getElementById('psTitle').textContent = document.getElementById('pTitle').value;
document.getElementById('psSubtitle').textContent = document.getElementById('pSubtitle').value;
document.getElementById('psInfo').textContent = document.getElementById('pInfo').value;
document.getElementById('psCompany').textContent = document.getElementById('pCompany').value;
const canvas = document.getElementById('posterCanvas');
canvas.style.background = posterBg;
canvas.style.color = posterText;
const info = document.getElementById('psInfo');
info.style.color = posterText;
info.style.opacity = '0.7';
const subtitle = document.getElementById('psSubtitle');
subtitle.style.opacity = '0.85';
}
// ═══ LOGO UPLOAD ═══
function handleLogoUpload(input, targetImg, headerImg) {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
const url = e.target.result;
if (targetImg) { targetImg.src = url; targetImg.classList.add('visible'); }
if (headerImg) { headerImg.src = url; headerImg.classList.add('visible'); }
};
reader.readAsDataURL(file);
}
document.getElementById('rLogo').addEventListener('change', function() {
handleLogoUpload(this, document.getElementById('rsLogo'), document.getElementById('headerLogo'));
});
document.getElementById('pLogo').addEventListener('change', function() {
handleLogoUpload(this, document.getElementById('psLogo'), document.getElementById('headerLogo'));
});
// ═══ DOWNLOAD ═══
function downloadAs(format) {
let target;
let filename;
if (currentMode === 'report') {
target = document.getElementById('reportSlide');
const period = document.getElementById('rPeriod').value || 'отчёт';
filename = 'отчёт_' + period.replace(/\s+/g, '_');
} else {
target = document.getElementById('posterCanvas');
const title = document.getElementById('pTitle').value || 'постер';
filename = 'постер_' + title.replace(/\s+/g, '_').substring(0, 30);
}
if (format === 'pdf') {
const originalDisplay = target.style.display;
target.style.display = '';
window.print();
if (currentMode !== 'report') target.style.display = originalDisplay;
return;
}
html2canvas(target, {
scale: 2,
useCORS: true,
backgroundColor: null
}).then(canvas => {
let mimeType;
if (format === 'jpeg') {
mimeType = 'image/jpeg';
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(canvas, 0, 0);
} else {
mimeType = 'image/png';
}
const link = document.createElement('a');
link.download = filename + '.' + format;
link.href = canvas.toDataURL(mimeType, 0.95);
link.click();
});
}
// ═══ INIT ═══
document.getElementById('rsTitle').textContent = 'Ежемесячный отчёт';
</script>
</body>
</html>