mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 16:24:47 +00:00
576 lines
37 KiB
HTML
576 lines
37 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>@wasrusgen1 CRM — Администратор</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Montserrat:wght@700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{background:#C8CACD;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:20px;font-family:'Inter',sans-serif}
|
||
body{--accent:#003E7E;--accent2:#76BD22;--bg:#F5F6F8;--card:#FFFFFF;--ink:#1A1A2E;--muted:#8A94A6;--danger:#EF4444;--warn:#F59E0B;--success:#10B981}
|
||
body[data-theme="dark"]{--accent:#4338CA;--accent2:#6366F1;--bg:#111827;--card:#1F2937;--ink:#F9FAFB;--muted:#9CA3AF}
|
||
|
||
#controls{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap;justify-content:center;width:100%;max-width:700px}
|
||
#controls label{color:#fff;font-size:13px;font-weight:600}
|
||
#screenSelect{padding:8px 12px;border-radius:10px;border:none;background:#fff;font-size:13px;color:#333;cursor:pointer;min-width:220px;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
||
#themeButtons{display:flex;gap:6px}
|
||
.theme-btn{padding:7px 14px;border-radius:9px;border:2px solid transparent;font-size:12px;font-weight:700;cursor:pointer;transition:all .2s}
|
||
.theme-btn.active{border-color:#fff}
|
||
.theme-btn[data-t="zov"]{background:#003E7E;color:#fff}
|
||
.theme-btn[data-t="dark"]{background:#111827;color:#6366F1}
|
||
|
||
#phoneFrame{width:390px;height:844px;background:var(--bg);border-radius:44px;overflow:hidden;box-shadow:0 24px 80px rgba(0,0,0,.4),inset 0 0 0 1px rgba(255,255,255,.15);position:relative;display:flex;flex-direction:column}
|
||
#statusBar{height:44px;background:var(--card);display:flex;align-items:center;justify-content:space-between;padding:0 24px;flex-shrink:0;font-size:13px;font-weight:600;color:var(--ink);z-index:10}
|
||
#screen{flex:1;overflow-y:auto;overflow-x:hidden;scrollbar-width:none;background:var(--bg)}
|
||
#screen::-webkit-scrollbar{display:none}
|
||
|
||
.bottom-nav{height:60px;background:rgba(255,255,255,.92);backdrop-filter:blur(12px);border-top:1px solid rgba(0,0,0,.06);display:flex;align-items:center;justify-content:space-around;flex-shrink:0;z-index:100}
|
||
.nav-item{display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;padding:6px 10px;border-radius:10px;transition:all .15s;flex:1}
|
||
.nav-item svg{width:22px;height:22px;color:var(--muted)}
|
||
.nav-item span{font-size:10px;color:var(--muted);font-weight:500}
|
||
.nav-item.active svg,.nav-item.active span{color:var(--accent)}
|
||
|
||
.page{padding:0 0 80px;min-height:100%}
|
||
.page-header{display:flex;align-items:center;gap:12px;padding:16px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06);position:sticky;top:0;z-index:50}
|
||
.page-header h2{font-size:17px;font-weight:700;color:var(--ink);flex:1}
|
||
.back-btn{width:32px;height:32px;border-radius:50%;background:var(--bg);display:flex;align-items:center;justify-content:center;cursor:pointer;border:none;flex-shrink:0}
|
||
.back-btn svg{color:var(--accent);width:18px;height:18px}
|
||
.header-action{width:32px;height:32px;border-radius:50%;background:var(--bg);display:flex;align-items:center;justify-content:center;cursor:pointer}
|
||
.header-action svg{width:18px;height:18px;color:var(--muted)}
|
||
|
||
.card{background:var(--card);border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.07);padding:16px;margin-bottom:12px}
|
||
.card.warn-b{border-left:4px solid var(--warn)}
|
||
.card.ok-b{border-left:4px solid var(--success)}
|
||
.card.accent-b{border-left:4px solid var(--accent)}
|
||
.card.danger-b{border-left:4px solid var(--danger)}
|
||
|
||
.section-label{text-transform:uppercase;font-size:11px;letter-spacing:.06em;color:var(--muted);margin:20px 16px 8px;font-weight:600}
|
||
@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
|
||
@keyframes btn-shimmer{0%{background-position:250% center}100%{background-position:-250% center}}
|
||
|
||
.btn-primary{width:100%;background:linear-gradient(135deg,#002450 0%,#003E7E 30%,#1560BD 60%,#0A4DA8 90%,#002450 100%);background-size:280% auto;color:#fff;border:none;border-radius:14px;padding:15px 20px;font-size:15px;font-weight:700;cursor:pointer;box-shadow:0 6px 20px rgba(0,62,126,.38);transition:transform .15s;animation:btn-shimmer 5s ease infinite}
|
||
.btn-primary:active{transform:scale(.97)}
|
||
.btn-secondary{width:100%;background:transparent;color:var(--accent);border:2px solid var(--accent);border-radius:14px;padding:13px;font-size:15px;font-weight:700;cursor:pointer;margin-top:8px}
|
||
.btn-sm{padding:8px 15px;font-size:12px;font-weight:700;border-radius:10px;cursor:pointer;border:none;background:linear-gradient(135deg,#003E7E,#1565C0);color:#fff}
|
||
.btn-pill{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:20px;font-size:11px;font-weight:700;cursor:pointer;border:none;white-space:nowrap}
|
||
.btn-pill.accent{background:rgba(0,62,126,.1);color:var(--accent);border:1px solid rgba(0,62,126,.2)}
|
||
.btn-pill.warn{background:#FEF3C7;color:#D97706;border:1px solid #FDE68A}
|
||
.btn-pill.success{background:rgba(16,185,129,.08);color:var(--success);border:1px solid rgba(16,185,129,.2)}
|
||
.btn-pill.danger{background:rgba(239,68,68,.08);color:var(--danger);border:1px solid rgba(239,68,68,.2)}
|
||
.btn-pill.muted{background:var(--bg);color:var(--muted);border:1px solid #E2E8F0}
|
||
|
||
.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:20px;font-size:11px;font-weight:700}
|
||
.badge.yellow{background:#FEF3C7;color:#D97706}
|
||
.badge.green{background:#DCFCE7;color:#15803D}
|
||
.badge.blue{background:#DBEAFE;color:#1D4ED8}
|
||
.badge.red{background:#FEE2E2;color:#DC2626}
|
||
.badge.gray{background:#F1F5F9;color:#64748B}
|
||
.badge.orange{background:#FFF7ED;color:#EA580C}
|
||
|
||
.divider{height:1px;background:rgba(0,0,0,.07);margin:10px 0}
|
||
.row{display:flex;align-items:center;gap:10px}
|
||
.row.sb{justify-content:space-between}
|
||
.info-row{display:flex;justify-content:space-between;align-items:flex-start;padding:5px 0;border-bottom:1px solid rgba(0,0,0,.04)}
|
||
.info-row:last-child{border-bottom:none}
|
||
.info-label{font-size:13px;color:var(--muted)}
|
||
.info-val{font-size:13px;color:var(--ink);font-weight:600;text-align:right;max-width:60%}
|
||
|
||
/* Staff dot status */
|
||
.dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
|
||
.dot.green{background:#10B981}
|
||
.dot.yellow{background:#F59E0B}
|
||
.dot.gray{background:#CBD5E1}
|
||
.dot.red{background:#EF4444}
|
||
|
||
/* Stat tiles */
|
||
.stat-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin:0 16px 4px}
|
||
.stat-tile{background:var(--card);border-radius:14px;padding:14px;box-shadow:0 2px 8px rgba(0,0,0,.07)}
|
||
.stat-tile .num{font-size:28px;font-weight:800;color:var(--ink);line-height:1}
|
||
.stat-tile .lbl{font-size:11px;color:var(--muted);margin-top:4px;font-weight:500}
|
||
.stat-tile .delta{font-size:11px;font-weight:700;margin-top:6px}
|
||
.stat-tile .delta.up{color:var(--success)}
|
||
.stat-tile .delta.down{color:var(--danger)}
|
||
|
||
/* Alert card */
|
||
.alert-card{border-radius:14px;padding:12px 14px;margin-bottom:10px;display:flex;align-items:flex-start;gap:10px}
|
||
.alert-card.warn{background:#FFFBEB;border:1.5px solid #FDE68A}
|
||
.alert-card.danger{background:#FEF2F2;border:1.5px solid #FECACA}
|
||
.alert-card.info{background:#EFF6FF;border:1.5px solid #BFDBFE}
|
||
.alert-card.success{background:#ECFDF5;border:1.5px solid #A7F3D0}
|
||
.alert-icon{font-size:20px;flex-shrink:0;margin-top:1px}
|
||
.alert-title{font-size:13px;font-weight:700;color:var(--ink);margin-bottom:2px}
|
||
.alert-sub{font-size:12px;color:var(--muted);line-height:1.4}
|
||
.alert-action{margin-top:8px}
|
||
|
||
/* Schedule row */
|
||
.sch-row{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.05)}
|
||
.sch-row:last-child{border-bottom:none}
|
||
.sch-avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,#003E7E,#1560BD);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;color:#fff;flex-shrink:0}
|
||
.sch-avatar.msrr{background:linear-gradient(135deg,#0F766E,#059669)}
|
||
.sch-info{flex:1;min-width:0}
|
||
.sch-name{font-size:13px;font-weight:700;color:var(--ink)}
|
||
.sch-jobs{font-size:11px;color:var(--muted);margin-top:2px}
|
||
.sch-status{text-align:right;flex-shrink:0}
|
||
|
||
/* Order list item */
|
||
.order-item{background:var(--card);border-radius:14px;padding:13px 14px;margin-bottom:10px;box-shadow:0 2px 8px rgba(0,0,0,.06);cursor:pointer;position:relative;overflow:hidden}
|
||
.order-item::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px}
|
||
.order-item.new::before{background:var(--accent2)}
|
||
.order-item.progress::before{background:var(--warn)}
|
||
.order-item.done::before{background:var(--success)}
|
||
.order-item.problem::before{background:var(--danger)}
|
||
|
||
/* Filter tabs */
|
||
.filter-tabs{display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;padding:0 16px 12px}
|
||
.filter-tabs::-webkit-scrollbar{display:none}
|
||
.ftab{padding:6px 14px;border-radius:20px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;border:1.5px solid #E2E8F0;background:var(--card);color:var(--muted)}
|
||
.ftab.active{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<a href="index.html" style="position:fixed;top:16px;left:16px;color:#fff;font-size:12px;font-weight:700;text-decoration:none;background:rgba(0,0,0,.3);padding:6px 12px;border-radius:20px;z-index:9999">← Мокапы кабинетов</a>
|
||
|
||
<div id="controls">
|
||
<label>Экран:</label>
|
||
<select id="screenSelect" onchange="go(this.value)">
|
||
<option value="home">Главная</option>
|
||
<option value="schedule">Расписание</option>
|
||
<option value="orders">Заявки</option>
|
||
<option value="staff">Сотрудники</option>
|
||
<option value="order_detail">Детали заявки</option>
|
||
</select>
|
||
<div id="themeButtons">
|
||
<button class="theme-btn active" data-t="zov" onclick="setTheme('')">CRM</button>
|
||
<button class="theme-btn" data-t="dark" onclick="setTheme('dark')">Dark</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="phoneFrame">
|
||
<div id="statusBar">
|
||
<span>9:41</span>
|
||
<div style="display:flex;align-items:center;gap:6px">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M1.5 8.5a13 13 0 0 1 21 0M5 12a10 10 0 0 1 14 0M8.5 15.5a6 6 0 0 1 7 0M12 19h.01" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round"/></svg>
|
||
<svg width="20" height="14" viewBox="0 0 23 14" fill="currentColor"><rect x="0" y="1" width="18" height="12" rx="2" fill="none" stroke="currentColor" stroke-width="1.5"/><rect x="1.5" y="2.5" width="13" height="9" rx="1" fill="currentColor"/><path d="M19.5 4.5v5a2.5 2.5 0 000-5z"/></svg>
|
||
</div>
|
||
</div>
|
||
<div id="screen"></div>
|
||
<nav class="bottom-nav" id="bottomNav">
|
||
<div class="nav-item" onclick="go('home')" id="nav-home">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||
<span>Главная</span>
|
||
</div>
|
||
<div class="nav-item" onclick="go('schedule')" id="nav-schedule">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
||
<span>Расписание</span>
|
||
</div>
|
||
<div class="nav-item" onclick="go('orders')" id="nav-orders">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="13" y2="17"/></svg>
|
||
<span>Заявки</span>
|
||
</div>
|
||
<div class="nav-item" onclick="go('staff')" id="nav-staff">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
|
||
<span>Сотрудники</span>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
|
||
<script>
|
||
var _screen = 'home';
|
||
function setTheme(t){
|
||
document.body.dataset.theme = t;
|
||
document.querySelectorAll('.theme-btn').forEach(b=>b.classList.toggle('active', b.dataset.t===(t||'zov')));
|
||
}
|
||
function go(s){
|
||
_screen = s;
|
||
document.getElementById('screen').innerHTML = render(s);
|
||
document.getElementById('screenSelect').value = s;
|
||
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
|
||
var map = {home:'nav-home',schedule:'nav-schedule',orders:'nav-orders',staff:'nav-staff'};
|
||
if(map[s]) document.getElementById(map[s]).classList.add('active');
|
||
}
|
||
function render(s){
|
||
if(s==='home') return screenHome();
|
||
if(s==='schedule') return screenSchedule();
|
||
if(s==='orders') return screenOrders();
|
||
if(s==='staff') return screenStaff();
|
||
if(s==='order_detail') return screenOrderDetail();
|
||
return screenHome();
|
||
}
|
||
|
||
/* ─── HOME ─── */
|
||
function screenHome(){
|
||
return `<div class="page">
|
||
<div style="background:linear-gradient(135deg,#002450,#003E7E,#1560BD);padding:20px 16px 24px">
|
||
<div style="font-size:11px;color:rgba(255,255,255,.65);font-weight:600;text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px">ЧЕТВЕРГ, 29 МАЯ 2026</div>
|
||
<div style="font-size:22px;font-weight:800;color:#fff;margin-bottom:2px">Привет, Алина 👋</div>
|
||
<div style="font-size:13px;color:rgba(255,255,255,.75)">3 требуют внимания · 8 заявок сегодня</div>
|
||
</div>
|
||
|
||
<div class="stat-grid" style="margin-top:-14px;margin-bottom:0">
|
||
<div class="stat-tile" onclick="go('orders')">
|
||
<div class="num">8</div>
|
||
<div class="lbl">Заявок сегодня</div>
|
||
<div class="delta up">↑ +2 к вчера</div>
|
||
</div>
|
||
<div class="stat-tile" onclick="go('schedule')">
|
||
<div class="num" style="color:var(--warn)">3</div>
|
||
<div class="lbl">Не назначены</div>
|
||
<div class="delta down">⚠️ Срочно</div>
|
||
</div>
|
||
<div class="stat-tile" onclick="go('staff')">
|
||
<div class="num" style="color:var(--success)">5</div>
|
||
<div class="lbl">На выездах</div>
|
||
<div class="delta up">GPS активен</div>
|
||
</div>
|
||
<div class="stat-tile">
|
||
<div class="num" style="color:var(--accent2)">2</div>
|
||
<div class="lbl">Завершено</div>
|
||
<div class="delta" style="color:var(--muted)">из 8 на сегодня</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-label">⚡ Требуют внимания</div>
|
||
<div style="padding:0 16px">
|
||
|
||
<div class="alert-card danger" onclick="go('order_detail')">
|
||
<div class="alert-icon">🚨</div>
|
||
<div style="flex:1">
|
||
<div class="alert-title">Заявка А-2855 — сборщик не назначен</div>
|
||
<div class="alert-sub">Сегодня 14:00 · Иванов Пётр · пр. Просвещения, 44<br>Осталось 4 часа до визита</div>
|
||
<div class="alert-action"><button class="btn-pill accent" onclick="event.stopPropagation();go('schedule')">Назначить →</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert-card warn">
|
||
<div class="alert-icon">⏰</div>
|
||
<div style="flex:1">
|
||
<div class="alert-title">Замерщик Игорь опаздывает</div>
|
||
<div class="alert-sub">З-1043 · Корнилов Виктор · 13:00<br>GPS: выехал, но в 2.1 км от объекта</div>
|
||
<div class="alert-action">
|
||
<button class="btn-pill warn" onclick="alert('📞 Звонок Игорю…\\n+7 921 445-67-89')">📞 Позвонить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert-card info">
|
||
<div class="alert-icon">📋</div>
|
||
<div style="flex:1">
|
||
<div class="alert-title">Клиент просит перенести замер</div>
|
||
<div class="alert-sub">З-1044 · Лебедева Марина · сегодня 16:00<br>Сообщение: «Буду дома только после 18:00»</div>
|
||
<div class="alert-action" style="display:flex;gap:6px">
|
||
<button class="btn-pill accent" onclick="go('order_detail')">Открыть</button>
|
||
<button class="btn-pill muted" onclick="alert('Уведомление отложено')">Позже</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-label">📅 Сегодня на выездах</div>
|
||
<div style="padding:0 16px">
|
||
<div class="card" style="padding:12px 14px">
|
||
${[
|
||
{name:'Игорь К.',role:'Замерщик',jobs:3,status:'В пути',sc:'msrr',dot:'yellow'},
|
||
{name:'Олег Р.',role:'Замерщик',jobs:2,status:'На объекте',sc:'msrr',dot:'green'},
|
||
{name:'Дмитрий В.',role:'Сборщик',jobs:1,status:'Завершил',sc:'',dot:'gray'},
|
||
{name:'Алексей М.',role:'Сборщик',jobs:2,status:'На объекте',sc:'',dot:'green'},
|
||
{name:'Сергей П.',role:'Сборщик',jobs:1,status:'Не выехал',sc:'',dot:'red'},
|
||
].map(e=>`
|
||
<div class="sch-row">
|
||
<div class="sch-avatar ${e.sc}">${e.name.split(' ').map(w=>w[0]).join('')}</div>
|
||
<div class="sch-info">
|
||
<div class="sch-name">${e.name}</div>
|
||
<div class="sch-jobs">${e.role} · ${e.jobs} зак.</div>
|
||
</div>
|
||
<div class="sch-status">
|
||
<div style="display:flex;align-items:center;gap:5px;justify-content:flex-end">
|
||
<div class="dot ${e.dot}"></div>
|
||
<span style="font-size:11px;font-weight:600;color:var(--muted)">${e.status}</span>
|
||
</div>
|
||
</div>
|
||
</div>`).join('')}
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─── SCHEDULE ─── */
|
||
function screenSchedule(){
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Расписание · 29 мая</h2>
|
||
<div class="header-action" onclick="alert('Добавить заявку…')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:flex;gap:0;border-bottom:1px solid rgba(0,0,0,.08);background:var(--card);padding:0 16px">
|
||
${['Сегодня','Завтра','30 мая','31 мая'].map((d,i)=>`
|
||
<div style="padding:10px 12px;font-size:13px;font-weight:700;cursor:pointer;border-bottom:2px solid ${i===0?'var(--accent)':'transparent'};color:${i===0?'var(--accent)':'var(--muted)'};">${d}</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div style="padding:12px 16px 0">
|
||
|
||
<div class="section-label" style="margin-top:8px;margin-left:0">🔲 Не назначены (3)</div>
|
||
${[
|
||
{id:'А-2855',client:'Иванов Пётр',addr:'пр. Просвещения, 44',time:'14:00–17:00',type:'Сборка'},
|
||
{id:'А-2856',client:'Козлова Наталья',addr:'ул. Большая Пушкарская, 2',time:'15:00–18:00',type:'Сборка'},
|
||
{id:'З-1045',client:'Фёдоров Антон',addr:'пр. Ветеранов, 120',time:'16:00–17:00',type:'Замер'},
|
||
].map(o=>`
|
||
<div class="card warn-b" style="padding:12px">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:12px;font-weight:800;color:var(--accent)">${o.id}</span>
|
||
<span class="badge yellow">${o.type}</span>
|
||
</div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink);margin-bottom:2px">${o.client}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-bottom:8px">📍 ${o.addr} · ⏰ ${o.time}</div>
|
||
<button class="btn-sm" style="width:100%" onclick="alert('Выбор сотрудника для ${o.id}…')">Назначить сотрудника →</button>
|
||
</div>
|
||
`).join('')}
|
||
|
||
<div class="section-label" style="margin-left:0">✅ Назначены (5)</div>
|
||
${[
|
||
{id:'З-1042',client:'Смирнова Ольга',addr:'ул. Рубинштейна, 7',time:'10:30',emp:'Игорь К.',status:'В процессе',dot:'yellow',sc:'msrr'},
|
||
{id:'З-1043',client:'Корнилов Виктор',addr:'пр. Невский, 120',time:'13:00',emp:'Игорь К.',status:'В пути',dot:'yellow',sc:'msrr'},
|
||
{id:'А-2847',client:'Петрова Светлана',addr:'ул. Ленина, 45',time:'09:00',emp:'Алексей М.',status:'На объекте',dot:'green',sc:''},
|
||
{id:'А-2851',client:'Громов Илья',addr:'пр. Победы, 88',time:'11:00',emp:'Дмитрий В.',status:'Завершил',dot:'gray',sc:''},
|
||
{id:'А-2853',client:'Шилова Ирина',addr:'ул. Савушкина, 14',time:'13:30',emp:'Алексей М.',status:'Назначен',dot:'gray',sc:''},
|
||
].map(o=>`
|
||
<div class="card" style="padding:12px" onclick="go('order_detail')">
|
||
<div class="row sb" style="margin-bottom:5px">
|
||
<span style="font-size:12px;font-weight:800;color:var(--accent)">${o.id}</span>
|
||
<div style="display:flex;align-items:center;gap:5px">
|
||
<div class="dot ${o.dot}"></div>
|
||
<span style="font-size:11px;color:var(--muted);font-weight:600">${o.status}</span>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink);margin-bottom:2px">${o.client}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-bottom:8px">📍 ${o.addr} · ⏰ ${o.time}</div>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<div class="sch-avatar ${o.sc}" style="width:26px;height:26px;font-size:10px">${o.emp.split(' ').map(w=>w[0]).join('')}</div>
|
||
<span style="font-size:12px;font-weight:600;color:var(--ink)">${o.emp}</span>
|
||
<button class="btn-pill accent" style="margin-left:auto" onclick="event.stopPropagation();alert('Переназначить ${o.id}?')">Переназначить</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─── ORDERS ─── */
|
||
var _orderFilter = 'all';
|
||
function screenOrders(){
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Заявки</h2>
|
||
<div class="header-action" onclick="alert('Фильтры…')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="8" y1="12" x2="16" y2="12"/><line x1="11" y1="18" x2="13" y2="18"/></svg>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filter-tabs">
|
||
${[['all','Все (12)'],['new','Новые'],['progress','В работе'],['done','Завершены'],['problem','Проблемы']].map(([v,l])=>`
|
||
<div class="ftab ${_orderFilter===v?'active':''}" onclick="_orderFilter='${v}';document.getElementById('screen').innerHTML=render('orders')">${l}</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div style="padding:0 16px">
|
||
${[
|
||
{id:'А-2855',client:'Иванов Пётр',type:'Сборка',status:'new',label:'Не назначен',badge:'red',time:'Сегодня 14:00',note:'Сборщик не определён'},
|
||
{id:'З-1044',client:'Лебедева Марина',type:'Замер',status:'problem',label:'Перенос',badge:'orange',time:'Сегодня 16:00',note:'Клиент просит после 18:00'},
|
||
{id:'З-1042',client:'Смирнова Ольга',type:'Замер',status:'progress',label:'В процессе',badge:'yellow',time:'Сегодня 10:30',note:'Шаг 2 из 4 · Игорь К.'},
|
||
{id:'А-2847',client:'Петрова Светлана',type:'Сборка',status:'progress',label:'На объекте',badge:'yellow',time:'Сегодня 09:00',note:'Алексей М. · GPS ✓'},
|
||
{id:'А-2851',client:'Громов Илья',type:'Сборка',status:'done',label:'Завершено',badge:'green',time:'Сегодня 11:00',note:'Сдано · подпись клиента'},
|
||
{id:'З-1040',client:'Новиков Кирилл',type:'Замер',status:'done',label:'Завершено',badge:'green',time:'Вчера 15:00',note:'Отчёт отправлен'},
|
||
{id:'А-2856',client:'Козлова Наталья',type:'Сборка',status:'new',label:'Не назначен',badge:'red',time:'Сегодня 15:00',note:'Ждёт назначения'},
|
||
{id:'З-1043',client:'Корнилов Виктор',type:'Замер',status:'progress',label:'В пути',badge:'yellow',time:'Сегодня 13:00',note:'Игорь К. · 2.1 км'},
|
||
].filter(o=>_orderFilter==='all'||o.status===_orderFilter).map(o=>`
|
||
<div class="order-item ${o.status}" onclick="go('order_detail')">
|
||
<div class="row sb" style="margin-bottom:5px">
|
||
<div style="display:flex;align-items:center;gap:7px">
|
||
<span style="font-size:12px;font-weight:800;color:var(--accent)">${o.id}</span>
|
||
<span class="badge gray" style="font-size:10px">${o.type}</span>
|
||
</div>
|
||
<span class="badge ${o.badge}">${o.label}</span>
|
||
</div>
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink);margin-bottom:2px">${o.client}</div>
|
||
<div style="font-size:12px;color:var(--muted)">${o.time} · ${o.note}</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─── STAFF ─── */
|
||
function screenStaff(){
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Сотрудники</h2>
|
||
</div>
|
||
|
||
<div class="section-label">📍 Замерщики (2)</div>
|
||
<div style="padding:0 16px">
|
||
${[
|
||
{name:'Игорь Краснов',phone:'+7 921 445-67-89',jobs:3,done:1,status:'В пути',dot:'yellow',detail:'З-1043 · Корнилов В. · 13:00 · 2.1 км от объекта',sc:'msrr',warn:true},
|
||
{name:'Олег Романов',phone:'+7 911 234-56-78',jobs:2,done:1,status:'На объекте',dot:'green',detail:'З-1042 · Смирнова О. · с 10:35 · GPS ✓',sc:'msrr',warn:false},
|
||
].map(e=>`
|
||
<div class="card${e.warn?' warn-b':''}">
|
||
<div class="row sb" style="margin-bottom:10px">
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<div class="sch-avatar msrr" style="width:42px;height:42px;font-size:15px">${e.name.split(' ').map(w=>w[0]).join('')}</div>
|
||
<div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${e.name}</div>
|
||
<div style="font-size:12px;color:var(--accent)">${e.phone}</div>
|
||
</div>
|
||
</div>
|
||
<div style="text-align:right">
|
||
<div style="display:flex;align-items:center;gap:5px;justify-content:flex-end;margin-bottom:4px">
|
||
<div class="dot ${e.dot}"></div>
|
||
<span style="font-size:12px;font-weight:700;color:var(--muted)">${e.status}</span>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--muted)">${e.done}/${e.jobs} зак.</div>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--muted);background:var(--bg);padding:8px 10px;border-radius:8px;margin-bottom:10px">📍 ${e.detail}</div>
|
||
<div style="display:flex;gap:8px">
|
||
<button class="btn-pill accent" style="flex:1;justify-content:center" onclick="alert('📞 Звонок ${e.name}…')">📞 Позвонить</button>
|
||
<button class="btn-pill muted" style="flex:1;justify-content:center" onclick="alert('💬 Чат с ${e.name}…')">💬 Написать</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="section-label">🔧 Сборщики (3)</div>
|
||
<div style="padding:0 16px">
|
||
${[
|
||
{name:'Алексей Малинин',phone:'+7 981 876-54-32',jobs:2,done:0,status:'На объекте',dot:'green',detail:'А-2847 · Петрова С. · с 09:15 · GPS ✓',warn:false},
|
||
{name:'Дмитрий Власов',phone:'+7 965 321-44-55',jobs:1,done:1,status:'Завершил',dot:'gray',detail:'А-2851 · Громов И. · завершено в 13:40',warn:false},
|
||
{name:'Сергей Петров',phone:'+7 953 111-22-33',jobs:1,done:0,status:'Не выехал',dot:'red',detail:'А-2855 · НЕ НАЗНАЧЕН · сегодня 14:00',warn:true},
|
||
].map(e=>`
|
||
<div class="card${e.warn?' danger-b':''}">
|
||
<div class="row sb" style="margin-bottom:10px">
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<div class="sch-avatar" style="width:42px;height:42px;font-size:15px">${e.name.split(' ').map(w=>w[0]).join('')}</div>
|
||
<div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${e.name}</div>
|
||
<div style="font-size:12px;color:var(--accent)">${e.phone}</div>
|
||
</div>
|
||
</div>
|
||
<div style="text-align:right">
|
||
<div style="display:flex;align-items:center;gap:5px;justify-content:flex-end;margin-bottom:4px">
|
||
<div class="dot ${e.dot}"></div>
|
||
<span style="font-size:12px;font-weight:700;color:var(--muted)">${e.status}</span>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--muted)">${e.done}/${e.jobs} зак.</div>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--muted);background:var(--bg);padding:8px 10px;border-radius:8px;margin-bottom:10px">${e.warn?'⚠️':'📍'} ${e.detail}</div>
|
||
<div style="display:flex;gap:8px">
|
||
<button class="btn-pill accent" style="flex:1;justify-content:center" onclick="alert('📞 Звонок ${e.name}…')">📞 Позвонить</button>
|
||
<button class="btn-pill muted" style="flex:1;justify-content:center" onclick="alert('💬 Чат с ${e.name}…')">💬 Написать</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─── ORDER DETAIL ─── */
|
||
function screenOrderDetail(){
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('orders')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||
<h2>Заявка #А-2855</h2>
|
||
<div class="header-action" onclick="alert('История изменений…')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="background:#FEF2F2;border-bottom:1.5px solid #FECACA;padding:12px 16px;display:flex;align-items:center;gap:10px">
|
||
<span style="font-size:18px">🚨</span>
|
||
<div>
|
||
<div style="font-size:13px;font-weight:700;color:#DC2626">Сборщик не назначен</div>
|
||
<div style="font-size:12px;color:#991B1B">Осталось 4 часа · Сегодня 14:00–17:00</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="padding:12px 16px 0">
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Клиент</div>
|
||
<div class="info-row"><span class="info-label">Имя</span><span class="info-val">Иванов Пётр</span></div>
|
||
<div class="info-row"><span class="info-label">Телефон</span><span class="info-val" style="color:var(--accent)">+7 812 555-12-34</span></div>
|
||
<div class="info-row"><span class="info-label">Адрес</span><span class="info-val">пр. Просвещения, 44, кв. 7</span></div>
|
||
<div class="info-row"><span class="info-label">Время</span><span class="info-val">Сегодня 14:00–17:00</span></div>
|
||
<div class="info-row"><span class="info-label">Менеджер</span><span class="info-val">Анна Смирнова</span></div>
|
||
<div style="margin-top:10px">
|
||
<a href="#" onclick="event.preventDefault();alert('📞 Звонок клиенту…')" style="display:inline-flex;align-items:center;gap:6px;background:#EFF6FF;padding:8px 14px;border-radius:10px;text-decoration:none;font-size:12px;font-weight:700;color:var(--accent);border:1px solid #BFDBFE">
|
||
📞 Позвонить клиенту
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Объект сборки</div>
|
||
<div class="info-row"><span class="info-label">Тип</span><span class="info-val">Кухня угловая</span></div>
|
||
<div class="info-row"><span class="info-label">Размер</span><span class="info-val">2.8 × 3.8 м</span></div>
|
||
<div class="info-row"><span class="info-label">Корпуса</span><span class="info-val">14 ед.</span></div>
|
||
<div class="info-row"><span class="info-label">Фасады</span><span class="info-val">МДФ плёнка, белый</span></div>
|
||
<div class="info-row"><span class="info-label">Техника</span><span class="info-val">3 позиции</span></div>
|
||
</div>
|
||
|
||
<!-- Назначение сотрудника -->
|
||
<div class="card danger-b">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Назначить сборщика</div>
|
||
${[
|
||
{name:'Сергей Петров',jobs:1,free:'С 12:00',avail:true},
|
||
{name:'Константин Лев.',jobs:0,free:'Свободен',avail:true},
|
||
{name:'Дмитрий Власов',jobs:1,free:'Занят до 16:00',avail:false},
|
||
].map(e=>`
|
||
<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid rgba(0,0,0,.05)">
|
||
<div class="sch-avatar" style="width:34px;height:34px;font-size:12px">${e.name.split(' ').map(w=>w[0]).join('')}</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink)">${e.name}</div>
|
||
<div style="font-size:11px;color:var(--muted)">${e.jobs} зак. · ${e.free}</div>
|
||
</div>
|
||
${e.avail
|
||
?`<button class="btn-sm" onclick="alert('Сборщик ${e.name} назначен на А-2855!')">Назначить</button>`
|
||
:`<span class="badge gray">Занят</span>`}
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<!-- Документы -->
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Документы</div>
|
||
<div style="display:flex;gap:7px">
|
||
<a href="#" onclick="event.preventDefault();alert('📋 Договор А-2855…')" style="flex:1;display:flex;align-items:center;gap:6px;padding:9px 10px;background:#EFF6FF;border:1px solid #BFDBFE;border-radius:10px;text-decoration:none">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#1D4ED8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||
<div><div style="font-size:11px;font-weight:700;color:#1D4ED8">Договор</div><div style="font-size:10px;color:#93C5FD">PDF · 1.2 МБ</div></div>
|
||
</a>
|
||
<a href="#" onclick="event.preventDefault();alert('📐 Проект А-2855…')" style="flex:1;display:flex;align-items:center;gap:6px;padding:9px 10px;background:#F0FDF4;border:1px solid #BBF7D0;border-radius:10px;text-decoration:none">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="3" y1="9" x2="21" y2="9"/></svg>
|
||
<div><div style="font-size:11px;font-weight:700;color:#15803D">Проект</div><div style="font-size:10px;color:#86EFAC">PDF · 4 листа</div></div>
|
||
</a>
|
||
<a href="#" onclick="event.preventDefault();alert('📸 Фото замера А-2855…')" style="flex:1;display:flex;align-items:center;gap:6px;padding:9px 10px;background:#FFF7ED;border:1px solid #FED7AA;border-radius:10px;text-decoration:none">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#C2410C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="12" cy="12" r="3"/></svg>
|
||
<div><div style="font-size:11px;font-weight:700;color:#C2410C">Фото</div><div style="font-size:10px;color:#FDBA74">8 фото</div></div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="alert('Сохранить изменения заявки А-2855?')">💾 Сохранить изменения</button>
|
||
<button class="btn-secondary" onclick="alert('Перенос заявки А-2855…')">🔄 Перенести визит</button>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// init
|
||
go('home');
|
||
</script>
|
||
</body>
|
||
</html>
|