File manager - Edit - /home/webtrixia/.trash/admin/dashboard.php
Back
<?php $pageTitle = 'Super Admin · VETTA'; $bodyClass = 'dash-body'; $e = fn($v) => htmlspecialchars((string)($v??''), ENT_QUOTES, 'UTF-8'); $role_labels = ['owner'=>'Собственик','vet'=>'Ветеринар','clinic_admin'=>'Клиника Админ','super_admin'=>'Super Admin']; $role_colors = ['owner'=>'ds-pill-blue','vet'=>'ds-pill-teal','clinic_admin'=>'ds-pill-teal','super_admin'=>'ds-pill-amber']; $status_colors= ['active'=>'ds-pill-green','inactive'=>'ds-pill-gray','pending'=>'ds-pill-amber']; $premium_ratio = $stats['users'] ? round(($stats['premium'] / $stats['users']) * 100) : 0; $role_summary = [ ['label'=>'Собственици','value'=>$user_breakdown['owners'],'icon'=>'<i class="fa-solid fa-house"></i>'], ['label'=>'Ветеринари','value'=>$user_breakdown['vets'],'icon'=>'<i class="fa-solid fa-user-doctor"></i>'], ['label'=>'Клиника админ','value'=>$user_breakdown['clinic_admins'],'icon'=>'<i class="fa-solid fa-hospital-user"></i>'], ['label'=>'Super Admin','value'=>$user_breakdown['super_admins'],'icon'=>'<i class="fa-solid fa-shield-halved"></i>'], ]; $ops_summary = [ ['label'=>'Днешни часове','value'=>$ops['appointments_today'],'tone'=>'blue'], ['label'=>'Чакащи часове','value'=>$ops['appointments_pending'],'tone'=>'amber'], ['label'=>'Непрочетени чатове','value'=>$ops['messages_unread'],'tone'=>'teal'], ['label'=>'Непратени напомняния','value'=>$ops['reminders_pending'],'tone'=>'green'], ]; $today_label = date('d.m.Y', strtotime($today ?? 'now')); // Group cards by batch for summary $batches = []; foreach ($cards as $c) { $b = $c->batch ?? '—'; if (!isset($batches[$b])) $batches[$b] = ['batch'=>$b,'note'=>$c->note??'','total'=>0,'unused'=>0,'used'=>0,'months'=>$c->months,'price'=>$c->price,'date'=>$c->created_at]; $batches[$b]['total']++; if ($c->status === 'unused') $batches[$b]['unused']++; if ($c->status === 'used') $batches[$b]['used']++; } require __DIR__.'/../shared/header.php'; ?> <style> .adm-hero-pills,.adm-inline-stats,.adm-summary-grid,.adm-mini-grid,.adm-two-col,.adm-toolbar,.adm-filter-row,.adm-clinic-toolbar{display:grid;gap:12px} .adm-hero-pills{grid-template-columns:repeat(3,minmax(0,1fr));margin-top:18px} .adm-hero-pill,.adm-mini-card,.adm-summary-card,.adm-recent-item,.adm-top-clinic{background:#fff;border:1px solid rgba(15,23,42,.08);border-radius:22px;box-shadow:0 14px 30px rgba(15,23,42,.05)} .adm-hero-pill{padding:14px 16px;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.16);box-shadow:none;color:#fff} .adm-hero-pill strong{display:block;font-size:22px;font-weight:900;line-height:1.05} .adm-hero-pill span{display:block;font-size:12px;color:rgba(255,255,255,.78);margin-top:4px} .adm-summary-grid{grid-template-columns:repeat(4,minmax(0,1fr));margin:16px 0} .adm-summary-card{padding:18px} .adm-summary-card h3{margin:0 0 10px;font-size:14px;font-weight:800;color:var(--ink)} .adm-summary-card p{margin:6px 0 0;font-size:12px;line-height:1.6;color:var(--ink4)} .adm-summary-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px} .adm-summary-value{font-size:30px;font-weight:900;line-height:1;color:var(--ink)} .adm-summary-sub{font-size:12px;color:var(--ink4);font-weight:600} .adm-mini-grid{grid-template-columns:repeat(2,minmax(0,1fr))} .adm-mini-card{padding:12px 14px} .adm-mini-card strong{display:block;font-size:20px;font-weight:900;color:var(--ink)} .adm-mini-card span{display:block;font-size:11px;color:var(--ink4);margin-top:4px} .adm-two-col{grid-template-columns:1.2fr .8fr;margin-top:14px} .adm-list{display:grid;gap:10px} .adm-recent-item,.adm-top-clinic{padding:14px 16px} .adm-recent-item{display:flex;align-items:center;gap:12px} .adm-recent-main{flex:1;min-width:0} .adm-recent-main strong,.adm-top-clinic strong{display:block;font-size:14px;color:var(--ink)} .adm-recent-main span,.adm-top-clinic span{display:block;font-size:12px;color:var(--ink4);margin-top:4px} .adm-recent-meta{font-size:11px;color:var(--ink5);font-weight:700;white-space:nowrap} .adm-top-clinic{display:flex;align-items:flex-start;justify-content:space-between;gap:12px} .adm-top-stats{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px} .adm-toolbar{grid-template-columns:minmax(0,1fr) auto;align-items:end;margin-bottom:12px} .adm-filter-row{grid-template-columns:repeat(3,minmax(0,180px));align-items:end} .adm-clinic-toolbar{grid-template-columns:minmax(0,1fr) repeat(2,minmax(0,220px)) auto;align-items:end;margin-bottom:12px} .adm-users-overview,.adm-subs-overview{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:12px} .adm-kpi-chip{padding:12px 14px;border-radius:18px;background:#fff;border:1px solid rgba(15,23,42,.08);box-shadow:0 10px 24px rgba(15,23,42,.04)} .adm-kpi-chip strong{display:block;font-size:20px;font-weight:900;color:var(--ink)} .adm-kpi-chip span{display:block;font-size:11px;color:var(--ink4);margin-top:4px} .adm-filter-note{font-size:12px;color:var(--ink4);font-weight:600} .adm-clinic-card-v2.hidden,.user-row-hidden,.card-row-hidden{display:none !important} @media (max-width: 1100px){ .adm-summary-grid,.adm-users-overview,.adm-subs-overview{grid-template-columns:repeat(2,minmax(0,1fr))} .adm-two-col,.adm-toolbar,.adm-filter-row,.adm-clinic-toolbar{grid-template-columns:1fr} } @media (max-width: 700px){ .adm-hero-pills,.adm-summary-grid,.adm-users-overview,.adm-subs-overview,.adm-mini-grid{grid-template-columns:1fr} } </style> <div class="ds"> <!-- TOPBAR --> <div class="ds-top"> <div class="ds-top-l"> <div class="ds-top-brand"><img src="/assets/images/logo.png" alt="VETTA"></div> <div class="ds-top-sep"></div> <div class="ds-av" style="background:linear-gradient(135deg,#7C3AED,#4F46E5)"><?= mb_strtoupper(mb_substr($user->name,0,1)) ?></div> <div> <div class="ds-top-name"><?= $e($user->name) ?></div> <div class="ds-top-sub">Super Admin · пълен достъп</div> </div> </div> <div class="ds-top-r"> <span class="ds-pill" style="background:#EDE9FE;color:#7C3AED">⚡ Super Admin</span> <a href="/logout" class="ds-upgrade" style="border-color:var(--red);color:var(--red)">Изход</a> </div> </div> <div class="dash-hero"> <div> <span class="dash-hero-kicker"><i class="fa-solid fa-shield-halved"></i> Админ хъб</span> <h1><?=$e($user->name)?></h1> <p>Централизирано управление на потребители, клиники, карти, абонаменти и приходи с по-детайлна оперативна картина и по-ясни инструменти за контрол.</p> <div class="adm-hero-pills"> <div class="adm-hero-pill"> <strong><?= $user_breakdown['new_this_month'] ?></strong> <span>нови регистрации този месец</span> </div> <div class="adm-hero-pill"> <strong><?= $premium_ratio ?>%</strong> <span>от всички потребители са Premium</span> </div> <div class="adm-hero-pill"> <strong><?= $clinic_breakdown['active'] ?></strong> <span>активни клиники към <?= $today_label ?></span> </div> </div> </div> <div class="dash-hero-side"> <div><strong><?= number_format($stats['revenue'],0,',',' ') ?> €</strong><small>общ реализиран приход и централен поглед върху платформата</small></div> <div class="dash-hero-actions"><button type="button" class="dash-hero-btn primary" onclick="switchTab('adm-clinics')"><i class="fa-solid fa-hospital"></i> Прегледай клиники</button><button type="button" class="dash-hero-btn secondary" onclick="switchTab('adm-subs')"><i class="fa-solid fa-list"></i> Приходи и абонаменти</button></div> </div> </div> <!-- STATS --> <div class="adm-stats-6"> <div class="ds-stat ds-stat-blue" style="border-left:4px solid var(--blue)"> <div class="ds-stat-n"><?= $stats['users'] ?></div><div class="ds-stat-l">Потребители</div> </div> <div class="ds-stat ds-stat-teal" style="border-left:4px solid var(--teal)"> <div class="ds-stat-n"><?= $stats['pets'] ?></div><div class="ds-stat-l">Животни</div> </div> <div class="ds-stat" style="border-left:4px solid #7C3AED"> <div class="ds-stat-n"><?= $stats['clinics'] ?></div><div class="ds-stat-l">Клиники</div> </div> <div class="ds-stat ds-stat-amber" style="border-left:4px solid #F59E0B"> <div class="ds-stat-n"><?= $stats['premium'] ?></div><div class="ds-stat-l">Premium</div> </div> <div class="ds-stat" style="border-left:4px solid #6B7280"> <div class="ds-stat-n"><?= $stats['cards_unused'] ?></div><div class="ds-stat-l">Карти</div> </div> <div class="ds-stat ds-stat-green" style="border-left:4px solid #22C55E"> <div class="ds-stat-n"><?= number_format($stats['revenue'],0,',',' ') ?> €</div><div class="ds-stat-l">Приход</div> </div> </div> <!-- TABS --> <div class="ds-tabs"> <button class="ds-tab active" data-panel="adm-stats">📊 Статистики</button> <button class="ds-tab" data-panel="adm-users"><i class="fa-solid fa-users"></i> Потребители <span class="ds-cnt"><?= $stats['users'] ?></span></button> <button class="ds-tab" data-panel="adm-clinics"><i class="fa-solid fa-hospital"></i> Клиники <span class="ds-cnt"><?= $stats['clinics'] ?></span></button> <button class="ds-tab" data-panel="adm-cards"><i class="fa-solid fa-credit-card"></i> Карти <span class="ds-cnt"><?= $stats['cards_unused'] ?></span></button> <button class="ds-tab" data-panel="adm-subs"><i class="fa-solid fa-list"></i> Абонаменти</button> </div> <!-- ── СТАТИСТИКИ ── --> <div class="ds-panel active" id="panel-adm-stats"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:14px"> <div class="ds-card"><div class="ds-card-title">Нови потребители — 6 месеца</div><div class="adm-chart-wrap"><canvas id="chart-users"></canvas></div></div> <div class="ds-card"><div class="ds-card-title">Premium активации — 6 месеца</div><div class="adm-chart-wrap"><canvas id="chart-premium"></canvas></div></div> </div> <div class="adm-summary-grid"> <div class="adm-summary-card"> <div class="adm-summary-head"> <h3>Потребители</h3> <span class="ds-pill ds-pill-blue"><?= $stats['users'] ?> общо</span> </div> <div class="adm-summary-value"><?= $premium_ratio ?>%</div> <div class="adm-summary-sub">Premium penetration</div> <div class="adm-mini-grid" style="margin-top:12px"> <?php foreach ($role_summary as $item): ?> <div class="adm-mini-card"> <strong><?= $item['value'] ?></strong> <span><?= $item['icon'] ?> <?= $item['label'] ?></span> </div> <?php endforeach; ?> </div> <p><?= $user_breakdown['premium_expiring_soon'] ?> профила изтичат в следващите 30 дни.</p> </div> <div class="adm-summary-card"> <div class="adm-summary-head"> <h3>Клиники</h3> <span class="ds-pill ds-pill-teal"><?= $stats['clinics'] ?> общо</span> </div> <div class="adm-summary-value"><?= $clinic_breakdown['active'] ?></div> <div class="adm-summary-sub">активни клиники</div> <div class="adm-mini-grid" style="margin-top:12px"> <div class="adm-mini-card"><strong><?= $clinic_breakdown['inactive'] ?></strong><span>неактивни</span></div> <div class="adm-mini-card"><strong><?= $clinic_breakdown['pending'] ?></strong><span>чакащи активиране</span></div> <div class="adm-mini-card"><strong><?= $clinic_breakdown['with_cover'] ?></strong><span>с качена корица</span></div> <div class="adm-mini-card"><strong><?= $clinic_breakdown['with_website'] ?></strong><span>с уебсайт</span></div> </div> <p><?= $clinic_breakdown['with_email'] ?> клиники имат въведен служебен имейл.</p> </div> <div class="adm-summary-card"> <div class="adm-summary-head"> <h3>Абонаменти и приходи</h3> <span class="ds-pill ds-pill-green"><?= $subscription_breakdown['active'] ?> активни</span> </div> <div class="adm-summary-value"><?= number_format($subscription_breakdown['mrr'],0,',',' ') ?> €</div> <div class="adm-summary-sub">активна стойност в момента</div> <div class="adm-mini-grid" style="margin-top:12px"> <div class="adm-mini-card"><strong><?= $subscription_breakdown['card'] ?></strong><span>card активации</span></div> <div class="adm-mini-card"><strong><?= $subscription_breakdown['manual'] ?></strong><span>manual активации</span></div> <div class="adm-mini-card"><strong><?= $subscription_breakdown['stripe'] ?></strong><span>stripe активации</span></div> <div class="adm-mini-card"><strong><?= $subscription_breakdown['expired'] ?></strong><span>изтекли</span></div> </div> <p>Общ приход до момента: <?= number_format($stats['revenue'],2,',',' ') ?> €.</p> </div> <div class="adm-summary-card"> <div class="adm-summary-head"> <h3>Операции</h3> <span class="ds-pill ds-pill-amber"><?= $stats['pending_requests'] ?> чакащи заявки</span> </div> <div class="adm-mini-grid"> <?php foreach ($ops_summary as $item): ?> <div class="adm-mini-card"> <strong><?= $item['value'] ?></strong> <span><?= $item['label'] ?></span> </div> <?php endforeach; ?> </div> <p><?= $stats['cards_usage_pct'] ?>% от картите вече са използвани; <?= $cards_breakdown['new_this_month'] ?> са създадени този месец.</p> </div> </div> <div class="adm-stats-6" style="margin-bottom:0"> <?php foreach([ ['val'=>$stats['pets'], 'lbl'=>'Животни', 'ic'=>'<i class="fa-solid fa-paw"></i>'], ['val'=>$stats['records'], 'lbl'=>'Мед. записи', 'ic'=>'<i class="fa-solid fa-list"></i>'], ['val'=>$stats['cards_used'], 'lbl'=>'Използвани карти', 'ic'=>'<i class="fa-solid fa-credit-card"></i>'], ['val'=>$stats['grants'], 'lbl'=>'Активни достъпи', 'ic'=>'🔓'], ['val'=>$stats['pending_requests'],'lbl'=>'Чакащи заявки', 'ic'=>'⏳'], ['val'=>$stats['banned'], 'lbl'=>'Блокирани', 'ic'=>'<i class="fa-solid fa-ban"></i>'], ] as $qs): ?> <div class="ds-card" style="text-align:center;padding:14px 10px"> <div style="font-size:22px;margin-bottom:4px"><?= $qs['ic'] ?></div> <div style="font-size:22px;font-weight:800;color:var(--ink);line-height:1"><?= $qs['val'] ?></div> <div style="font-size:10px;font-weight:600;color:var(--ink4);margin-top:4px;text-transform:uppercase;letter-spacing:.4px"><?= $qs['lbl'] ?></div> </div> <?php endforeach; ?> </div> <div class="adm-two-col"> <div class="ds-card"> <div class="ds-card-title" style="margin-bottom:12px">Последни регистрации</div> <div class="adm-list"> <?php foreach ($recent_users as $ru): ?> <div class="adm-recent-item"> <div class="adm-user-av"><?= mb_strtoupper(mb_substr($ru->name,0,1)) ?></div> <div class="adm-recent-main"> <strong><?= $e($ru->name) ?></strong> <span><?= $e($ru->email) ?> · <?= $role_labels[$ru->role] ?? $ru->role ?></span> </div> <div class="adm-recent-meta"><?= date('d.m.Y', strtotime($ru->created_at)) ?></div> </div> <?php endforeach; ?> </div> </div> <div class="ds-card"> <div class="ds-card-title" style="margin-bottom:12px">Топ клиники по активност</div> <div class="adm-list"> <?php foreach ($top_clinics as $tc): ?> <div class="adm-top-clinic"> <div> <strong><?= $e($tc->name) ?></strong> <span><?= $e($tc->city ?: 'Без град') ?> · <?= date('d.m.Y', strtotime($tc->created_at)) ?></span> <div class="adm-top-stats"> <span class="ds-pill ds-pill-blue"><?= (int)$tc->team_count ?> екип</span> <span class="ds-pill ds-pill-teal"><?= (int)$tc->patient_count ?> пациента</span> <span class="ds-pill ds-pill-amber"><?= (int)$tc->record_count ?> записа</span> </div> </div> <span class="ds-pill <?= $status_colors[$tc->status] ?? 'ds-pill-gray' ?>"><?= $e($tc->status) ?></span> </div> <?php endforeach; ?> </div> </div> </div> </div> <!-- ── ПОТРЕБИТЕЛИ ── --> <div class="ds-panel" id="panel-adm-users"> <div class="adm-users-overview"> <div class="adm-kpi-chip"><strong><?= $stats['users'] ?></strong><span>общо потребители</span></div> <div class="adm-kpi-chip"><strong><?= $user_breakdown['new_this_month'] ?></strong><span>нови този месец</span></div> <div class="adm-kpi-chip"><strong><?= $stats['premium'] ?></strong><span>активен Premium</span></div> <div class="adm-kpi-chip"><strong><?= $stats['banned'] ?></strong><span>блокирани акаунти</span></div> </div> <div class="adm-toolbar"> <div> <h2 class="ds-ptitle" style="margin-bottom:4px">Потребители</h2> <div class="adm-filter-note" id="user-filter-note">Показани са всички потребители.</div> </div> <div class="adm-filter-row"> <input type="text" id="user-search" class="ds-fi" placeholder="🔍 Търси по име или email..."> <select id="user-role-filter" class="ds-fi"> <option value="">Всички роли</option> <?php foreach ($role_labels as $role_key => $role_label): ?> <option value="<?= $e($role_key) ?>"><?= $e($role_label) ?></option> <?php endforeach; ?> </select> <select id="user-status-filter" class="ds-fi"> <option value="">Всички статуси</option> <option value="active">Само активни</option> <option value="premium">Само Premium</option> <option value="banned">Само блокирани</option> </select> </div> </div> <div class="ds-card" style="padding:0;overflow:hidden"> <div style="overflow-x:auto"> <table class="adm-table" id="users-table"> <thead> <tr><th style="width:32px">#</th><th>Потребител</th><th>Email</th><th>Роля</th><th>Клиника</th><th>Premium до</th><th>Статус</th><th>Действия</th></tr> </thead> <tbody> <?php foreach ($users as $u): ?> <tr id="urow-<?= $u->id ?>" data-name="<?= strtolower($e($u->name)) ?>" data-email="<?= strtolower($e($u->email)) ?>" data-role="<?= $e($u->role) ?>" data-banned="<?= $u->is_banned?1:0 ?>" data-premium="<?= (!empty($u->premium_until) && strtotime($u->premium_until) >= strtotime($today ?? date('Y-m-d'))) ? 1 : 0 ?>"> <td style="color:var(--ink5);font-size:11px;font-weight:600"><?= $u->id ?></td> <td> <div style="display:flex;align-items:center;gap:9px"> <div class="adm-user-av"><?= mb_strtoupper(mb_substr($u->name,0,1)) ?></div> <div> <div style="font-weight:600;font-size:13px"><?= $e($u->name) ?></div> <div style="font-size:11px;color:var(--ink5)">Регистрация: <?= date('d.m.Y', strtotime($u->created_at)) ?></div> </div> </div> </td> <td style="color:var(--ink3);font-size:12px"><?= $e($u->email) ?></td> <td><span class="ds-pill <?= $role_colors[$u->role]??'ds-pill-gray' ?>"><?= $role_labels[$u->role]??$u->role ?></span></td> <td style="font-size:12px;color:var(--ink3)"><?= $e($u->clinic_name??'—') ?></td> <td><?php if($u->premium_until): ?><span style="color:var(--green);font-size:12px;font-weight:600"><i class="fa-solid fa-star"></i> <?= date('d.m.Y',strtotime($u->premium_until)) ?></span><?php else: ?><span style="color:var(--ink5);font-size:12px">—</span><?php endif; ?></td> <td><?= $u->is_banned ? '<span class="ds-pill ds-pill-red"><i class="fa-solid fa-ban"></i> Блокиран</span>' : '<span class="ds-pill ds-pill-green"><i class="fa-solid fa-check"></i> Активен</span>' ?></td> <td> <button class="ds-btn-sm ds-btn-blue btn-edit-user" data-uid="<?= $u->id ?>" data-name="<?= $e($u->name) ?>" data-role="<?= $e($u->role) ?>" data-clinic="<?= $e($u->clinic_id??'') ?>" data-until="<?= $e($u->premium_until??'') ?>" data-banned="<?= $u->is_banned?1:0 ?>"> ✏️ Управлявай </button> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> </div> <!-- ── КЛИНИКИ ── --> <div class="ds-panel" id="panel-adm-clinics"> <div class="adm-users-overview"> <div class="adm-kpi-chip"><strong><?= $clinic_breakdown['active'] ?></strong><span>активни клиники</span></div> <div class="adm-kpi-chip"><strong><?= $clinic_breakdown['inactive'] ?></strong><span>неактивни</span></div> <div class="adm-kpi-chip"><strong><?= $clinic_breakdown['with_cover'] ?></strong><span>с качена корица</span></div> <div class="adm-kpi-chip"><strong><?= $clinic_breakdown['with_website'] ?></strong><span>с уебсайт</span></div> </div> <div class="adm-clinic-toolbar"> <div> <h2 class="ds-ptitle" style="margin-bottom:4px">Клиники</h2> <div class="adm-filter-note" id="clinic-filter-note">Показани са всички клиники.</div> </div> <input type="text" id="clinic-search" class="ds-fi" placeholder="🔍 Търси по име, град или email..."> <select id="clinic-status-filter" class="ds-fi"> <option value="">Всички статуси</option> <option value="active">Активни</option> <option value="inactive">Неактивни</option> <option value="pending">Pending</option> </select> <button class="ds-btn-pri" id="btn-new-clinic">+ Нова клиника</button> </div> <div class="ds-addform" id="new-clinic-form" style="display:none"> <h3>➕ Добави клиника</h3> <form id="form-add-clinic"> <?= Csrf::field() ?> <div class="ds-grid3" style="margin-bottom:10px"> <div class="ds-f"><label class="ds-fl">Наименование *</label><input type="text" name="name" class="ds-fi" required></div> <div class="ds-f"><label class="ds-fl">Град</label><input type="text" name="city" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Телефон</label><input type="text" name="phone" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Email</label><input type="email" name="email" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Уебсайт</label><input type="text" name="website" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Адрес</label><input type="text" name="address" class="ds-fi"></div> </div> <div class="ds-f" style="margin-bottom:12px"><label class="ds-fl">Описание</label><textarea name="description" class="ds-fi ds-ta"></textarea></div> <div class="ds-fa"> <button type="submit" class="ds-btn-pri">Запази</button> <button type="button" class="ds-btn-ghost" onclick="this.closest('.ds-addform').style.display='none'">Откажи</button> </div> </form> </div> <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px" id="clinics-grid"> <?php foreach ($clinics as $c): $vet_count = (int)DB::val('SELECT COUNT(*) FROM clinic_users WHERE clinic_id=?',[$c->id]); $pat_count = (int)DB::val('SELECT COUNT(DISTINCT owner_id) FROM access_grants ag JOIN pets p ON p.id=ag.pet_id WHERE ag.clinic_id=?',[$c->id]); ?> <div class="adm-clinic-card-v2" id="ccard-<?= $c->id ?>" data-name="<?= strtolower($e($c->name)) ?>" data-city="<?= strtolower($e($c->city)) ?>" data-email="<?= strtolower($e($c->email)) ?>" data-status="<?= $e($c->status) ?>"> <div class="adm-clinic-cover-v2" style="<?= $c->cover_url ? 'background-image:url('.$e($c->cover_url).')' : '' ?>"> <?php if (!$c->cover_url): ?><span style="font-size:36px"><i class="fa-solid fa-hospital"></i></span><?php endif; ?> <span class="ds-pill <?= $c->status==='active'?'ds-pill-green':'ds-pill-gray' ?>" style="position:absolute;top:10px;right:10px"> <?= $c->status==='active'?'<i class="fa-solid fa-check"></i> Активна':'⏸ Неактивна' ?> </span> </div> <div style="padding:14px"> <div style="font-size:15px;font-weight:700;margin-bottom:4px"><?= $e($c->name) ?></div> <div style="font-size:12px;color:var(--ink4);margin-bottom:10px"> <?php if($c->city): ?>📍 <?= $e($c->city) ?><?php endif; ?> <?php if($c->phone): ?> · <?= $e($c->phone) ?><?php endif; ?> </div> <div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px"> <span class="ds-pill ds-pill-blue">👤 <?= $vet_count ?> екип</span> <span class="ds-pill ds-pill-teal"><i class="fa-solid fa-paw"></i> <?= $pat_count ?> пациента</span> </div> <div style="display:flex;gap:6px;flex-wrap:wrap"> <button class="ds-btn-sm ds-btn-blue btn-edit-clinic" data-id="<?= $c->id ?>" data-name="<?= $e($c->name) ?>" data-city="<?= $e($c->city) ?>" data-phone="<?= $e($c->phone) ?>" data-email="<?= $e($c->email) ?>" data-status="<?= $e($c->status) ?>" data-desc="<?= $e($c->description) ?>" data-services="<?= $e($c->services) ?>" data-hours="<?= $e($c->working_hours) ?>" data-website="<?= $e($c->website) ?>">✏️ Редактирай</button> <button class="ds-btn-sm ds-btn-teal btn-manage-vets" data-id="<?= $c->id ?>" data-name="<?= $e($c->name) ?>"><i class="fa-solid fa-users"></i> Екип</button> <button class="ds-btn-sm <?= $c->status==='active'?'ds-btn-amber':'ds-btn-green' ?> btn-toggle-clinic" data-id="<?= $c->id ?>" data-status="<?= $e($c->status) ?>"><?= $c->status==='active'?'⏸ Деактивирай':'▶ Активирай' ?></button> <button class="ds-btn-sm ds-btn-danger btn-del-clinic" data-id="<?= $c->id ?>" data-name="<?= $e($c->name) ?>">🗑</button> </div> </div> </div> <?php endforeach; ?> <?php if (!$clinics): ?> <div class="ds-empty" style="grid-column:1/-1"><span class="ds-empty-ic"><i class="fa-solid fa-hospital"></i></span><h3>Няма клиники</h3><p>Добави с бутона горе.</p></div> <?php endif; ?> </div> </div> <!-- ── КАРТИ ── --> <div class="ds-panel" id="panel-adm-cards"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px;align-items:start"> <!-- LEFT: Generate form --> <div class="ds-card"> <div class="ds-card-title" style="margin-bottom:14px">🎟 Генерирай нова партида</div> <form id="form-gen-cards"> <?= Csrf::field() ?> <div class="ds-f" style="margin-bottom:10px"> <label class="ds-fl">Код на локация / партньор</label> <div style="display:flex;align-items:center"> <span style="background:var(--blue-ll);color:var(--blue);font-family:monospace;font-size:14px;font-weight:800;padding:10px 12px;border:1.5px solid var(--blue);border-right:none;border-radius:8px 0 0 8px;white-space:nowrap">VT</span> <input type="text" name="location" id="loc-input" class="ds-fi" placeholder="VEMA" maxlength="8" style="border-radius:0 8px 8px 0;font-family:monospace;text-transform:uppercase;letter-spacing:2px;font-weight:700"> </div> <div style="margin-top:5px;font-size:12px;color:var(--ink4)"> Формат: <code id="prefix-preview" style="color:var(--blue);font-weight:700">VT</code>-XXXXXX-XXXXXX </div> </div> <div class="ds-f" style="margin-bottom:10px"> <label class="ds-fl">Описание на партидата</label> <input type="text" name="note" class="ds-fi" placeholder="Магазин Вема, Кърджали"> </div> <div style="display:grid;grid-template-columns:80px 1fr 90px;gap:8px;margin-bottom:14px"> <div class="ds-f"><label class="ds-fl">Брой</label><input type="number" name="count" class="ds-fi" value="10" min="1" max="500"></div> <div class="ds-f"><label class="ds-fl">Месеци</label> <select name="months" class="ds-fi"> <option value="1">1 месец</option> <option value="3" selected>3 месеца</option> <option value="6">6 месеца</option> <option value="12">12 месеца</option> </select> </div> <div class="ds-f"><label class="ds-fl">Цена (€)</label><input type="number" name="price" class="ds-fi" value="0" min="0" step="0.01"></div> </div> <button type="button" id="btn-gen-cards" class="ds-btn-pri" style="width:100%"><i class="fa-solid fa-check"></i> Генерирай карти</button> </form> <div id="cards-result" style="margin-top:12px"></div> </div> <!-- RIGHT: Batch list --> <div class="ds-card" style="padding:0;overflow:hidden"> <div style="padding:12px 16px;font-size:13px;font-weight:700;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center"> <span>📦 Партиди</span> <span style="font-size:11px;font-weight:500;color:var(--ink4)"><?= count($batches) ?> партиди</span> </div> <?php if ($batches): ?> <div style="max-height:420px;overflow-y:auto"> <?php foreach (array_reverse($batches) as $b): ?> <div style="padding:11px 16px;border-bottom:1px solid var(--line-l)"> <div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px"> <code style="font-size:12px;font-weight:700;color:var(--blue)"><?= $e($b['batch']) ?></code> <div style="display:flex;gap:4px"> <span class="ds-pill ds-pill-green" style="font-size:10px;padding:2px 7px"><?= $b['unused'] ?> налични</span> <?php if ($b['used'] > 0): ?> <span class="ds-pill ds-pill-gray" style="font-size:10px;padding:2px 7px"><?= $b['used'] ?> използвани</span> <?php endif; ?> </div> </div> <?php if ($b['note']): ?> <div style="font-size:12px;color:var(--ink3);margin-bottom:3px">📍 <?= $e($b['note']) ?></div> <?php endif; ?> <div style="font-size:11px;color:var(--ink4);margin-bottom:7px"> <?= $b['months'] ?> мес. · <?= $b['price'] > 0 ? number_format($b['price'],2).' €' : 'безплатна' ?> · <?= date('d.m.Y', strtotime($b['date'])) ?> </div> <div style="display:flex;gap:6px"> <button class="ds-btn-sm ds-btn-blue" data-batch="<?= $e($b['batch']) ?>" onclick="cardFilterByBatch(this.dataset.batch)">🔍 Филтрирай</button> <?php if ($b['used'] == 0): ?> <button class="ds-btn-sm ds-btn-danger" data-batch="<?= $e($b['batch']) ?>" data-count="<?= $b['total'] ?>" onclick="cardDeleteBatch(this.dataset.batch, this.dataset.count, this)">🗑 Изтрий партидата</button> <?php endif; ?> </div> </div> <?php endforeach; ?> </div> <?php else: ?> <div style="padding:40px;text-align:center;color:var(--ink4)"> <div style="font-size:32px;margin-bottom:8px">🎟</div> <div style="font-size:13px">Няма генерирани карти</div> </div> <?php endif; ?> </div> </div> <!-- Cards table --> <div class="ds-card" style="padding:0;overflow:hidden"> <div style="padding:10px 14px;border-bottom:1px solid var(--line);display:flex;gap:6px;flex-wrap:wrap;align-items:center"> <button class="ds-btn-sm ds-btn-blue" onclick="cardFilter('all')">Всички</button> <button class="ds-btn-sm ds-btn-green" onclick="cardFilter('unused')">Налични</button> <button class="ds-btn-sm" style="background:var(--line-l);color:var(--ink4)" onclick="cardFilter('used')">Използвани</button> <div style="flex:1"></div> <div id="card-batch-badge" style="display:none;background:var(--blue-ll);color:var(--blue);font-size:11px;font-weight:600;padding:4px 10px;border-radius:8px;align-items:center;gap:6px"> <span id="card-batch-label"></span> <button onclick="cardClearFilter()" style="background:none;border:none;cursor:pointer;color:var(--blue);font-size:14px;padding:0;line-height:1;margin-left:4px">✕</button> </div> <input type="text" id="card-search-inp" class="ds-fi" placeholder="🔍 Търси код..." style="width:170px" oninput="cardSearch(this.value)"> </div> <div style="overflow-x:auto"> <table class="adm-table" id="cards-table"> <thead> <tr> <th>Код</th> <th>Партида</th> <th>Описание</th> <th>Мес.</th> <th>Цена</th> <th>Статус</th> <th>Използван от</th> <th>Дата</th> <th></th> </tr> </thead> <tbody id="cards-tbody"> <?php foreach ($cards as $c): ?> <tr class="card-row card-<?= $e($c->status) ?>" data-batch="<?= $e($c->batch ?? '') ?>" data-code="<?= strtolower($e($c->code)) ?>" data-status="<?= $e($c->status) ?>"> <td><code class="adm-code"><?= $e($c->code) ?></code></td> <td><code style="color:var(--blue);font-size:11px;font-weight:700"><?= $e($c->batch ?? '—') ?></code></td> <td style="font-size:12px;color:var(--ink3)"><?= $e($c->note ?? '—') ?></td> <td><?= $e($c->months) ?></td> <td><?= $c->price > 0 ? number_format($c->price,2).' €' : '—' ?></td> <td> <span class="ds-pill <?= $c->status==='unused'?'ds-pill-green':($c->status==='used'?'ds-pill-gray':'ds-pill-red') ?>"> <?= $c->status==='unused'?'<i class="fa-solid fa-check"></i> Наличен':($c->status==='used'?'Използван':'Деактивиран') ?> </span> </td> <td style="font-size:12px"><?= $c->used_by ? $e($c->used_name ?? '#'.$c->used_by) : '—' ?></td> <td style="font-size:11px;color:var(--ink4)"><?= date('d.m.Y', strtotime($c->created_at)) ?></td> <td> <?php if ($c->status === 'unused'): ?> <button class="ds-btn-sm ds-btn-danger" data-id="<?= $c->id ?>" data-code="<?= $e($c->code) ?>" onclick="cardDeleteOne(this.dataset.id, this.dataset.code, this)">🗑</button> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> </div> <!-- ── АБОНАМЕНТИ ── --> <div class="ds-panel" id="panel-adm-subs"> <div class="adm-subs-overview"> <div class="adm-kpi-chip"><strong><?= $subscription_breakdown['active'] ?></strong><span>активни абонаменти</span></div> <div class="adm-kpi-chip"><strong><?= $subscription_breakdown['expired'] ?></strong><span>изтекли абонаменти</span></div> <div class="adm-kpi-chip"><strong><?= number_format($subscription_breakdown['mrr'],0,',',' ') ?> €</strong><span>активна стойност</span></div> <div class="adm-kpi-chip"><strong><?= $stats['revenue'] > 0 ? number_format($stats['revenue'],0,',',' ') : 0 ?> €</strong><span>общ приход</span></div> </div> <div class="ds-ph"><h2 class="ds-ptitle">Абонаменти</h2></div> <div class="ds-card" style="padding:0;overflow:hidden"> <div style="overflow-x:auto"> <table class="adm-table"> <thead><tr><th>#</th><th>Потребител</th><th>Тип</th><th>Месеци</th><th>Сума</th><th>От</th><th>До</th><th>Статус</th></tr></thead> <tbody> <?php foreach ($subscriptions as $s): ?> <tr> <td style="font-size:12px;color:var(--ink4)"><?= $s->id ?></td> <td><div style="font-weight:600;font-size:13px"><?= $e($s->user_name) ?></div><div style="font-size:11px;color:var(--ink4)"><?= $e($s->user_email) ?></div></td> <td><span class="ds-pill <?= $s->type==='stripe'?'ds-pill-blue':($s->type==='card'?'ds-pill-teal':'ds-pill-gray') ?>"><?= $e($s->type) ?></span></td> <td><?= $s->months ?></td> <td style="font-size:12px;font-weight:600"><?= $s->amount > 0 ? number_format($s->amount,2).' €' : '—' ?></td> <td style="font-size:11px"><?= date('d.m.Y',strtotime($s->starts_at)) ?></td> <td style="font-size:11px;<?= strtotime($s->ends_at)<time()?'color:var(--red)':'' ?>"><?= date('d.m.Y',strtotime($s->ends_at)) ?></td> <td><span class="ds-pill <?= $s->status==='active'?'ds-pill-green':($s->status==='expired'?'ds-pill-gray':'ds-pill-red') ?>"><?= $e($s->status) ?></span></td> </tr> <?php endforeach; ?> <?php if (!$subscriptions): ?> <tr><td colspan="8" style="text-align:center;padding:28px;color:var(--ink4)">Няма абонаменти.</td></tr> <?php endif; ?> </tbody> </table> </div></div> </div> </div> <!-- ═══ MODAL: Управлявай потребител ═══ --> <div class="modal-ov" id="modal-edit-user"> <div class="modal-box" style="max-width:460px"> <div class="modal-head"><h3>👤 <span id="eu-name-title"></span></h3><button class="modal-x" onclick="closeModal('modal-edit-user')">✕</button></div> <div class="modal-body"> <div class="ds-card" style="margin-bottom:10px"> <div class="ds-card-title">Роля и клиника</div> <form id="form-set-role"> <?= Csrf::field() ?> <input type="hidden" name="user_id" id="eu-uid"> <div class="ds-grid2" style="margin-bottom:10px"> <div class="ds-f"><label class="ds-fl">Роля</label> <select name="role" id="eu-role" class="ds-fi"> <?php foreach (['owner'=>'Собственик','vet'=>'Ветеринар','clinic_admin'=>'Клиника Админ','super_admin'=>'Super Admin'] as $r=>$l): ?> <option value="<?= $r ?>"><?= $l ?></option> <?php endforeach; ?> </select> </div> <div class="ds-f"><label class="ds-fl">Клиника</label> <select name="clinic_id" id="eu-clinic" class="ds-fi"> <option value="">— без клиника —</option> <?php foreach ($clinics as $c): ?> <option value="<?= $c->id ?>"><?= $e($c->name) ?></option> <?php endforeach; ?> </select> </div> </div> <button type="submit" class="ds-btn-pri" style="width:100%">Запази роля</button> </form> </div> <div class="ds-card" style="margin-bottom:10px"> <div class="ds-card-title"><i class="fa-solid fa-star"></i> Premium достъп</div> <form id="form-set-premium"> <?= Csrf::field() ?> <input type="hidden" name="user_id" id="eu-uid2"> <div class="ds-f" style="margin-bottom:10px"><label class="ds-fl">Активен до</label><input type="date" name="premium_until" id="eu-pdate" class="ds-fi"></div> <div style="display:flex;gap:5px;flex-wrap:wrap;margin-bottom:10px"> <?php foreach([1,3,6,12] as $m): ?> <button type="button" class="ds-btn-sm ds-btn-blue btn-add-months" data-months="<?= $m ?>">+<?= $m ?> мес.</button> <?php endforeach; ?> </div> <div style="display:flex;gap:8px"> <button type="submit" class="ds-btn-pri" style="flex:1">Запази</button> <button type="button" class="ds-btn-sm ds-btn-danger" id="btn-stop-premium" style="padding:9px 14px;font-size:12px"><i class="fa-solid fa-ban"></i> Спри</button> </div> </form> </div> <button class="ds-btn-ghost btn-do-ban" id="eu-ban-btn" style="width:100%;font-size:12px"></button> </div> </div> </div> <!-- ═══ MODAL: Edit clinic ═══ --> <div class="modal-ov" id="modal-edit-clinic"> <div class="modal-box modal-lg"> <div class="modal-head"><h3><i class="fa-solid fa-hospital"></i> Редактирай клиника</h3><button class="modal-x" onclick="closeModal('modal-edit-clinic')">✕</button></div> <div class="modal-body"> <form id="form-edit-clinic" enctype="multipart/form-data"> <?= Csrf::field() ?> <input type="hidden" name="clinic_id" id="ec-id"> <div class="ds-tabs" style="margin-bottom:14px"> <button type="button" class="ds-tab active" data-panel="ec-basic">Основни</button> <button type="button" class="ds-tab" data-panel="ec-details">Услуги & Часове</button> <button type="button" class="ds-tab" data-panel="ec-gallery">Галерия</button> </div> <div class="ds-panel active" id="panel-ec-basic"> <div class="ds-grid2" style="margin-bottom:10px"> <div class="ds-f"><label class="ds-fl">Наименование *</label><input type="text" name="name" id="ec-name" class="ds-fi" required></div> <div class="ds-f"><label class="ds-fl">Град</label><input type="text" name="city" id="ec-city" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Адрес</label><input type="text" name="address" id="ec-address" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Телефон</label><input type="text" name="phone" id="ec-phone" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Email</label><input type="email" name="email" id="ec-email" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Уебсайт</label><input type="text" name="website" id="ec-website" class="ds-fi"></div> <div class="ds-f"><label class="ds-fl">Статус</label> <select name="status" id="ec-status" class="ds-fi"> <option value="active"><i class="fa-solid fa-check"></i> Активна</option> <option value="inactive">⏸ Неактивна</option> </select> </div> </div> <div class="ds-f"><label class="ds-fl">Описание</label><textarea name="description" id="ec-desc" class="ds-fi ds-ta"></textarea></div> </div> <div class="ds-panel" id="panel-ec-details"> <div class="ds-f" style="margin-bottom:11px"><label class="ds-fl">Услуги (по един ред)</label><textarea name="services" id="ec-services" class="ds-fi ds-ta" style="min-height:85px"></textarea></div> <div class="ds-f"><label class="ds-fl">Работно време</label><textarea name="working_hours" id="ec-hours" class="ds-fi ds-ta" style="min-height:85px" placeholder="Пон-Пет: 09:00-18:00"></textarea></div> </div> <div class="ds-panel" id="panel-ec-gallery"> <div class="ds-f" style="margin-bottom:11px"><label class="ds-fl">Корица</label><input type="file" name="cover" accept="image/*" class="ds-fi"></div> <div id="ec-gallery-list" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:7px;margin-bottom:11px"></div> <div class="ds-f"><label class="ds-fl">Добави снимки</label><input type="file" name="gallery[]" accept="image/*" multiple class="ds-fi"></div> </div> <div class="ds-fa" style="margin-top:16px"> <button type="submit" class="ds-btn-pri">Запази промените</button> <button type="button" class="ds-btn-ghost" onclick="closeModal('modal-edit-clinic')">Откажи</button> </div> </form> </div> </div> </div> <!-- ═══ MODAL: Manage vets ═══ --> <div class="modal-ov" id="modal-vets"> <div class="modal-box" style="max-width:560px"> <div class="modal-head"><h3><i class="fa-solid fa-users"></i> Екип — <span id="vets-clinic-name"></span></h3><button class="modal-x" onclick="closeModal('modal-vets')">✕</button></div> <div class="modal-body"> <div class="ds-card" style="margin-bottom:14px"> <div class="ds-card-title">Добави потребител</div> <form id="form-add-to-clinic"> <?= Csrf::field() ?> <input type="hidden" name="clinic_id" id="vc-cid"> <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end"> <div class="ds-f" style="flex:2;min-width:140px"> <label class="ds-fl">Потребител</label> <select name="user_id" id="vc-user" class="ds-fi"> <option value="">— избери —</option> <?php foreach ($users as $u): if ($u->role !== 'super_admin'): ?> <option value="<?= $u->id ?>"><?= $e($u->name) ?> (<?= $e($u->email) ?>)</option> <?php endif; endforeach; ?> </select> </div> <div class="ds-f" style="flex:1;min-width:120px"> <label class="ds-fl">Роля</label> <select name="role" id="vc-role" class="ds-fi"> <option value="vet">Ветеринар</option> <option value="clinic_admin">Клиника Админ</option> </select> </div> <button type="submit" class="ds-btn-pri" style="align-self:flex-end">Добави</button> </div> </form> </div> <div class="ds-card-title" style="margin-bottom:8px">Текущ екип</div> <div id="vets-list"><div class="ds-loading">Зарежда...</div></div> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script> <script> // ── GLOBAL FUNCTIONS (accessible from HTML onclick) ────────── let cardActiveBatch=null; let cardStatusFilter='all'; let cardSearchTerm=''; function makeChart(id,data,color){ const canvas=document.getElementById(id);if(!canvas)return; const ctx=canvas.getContext('2d'),grad=ctx.createLinearGradient(0,0,0,175); grad.addColorStop(0,color+'28');grad.addColorStop(1,color+'00'); new Chart(ctx,{type:'line',data:{labels:chartLabels,datasets:[{data,borderColor:color,borderWidth:2,backgroundColor:grad,fill:true,tension:0.4,pointRadius:3,pointBackgroundColor:color,pointBorderColor:'#fff',pointBorderWidth:2}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}}, scales:{x:{grid:{display:false},ticks:{font:{family:'Montserrat',size:11},color:'#9CA3AF'}}, y:{grid:{color:'#F3F4F6'},ticks:{font:{family:'Montserrat',size:11},color:'#9CA3AF'},stepSize:1}}}}); } function applyCardFilters(){ document.querySelectorAll('.card-row').forEach(r=>{ const statusOk=cardStatusFilter==='all'||r.dataset.status===cardStatusFilter; const batchOk=!cardActiveBatch||r.dataset.batch===cardActiveBatch; const searchOk=!cardSearchTerm||r.dataset.code.includes(cardSearchTerm); r.style.display=(statusOk&&batchOk&&searchOk)?'':'none'; }); } function cardFilter(status){ cardStatusFilter=status; applyCardFilters(); } function cardFilterByBatch(batch){ cardActiveBatch=batch; const badge=document.getElementById('card-batch-badge'); badge.style.display='flex'; document.getElementById('card-batch-label').textContent=batch; applyCardFilters(); } function cardClearFilter(){ cardActiveBatch=null; document.getElementById('card-batch-badge').style.display='none'; applyCardFilters(); } function cardSearch(q){ cardSearchTerm=(q||'').toLowerCase(); applyCardFilters(); } async function cardDeleteOne(id,code,btn){ if(!confirm('Изтрий карта '+code+'?'))return; const fd=new FormData();fd.append('card_id',id);fd.append('csrf',csrf()); try{ const d=await(await fetch('/admin/cards/delete',{method:'POST',body:fd})).json(); if(d.ok){btn.closest('tr').remove();toast('Картата е изтрита.','success');} else toast(d.msg||'Грешка.','error'); }catch(err){toast('Грешка: '+err.message,'error');} } async function cardDeleteBatch(batch,count,btn){ if(!confirm('Изтрий партида "'+batch+'" ('+count+' карти)?'))return; const fd=new FormData();fd.append('batch',batch);fd.append('csrf',csrf()); try{ const d=await(await fetch('/admin/cards/delete-batch',{method:'POST',body:fd})).json(); if(d.ok){toast('Изтрити '+d.deleted+' карти.','success');setTimeout(()=>location.reload(),700);} else toast(d.msg||'Грешка.','error'); }catch(err){toast('Грешка: '+err.message,'error');} } async function delGallery(id,btn){ const fd=new FormData();fd.append('photo_id',id);fd.append('csrf',csrf()); const d=await(await fetch('/admin/clinic/gallery/delete',{method:'POST',body:fd})).json(); if(d.ok)btn.closest('div').remove(); } async function loadVets(cid){ document.getElementById('vets-list').innerHTML='<div class="ds-loading">Зарежда...</div>'; const d=await(await fetch('/admin/clinic/vets?clinic_id='+cid)).json(); if(!d.vets||!d.vets.length){document.getElementById('vets-list').innerHTML='<div class="ds-empty"><h3>Няма членове</h3></div>';return;} document.getElementById('vets-list').innerHTML=d.vets.map(v=>` <div style="display:flex;align-items:center;gap:10px;padding:9px 12px;border:1px solid var(--line);border-radius:9px;margin-bottom:6px"> <div class="adm-user-av">${v.name.charAt(0).toUpperCase()}</div> <div style="flex:1"><div style="font-weight:600;font-size:13px">${v.name}</div><div style="font-size:11px;color:var(--ink4)">${v.email}</div></div> <button class="ds-btn-sm ds-btn-danger" onclick="removeVet(${v.id},${cid},this)">Премахни</button> </div>`).join(''); } async function removeVet(uid,cid,btn){ if(!confirm('Премахни?'))return; const fd=new FormData();fd.append('user_id',uid);fd.append('clinic_id',cid);fd.append('csrf',csrf()); const d=await(await fetch('/admin/clinic/remove-vet',{method:'POST',body:fd})).json(); if(d.ok){btn.closest('div').remove();toast('Премахнат!','success');} } // ── INIT (runs after DOM + app.js ready) ───────────────────── document.addEventListener("DOMContentLoaded", function() { // ── CHARTS ────────────────────────────────────────────────── const chartLabels=<?= json_encode($chart_labels) ?>,chartUsers=<?= json_encode($chart_users) ?>,chartPremium=<?= json_encode($chart_premium) ?>; window.addEventListener('load',function(){ makeChart('chart-users',chartUsers,'#116AD2'); makeChart('chart-premium',chartPremium,'#1EADBE'); }); // ── USER SEARCH ────────────────────────────────────────────── function applyUserFilters(){ const q=(document.getElementById('user-search')?.value||'').toLowerCase(); const role=document.getElementById('user-role-filter')?.value||''; const status=document.getElementById('user-status-filter')?.value||''; let visible=0; document.querySelectorAll('#users-table tbody tr').forEach(r=>{ const textOk=!q||r.dataset.name.includes(q)||r.dataset.email.includes(q); const roleOk=!role||r.dataset.role===role; const statusOk=!status || (status==='banned' && r.dataset.banned==='1') || (status==='premium' && r.dataset.premium==='1') || (status==='active' && r.dataset.banned!=='1'); const show=textOk&&roleOk&&statusOk; r.style.display=show?'':'none'; if(show) visible++; }); const note=document.getElementById('user-filter-note'); if(note) note.textContent=`Показани ${visible} от <?= count($users) ?> потребители.`; } document.getElementById('user-search')?.addEventListener('input',applyUserFilters); document.getElementById('user-role-filter')?.addEventListener('change',applyUserFilters); document.getElementById('user-status-filter')?.addEventListener('change',applyUserFilters); function applyClinicFilters(){ const q=(document.getElementById('clinic-search')?.value||'').toLowerCase(); const status=document.getElementById('clinic-status-filter')?.value||''; let visible=0; document.querySelectorAll('#clinics-grid .adm-clinic-card-v2').forEach(card=>{ const textOk=!q||card.dataset.name.includes(q)||card.dataset.city.includes(q)||card.dataset.email.includes(q); const statusOk=!status||card.dataset.status===status; const show=textOk&&statusOk; card.classList.toggle('hidden',!show); if(show) visible++; }); const note=document.getElementById('clinic-filter-note'); if(note) note.textContent=`Показани ${visible} от <?= count($clinics) ?> клиники.`; } document.getElementById('clinic-search')?.addEventListener('input',applyClinicFilters); document.getElementById('clinic-status-filter')?.addEventListener('change',applyClinicFilters); // ── CARDS ──────────────────────────────────────────────────── document.getElementById('loc-input')?.addEventListener('input',function(){ this.value=this.value.toUpperCase().replace(/[^A-Z0-9]/g,''); document.getElementById('prefix-preview').textContent='VT'+(this.value||''); }); document.getElementById('btn-gen-cards')?.addEventListener('click',async function(){ const fd=new FormData(document.getElementById('form-gen-cards')); this.textContent='Генерира...';this.disabled=true; try{ const res=await fetch('/admin/cards/gen',{method:'POST',body:fd}); const d=await res.json(); if(d.ok){ document.getElementById('cards-result').innerHTML=` <div style="background:#F0FDF4;border:1px solid #BBF7D0;border-radius:10px;padding:14px;margin-top:4px"> <div style="font-size:13px;font-weight:700;color:var(--green);margin-bottom:6px"> <i class="fa-solid fa-check"></i> ${d.codes.length} карти — <code>${d.batch}</code> ${d.note?` · <span style="color:var(--ink3)">${d.note}</span>`:''} </div> <textarea style="width:100%;height:90px;font-family:monospace;font-size:11px;border:1px solid var(--line);border-radius:6px;padding:8px;resize:none" readonly>${d.codes.join('\n')}</textarea> <button class="ds-btn-sm ds-btn-blue" style="margin-top:8px" onclick="navigator.clipboard.writeText(this.previousElementSibling.value);toast('Копирано!','success')"> <i class="fa-solid fa-list"></i> Копирай всички </button> </div>`; toast(d.codes.length+' карти генерирани!','success'); }else{ toast(d.msg||'Грешка.','error'); } }catch(err){toast('Грешка: '+err.message,'error');} this.textContent='<i class="fa-solid fa-check"></i> Генерирай карти';this.disabled=false; }); // ── USER MODAL ─────────────────────────────────────────────── document.addEventListener('click',e=>{ const btn=e.target.closest('.btn-edit-user');if(!btn)return; const d=btn.dataset; document.getElementById('eu-name-title').textContent=d.name; document.getElementById('eu-uid').value=d.uid; document.getElementById('eu-uid2').value=d.uid; document.getElementById('eu-role').value=d.role||'owner'; document.getElementById('eu-clinic').value=d.clinic||''; document.getElementById('eu-pdate').value=d.until||''; const banned=d.banned==='1'; const banBtn=document.getElementById('eu-ban-btn'); banBtn.innerHTML=banned?'<i class="fa-solid fa-check"></i> Деблокирай потребителя':'<i class="fa-solid fa-ban"></i> Блокирай потребителя'; banBtn.style.cssText=banned?'width:100%;font-size:12px;border-color:var(--green);color:var(--green)':'width:100%;font-size:12px;border-color:var(--red);color:var(--red)'; banBtn.dataset.uid=d.uid;banBtn.dataset.ban=banned?'0':'1'; openModal('modal-edit-user'); }); document.getElementById('form-set-role')?.addEventListener('submit',async e=>{ e.preventDefault(); const d=await(await fetch('/admin/user/role',{method:'POST',body:new FormData(e.target)})).json(); toast(d.ok?'Ролята е обновена!':(d.msg||'Грешка.'),d.ok?'success':'error'); if(d.ok)setTimeout(()=>location.reload(),700); }); document.querySelectorAll('.btn-add-months').forEach(b=>b.addEventListener('click',()=>{ const inp=document.getElementById('eu-pdate'),base=inp.value?new Date(inp.value):new Date(); base.setMonth(base.getMonth()+parseInt(b.dataset.months));inp.value=base.toISOString().split('T')[0]; })); document.getElementById('btn-stop-premium')?.addEventListener('click',async()=>{ if(!confirm('Спри Premium?'))return; const fd=new FormData();fd.append('user_id',document.getElementById('eu-uid2').value);fd.append('premium_until','');fd.append('csrf',csrf()); const d=await(await fetch('/admin/user/premium',{method:'POST',body:fd})).json(); if(d.ok){closeModal('modal-edit-user');toast('Premium спрян.','success');setTimeout(()=>location.reload(),600);} else toast(d.msg||'Грешка.','error'); }); document.getElementById('form-set-premium')?.addEventListener('submit',async e=>{ e.preventDefault(); const d=await(await fetch('/admin/user/premium',{method:'POST',body:new FormData(e.target)})).json(); if(d.ok){closeModal('modal-edit-user');toast('Premium обновен!','success');setTimeout(()=>location.reload(),600);} else toast(d.msg||'Грешка.','error'); }); document.addEventListener('click',async e=>{ const btn=e.target.closest('.btn-do-ban');if(!btn)return; if(!confirm(btn.dataset.ban==='1'?'Блокирай?':'Деблокирай?'))return; const fd=new FormData();fd.append('user_id',btn.dataset.uid);fd.append('ban',btn.dataset.ban);fd.append('csrf',csrf()); const d=await(await fetch('/admin/user/ban',{method:'POST',body:fd})).json(); toast(d.ok?d.msg:(d.msg||'Грешка.'),d.ok?'success':'error'); if(d.ok)setTimeout(()=>location.reload(),600); }); // ── CLINICS ────────────────────────────────────────────────── document.getElementById('btn-new-clinic')?.addEventListener('click',()=>{ const f=document.getElementById('new-clinic-form');f.style.display=f.style.display==='none'?'block':'none'; }); document.getElementById('form-add-clinic')?.addEventListener('submit',async e=>{ e.preventDefault(); const d=await(await fetch('/admin/clinic/add',{method:'POST',body:new FormData(e.target)})).json(); toast(d.ok?'Клиниката е добавена!':(d.msg||'Грешка.'),d.ok?'success':'error'); if(d.ok)setTimeout(()=>location.reload(),700); }); document.addEventListener('click',e=>{ const btn=e.target.closest('.btn-edit-clinic');if(!btn)return; const d=btn.dataset; ['id','name','city','phone','email','website','status','desc','services','hours'].forEach(k=>{const el=document.getElementById('ec-'+k);if(el)el.value=d[k]||'';}); fetch('/admin/clinic/gallery?id='+d.id).then(r=>r.json()).then(data=>{ document.getElementById('ec-gallery-list').innerHTML=(data.photos||[]).map(p=> `<div style="position:relative"><img src="${p.url}" style="width:100%;height:70px;object-fit:cover;border-radius:7px"><button onclick="delGallery(${p.id},this)" style="position:absolute;top:3px;right:3px;width:17px;height:17px;border-radius:50%;background:var(--red);color:#fff;border:none;cursor:pointer;font-size:9px">✕</button></div>` ).join('')||'<p style="font-size:12px;color:var(--ink4)">Няма снимки.</p>'; }); openModal('modal-edit-clinic'); }); document.getElementById('form-edit-clinic')?.addEventListener('submit',async e=>{ e.preventDefault(); const d=await(await fetch('/admin/clinic/edit',{method:'POST',body:new FormData(e.target)})).json(); if(d.ok){closeModal('modal-edit-clinic');toast('Обновено!','success');setTimeout(()=>location.reload(),700);} else toast(d.msg||'Грешка.','error'); }); document.addEventListener('click',async e=>{ const btn=e.target.closest('.btn-toggle-clinic');if(!btn)return; const fd=new FormData();fd.append('clinic_id',btn.dataset.id);fd.append('status',btn.dataset.status==='active'?'inactive':'active');fd.append('csrf',csrf()); const d=await(await fetch('/admin/clinic/status',{method:'POST',body:fd})).json(); if(d.ok){toast('Обновено!','success');setTimeout(()=>location.reload(),600);} }); document.addEventListener('click',async e=>{ const btn=e.target.closest('.btn-del-clinic');if(!btn)return; if(!confirm('Изтрий "'+btn.dataset.name+'"?'))return; const fd=new FormData();fd.append('clinic_id',btn.dataset.id);fd.append('csrf',csrf()); const d=await(await fetch('/admin/clinic/delete',{method:'POST',body:fd})).json(); if(d.ok){toast('Изтрита.','success');document.getElementById('ccard-'+btn.dataset.id)?.remove();} else toast(d.msg||'Грешка.','error'); }); // ── VETS MODAL ─────────────────────────────────────────────── document.addEventListener('click',e=>{ const btn=e.target.closest('.btn-manage-vets');if(!btn)return; document.getElementById('vets-clinic-name').textContent=btn.dataset.name; document.getElementById('vc-cid').value=btn.dataset.id; loadVets(btn.dataset.id); openModal('modal-vets'); }); document.getElementById('form-add-to-clinic')?.addEventListener('submit',async e=>{ e.preventDefault(); const d=await(await fetch('/admin/user/role',{method:'POST',body:new FormData(e.target)})).json(); if(d.ok){toast('Добавен!','success');loadVets(document.getElementById('vc-cid').value);e.target.reset();} else toast(d.msg||'Грешка.','error'); }); // ── EDIT CLINIC MODAL TABS ─────────────────────────────────── document.getElementById('modal-edit-clinic')?.addEventListener('click',e=>{ const t=e.target.closest('.ds-tab[data-panel]');if(!t)return; const box=t.closest('.modal-body'); box.querySelectorAll('.ds-tab[data-panel]').forEach(x=>x.classList.toggle('active',x===t)); box.querySelectorAll('.ds-panel').forEach(p=>p.classList.toggle('active',p.id==='panel-'+t.dataset.panel)); }); applyUserFilters(); applyClinicFilters(); applyCardFilters(); }); </script> <?php require __DIR__.'/../shared/footer.php'; ?>
| ver. 1.4 |
Github
|
.
| PHP 8.2.31 | Generation time: 0 |
proxy
|
phpinfo
|
Settings