<?php
declare(strict_types=1);
/*
|--------------------------------------------------------------------------
| AZKITOOLS
| Dashboard
| Code ID: AZK-DASH-PHASE1-001
|--------------------------------------------------------------------------
*/
require_once __DIR__ . '/config.php';
require_login();
$user = current_user();
if (!$user) {
redirect('login.php');
}
/*
|--------------------------------------------------------------------------
| دریافت اطلاعات کامل کاربر
|--------------------------------------------------------------------------
*/
$currentUser = null;
try {
$stmt = db()->prepare("
SELECT
id,
username,
first_name,
last_name,
mobile,
agency_name,
full_name,
role,
is_active,
created_at
FROM users
WHERE id = ?
LIMIT 1
");
$stmt->execute([
(int)($user['id'] ?? 0)
]);
$currentUser = $stmt->fetch();
if (!$currentUser) {
redirect('logout.php');
}
} catch (Throwable $e) {
http_response_code(500);
exit('خطا در دریافت اطلاعات کاربر.');
}
/*
|--------------------------------------------------------------------------
| ROLE
|--------------------------------------------------------------------------
*/
$role = (string)($currentUser['role'] ?? 'member');
$isSuperAdmin = ($role === 'super_admin');
/* AZK-DASH-009: ذخیره ترتیب کارتها برای هر حساب کاربری */
if (session_status() !== PHP_SESSION_ACTIVE) {
@session_start();
}
if (empty($_SESSION['azk_dashboard_order_csrf'])) {
$_SESSION['azk_dashboard_order_csrf'] = bin2hex(random_bytes(32));
}
$dashboardOrderCsrf = (string)$_SESSION['azk_dashboard_order_csrf'];
$dashboardCardOrder = [];
$dashboardOrderTableReady = false;
try {
db()->exec("CREATE TABLE IF NOT EXISTS user_dashboard_card_order (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
card_key VARCHAR(80) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_user_dashboard_card (user_id, card_key),
INDEX idx_user_dashboard_order (user_id, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
$dashboardOrderTableReady = true;
} catch (Throwable $e) {
// Dashboard must remain usable even if this optional preference table cannot be created.
}
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'
&& (string)($_POST['dashboard_action'] ?? '') === 'save_card_order') {
header('Content-Type: application/json; charset=utf-8');
$postedCsrf = (string)($_POST['csrf'] ?? '');
if (!hash_equals($dashboardOrderCsrf, $postedCsrf)) {
http_response_code(403);
echo json_encode(['ok' => false, 'message' => 'اعتبارسنجی امنیتی ناموفق بود.']);
exit;
}
if (!$dashboardOrderTableReady) {
http_response_code(500);
echo json_encode(['ok' => false, 'message' => 'ذخیره ترتیب کارتها در دیتابیس فعال نیست.']);
exit;
}
$decodedOrder = json_decode((string)($_POST['order'] ?? '[]'), true);
if (!is_array($decodedOrder)) {
http_response_code(400);
echo json_encode(['ok' => false, 'message' => 'ترتیب کارتها معتبر نیست.']);
exit;
}
$cleanOrder = [];
foreach ($decodedOrder as $cardKey) {
if (!is_string($cardKey) || !preg_match('/^(menu-[0-9]+|visa-tracking|bank-slips|wallet)$/', $cardKey)) {
continue;
}
if (!in_array($cardKey, $cleanOrder, true)) {
$cleanOrder[] = $cardKey;
}
}
try {
$pdoOrder = db();
$pdoOrder->beginTransaction();
$deleteOrder = $pdoOrder->prepare('DELETE FROM user_dashboard_card_order WHERE user_id = ?');
$deleteOrder->execute([(int)($currentUser['id'] ?? 0)]);
$insertOrder = $pdoOrder->prepare('INSERT INTO user_dashboard_card_order (user_id, card_key, sort_order) VALUES (?, ?, ?)');
foreach ($cleanOrder as $position => $cardKey) {
$insertOrder->execute([(int)($currentUser['id'] ?? 0), $cardKey, $position + 1]);
}
$pdoOrder->commit();
echo json_encode(['ok' => true, 'message' => 'ترتیب کارتها ذخیره شد.']);
} catch (Throwable $e) {
if (isset($pdoOrder) && $pdoOrder->inTransaction()) {
$pdoOrder->rollBack();
}
http_response_code(500);
echo json_encode(['ok' => false, 'message' => 'ذخیره ترتیب کارتها انجام نشد.']);
}
exit;
}
if ($dashboardOrderTableReady) {
try {
$orderStmt = db()->prepare('SELECT card_key FROM user_dashboard_card_order WHERE user_id = ? ORDER BY sort_order ASC, id ASC');
$orderStmt->execute([(int)($currentUser['id'] ?? 0)]);
$dashboardCardOrder = array_map('strval', $orderStmt->fetchAll(PDO::FETCH_COLUMN));
} catch (Throwable $e) {
$dashboardCardOrder = [];
}
}
/* AZK-VISA-001: دسترسی و تفکیک نقشهای مدیریتی و طرف تجاری */
$visaAdminRoles = ['super_admin', 'admin', 'administrator', 'visa_admin', 'admin_supervisor'];
$isVisaAdmin = in_array($role, $visaAdminRoles, true);
$canAccessVisa = $isVisaAdmin;
if (!$canAccessVisa) {
try {
$visaPermissionStmt = db()->prepare("SELECT is_enabled FROM user_module_permissions WHERE user_id = ? AND module_key = 'visa' LIMIT 1");
$visaPermissionStmt->execute([(int)($currentUser['id'] ?? 0)]);
$canAccessVisa = ((int)$visaPermissionStmt->fetchColumn() === 1);
} catch (Throwable $e) {
$canAccessVisa = false;
}
}
/* AZK-VISA-LIST-007: شمارش اعلانها با رعایت مالکیت پروندهها */
$visaNewTotal = 0;
$visaNewCounts = ['russia' => 0, 'thailand' => 0, 'iran' => 0, 'azerbaijan' => 0, 'health' => 0];
if ($canAccessVisa) {
$visaDb = db();
if ($isVisaAdmin) {
// مدیر کل و ادمینها: آمار سراسری درخواستهای جدید.
try {
$visaNewTotal = (int)$visaDb->query("SELECT COUNT(*) FROM visa_applications WHERE status = 'registered'")->fetchColumn();
$visaCountStmt = $visaDb->prepare("SELECT COUNT(*) FROM visa_applications WHERE status = 'registered' AND destination_country = ? AND visa_type <> 'ویزای سلامت'");
$visaCountries = ['russia' => 'روسیه', 'thailand' => 'تایلند', 'iran' => 'ایران', 'azerbaijan' => 'آذربایجان'];
foreach ($visaCountries as $visaKey => $visaCountry) {
$visaCountStmt->execute([$visaCountry]);
$visaNewCounts[$visaKey] = (int)$visaCountStmt->fetchColumn();
}
$visaHealthStmt = $visaDb->prepare("SELECT COUNT(*) FROM visa_applications WHERE status = 'registered' AND visa_type = ?");
$visaHealthStmt->execute(['ویزای سلامت']);
$visaNewCounts['health'] = (int)$visaHealthStmt->fetchColumn();
} catch (Throwable $e) {
$visaNewTotal = 0;
$visaNewCounts = array_fill_keys(array_keys($visaNewCounts), 0);
}
} else {
// طرف تجاری: فقط پروندههایی که خودش ایجاد کرده یا به شناسه او متصلاند.
$visaUid = (int)($currentUser['id'] ?? 0);
try {
$ownCountStmt = $visaDb->prepare("SELECT COUNT(*) FROM russia_visa_cases WHERE status = 'registered' AND archived = 0 AND (created_by = ? OR partner_id = ?)");
$ownCountStmt->execute([$visaUid, (string)$visaUid]);
$visaNewTotal = (int)$ownCountStmt->fetchColumn();
$visaNewCounts['russia'] = $visaNewTotal;
} catch (Throwable $e) {
// سازگاری با پروندههای قدیمی تا قبل از ایجاد جدول جدید.
try {
$ownLegacyStmt = $visaDb->prepare("SELECT COUNT(*) FROM visa_applications WHERE status = 'registered' AND created_by = ?");
$ownLegacyStmt->execute([$visaUid]);
$visaNewTotal = (int)$ownLegacyStmt->fetchColumn();
$visaNewCounts['russia'] = $visaNewTotal;
} catch (Throwable $ignored) {
$visaNewTotal = 0;
$visaNewCounts = array_fill_keys(array_keys($visaNewCounts), 0);
}
}
}
}
$roleTitle = $isSuperAdmin
? 'مدیر کل'
: 'عضو سیستم';
/*
|--------------------------------------------------------------------------
| FULL NAME
|--------------------------------------------------------------------------
*/
$fullName = trim(
(string)($currentUser['full_name'] ?? '')
);
if ($fullName === '') {
$fullName = trim(
(string)($currentUser['first_name'] ?? '') .
' ' .
(string)($currentUser['last_name'] ?? '')
);
}
if ($fullName === '') {
$fullName = 'کاربر';
}
/*
|--------------------------------------------------------------------------
| درخواستهای فیش بانکی
| Code ID: AZK-DASH-005-WDR-COUNT
|--------------------------------------------------------------------------
*/
$pendingSlipRequests = 0;
if ($isSuperAdmin) {
try {
$stmt = db()->query("
SELECT COUNT(*)
FROM wallet_deposit_requests
WHERE status = 'pending'
");
$pendingSlipRequests = (int)$stmt->fetchColumn();
} catch (Throwable $e) {
/*
* در صورت هرگونه مشکل در جدول،
* داشبورد اصلی نباید از کار بیفتد.
*/
$pendingSlipRequests = 0;
}
}
/*
|--------------------------------------------------------------------------
| MENUS
|--------------------------------------------------------------------------
*/
$menus = [];
try {
$stmt = db()->query("
SELECT
id,
title,
icon,
sort_order
FROM menu_items
WHERE is_active = 1
ORDER BY sort_order ASC, id ASC
");
$menus = $stmt->fetchAll();
} catch (Throwable $e) {
$menus = [];
}
/*
|--------------------------------------------------------------------------
| آمار اعضا
|--------------------------------------------------------------------------
*/
$totalMembers = 0;
$activeMembers = 0;
$verifiedMembers = 0;
$todayMembers = 0;
if ($isSuperAdmin) {
try {
$stmt = db()->query("
SELECT
COUNT(*) AS total_members,
COALESCE(
SUM(
CASE
WHEN is_active = 1
THEN 1
ELSE 0
END
),
0
) AS active_members,
COALESCE(
SUM(
CASE
WHEN mobile_verified = 1
THEN 1
ELSE 0
END
),
0
) AS verified_members,
COALESCE(
SUM(
CASE
WHEN DATE(created_at) = CURDATE()
THEN 1
ELSE 0
END
),
0
) AS today_members
FROM users
");
$stats = $stmt->fetch();
if ($stats) {
$totalMembers =
(int)($stats['total_members'] ?? 0);
$activeMembers =
(int)($stats['active_members'] ?? 0);
$verifiedMembers =
(int)($stats['verified_members'] ?? 0);
$todayMembers =
(int)($stats['today_members'] ?? 0);
}
} catch (Throwable $e) {
$totalMembers = 0;
$activeMembers = 0;
$verifiedMembers = 0;
$todayMembers = 0;
}
}
/*
|--------------------------------------------------------------------------
| تابع مسیر منو
|--------------------------------------------------------------------------
*/
function dashboard_menu_url(string $title): string
{
$title = trim($title);
/*
|--------------------------------------------------------------------------
| داشبورد
|--------------------------------------------------------------------------
*/
if ($title === 'داشبورد') {
return 'dashboard.php';
}
/*
|--------------------------------------------------------------------------
| اعضا
|--------------------------------------------------------------------------
*/
if (
$title === 'اعضا' ||
$title === 'مدیریت اعضا'
) {
return 'members.php';
}
/*
|--------------------------------------------------------------------------
| قرارداد تور
|--------------------------------------------------------------------------
*/
if (
$title === 'قرارداد تور' ||
$title === 'قرارداد'
) {
return 'contract.php';
}
/*
|--------------------------------------------------------------------------
| کیف پول
|--------------------------------------------------------------------------
*/
if (
$title === 'کیف پول' ||
$title === 'کیف پول کاربران'
) {
return 'wallet.php';
}
if (in_array($title, ['مدیریت ویزا', 'ویزای الکترونیک', 'درخواست ویزا'], true)) {
return 'visa.php';
}
/*
|--------------------------------------------------------------------------
| سایر منوها
|--------------------------------------------------------------------------
*/
return '#';
}
/*
|--------------------------------------------------------------------------
| تشخیص کیف پول
|--------------------------------------------------------------------------
*/
function dashboard_is_wallet(string $title): bool
{
return in_array(
trim($title),
[
'کیف پول',
'کیف پول کاربران'
],
true
);
}
/*
|--------------------------------------------------------------------------
| تشخیص قرارداد
|--------------------------------------------------------------------------
*/
function dashboard_is_contract(string $title): bool
{
return in_array(
trim($title),
[
'قرارداد تور',
'قرارداد'
],
true
);
}
?>
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<title>
داشبورد | AZKITOOLS
</title>
<link
rel="stylesheet"
href="assets/style.css"
>
<style>
/* AZK-DASH-007: Sidebar scroll + dropdown menu */
.sidebar {
height: 100vh;
height: 100dvh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.main-menu {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
padding-bottom: 10px;
scrollbar-width: thin;
}
.sidebar-bottom {
flex: 0 0 auto;
position: relative;
z-index: 2;
padding-bottom: max(12px, env(safe-area-inset-bottom));
background: inherit;
}
.visa-menu-parent {
width: 100%;
border: 0;
font: inherit;
text-align: right;
cursor: pointer;
background: transparent;
}
.visa-menu-title {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 8px;
}
.visa-menu-actions {
display: inline-flex;
align-items: center;
gap: 7px;
flex: 0 0 auto;
}
.visa-count-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 22px;
height: 22px;
padding: 0 6px;
border-radius: 999px;
background: #e53935;
color: #fff;
font-size: 10px;
font-weight: 900;
line-height: 1;
box-shadow: 0 2px 7px rgba(229, 57, 53, .25);
}
.visa-menu-chevron {
flex: 0 0 auto;
font-size: 15px;
line-height: 1;
}
.visa-submenu {
margin: 0 12px 10px 8px;
padding: 5px 12px 7px 0;
border-right: 2px solid #2d77b9;
display: flex;
flex-direction: column;
gap: 3px;
}
.visa-submenu[hidden] {
display: none !important;
}
.visa-submenu a {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 9px 10px;
border-radius: 7px;
color: inherit;
text-decoration: none;
font-size: 11px;
line-height: 1.7;
}
.visa-submenu a:hover,
.visa-submenu a:focus-visible {
background: rgba(45, 119, 185, .12);
outline: none;
}
.visa-menu-parent:focus-visible {
outline: 2px solid #2d77b9;
outline-offset: -2px;
}
@media (max-height: 620px) {
.brand { flex: 0 0 auto; }
.sidebar-bottom {
padding-top: 6px;
padding-bottom: max(6px, env(safe-area-inset-bottom));
}
.user-mini { padding-top: 6px; padding-bottom: 6px; }
.logout-link {
display: flex;
align-items: center;
justify-content: center;
min-height: 38px;
}
}
/*
|--------------------------------------------------------------------------
| AZKITOOLS DASHBOARD
| Code ID: AZK-DASH-005-CSS
|--------------------------------------------------------------------------
*/
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.dashboard-page {
width: 100%;
}
/*
|--------------------------------------------------------------------------
| WELCOME
|--------------------------------------------------------------------------
*/
.welcome-box {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 25px;
padding: 28px;
border-radius: 20px;
background:
linear-gradient(
135deg,
#0b3f75,
#1473ba
);
color: #fff;
box-shadow:
0 12px 35px
rgba(11,63,117,.18);
}
.welcome-label {
display: block;
margin-bottom: 7px;
color:
rgba(255,255,255,.78);
font-size: 12px;
}
.welcome-box h1 {
margin:
0 0 8px;
font-size: 25px;
font-weight: 900;
}
.welcome-box p {
margin: 0;
color:
rgba(255,255,255,.86);
font-size: 13px;
}
.welcome-icon {
width: 78px;
height: 78px;
flex:
0 0 78px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 22px;
background:
rgba(255,255,255,.13);
font-size: 35px;
}
/*
|--------------------------------------------------------------------------
| SECTION
|--------------------------------------------------------------------------
*/
.section-heading {
display: flex;
align-items: center;
justify-content: space-between;
margin:
0 0 15px;
}
.section-heading h2 {
margin:
0 0 5px;
color: #23384d;
font-size: 18px;
}
.section-heading span {
color: #84909c;
font-size: 11px;
}
/*
|--------------------------------------------------------------------------
| STATS
|--------------------------------------------------------------------------
*/
.admin-stats {
display: grid;
grid-template-columns:
repeat(4, minmax(0, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.admin-stat-card {
position: relative;
overflow: hidden;
padding: 20px;
border:
1px solid #e5ebf3;
border-radius: 16px;
background: #fff;
box-shadow:
0 5px 20px
rgba(0,0,0,.05);
}
.admin-stat-card::before {
content: '';
position: absolute;
right: 0;
top: 0;
width: 4px;
height: 100%;
background: #1256a0;
}
.admin-stat-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
border-radius: 12px;
background: #edf5ff;
font-size: 20px;
}
.admin-stat-card span {
display: block;
margin-bottom: 6px;
color: #777;
font-size: 12px;
}
.admin-stat-card strong {
display: block;
color: #1256a0;
font-size: 24px;
font-weight: 900;
}
/*
|--------------------------------------------------------------------------
| ADMIN MANAGEMENT
|--------------------------------------------------------------------------
*/
.admin-management {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 25px;
padding: 22px 25px;
border-radius: 18px;
background:
linear-gradient(
135deg,
#0d4380,
#1267b5
);
color: #fff;
box-shadow:
0 10px 30px
rgba(13,67,128,.20);
}
.admin-management h3 {
margin:
0 0 7px;
font-size: 16px;
}
.admin-management p {
margin: 0;
color:
rgba(255,255,255,.84);
font-size: 12px;
}
.admin-members-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding:
11px 18px;
border-radius: 10px;
background: #fff;
color: #0d4380;
font-weight: 900;
white-space: nowrap;
}
/*
|--------------------------------------------------------------------------
| TOOLS GRID
|--------------------------------------------------------------------------
*/
.tools-grid {
display: grid;
grid-template-columns:
repeat(4, minmax(0, 1fr));
gap: 15px;
margin-bottom: 25px;
}
.tools-grid > [data-card-key] { touch-action: pan-y; }
.tools-grid > [data-card-key][draggable="true"] { cursor: grab; }
.tools-grid > [data-card-key].azk-card-dragging { opacity: .45; cursor: grabbing; }
.tools-grid > [data-card-key].azk-card-drop-target { outline: 2px dashed #2f80c7; outline-offset: 3px; }
.dashboard-order-status { display:inline-block; margin-top:8px; font-size:11px; color:#587086; min-height:16px; }
.tool-card {
display: block;
min-height: 165px;
padding: 18px;
border:
1px solid #e4eaf1;
border-radius: 17px;
background: #fff;
color: #26384a;
text-decoration: none;
box-shadow:
0 5px 20px
rgba(0,0,0,.045);
transition:
transform .18s ease,
box-shadow .18s ease,
border-color .18s ease;
cursor: pointer;
}
.tool-card:hover {
transform:
translateY(-3px);
border-color:
#bcd4ea;
box-shadow:
0 10px 28px
rgba(0,0,0,.08);
}
.tool-card-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.tool-icon {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 14px;
background: #edf5ff;
font-size: 24px;
}
.tool-arrow {
color: #9aa7b4;
font-size: 18px;
}
.tool-card h3 {
margin:
0 0 7px;
color: #24394d;
font-size: 15px;
}
.tool-card p {
margin: 0;
color: #8a96a2;
font-size: 11px;
}
/*
|--------------------------------------------------------------------------
| SPECIAL CARDS
|--------------------------------------------------------------------------
*/
.contract-card {
border-color:
#cfe0ef;
background:
linear-gradient(
180deg,
#ffffff,
#f7fbff
);
}
.wallet-card {
border-color:
#cfe0ef;
background:
linear-gradient(
180deg,
#ffffff,
#f7fbff
);
}
.contract-card .tool-icon,
.wallet-card .tool-icon {
background:
#e5f1ff;
}
/*
|--------------------------------------------------------------------------
| PANELS
|--------------------------------------------------------------------------
*/
.content-grid {
display: grid;
grid-template-columns:
1fr 1fr;
gap: 15px;
}
.panel {
overflow: hidden;
border:
1px solid #e5ebf3;
border-radius: 17px;
background: #fff;
box-shadow:
0 5px 20px
rgba(0,0,0,.045);
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding:
18px 20px;
border-bottom:
1px solid #edf1f5;
}
.panel-header h3 {
margin:
0 0 5px;
color: #26384a;
font-size: 14px;
}
.panel-header span {
color: #8a96a2;
font-size: 10px;
}
.panel-badge {
padding:
5px 9px;
border-radius: 20px;
background:
#edf5ff;
color:
#1256a0 !important;
font-weight: 800;
}
.empty-state {
min-height: 180px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 8px;
padding: 25px;
text-align: center;
}
.empty-icon {
width: 45px;
height: 45px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 5px;
border-radius: 50%;
background:
#edf8f0;
color:
#29934f;
font-size: 20px;
}
.empty-state strong {
color:
#4c5b69;
font-size: 12px;
}
.empty-state span {
max-width: 300px;
color:
#9aa3ac;
font-size: 10px;
line-height: 1.8;
}
/*
|--------------------------------------------------------------------------
| STATUS
|--------------------------------------------------------------------------
*/
.status-list {
padding:
10px 20px 15px;
}
.status-row {
display: flex;
align-items: center;
justify-content: space-between;
padding:
12px 0;
border-bottom:
1px solid #f0f3f6;
font-size: 11px;
}
.status-row:last-child {
border-bottom: 0;
}
.status-row span {
color:
#778492;
}
.status-row strong {
font-size: 11px;
}
.status-ok {
color:
#27904c;
}
/*
|--------------------------------------------------------------------------
| BADGES
|--------------------------------------------------------------------------
*/
.role-badge,
.member-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding:
5px 11px;
border-radius: 20px;
font-size: 11px;
font-weight: 900;
}
.role-badge {
background:
#fff4c9;
color:
#8a6500;
}
.member-badge {
background:
#edf5ff;
color:
#1256a0;
}
/*
|--------------------------------------------------------------------------
| MOBILE
|--------------------------------------------------------------------------
*/
@media (max-width: 1100px) {
.tools-grid {
grid-template-columns:
repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 900px) {
.admin-stats {
grid-template-columns:
repeat(2, minmax(0, 1fr));
}
.tools-grid {
grid-template-columns:
repeat(2, minmax(0, 1fr));
}
.content-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 650px) {
.welcome-box {
flex-direction: column;
align-items: flex-start;
}
.welcome-icon {
display: none;
}
.admin-stats {
grid-template-columns: 1fr;
}
.tools-grid {
grid-template-columns: 1fr;
}
.admin-management {
flex-direction: column;
align-items: stretch;
}
.admin-members-button {
width: 100%;
}
}
</style>
</head>
<body>
<div class="app-shell">
<!-- ====================================================================
SIDEBAR
===================================================================== -->
<aside
class="sidebar"
id="sidebar"
>
<!-- BRAND -->
<div class="brand">
<div class="brand-logo">
AZ
</div>
<div class="brand-text">
<strong>
AZKITOOLS
</strong>
<span>
Travel Tools
</span>
</div>
</div>
<div class="sidebar-divider"></div>
<!-- MENU -->
<nav class="main-menu">
<!-- DASHBOARD -->
<a
href="dashboard.php"
class="menu-item active"
>
<span class="menu-icon">
🏠
</span>
<span class="menu-title">
داشبورد
</span>
</a>
<!-- MEMBERS -->
<?php if ($isSuperAdmin): ?>
<a
href="members.php"
class="menu-item"
>
<span class="menu-icon">
👥
</span>
<span class="menu-title">
اعضا
</span>
</a>
<?php endif; ?>
<!-- BANK SLIP REQUESTS -->
<?php if ($isSuperAdmin): ?>
<a
href="wallet_deposit_requests.php"
class="menu-item"
>
<span class="menu-icon">
🧾
</span>
<span
class="menu-title"
style="
display:flex;
align-items:center;
justify-content:space-between;
gap:8px;
width:100%;
"
>
<span>
درخواستهای فیش بانکی
</span>
<?php if ($pendingSlipRequests > 0): ?>
<span
style="
min-width:22px;
height:22px;
padding:0 6px;
display:inline-flex;
align-items:center;
justify-content:center;
border-radius:20px;
background:#e53935;
color:#fff;
font-size:10px;
font-weight:900;
"
>
<?= number_format($pendingSlipRequests) ?>
</span>
<?php endif; ?>
</span>
</a>
<?php endif; ?>
<!-- AZK-DASH-PHASE1-001: کشوی مدیریت ویزا با نشانگر درخواستهای جدید -->
<?php if ($canAccessVisa): ?>
<?php
$activeVisaPage = basename((string)($_SERVER['PHP_SELF'] ?? ''));
$visaSectionIsActive = in_array($activeVisaPage, ['visa.php', 'visa_cases.php'], true);
$visaSubmenuInitiallyOpen = $visaSectionIsActive;
?>
<button
type="button"
class="menu-item visa-menu-parent"
id="visaMenuToggle"
aria-expanded="<?= $visaSubmenuInitiallyOpen ? 'true' : 'false' ?>"
aria-controls="visaSubmenu"
>
<span class="menu-icon">🛂</span>
<span class="menu-title visa-menu-title">
<span>مدیریت درخواست ویزا</span>
<span class="visa-menu-actions">
<?php if ($isVisaAdmin && $visaNewTotal > 0): ?>
<span class="visa-count-badge" title="تعداد کل درخواستهای جدید ویزا"><?= number_format($visaNewTotal) ?></span>
<?php endif; ?>
<span class="visa-menu-chevron" aria-hidden="true"><?= $visaSubmenuInitiallyOpen ? '⌃' : '⌄' ?></span>
</span>
</span>
</button>
<div
class="visa-submenu"
id="visaSubmenu"
<?= $visaSubmenuInitiallyOpen ? '' : 'hidden' ?>
>
<a href="visa.php?section=russia">🇷🇺 درخواست ویزای روسیه <?php if ($visaNewCounts['russia'] > 0): ?><span class="visa-count-badge" title="درخواستهای جدید روسیه"><?= number_format($visaNewCounts['russia']) ?></span><?php endif; ?></a>
<a href="visa.php?section=thailand">🇹🇭 درخواست ویزای تایلند <?php if ($visaNewCounts['thailand'] > 0): ?><span class="visa-count-badge" title="درخواستهای جدید تایلند"><?= number_format($visaNewCounts['thailand']) ?></span><?php endif; ?></a>
<a href="visa.php?section=iran">🇮🇷 درخواست ویزای ایران <?php if ($visaNewCounts['iran'] > 0): ?><span class="visa-count-badge" title="درخواستهای جدید ایران"><?= number_format($visaNewCounts['iran']) ?></span><?php endif; ?></a>
<a href="visa.php?section=azerbaijan">🇦🇿 درخواست ویزای آذربایجان <?php if ($visaNewCounts['azerbaijan'] > 0): ?><span class="visa-count-badge" title="درخواستهای جدید آذربایجان"><?= number_format($visaNewCounts['azerbaijan']) ?></span><?php endif; ?></a>
<a href="visa.php?section=health">🏥 درخواست ویزای سلامت <?php if ($visaNewCounts['health'] > 0): ?><span class="visa-count-badge" title="درخواستهای جدید ویزای سلامت"><?= number_format($visaNewCounts['health']) ?></span><?php endif; ?></a>
</div>
<?php endif; ?>
<!-- DATABASE MENUS -->
<?php foreach ($menus as $menu): ?>
<?php
$menuTitle = trim(
(string)($menu['title'] ?? '')
);
if ($menuTitle === '') {
continue;
}
if ($menuTitle === 'داشبورد') {
continue;
}
/*
|--------------------------------------------------------------------------
| اعضا تکراری نشود
|--------------------------------------------------------------------------
*/
if (
$menuTitle === 'اعضا' ||
$menuTitle === 'مدیریت اعضا'
) {
if ($isSuperAdmin) {
continue;
}
continue;
}
/*
|--------------------------------------------------------------------------
| درخواست فیش بانکی تکراری نشود
|--------------------------------------------------------------------------
*/
if (
$menuTitle === 'درخواستهای فیش بانکی' ||
$menuTitle === 'درخواست فیش بانکی'
) {
continue;
}
$menuUrl =
dashboard_menu_url(
$menuTitle
);
$isWallet =
dashboard_is_wallet(
$menuTitle
);
$isContract =
dashboard_is_contract(
$menuTitle
);
?>
<a
href="<?= e($menuUrl) ?>"
class="menu-item"
<?php if ($menuUrl === '#'): ?>
onclick="return false;"
<?php endif; ?>
>
<span class="menu-icon">
<?= e(
(string)(
$menu['icon']
?? '📁'
)
) ?>
</span>
<span class="menu-title">
<?= e($menuTitle) ?>
</span>
</a>
<?php endforeach; ?>
<!-- ============================================================
اگر کیف پول در دیتابیس منو نشده باشد
============================================================ -->
<?php
$walletMenuExists = false;
foreach ($menus as $menuCheck) {
$checkTitle = trim(
(string)(
$menuCheck['title']
?? ''
)
);
if (
$checkTitle === 'کیف پول' ||
$checkTitle === 'کیف پول کاربران'
) {
$walletMenuExists = true;
break;
}
}
?>
<?php if (!$walletMenuExists): ?>
<a
href="wallet.php"
class="menu-item"
>
<span class="menu-icon">
💳
</span>
<span class="menu-title">
کیف پول
</span>
</a>
<?php endif; ?>
</nav>
<!-- USER -->
<div class="sidebar-bottom">
<div class="user-mini">
<div class="user-avatar">
<?= e(
mb_substr(
$fullName,
0,
1
)
) ?>
</div>
<div class="user-mini-info">
<strong>
<?= e($fullName) ?>
</strong>
<span>
<?= e($roleTitle) ?>
</span>
</div>
</div>
<a
href="logout.php"
class="logout-link"
>
خروج از سیستم
</a>
</div>
</aside>
<!-- ====================================================================
MAIN
===================================================================== -->
<main class="main-content">
<!-- TOPBAR -->
<header class="topbar">
<button
type="button"
class="mobile-menu-button"
id="mobileMenuButton"
>
☰
</button>
<div class="topbar-title">
<span>
داشبورد
</span>
<small>
<?= $isSuperAdmin
? 'مدیریت سیستم و ابزارهای آژانس'
: 'ابزارهای آژانس'
?>
</small>
</div>
<div class="topbar-user">
<div class="topbar-user-text">
<strong>
<?= e($fullName) ?>
</strong>
<span>
<?= e(
(string)(
$currentUser['username']
?? ''
)
) ?>
</span>
</div>
<div class="topbar-avatar">
<?= e(
mb_substr(
$fullName,
0,
1
)
) ?>
</div>
</div>
</header>
<!-- PAGE -->
<section class="dashboard-page">
<!-- ============================================================
WELCOME
============================================================= -->
<div class="welcome-box">
<div>
<span class="welcome-label">
خوش آمدید
</span>
<h1>
<?= e($fullName) ?>
</h1>
<p>
<?= $isSuperAdmin
? 'از این قسمت میتوانید سیستم و ابزارهای آژانس را مدیریت کنید.'
: 'از این قسمت میتوانید از ابزارهای مورد نیاز آژانس استفاده کنید.'
?>
</p>
<div style="margin-top:12px;">
<?php if ($isSuperAdmin): ?>
<span class="role-badge">
👑 مدیر کل
</span>
<?php else: ?>
<span class="member-badge">
👤 عضو سیستم
</span>
<?php endif; ?>
</div>
</div>
<div class="welcome-icon">
✈️
</div>
</div>
<!-- ============================================================
SUPER ADMIN STATS
============================================================= -->
<?php if ($isSuperAdmin): ?>
<div class="section-heading">
<div>
<h2>
نمای کلی سیستم
</h2>
<span>
وضعیت اعضا و فعالیت کاربران
</span>
</div>
</div>
<div class="admin-stats">
<div class="admin-stat-card">
<div class="admin-stat-icon">
👥
</div>
<span>
کل اعضا
</span>
<strong>
<?= $totalMembers ?>
</strong>
</div>
<div class="admin-stat-card">
<div class="admin-stat-icon">
🟢
</div>
<span>
اعضای فعال
</span>
<strong>
<?= $activeMembers ?>
</strong>
</div>
<div class="admin-stat-card">
<div class="admin-stat-icon">
📱
</div>
<span>
موبایل تأیید شده
</span>
<strong>
<?= $verifiedMembers ?>
</strong>
</div>
<div class="admin-stat-card">
<div class="admin-stat-icon">
🆕
</div>
<span>
ثبتنام امروز
</span>
<strong>
<?= $todayMembers ?>
</strong>
</div>
</div>
<div class="admin-management">
<div>
<h3>
👥 مدیریت اعضای سیستم
</h3>
<p>
مشاهده، ویرایش و مدیریت کاربران ثبتنامشده AZKITOOLS
</p>
</div>
<a
href="members.php"
class="admin-members-button"
>
ورود به بخش اعضا ←
</a>
</div>
<?php endif; ?>
<!-- ============================================================
TOOLS
============================================================= -->
<div class="section-heading">
<div>
<h2>
ابزارهای اصلی
</h2>
<span>
دسترسی سریع به امکانات سیستم؛ برای تغییر ترتیب، کارتها را بکشید و جابهجا کنید.
</span>
</div>
</div>
<div class="tools-grid" id="dashboardToolsGrid">
<?php foreach ($menus as $menu): ?>
<?php
$menuTitle = trim(
(string)(
$menu['title']
?? ''
)
);
if ($menuTitle === '') {
continue;
}
if ($menuTitle === 'داشبورد') {
continue;
}
if (
$menuTitle === 'اعضا' ||
$menuTitle === 'مدیریت اعضا'
) {
continue;
}
$toolUrl =
dashboard_menu_url(
$menuTitle
);
$isContract =
dashboard_is_contract(
$menuTitle
);
$isWallet =
dashboard_is_wallet(
$menuTitle
);
?>
<a
href="<?= e($toolUrl) ?>"
class="
tool-card
<?= (
$isContract ||
$isWallet
)
? 'contract-card'
: ''
?>
"
data-card-key="<?= e('menu-' . (string)($menu['id'] ?? '0')) ?>"
draggable="true"
<?php if ($toolUrl === '#'): ?>
onclick="return false;"
<?php endif; ?>
>
<div class="tool-card-top">
<div class="tool-icon">
<?= e(
(string)(
$menu['icon']
?? '📁'
)
) ?>
</div>
<span class="tool-arrow">
←
</span>
</div>
<h3>
<?= e($menuTitle) ?>
</h3>
<p>
<?php if ($isContract): ?>
ورود به قرارداد تور
<?php elseif ($isWallet): ?>
مدیریت اعتبار و کیف پول
<?php else: ?>
ورود به بخش
<?= e($menuTitle) ?>
<?php endif; ?>
</p>
</a>
<?php endforeach; ?>
<?php if ($canAccessVisa): ?>
<div class="tool-card contract-card" data-card-key="visa-tracking" draggable="true" style="min-height:0;">
<div class="tool-card-top">
<div class="tool-icon">🛂</div>
<div style="display:flex;align-items:center;gap:8px;">
<?php if ($visaNewTotal > 0): ?>
<span class="visa-count-badge" title="تعداد درخواستهای جدید ویزا" aria-label="<?= number_format($visaNewTotal) ?> درخواست جدید"><?= number_format($visaNewTotal) ?></span>
<?php endif; ?>
<span class="tool-arrow">←</span>
</div>
</div>
<h3><a href="visa_cases.php" style="color:inherit;text-decoration:none;display:block;">پیگیری وضعیت ویزاها</a></h3>
<p style="margin-bottom:12px;">مشاهده آخرین وضعیت پروندهها، جزئیات، ویرایش، چاپ و خروجی پروندههای فعال.</p>
<div style="display:flex;flex-direction:column;gap:8px;">
<a href="visa_cases.php" style="color:#1256a0;text-decoration:none;">🇷🇺 مشاهده فهرست و مدیریت پروندههای روسیه</a>
<a href="visa.php?section=thailand" style="color:#1256a0;text-decoration:none;">🇹🇭 درخواست ویزای تایلند</a>
<a href="visa.php?section=iran" style="color:#1256a0;text-decoration:none;">🇮🇷 درخواست ویزای ایران</a>
<a href="visa.php?section=azerbaijan" style="color:#1256a0;text-decoration:none;">🇦🇿 درخواست ویزای آذربایجان</a>
<a href="visa.php?section=health" style="color:#1256a0;text-decoration:none;">🏥 درخواست ویزای سلامت</a>
</div>
</div>
<?php endif; ?>
<!-- ========================================================
BANK SLIP REQUEST CARD
========================================================= -->
<?php if ($isSuperAdmin): ?>
<a
href="wallet_deposit_requests.php"
class="tool-card"
data-card-key="bank-slips"
draggable="true"
style="
border-color:#cfe0ef;
background:
linear-gradient(
180deg,
#ffffff,
#f7fbff
);
"
>
<div class="tool-card-top">
<div
class="tool-icon"
style="
position:relative;
background:#e5f1ff;
"
>
🧾
<?php if ($pendingSlipRequests > 0): ?>
<span
style="
position:absolute;
top:-7px;
right:-7px;
min-width:22px;
height:22px;
padding:0 5px;
display:flex;
align-items:center;
justify-content:center;
border-radius:50%;
background:#e53935;
color:#fff;
font-size:10px;
font-weight:900;
"
>
<?= number_format($pendingSlipRequests) ?>
</span>
<?php endif; ?>
</div>
<span class="tool-arrow">
←
</span>
</div>
<h3>
درخواستهای فیش بانکی
</h3>
<p>
بررسی و تأیید شارژهای بانکی اعضا
</p>
</a>
<?php endif; ?>
<!-- ========================================================
WALLET CARD FALLBACK
========================================================= -->
<?php if (!$walletMenuExists): ?>
<a
href="wallet.php"
class="tool-card wallet-card"
data-card-key="wallet"
draggable="true"
>
<div class="tool-card-top">
<div class="tool-icon">
💳
</div>
<span class="tool-arrow">
←
</span>
</div>
<h3>
کیف پول
</h3>
<p>
مدیریت اعتبار و کیف پول
</p>
</a>
<?php endif; ?>
</div>
<!-- ============================================================
PANELS
============================================================= -->
<div class="content-grid">
<!-- ACTIVITY -->
<div class="panel">
<div class="panel-header">
<div>
<h3>
آخرین فعالیتها
</h3>
<span>
فعالیتهای اخیر سیستم
</span>
</div>
<span class="panel-badge">
امروز
</span>
</div>
<div class="empty-state">
<div class="empty-icon">
✓
</div>
<strong>
هنوز فعالیتی ثبت نشده است
</strong>
<span>
فعالیتهای ابزارها در این قسمت نمایش داده خواهند شد.
</span>
</div>
</div>
<!-- STATUS -->
<div class="panel">
<div class="panel-header">
<div>
<h3>
وضعیت حساب
</h3>
<span>
اطلاعات حساب کاربری
</span>
</div>
</div>
<div class="status-list">
<div class="status-row">
<span>
وضعیت سیستم
</span>
<strong class="status-ok">
فعال
</strong>
</div>
<div class="status-row">
<span>
پایگاه داده
</span>
<strong class="status-ok">
متصل
</strong>
</div>
<div class="status-row">
<span>
نوع حساب
</span>
<strong class="status-ok">
<?= e($roleTitle) ?>
</strong>
</div>
<div class="status-row">
<span>
وضعیت حساب
</span>
<strong class="status-ok">
<?= (
(int)(
$currentUser['is_active']
?? 1
) === 1
)
? 'فعال'
: 'غیرفعال'
?>
</strong>
</div>
</div>
</div>
</div>
<div id="dashboardOrderStatus" class="dashboard-order-status" role="status" aria-live="polite"></div>
<!-- ========================================================
FOOTER
========================================================= -->
<div
style="
text-align:center;
margin-top:35px;
padding:20px;
color:#777;
font-size:11px;
line-height:2;
"
>
<strong style="color:#1256a0;">
AZKITOOLS
</strong>
|
سامانه ابزارهای آژانس
<br>
© تمامی حقوق محفوظ است
<br>
برنامهنویسی توسط تیم برنامهنویسی
<strong style="color:#1256a0;">
AZKITOUR.COM
</strong>
</div>
</section>
</main>
</div>
<script>
/*
|--------------------------------------------------------------------------
| AZKITOOLS DASHBOARD JS
| Code ID: AZK-DASH-007-JS
|--------------------------------------------------------------------------
*/
(function () {
const visaToggle = document.getElementById('visaMenuToggle');
const visaSubmenu = document.getElementById('visaSubmenu');
if (visaToggle && visaSubmenu) {
visaToggle.addEventListener('click', function () {
const isOpen = visaToggle.getAttribute('aria-expanded') === 'true';
visaToggle.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
visaSubmenu.hidden = isOpen;
const chevron = visaToggle.querySelector('.visa-menu-chevron');
if (chevron) {
chevron.textContent = isOpen ? '⌄' : '⌃';
}
});
}
const button =
document.getElementById(
'mobileMenuButton'
);
const sidebar =
document.getElementById(
'sidebar'
);
if (
button &&
sidebar
) {
button.addEventListener(
'click',
function () {
sidebar.classList.toggle(
'open'
);
}
);
}
// AZK-DASH-PHASE1-001: جابهجایی کارتها و ذخیره ترتیب برای همین حساب کاربری
const grid = document.getElementById('dashboardToolsGrid');
const orderStatus = document.getElementById('dashboardOrderStatus');
const savedOrder = <?php echo json_encode($dashboardCardOrder, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const orderCsrf = <?php echo json_encode($dashboardOrderCsrf); ?>;
if (grid) {
const getCards = () => Array.from(grid.children).filter((el) => el.hasAttribute('data-card-key'));
if (Array.isArray(savedOrder) && savedOrder.length) {
const cards = getCards();
const rank = new Map(savedOrder.map((key, index) => [key, index]));
cards.sort((a, b) => {
const ar = rank.has(a.dataset.cardKey) ? rank.get(a.dataset.cardKey) : 100000;
const br = rank.has(b.dataset.cardKey) ? rank.get(b.dataset.cardKey) : 100000;
return ar - br;
}).forEach((card) => grid.appendChild(card));
}
let draggedCard = null;
getCards().forEach((card) => {
card.addEventListener('dragstart', (event) => {
draggedCard = card;
card.classList.add('azk-card-dragging');
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', card.dataset.cardKey || '');
}
});
card.addEventListener('dragend', () => {
card.classList.remove('azk-card-dragging');
getCards().forEach((item) => item.classList.remove('azk-card-drop-target'));
draggedCard = null;
saveOrder();
});
card.addEventListener('dragover', (event) => {
if (!draggedCard || draggedCard === card) return;
event.preventDefault();
card.classList.add('azk-card-drop-target');
const rect = card.getBoundingClientRect();
const before = (event.clientY - rect.top) < rect.height / 2;
if (before) grid.insertBefore(draggedCard, card);
else grid.insertBefore(draggedCard, card.nextSibling);
});
card.addEventListener('dragleave', () => card.classList.remove('azk-card-drop-target'));
card.addEventListener('drop', (event) => {
event.preventDefault();
card.classList.remove('azk-card-drop-target');
});
});
async function saveOrder() {
const order = getCards().map((card) => card.dataset.cardKey).filter(Boolean);
if (orderStatus) orderStatus.textContent = 'در حال ذخیره ترتیب کارتها...';
try {
const response = await fetch(window.location.pathname, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest'},
body: new URLSearchParams({dashboard_action: 'save_card_order', csrf: orderCsrf, order: JSON.stringify(order)})
});
const result = await response.json();
if (!response.ok || !result.ok) throw new Error(result.message || 'ذخیره انجام نشد');
if (orderStatus) orderStatus.textContent = 'ترتیب کارتها برای حساب شما ذخیره شد.';
} catch (error) {
if (orderStatus) orderStatus.textContent = 'ذخیره ترتیب انجام نشد؛ لطفاً صفحه را تازهسازی کنید و دوباره امتحان کنید.';
}
}
}
})();
</script>
</body>
</html>